package logic import ( "context" "errors" "strings" "time" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/net/ghttp" "github.com/gogf/gf/v2/os/gctx" "github.com/golang-jwt/jwt/v5" "tool-api/internal/consts" ) type jwtClaims struct { UserId int64 `json:"userId,omitempty"` LevelKey string `json:"levelKey,omitempty"` NickName string `json:"nickName,omitempty"` RoleName string `json:"roleName,omitempty"` RoleValue string `json:"roleValue,omitempty"` jwt.RegisteredClaims } func jwtSecret(ctx context.Context) []byte { return []byte(g.Cfg().MustGet(ctx, "jwt.secret", "tool-box-secret-change-me").String()) } // IssueToken 签发 JWT;aud 区分 user / admin func IssueToken(ctx context.Context, aud string, userId int64, extra map[string]string) (string, error) { hours := g.Cfg().MustGet(ctx, "jwt.expireHours", 168).Int() claims := jwtClaims{ UserId: userId, RegisteredClaims: jwt.RegisteredClaims{ Audience: jwt.ClaimStrings{aud}, Issuer: "tool-api", IssuedAt: jwt.NewNumericDate(time.Now()), ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Duration(hours) * time.Hour)), }, } for k, v := range extra { switch k { case "levelKey": claims.LevelKey = v case "nickName": claims.NickName = v case "roleName": claims.RoleName = v case "roleValue": claims.RoleValue = v } } return jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString(jwtSecret(ctx)) } func ParseToken(tokenStr, aud string) (*jwtClaims, error) { claims := &jwtClaims{} _, err := jwt.ParseWithClaims(tokenStr, claims, func(t *jwt.Token) (interface{}, error) { return jwtSecret(gctx.GetInitCtx()), nil }, jwt.WithValidMethods([]string{"HS256"}), jwt.WithAudience(aud)) if err != nil { return nil, err } return claims, nil } func parseBearer(r *ghttp.Request, aud string) (*jwtClaims, error) { auth := r.Header.Get("Authorization") tokenStr := strings.TrimPrefix(auth, "Bearer ") if tokenStr == "" { return nil, errors.New("empty token") } return ParseToken(tokenStr, aud) } func writeAuthFail(r *ghttp.Request) { r.Response.WriteJson(g.Map{"code": 401, "message": "登录已失效,请重新登录", "result": nil}) r.Exit() } // UserAuth 小程序用户鉴权 func UserAuth(r *ghttp.Request) { claims, err := parseBearer(r, consts.AudUser) if err != nil { writeAuthFail(r) return } r.SetCtxVar(consts.CtxUserId, claims.UserId) r.SetCtxVar(consts.CtxLevelKey, claims.LevelKey) r.Middleware.Next() } // AdminAuth 管理端鉴权 func AdminAuth(r *ghttp.Request) { claims, err := parseBearer(r, consts.AudAdmin) if err != nil { writeAuthFail(r) return } r.SetCtxVar(consts.CtxAdminId, claims.UserId) r.Middleware.Next() } // ===== 业务上下文取值 ===== func CtxUserId(ctx context.Context) int64 { return g.RequestFromCtx(ctx).GetCtxVar(consts.CtxUserId).Int64() } func CtxLevelKey(ctx context.Context) string { return g.RequestFromCtx(ctx).GetCtxVar(consts.CtxLevelKey).String() } func CtxAdminId(ctx context.Context) int64 { // 非 HTTP 上下文(如单测 / 后台任务)无 *ghttp.Request:返回 0,避免 None 解引用 panic。 // 与 CtxAdminAccount / CtxAdminRole / CtxAdminName(admin_perm.go)的 nil 处理保持一致。 req := g.RequestFromCtx(ctx) if req == nil { return 0 } return req.GetCtxVar(consts.CtxAdminId).Int64() } func IsDebug(ctx context.Context) bool { return g.Cfg().MustGet(ctx, "debug", false).Bool() }