Files
soul-yongping/soul-api/internal/handler/admin.go

153 lines
4.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 鉴权检查JWTAuthorization 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]
}