package handler import ( "net/http" "soul-api/internal/auth" "soul-api/internal/config" "soul-api/internal/database" "soul-api/internal/model" "github.com/gin-gonic/gin" "golang.org/x/crypto/bcrypt" "gorm.io/gorm" ) // AdminCheck GET /api/admin 鉴权检查(JWT:Authorization Bearer 或 Cookie),已登录返回 success 或概览占位 func AdminCheck(c *gin.Context) { cfg := config.Get() if cfg == nil { c.JSON(http.StatusOK, gin.H{"success": true}) return } token := auth.GetAdminJWTFromRequest(c.Request) if _, ok := auth.ParseAdminJWT(token, cfg.AdminSessionSecret); !ok { c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"success": false, "error": "未授权访问,请先登录"}) return } c.JSON(http.StatusOK, gin.H{ "success": true, "content": gin.H{ "totalChapters": 0, "totalWords": 0, "publishedChapters": 0, "draftChapters": 0, "lastUpdate": nil, }, "payment": gin.H{ "totalRevenue": 0, "todayRevenue": 0, "totalOrders": 0, "todayOrders": 0, "averagePrice": 0, }, "referral": gin.H{ "totalReferrers": 0, "activeReferrers": 0, "totalCommission": 0, "paidCommission": 0, "pendingCommission": 0, }, "users": gin.H{ "totalUsers": 0, "purchasedUsers": 0, "activeUsers": 0, "todayNewUsers": 0, }, }) } // AdminLogin POST /api/admin 登录(优先校验 admin_users 表,表空时回退 ADMIN_USERNAME/PASSWORD 并自动初始化) func AdminLogin(c *gin.Context) { cfg := config.Get() if cfg == nil { c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": "配置未加载"}) return } var body struct { Username string `json:"username" binding:"required"` Password string `json:"password" binding:"required"` } if err := c.ShouldBindJSON(&body); err != nil { c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": "参数错误"}) return } username := trimSpace(body.Username) password := body.Password db := database.DB() // 1. 尝试从 admin_users 表校验 var u model.AdminUser err := db.Where("username = ?", username).First(&u).Error if err == nil { if u.Status != "active" { c.JSON(http.StatusUnauthorized, gin.H{"success": false, "error": "账号已禁用"}) return } if bcryptErr := bcrypt.CompareHashAndPassword([]byte(u.PasswordHash), []byte(password)); bcryptErr != nil { c.JSON(http.StatusUnauthorized, gin.H{"success": false, "error": "用户名或密码错误"}) return } token, err := auth.IssueAdminJWT(cfg.AdminSessionSecret, u.Username, u.Role) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": "签发失败"}) return } c.JSON(http.StatusOK, gin.H{ "success": true, "token": token, "user": gin.H{"id": u.ID, "username": u.Username, "role": u.Role, "name": u.Name}, }) return } // 2. 表内无匹配:若表为空且 env 账号正确,则创建初始 super_admin 并登录 if err != gorm.ErrRecordNotFound { c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": "系统错误"}) return } if cfg.AdminUsername == "" || cfg.AdminPassword == "" { c.JSON(http.StatusUnauthorized, gin.H{"success": false, "error": "用户名或密码错误"}) return } if username != cfg.AdminUsername || password != cfg.AdminPassword { c.JSON(http.StatusUnauthorized, gin.H{"success": false, "error": "用户名或密码错误"}) return } // 表为空时初始化超级管理员 var cnt int64 if db.Model(&model.AdminUser{}).Count(&cnt).Error != nil || cnt > 0 { c.JSON(http.StatusUnauthorized, gin.H{"success": false, "error": "用户名或密码错误"}) return } hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": "初始化失败"}) return } initial := model.AdminUser{ Username: cfg.AdminUsername, PasswordHash: string(hash), Role: "super_admin", Name: "卡若", Status: "active", } if err := db.Create(&initial).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": "初始化失败"}) return } token, err := auth.IssueAdminJWT(cfg.AdminSessionSecret, initial.Username, initial.Role) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": "签发失败"}) return } c.JSON(http.StatusOK, gin.H{ "success": true, "token": token, "user": gin.H{"id": initial.ID, "username": initial.Username, "role": initial.Role, "name": initial.Name}, }) } // AdminLogout POST /api/admin/logout 服务端无状态,仅返回成功;前端需清除本地 token func AdminLogout(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"success": true}) } func trimSpace(s string) string { start := 0 for start < len(s) && (s[start] == ' ' || s[start] == '\t') { start++ } end := len(s) for end > start && (s[end-1] == ' ' || s[end-1] == '\t') { end-- } return s[start:end] }