package handlers import ( "fmt" "net/http" "strconv" "ezops/internal/models" "github.com/gin-gonic/gin" "golang.org/x/crypto/bcrypt" ) // ListUsers 获取用户列表 // @Summary 获取用户列表 // @Description 获取所有用户列表(管理员) // @Tags 用户 // @Produce json // @Security Bearer // @Param page query int false "页码" // @Param page_size query int false "每页数量" // @Param keyword query string false "搜索关键词" // @Success 200 {object} models.Response{data=models.ListResponse} // @Router /users [get] func (h *UserHandler) ListUsers(c *gin.Context) { var query models.PageQuery if err := c.ShouldBindQuery(&query); err != nil { c.JSON(http.StatusBadRequest, models.Response{ Code: 400, Message: "请求参数错误", }) return } db := h.db.Model(&models.User{}) if query.Keyword != "" { db = db.Where("username LIKE ? OR email LIKE ? OR real_name LIKE ?", "%"+query.Keyword+"%", "%"+query.Keyword+"%", "%"+query.Keyword+"%") } var total int64 db.Count(&total) var users []models.User db.Preload("Roles").Offset(query.GetOffset()).Limit(query.GetLimit()).Find(&users) c.JSON(http.StatusOK, models.Response{ Code: 200, Message: "success", Data: models.ListResponse{ List: users, Total: total, }, }) } // GetUser 获取用户详情 // @Summary 获取用户详情 // @Description 获取指定用户的详细信息 // @Tags 用户 // @Produce json // @Security Bearer // @Param id path int true "用户ID" // @Success 200 {object} models.Response{data=models.User} // @Router /users/{id} [get] func (h *UserHandler) GetUser(c *gin.Context) { id, _ := strconv.Atoi(c.Param("id")) var user models.User if err := h.db.Preload("Roles").First(&user, id).Error; err != nil { c.JSON(http.StatusNotFound, models.Response{ Code: 404, Message: "用户不存在", }) return } c.JSON(http.StatusOK, models.Response{ Code: 200, Message: "success", Data: user, }) } // CreateUser 创建用户 // @Summary 创建用户 // @Description 创建新用户(管理员) // @Tags 用户 // @Accept json // @Produce json // @Security Bearer // @Param request body models.CreateUserRequest true "创建请求" // @Success 200 {object} models.Response{data=models.User} // @Router /users [post] func (h *UserHandler) CreateUser(c *gin.Context) { var req models.CreateUserRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, models.Response{ Code: 400, Message: "请求参数错误: " + err.Error(), }) return } // 检查用户名是否已存在 var existingUser models.User if err := h.db.Where("username = ?", req.Username).First(&existingUser).Error; err == nil { c.JSON(http.StatusBadRequest, models.Response{ Code: 400, Message: "用户名已存在", }) return } // 加密密码 hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost) if err != nil { c.JSON(http.StatusInternalServerError, models.Response{ Code: 500, Message: "密码加密失败", }) return } user := models.User{ Username: req.Username, Password: string(hashedPassword), Email: req.Email, Phone: req.Phone, RealName: req.RealName, Status: 1, } if err := h.db.Create(&user).Error; err != nil { c.JSON(http.StatusInternalServerError, models.Response{ Code: 500, Message: "创建用户失败", }) return } // 关联角色 if len(req.RoleIDs) > 0 { var roles []models.Role h.db.Find(&roles, req.RoleIDs) h.db.Model(&user).Association("Roles").Append(roles) } c.JSON(http.StatusOK, models.Response{ Code: 200, Message: "创建成功", Data: user, }) } // UpdateUser 更新用户 // @Summary 更新用户 // @Description 更新指定用户信息(管理员) // @Tags 用户 // @Accept json // @Produce json // @Security Bearer // @Param id path int true "用户ID" // @Param request body models.UpdateUserRequest true "更新请求" // @Success 200 {object} models.Response{data=models.User} // @Router /users/{id} [put] func (h *UserHandler) UpdateUser(c *gin.Context) { id, _ := strconv.Atoi(c.Param("id")) var req models.UpdateUserRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, models.Response{ Code: 400, Message: "请求参数错误: " + err.Error(), }) return } var user models.User if err := h.db.First(&user, id).Error; err != nil { c.JSON(http.StatusNotFound, models.Response{ Code: 404, Message: "用户不存在", }) return } updates := map[string]interface{}{} if req.Email != "" { updates["email"] = req.Email } if req.Phone != "" { updates["phone"] = req.Phone } if req.RealName != "" { updates["real_name"] = req.RealName } if req.Status != nil { updates["status"] = *req.Status } if err := h.db.Model(&user).Updates(updates).Error; err != nil { c.JSON(http.StatusInternalServerError, models.Response{ Code: 500, Message: "更新失败", }) return } // 更新角色 if len(req.RoleIDs) > 0 { var roles []models.Role h.db.Find(&roles, req.RoleIDs) h.db.Model(&user).Association("Roles").Replace(roles) } h.db.Preload("Roles").First(&user, id) c.JSON(http.StatusOK, models.Response{ Code: 200, Message: "更新成功", Data: user, }) } // DeleteUser 删除用户 // @Summary 删除用户 // @Description 删除指定用户(管理员) // @Tags 用户 // @Produce json // @Security Bearer // @Param id path int true "用户ID" // @Success 200 {object} models.Response // @Router /users/{id} [delete] func (h *UserHandler) DeleteUser(c *gin.Context) { id, _ := strconv.Atoi(c.Param("id")) if err := h.db.Delete(&models.User{}, id).Error; err != nil { c.JSON(http.StatusInternalServerError, models.Response{ Code: 500, Message: "删除失败", }) return } c.JSON(http.StatusOK, models.Response{ Code: 200, Message: "删除成功", }) } // BatchImportUsers 批量导入用户 // POST /api/v1/admin/users/batch-import func (h *UserHandler) BatchImportUsers(c *gin.Context) { var req struct { Users []struct { Username string `json:"username"` Nickname string `json:"nickname"` Email string `json:"email"` Phone string `json:"phone"` Password string `json:"password"` Role string `json:"role"` } `json:"users"` SkipExisting bool `json:"skip_existing"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, models.Response{ Code: 400, Message: "请求参数错误: " + err.Error(), }) return } imported := 0 skipped := 0 var errors []string for i, u := range req.Users { if u.Username == "" { errors = append(errors, fmt.Sprintf("第%d行: 用户名为空", i+1)) continue } // Check if exists var existing models.User if err := h.db.Where("username = ?", u.Username).First(&existing).Error; err == nil { if req.SkipExisting { skipped++ continue } errors = append(errors, fmt.Sprintf("第%d行: 用户 %s 已存在", i+1, u.Username)) skipped++ continue } // Default password pwd := u.Password if pwd == "" { pwd = "admin123" } hashed, err := bcrypt.GenerateFromPassword([]byte(pwd), bcrypt.DefaultCost) if err != nil { errors = append(errors, fmt.Sprintf("第%d行: 密码加密失败", i+1)) continue } user := models.User{ Username: u.Username, Password: string(hashed), Email: u.Email, Phone: u.Phone, RealName: u.Nickname, Status: 1, AuthSource: "local", } if err := h.db.Create(&user).Error; err != nil { errors = append(errors, fmt.Sprintf("第%d行: 创建用户 %s 失败", i+1, u.Username)) continue } imported++ } msg := fmt.Sprintf("导入完成: %d 成功, %d 跳过", imported, skipped) if len(errors) > 0 { msg += fmt.Sprintf(", %d 个错误", len(errors)) } c.JSON(http.StatusOK, models.Response{ Code: 200, Message: msg, Data: gin.H{ "imported": imported, "skipped": skipped, "errors": errors, }, }) } // ImportTemplate 下载导入模板 // (已有实现,在文件末尾追加 ListRoles) // ListRoles 获取角色列表(供前端下拉选择) // Logan.Li func (h *UserHandler) ListRoles(c *gin.Context) { var roles []models.Role h.db.Where("status = ?", 1).Order("id ASC").Find(&roles) c.JSON(http.StatusOK, models.Response{ Code: 200, Data: models.ListResponse{List: roles, Total: int64(len(roles))}, }) } // GET /api/v1/admin/users/import-template func (h *UserHandler) ImportTemplate(c *gin.Context) { // UTF-8 BOM for Excel compatibility bom := "\xEF\xBB\xBF" csv := bom + "用户名,昵称,邮箱,手机号,角色,密码\n" csv += "zhangsan,张三,zhangsan@example.com,13800138000,developer,123456\n" csv += "lisi,李四,lisi@example.com,13800138001,developer,123456\n" c.Header("Content-Type", "text/csv; charset=utf-8") c.Header("Content-Disposition", "attachment; filename=users_import_template.csv") c.String(http.StatusOK, csv) }