From 7ce5cccd303f8a6558e9a8b22358ef75d712fd26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E6=96=8C?= <259278618@qq.com> Date: Thu, 13 Aug 2026 12:12:23 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=B6=E9=95=BF=20token=20=E9=89=B4?= =?UTF-8?q?=E6=9D=83=E4=B8=AD=E9=97=B4=E4=BB=B6=E4=B8=8E=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E5=88=86=E7=BB=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- biz/controller/router.go | 15 ++++++- biz/service/auth.go | 27 +++++++++++++ common/auth/auth.go | 84 ++++++++++++++++++++++++++++++++++++++++ config.yml | 3 ++ 4 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 biz/service/auth.go create mode 100644 common/auth/auth.go diff --git a/biz/controller/router.go b/biz/controller/router.go index af4300c..c424337 100644 --- a/biz/controller/router.go +++ b/biz/controller/router.go @@ -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, "")) + }) } diff --git a/biz/service/auth.go b/biz/service/auth.go new file mode 100644 index 0000000..981fdde --- /dev/null +++ b/biz/service/auth.go @@ -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() +} diff --git a/common/auth/auth.go b/common/auth/auth.go new file mode 100644 index 0000000..77c2723 --- /dev/null +++ b/common/auth/auth.go @@ -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 ,注入 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 "" +} diff --git a/config.yml b/config.yml index 7923281..04b6cd8 100644 --- a/config.yml +++ b/config.yml @@ -12,3 +12,6 @@ database: maxSize: 10000 pool: defaultSize: 10 +auth: + secret: "36wisdom-dev-secret-change-in-prod" + expire: 604800