package handler import ( "net/http" "strconv" "strings" "soul-api/internal/database" "soul-api/internal/middleware" "soul-api/internal/model" "github.com/gin-gonic/gin" "golang.org/x/crypto/bcrypt" "gorm.io/gorm" ) // AdminUsersList GET /api/admin/users 管理员用户列表(仅 super_admin) func AdminUsersList(c *gin.Context) { claims := middleware.GetAdminClaims(c) if claims == nil || claims.Role != "super_admin" { c.JSON(http.StatusForbidden, gin.H{"success": false, "error": "无权限"}) return } db := database.DB() page, _ := strconv.Atoi(c.Query("page")) if page < 1 { page = 1 } pageSize, _ := strconv.Atoi(c.Query("pageSize")) if pageSize < 1 { pageSize = 10 } if pageSize > 100 { pageSize = 100 } search := strings.TrimSpace(c.Query("search")) var total int64 q := db.Model(&model.AdminUser{}) if search != "" { q = q.Where("username LIKE ? OR name LIKE ?", "%"+search+"%", "%"+search+"%") } if err := q.Count(&total).Error; err != nil { c.JSON(http.StatusOK, gin.H{"success": false, "error": err.Error()}) return } var list []model.AdminUser offset := (page - 1) * pageSize if err := q.Order("id ASC").Offset(offset).Limit(pageSize).Find(&list).Error; err != nil { c.JSON(http.StatusOK, gin.H{"success": false, "error": err.Error()}) return } records := make([]gin.H, 0, len(list)) for _, u := range list { records = append(records, gin.H{ "id": u.ID, "username": u.Username, "role": u.Role, "name": u.Name, "status": u.Status, "createdAt": u.CreatedAt, "updatedAt": u.UpdatedAt, }) } totalPages := (int(total) + pageSize - 1) / pageSize c.JSON(http.StatusOK, gin.H{ "success": true, "records": records, "total": total, "page": page, "pageSize": pageSize, "totalPages": totalPages, }) } // AdminUsersAction POST/PUT/DELETE /api/admin/users 管理员用户增删改(仅 super_admin) func AdminUsersAction(c *gin.Context) { claims := middleware.GetAdminClaims(c) if claims == nil || claims.Role != "super_admin" { c.JSON(http.StatusForbidden, gin.H{"success": false, "error": "无权限"}) return } db := database.DB() switch c.Request.Method { case http.MethodPost: var body struct { Username string `json:"username" binding:"required"` Password string `json:"password" binding:"required"` Name string `json:"name"` Role string `json:"role"` } if err := c.ShouldBindJSON(&body); err != nil { c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": "参数错误"}) return } username := trimSpace(body.Username) if len(username) < 2 { c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": "用户名至少 2 个字符"}) return } if len(body.Password) < 6 { c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": "密码至少 6 位"}) return } role := trimSpace(body.Role) if role != "super_admin" && role != "admin" { role = "admin" } hash, err := bcrypt.GenerateFromPassword([]byte(body.Password), bcrypt.DefaultCost) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": "密码加密失败"}) return } u := model.AdminUser{ Username: username, PasswordHash: string(hash), Role: role, Name: trimSpace(body.Name), Status: "active", } if err := db.Create(&u).Error; err != nil { if strings.Contains(err.Error(), "Duplicate") || strings.Contains(err.Error(), "1062") { c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": "用户名已存在"}) return } c.JSON(http.StatusOK, gin.H{"success": false, "error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{ "success": true, "data": gin.H{ "id": u.ID, "username": u.Username, "role": u.Role, "name": u.Name, "status": u.Status, "createdAt": u.CreatedAt, }, }) case http.MethodPut: var body struct { ID uint `json:"id" binding:"required"` Password string `json:"password"` Name string `json:"name"` Role string `json:"role"` Status string `json:"status"` } if err := c.ShouldBindJSON(&body); err != nil { c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": "参数错误"}) return } var u model.AdminUser if err := db.First(&u, body.ID).Error; err != nil { if err == gorm.ErrRecordNotFound { c.JSON(http.StatusNotFound, gin.H{"success": false, "error": "用户不存在"}) return } c.JSON(http.StatusOK, gin.H{"success": false, "error": err.Error()}) return } updates := make(map[string]interface{}) updates["name"] = trimSpace(body.Name) if body.Role == "super_admin" || body.Role == "admin" { updates["role"] = body.Role } if body.Status == "active" || body.Status == "disabled" { updates["status"] = body.Status } if body.Password != "" { if len(body.Password) < 6 { c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": "新密码至少 6 位"}) return } hash, err := bcrypt.GenerateFromPassword([]byte(body.Password), bcrypt.DefaultCost) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": "密码加密失败"}) return } updates["password_hash"] = string(hash) } if len(updates) > 0 { if err := db.Model(&u).Updates(updates).Error; err != nil { c.JSON(http.StatusOK, gin.H{"success": false, "error": err.Error()}) return } } var updated model.AdminUser _ = db.First(&updated, u.ID) c.JSON(http.StatusOK, gin.H{ "success": true, "data": gin.H{ "id": updated.ID, "username": updated.Username, "role": updated.Role, "name": updated.Name, "status": updated.Status, "updatedAt": updated.UpdatedAt, }, }) case http.MethodDelete: id := c.Query("id") if id == "" { c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": "缺少 id"}) return } uid64, err := strconv.ParseUint(strings.TrimSpace(id), 10, 32) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": "id 无效"}) return } uid := uint(uid64) var u model.AdminUser if err := db.First(&u, uid).Error; err != nil { if err == gorm.ErrRecordNotFound { c.JSON(http.StatusOK, gin.H{"success": true}) return } c.JSON(http.StatusOK, gin.H{"success": false, "error": err.Error()}) return } if u.Username == claims.Username { c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": "不能删除当前登录账号"}) return } if u.Role == "super_admin" { var cnt int64 db.Model(&model.AdminUser{}).Where("role = ?", "super_admin").Count(&cnt) if cnt <= 1 { c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": "至少保留一个超级管理员"}) return } } if err := db.Delete(&u).Error; err != nil { c.JSON(http.StatusOK, gin.H{"success": false, "error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"success": true}) default: c.JSON(http.StatusMethodNotAllowed, gin.H{"success": false, "error": "方法不支持"}) } }