package handlers import ( "context" "errors" "net/http" "time" "go.mongodb.org/mongo-driver/v2/bson" "go.mongodb.org/mongo-driver/v2/mongo" "yh_web/server/config" "yh_web/server/models" "yh_web/server/pkg/logger" "yh_web/server/utils" "github.com/gin-gonic/gin" "github.com/golang-jwt/jwt/v5" ) const jwtSecret = "yh_web_admin_jwt_secret_change_in_production" const jwtExpire = 24 * time.Hour type LoginInput struct { Username string `json:"username" binding:"required"` Password string `json:"password" binding:"required"` } type Claims struct { UserID string `json:"user_id"` Username string `json:"username"` RoleID int `json:"role_id"` Role string `json:"role"` jwt.RegisteredClaims } // Login 后台登录,仅 role_id=9527 超级管理员可登录 func Login(c *gin.Context) { var input LoginInput if err := c.ShouldBindJSON(&input); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "用户名和密码不能为空"}) return } ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() coll := config.GetDB(config.DBName).Collection("users") var user models.User err := coll.FindOne(ctx, bson.M{"username": input.Username}).Decode(&user) if err != nil { if errors.Is(err, mongo.ErrNoDocuments) { // 尝试用手机号登录 err = coll.FindOne(ctx, bson.M{"mobile": input.Username}).Decode(&user) } if err != nil { if errors.Is(err, mongo.ErrNoDocuments) { c.JSON(http.StatusUnauthorized, gin.H{"error": "用户名或密码错误"}) return } logger.Err("handlers/auth", "[Login] FindOne error: %v", err) resp := gin.H{"error": "登录失败,请稍后重试"} if gin.Mode() == gin.DebugMode { resp["debug"] = err.Error() } c.JSON(http.StatusInternalServerError, resp) return } } // 超级管理员(9527)或超级用户(role_id=0, role=admin)可登录后台 if user.RoleID != models.RoleIDSuperAdmin && !(user.RoleID == models.RoleIDSuperUser && user.Role == "admin") { c.JSON(http.StatusForbidden, gin.H{"error": "无后台访问权限"}) return } roleID := user.RoleID if roleID == 0 && user.Role == "admin" { roleID = models.RoleIDSuperAdmin } hashed := utils.HashPassword(input.Password) if hashed != user.Password { c.JSON(http.StatusUnauthorized, gin.H{"error": "用户名或密码错误"}) return } claims := Claims{ UserID: user.ID.Hex(), Username: user.Username, RoleID: roleID, Role: user.Role, RegisteredClaims: jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(time.Now().Add(jwtExpire)), IssuedAt: jwt.NewNumericDate(time.Now()), }, } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) tokenStr, err := token.SignedString([]byte(jwtSecret)) if err != nil { logger.Err("handlers/auth", "JWT SignedString error: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "生成令牌失败"}) return } c.JSON(http.StatusOK, gin.H{ "token": tokenStr, "user": gin.H{ "id": user.ID.Hex(), "username": user.Username, "role_id": roleID, "role": user.Role, }, "expires_in": int64(jwtExpire.Seconds()), }) } // AuthRequired 鉴权中间件,要求 role_id=9527 func AuthRequired() gin.HandlerFunc { return func(c *gin.Context) { tokenStr := c.GetHeader("Authorization") if tokenStr == "" { tokenStr = c.Query("token") } if len(tokenStr) > 7 && tokenStr[:7] == "Bearer " { tokenStr = tokenStr[7:] } if tokenStr == "" { c.JSON(http.StatusUnauthorized, gin.H{"error": "请先登录"}) c.Abort() return } var claims Claims token, err := jwt.ParseWithClaims(tokenStr, &claims, func(t *jwt.Token) (interface{}, error) { return []byte(jwtSecret), nil }) if err != nil || !token.Valid { c.JSON(http.StatusUnauthorized, gin.H{"error": "登录已过期,请重新登录"}) c.Abort() return } // 仅超级管理员或超级用户(role_id=0, role=admin)可访问后台 if claims.RoleID != models.RoleIDSuperAdmin && !(claims.RoleID == models.RoleIDSuperUser && claims.Role == "admin") { c.JSON(http.StatusForbidden, gin.H{"error": "无后台访问权限"}) c.Abort() return } c.Set("user_id", claims.UserID) c.Set("username", claims.Username) c.Set("role_id", claims.RoleID) c.Set("role", claims.Role) c.Next() } } // SuperUserAuthRequired 超级用户鉴权:仅 role_id=0 且 role=admin 可访问(如短信平台配置) // 集团超级用户 username=admin 只能配置集团信息,不能配置短信 func SuperUserAuthRequired() gin.HandlerFunc { return func(c *gin.Context) { tokenStr := c.GetHeader("Authorization") if tokenStr == "" { tokenStr = c.Query("token") } if len(tokenStr) > 7 && tokenStr[:7] == "Bearer " { tokenStr = tokenStr[7:] } if tokenStr == "" { c.JSON(http.StatusUnauthorized, gin.H{"error": "请先登录"}) c.Abort() return } var claims Claims token, err := jwt.ParseWithClaims(tokenStr, &claims, func(t *jwt.Token) (interface{}, error) { return []byte(jwtSecret), nil }) if err != nil || !token.Valid { c.JSON(http.StatusUnauthorized, gin.H{"error": "登录已过期,请重新登录"}) c.Abort() return } // 仅 role_id=9527 且 role=admin 可配置短信平台 if claims.RoleID != models.RoleIDSuperAdmin || claims.Role != "admin" { c.JSON(http.StatusForbidden, gin.H{"error": "仅超级管理员可配置短信平台"}) c.Abort() return } c.Set("user_id", claims.UserID) c.Set("username", claims.Username) c.Set("role_id", claims.RoleID) c.Set("role", claims.Role) c.Next() } }