| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183 |
- 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": "已删除"})
- }
|