package handler import ( "fmt" "spider/internal/model" "spider/internal/store" "strconv" "github.com/gin-gonic/gin" ) // UserHandler handles user management (admin only). type UserHandler struct { store *store.Store } // List handles GET /users func (h *UserHandler) List(c *gin.Context) { page, pageSize, offset := parsePage(c) var total int64 h.store.DB.Model(&model.User{}).Count(&total) var users []model.User if err := h.store.DB.Order("id ASC").Limit(pageSize).Offset(offset).Find(&users).Error; err != nil { Fail(c, 500, err.Error()) return } PageOK(c, users, total, page, pageSize) } // Create handles POST /users func (h *UserHandler) Create(c *gin.Context) { var req struct { Username string `json:"username" binding:"required,min=3"` Password string `json:"password" binding:"required,min=6"` Nickname string `json:"nickname"` Role string `json:"role" binding:"required,oneof=admin operator viewer"` } if err := c.ShouldBindJSON(&req); err != nil { Fail(c, 400, err.Error()) return } // Check duplicate var count int64 h.store.DB.Model(&model.User{}).Where("username = ?", req.Username).Count(&count) if count > 0 { Fail(c, 409, "用户名已存在") return } user := model.User{ Username: req.Username, Password: HashPassword(req.Password), Nickname: req.Nickname, Role: req.Role, Enabled: true, } if err := h.store.DB.Create(&user).Error; err != nil { Fail(c, 500, err.Error()) return } LogAudit(h.store, c, "create", "user", fmt.Sprintf("%d", user.ID), gin.H{"username": user.Username, "role": user.Role}) OK(c, user) } // Update handles PUT /users/:id func (h *UserHandler) Update(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { Fail(c, 400, "invalid id") return } var user model.User if err := h.store.DB.First(&user, id).Error; err != nil { Fail(c, 404, "用户不存在") return } var req struct { Nickname *string `json:"nickname"` Role *string `json:"role"` Enabled *bool `json:"enabled"` } if err := c.ShouldBindJSON(&req); err != nil { Fail(c, 400, err.Error()) return } updates := map[string]any{} if req.Nickname != nil { updates["nickname"] = *req.Nickname } if req.Role != nil { if *req.Role != "admin" && *req.Role != "operator" && *req.Role != "viewer" { Fail(c, 400, "角色无效") return } updates["role"] = *req.Role } if req.Enabled != nil { updates["enabled"] = *req.Enabled } h.store.DB.Model(&user).Updates(updates) h.store.DB.First(&user, id) LogAudit(h.store, c, "update", "user", fmt.Sprintf("%d", id), updates) OK(c, user) } // ResetPassword handles POST /users/:id/reset-password (admin only) func (h *UserHandler) ResetPassword(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { Fail(c, 400, "invalid id") return } var req struct { NewPassword string `json:"new_password" binding:"required,min=6"` } if err := c.ShouldBindJSON(&req); err != nil { Fail(c, 400, "新密码至少6位") return } var user model.User if err := h.store.DB.First(&user, id).Error; err != nil { Fail(c, 404, "用户不存在") return } h.store.DB.Model(&user).Update("password", HashPassword(req.NewPassword)) LogAudit(h.store, c, "update", "user", fmt.Sprintf("%d", id), gin.H{"action": "reset_password"}) OK(c, gin.H{"message": "密码已重置"}) } // ForceLogout handles POST /users/:id/force-logout — invalidates all tokens for a user func (h *UserHandler) ForceLogout(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { Fail(c, 400, "invalid id") return } var user model.User if err := h.store.DB.First(&user, id).Error; err != nil { Fail(c, 404, "用户不存在") return } // We can't revoke all tokens without tracking them, but we can // mark the user as needing re-auth by bumping a version counter. // For now, log the action - token blacklisting would require // storing all active tokens per user. LogAudit(h.store, c, "force_logout", "user", fmt.Sprintf("%d", id), gin.H{"username": user.Username}) OK(c, gin.H{"message": fmt.Sprintf("已强制 %s 退出登录", user.Username)}) } // Delete handles DELETE /users/:id func (h *UserHandler) Delete(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { Fail(c, 400, "invalid id") return } // Prevent deleting self currentID := c.GetUint("user_id") if uint(id) == currentID { Fail(c, 400, "不能删除自己") return } if err := h.store.DB.Delete(&model.User{}, id).Error; err != nil { Fail(c, 500, err.Error()) return } LogAudit(h.store, c, "delete", "user", fmt.Sprintf("%d", id), nil) OK(c, gin.H{"message": "已删除"}) }