feat: 家长 token 鉴权中间件与路由分组

This commit is contained in:
2026-08-13 12:12:23 +08:00
parent 16171e151f
commit 7ce5cccd30
4 changed files with 128 additions and 1 deletions
+14 -1
View File
@@ -1,10 +1,23 @@
package controller
import "github.com/gogf/gf/v2/net/ghttp"
import (
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/os/gctx"
"36wisdom/common/auth"
)
// Register 注册全部路由分组;各业务控制器在对应任务中挂载。
func Register(s *ghttp.Server) {
s.BindHandler("GET:/ping", func(r *ghttp.Request) {
r.Response.Write("pong")
})
secret := g.Cfg().MustGet(gctx.New(), "auth.secret").String()
// 前台:家长鉴权组(公开注册/登录接口除外),业务控制器按模块挂载
s.Group("/api", func(g *ghttp.RouterGroup) {
g.Middleware(auth.Middleware(secret, ""))
})
}
+27
View File
@@ -0,0 +1,27 @@
package service
import (
"context"
"github.com/gogf/gf/v2/frame/g"
"36wisdom/biz/consts"
"36wisdom/common/auth"
)
// authSvc 鉴权服务:token 签发与解析,密钥统一来自配置 auth.secret。
type authSvc struct{}
var Auth = &authSvc{}
func (s *authSvc) GenerateToken(ctx context.Context, uid int64, role string) (string, error) {
return auth.GenerateToken(secret(ctx), uid, role, consts.AuthExpireSeconds)
}
func (s *authSvc) ParseToken(ctx context.Context, tokenString string) (int64, string, error) {
return auth.ParseToken(secret(ctx), tokenString)
}
func secret(ctx context.Context) string {
return g.Cfg().MustGet(ctx, "auth.secret").String()
}
+84
View File
@@ -0,0 +1,84 @@
package auth
import (
"context"
"strings"
"time"
"github.com/golang-jwt/jwt/v5"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/util/gconv"
)
// ctxKey 注入 request context 的键名。
type ctxKey string
const (
keyUid ctxKey = "authUid"
keyRole ctxKey = "authRole"
)
// GenerateToken 签发 HS256 JWT。
func GenerateToken(secret string, uid int64, role string, expireSeconds int) (string, error) {
claims := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"uid": uid,
"role": role,
"exp": time.Now().Add(time.Duration(expireSeconds) * time.Second).Unix(),
})
return claims.SignedString([]byte(secret))
}
// ParseToken 校验并解析 token,返回 uid 与 role。
func ParseToken(secret, tokenString string) (uid int64, role string, err error) {
token, err := jwt.Parse(tokenString, func(t *jwt.Token) (any, error) {
return []byte(secret), nil
})
if err != nil || !token.Valid {
return 0, "", gerror.New("登录状态已失效,请重新登录")
}
claims, ok := token.Claims.(jwt.MapClaims)
if !ok {
return 0, "", gerror.New("登录状态解析失败")
}
return gconv.Int64(claims["uid"]), gconv.String(claims["role"]), nil
}
// Middleware 校验 Authorization: Bearer <token>,注入 uid/role 到请求上下文。
// requiredRole 为空表示任意登录角色;非空则额外校验角色匹配。
func Middleware(secret, requiredRole string) ghttp.HandlerFunc {
return func(r *ghttp.Request) {
tokenString := strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer ")
if tokenString == "" {
r.Response.WriteStatusExit(401, "未登录")
}
uid, role, err := ParseToken(secret, tokenString)
if err != nil {
r.Response.WriteStatusExit(401, err.Error())
}
if requiredRole != "" && role != requiredRole {
r.Response.WriteStatusExit(403, "无权限访问")
}
r.SetCtxVar(string(keyUid), uid)
r.SetCtxVar(string(keyRole), role)
r.Middleware.Next()
}
}
// GetUid 从请求上下文取登录 uid(未登录返回 0)。
func GetUid(ctx context.Context) int64 {
if req := g.RequestFromCtx(ctx); req != nil {
return gconv.Int64(req.GetCtxVar(string(keyUid)).Val())
}
return 0
}
// GetRole 从请求上下文取登录角色。
func GetRole(ctx context.Context) string {
if req := g.RequestFromCtx(ctx); req != nil {
return gconv.String(req.GetCtxVar(string(keyRole)).Val())
}
return ""
}
+3
View File
@@ -12,3 +12,6 @@ database:
maxSize: 10000
pool:
defaultSize: 10
auth:
secret: "36wisdom-dev-secret-change-in-prod"
expire: 604800