package controller import ( "fmt" "net/http" "strconv" "ezops/model" "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} model.Response{data=model.ListResponse} // @Router /users [get] func (h *UserHandler) ListUsers(c *gin.Context) { var query model.PageQuery if err := c.ShouldBindQuery(&query); err != nil { c.JSON(http.StatusBadRequest, model.Response{ Code: 400, Message: "请求参数错误", }) return } db := h.db.Model(&model.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 []model.User db.Preload("Roles").Offset(query.GetOffset()).Limit(query.GetLimit()).Find(&users) c.JSON(http.StatusOK, model.Response{ Code: 200, Message: "success", Data: model.ListResponse{ List: users, Total: total, }, }) } // GetUser 获取用户详情 // @Summary 获取用户详情 // @Description 获取指定用户的详细信息 // @Tags 用户 // @Produce json // @Security Bearer // @Param id path int true "用户ID" // @Success 200 {object} model.Response{data=model.User} // @Router /users/{id} [get] func (h *UserHandler) GetUser(c *gin.Context) { id, _ := strconv.Atoi(c.Param("id")) var user model.User if err := h.db.Preload("Roles").First(&user, id).Error; err != nil { c.JSON(http.StatusNotFound, model.Response{ Code: 404, Message: "用户不存在", }) return } c.JSON(http.StatusOK, model.Response{ Code: 200, Message: "success", Data: user, }) } // CreateUser 创建用户 // @Summary 创建用户 // @Description 创建新用户(管理员) // @Tags 用户 // @Accept json // @Produce json // @Security Bearer // @Param request body model.CreateUserRequest true "创建请求" // @Success 200 {object} model.Response{data=model.User} // @Router /users [post] func (h *UserHandler) CreateUser(c *gin.Context) { var req model.CreateUserRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, model.Response{ Code: 400, Message: "请求参数错误: " + err.Error(), }) return } // 检查用户名是否已存在 var existingUser model.User if err := h.db.Where("username = ?", req.Username).First(&existingUser).Error; err == nil { c.JSON(http.StatusBadRequest, model.Response{ Code: 400, Message: "用户名已存在", }) return } // 加密密码 hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost) if err != nil { c.JSON(http.StatusInternalServerError, model.Response{ Code: 500, Message: "密码加密失败", }) return } user := model.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, model.Response{ Code: 500, Message: "创建用户失败", }) return } // 关联角色 if len(req.RoleIDs) > 0 { var roles []model.Role h.db.Find(&roles, req.RoleIDs) h.db.Model(&user).Association("Roles").Append(roles) } c.JSON(http.StatusOK, model.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 model.UpdateUserRequest true "更新请求" // @Success 200 {object} model.Response{data=model.User} // @Router /users/{id} [put] func (h *UserHandler) UpdateUser(c *gin.Context) { id, _ := strconv.Atoi(c.Param("id")) var req model.UpdateUserRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, model.Response{ Code: 400, Message: "请求参数错误: " + err.Error(), }) return } var user model.User if err := h.db.First(&user, id).Error; err != nil { c.JSON(http.StatusNotFound, model.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, model.Response{ Code: 500, Message: "更新失败", }) return } // 更新角色 if len(req.RoleIDs) > 0 { var roles []model.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, model.Response{ Code: 200, Message: "更新成功", Data: user, }) } // DeleteUser 删除用户 // @Summary 删除用户 // @Description 删除指定用户(管理员) // @Tags 用户 // @Produce json // @Security Bearer // @Param id path int true "用户ID" // @Success 200 {object} model.Response // @Router /users/{id} [delete] func (h *UserHandler) DeleteUser(c *gin.Context) { id, _ := strconv.Atoi(c.Param("id")) if err := h.db.Delete(&model.User{}, id).Error; err != nil { c.JSON(http.StatusInternalServerError, model.Response{ Code: 500, Message: "删除失败", }) return } c.JSON(http.StatusOK, model.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, model.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 model.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 := model.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, model.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 []model.Role h.db.Where("status = ?", 1).Preload("Permissions").Order("id ASC").Find(&roles) c.JSON(http.StatusOK, model.Response{ Code: 200, Data: model.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) } // ResetUserPassword 管理员重置用户密码(仅本地账号)// Logan.Li // POST /api/v1/admin/users/:id/reset-password func (h *UserHandler) ResetUserPassword(c *gin.Context) { id, _ := strconv.Atoi(c.Param("id")) var req struct { NewPassword string `json:"new_password" binding:"required,min=6,max=32"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, model.Response{Code: 400, Message: "请求参数错误: " + err.Error()}) return } var user model.User if err := h.db.First(&user, id).Error; err != nil { c.JSON(http.StatusNotFound, model.Response{Code: 404, Message: "用户不存在"}) return } if user.AuthSource == "ldap" { c.JSON(http.StatusBadRequest, model.Response{Code: 400, Message: "LDAP 用户请在 LDAP 目录中重置密码"}) return } hashed, err := bcrypt.GenerateFromPassword([]byte(req.NewPassword), bcrypt.DefaultCost) if err != nil { c.JSON(http.StatusInternalServerError, model.Response{Code: 500, Message: "密码加密失败"}) return } if err := h.db.Model(&user).Update("password", string(hashed)).Error; err != nil { c.JSON(http.StatusInternalServerError, model.Response{Code: 500, Message: "重置失败"}) return } c.JSON(http.StatusOK, model.Response{Code: 200, Message: "密码已重置"}) }