package web import ( "errors" "fmt" "net/http" "net/url" "strings" "time" "github.com/gin-gonic/gin" "cmautobuy/admin/model" "cmautobuy/admin/repository" "cmautobuy/admin/service" ) // AdminRequired 叠加在已认证路由上。采购员能使用业务页,但不能执行管理操作。 func AdminRequired() gin.HandlerFunc { return func(c *gin.Context) { user := currentUser(c) if user != nil && user.IsAdmin() { c.Next() return } fail(c, http.StatusForbidden, "只有管理员可以执行此管理操作。当前账号仍可使用其他业务页面。") c.Abort() } } // UserList 显示用户搜索、分页和管理操作。 func (h *Handler) UserList(c *gin.Context) { h.renderUserList(c, http.StatusOK, c.Query("msg"), c.Query("error"), false, "") } func (h *Handler) renderUserList(c *gin.Context, httpStatus int, message, errorMessage string, needCreate bool, createUsername string) { keyword := strings.TrimSpace(userListValue(c, "q")) listStatus := userListValue(c, "status") pageSize := service.ParsePageSize(userListValue(c, "page_size")) if c.Request.Method == http.MethodPost { listStatus = c.PostForm("list_status") } result, err := service.ListUsersWithPageSize(h.db, currentUser(c), keyword, model.UserStatus(listStatus), service.ParsePage(userListValue(c, "page")), pageSize) if err != nil { fail(c, http.StatusInternalServerError, "读取用户列表失败,账号数据没有被改动。刷新后重试。") return } values := url.Values{} if keyword != "" { values.Set("q", keyword) } if result.Status != "" { values.Set("status", string(result.Status)) } status := fmt.Sprintf("共 %d 个用户 · 第 %d/%d 页", result.Total, result.Page, result.TotalPages) c.HTML(httpStatus, "user/list", page(c, "users", "用户管理", gin.H{ "Rows": result.Rows, "Keyword": result.Keyword, "Status": status, "Pagination": service.NewPaginationView(result.Page, pageSize, result.TotalPages, values.Encode()), "StatusFilter": result.Status, "Message": message, "Error": errorMessage, "NeedCreate": needCreate, "CreateUsername": createUsername, "CurrentPage": result.Page, "CurrentPageSize": pageSize, })) } func userListValue(c *gin.Context, key string) string { if c.Request.Method == http.MethodPost { return c.PostForm(key) } return c.Query(key) } // UserCreate 只创建固定 purchaser 角色,密码不写入 URL、不回显。 func (h *Handler) UserCreate(c *gin.Context) { err := service.CreatePurchaser(h.db, currentUser(c), c.PostForm("username"), c.PostForm("password"), c.PostForm("password_confirm"), time.Now()) if err != nil { if isExpectedUserError(err) { h.renderUserList(c, http.StatusBadRequest, "", err.Error(), true, strings.TrimSpace(c.PostForm("username"))) return } fail(c, http.StatusInternalServerError, "创建采购员失败,已有账号数据保持不变。刷新后重试。") return } redirectUsers(c, "采购员创建成功", "") } // UserSetStatus 启用或禁用账号,并由 Repository 在同一事务撤销全部 Session。 func (h *Handler) UserSetStatus(c *gin.Context) { status := model.UserStatus(c.PostForm("status")) err := service.ChangeUserStatus(h.db, currentUser(c), c.PostForm("user_id"), status, time.Now()) if err != nil { h.userActionFailure(c, err, "修改账号状态失败") return } message := "账号已启用" if status == model.UserDisabled { message = "账号已禁用,原有登录已撤销" } redirectUsers(c, message, "") } // UserResetPassword 重置密码并撤销目标用户全部 Session。密码不会回显。 func (h *Handler) UserResetPassword(c *gin.Context) { err := service.ResetUserPassword(h.db, currentUser(c), c.PostForm("user_id"), c.PostForm("password"), c.PostForm("password_confirm"), time.Now()) if err != nil { h.userActionFailure(c, err, "重置密码失败") return } redirectUsers(c, "密码已重置,原有登录已撤销", "") } func (h *Handler) userActionFailure(c *gin.Context, err error, fallback string) { if isExpectedUserError(err) { redirectUsers(c, "", err.Error()) return } fail(c, http.StatusInternalServerError, fallback+",已有账号数据保持不变。刷新后重试。") } func isExpectedUserError(err error) bool { return service.IsValidationError(err) || errors.Is(err, repository.ErrUsernameExists) || errors.Is(err, repository.ErrUserNotFound) || errors.Is(err, repository.ErrLastActiveAdmin) } func redirectUsers(c *gin.Context, message, errorMessage string) { query := url.Values{} if keyword := c.PostForm("q"); keyword != "" { query.Set("q", keyword) } if status := c.PostForm("list_status"); status != "" { query.Set("status", status) } if page := c.PostForm("page"); page != "" { query.Set("page", page) } query.Set("page_size", fmt.Sprintf("%d", service.ParsePageSize(c.PostForm("page_size")))) if message != "" { query.Set("msg", message) } if errorMessage != "" { query.Set("error", errorMessage) } location := "/users" if encoded := query.Encode(); encoded != "" { location += "?" + encoded } c.Redirect(http.StatusSeeOther, location) }