今天学习的知识点:
- Gin middleware
- c.Next()
- c.Abort()
- 日志中间件
- 认证中间件
- JWT基础
- 把用户信息放进context
Gin middleware
Gin中间件的底层原理是:责任链与洋葱模型。在Spring MVC中,拦截器(interceptor)依赖的是反射和多层代理,而Go Gin中,中间件的本质是一个函数数组的顺序执行。
Gin的核心数据结构gin.Context中,包含了一个中间件和业务处理函数的切片:
1 2 3 4 5
| type Context struct { handlers HandlersChain index int8 }
|
当你注册了中间件,并写了业务路由时,Gin会把他们按顺序拼接成一条流水线。例如:[Logger,Auth, TargetBusinessFunc]。
此时index的初始值是-1。
c.Next()
Gin中的c.Next()的作用是:继续执行后续middleware和最终handler。它前面的代码类似迁至逻辑,后面的代码类似后置逻辑,常用于日志记录、耗时统计、链路追踪等。
下面是Next()的代码:
1 2 3 4 5 6 7
| func (c *Context) Next() { c.index++ for c.index < int8(len(c.handlers)) { c.handlers[c.index](c) c.index++ } }
|
原理解析:
c.Next()只是手动递增了index,并用一个for循环继续往后执行切片里的函数。
- 如果你在中间件里不写
c.Next():当前中间件函数执行完后,主流程的for循环以来会自动推进index,继续执行后面的中间件。
- 如果你在中间件里写了
c.Next(): 会提前出发后面的中间件执行。当后面的中间件和业务函数全部执行完毕弹栈后,代码会回到c.Next()之后的部分继续执行。(洋葱模型)
下面通过一个例子日志中间件来说明:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| func LoggerMiddleware() gin.HandlerFunc { return func(c *gin.Context) { start := time.Now()
path := c.Request.URL.Path method := c.Request.Method
c.Next()
duration := time.Since(start) status := c.Writer.Status()
log.Printf("%s %s %d %s", method, path, status, duration) } }
|
将原来main()方法修改为:
1 2 3
| r := gin.New() r.Use(gin.Recovery()) r.Use(LoggerMiddleware())
|
启动后,随便请求一个地址,此时就会在控制台输出如下内容:
1
| 2026/06/11 08:51:41 GET /admin/users 200 0s
|
由此看出,中间件的本质就是
1
| type HandlerFunc func(* gin.Context)
|
c.Next() 前面的代码在请求进入时执行
c.Next()后面的代码在后续handler执行玩抽后执行。
c.Abort()
下面通过一个例子来说明:
1 2 3 4 5 6 7 8 9 10 11 12
| func AuthMiddleware() gin.HandlerFunc { return func(c *gin.Context) { token := c.GetHeader("Authorization") if token == "" { response.Unauthorized(c, "missing token") c.Abort() return } c.Next() } }
|
使用:在main()中新增:
1 2 3 4 5
| admin := r.Group("/admin") admin.Use(AuthMiddleware()) { admin.GET("/users", listUser) }
|
请求:
1
| GET http://localhost:8080/admin/users
|
因为在Header中并没有设置Authorization,所以会提示如下:
1 2 3 4
| { "code": "UNAUTHORIZED", "message": "missing token" }
|
在Header中设置Authorization属性后,返回数据。
c.Abort()会组织后续handler执行,在认证失败、权限不足、非法请求时常用。
c.Abort和return的区别:return只是结束当前函数;c.Abort()会告诉Gin不再执行后续handler。实际中通常两者一起用;先写响应,再c.Abort(),然后return。
JWT认证中间件
JWT是比较常用的身份验证中间件,下面通过一个例子来实际编码体验一下:
安装jwt模块
1
| go get -u github.com/golang-jwt/jwt/v5
|
pkg/utils/jwt.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| package utils
import ( "errors" "time"
"github.com/golang-jwt/jwt/v5" )
var JWTSecret = []byte("your_secret")
type MyCustomClaims struct { ID int64 `json:"id"` UserName string `json:"username"` jwt.RegisteredClaims }
func GenerateToken(id int64, username string) (string, error) { claims := MyCustomClaims{ ID: id, UserName: username, RegisteredClaims: jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(time.Now().Add(2 * time.Hour)), IssuedAt: jwt.NewNumericDate(time.Now()), }, } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) return token.SignedString(JWTSecret) }
func ParseToken(tokenstr string) (*MyCustomClaims, error) { token, err := jwt.ParseWithClaims(tokenstr, &MyCustomClaims{}, func(token *jwt.Token) (interface{}, error) { return JWTSecret, nil })
if err != nil { return nil, err }
if claims, ok := token.Claims.(*MyCustomClaims); ok && token.Valid { return claims, nil }
return nil, errors.New("invalid token") }
|
使用方式,新建一个中间件:AuthMiddleware,我把它放在middleware/handlers.go里面:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| package middleware
import ( "log" "strings" "time"
"dev.net.cn/goweb/pkg/utils" "dev.net.cn/goweb/response" "github.com/gin-gonic/gin" )
func AuthMiddleware() gin.HandlerFunc { return func(c *gin.Context) { token := c.GetHeader("Authorization") if token == "" { response.Unauthorized(c, "missing token") c.Abort() return }
parts := strings.SplitN(token, " ", 2) if !(len(parts) == 2 && parts[0] == "Bearer") { response.Unauthorized(c, "invalid token") c.Abort() return }
claims, err := utils.ParseToken(parts[1]) if err != nil { response.Unauthorized(c, "invalid token") c.Abort() return } c.Set("currentUserID", claims.ID) c.Set("currentUsername", claims.UserName) c.Next() } }
|
然后在r.Group("/api/d2").Use(middleware.AuthMiddleware())使用它。
1 2 3 4 5
| d2Group := r.Group("/api/d2").Use(middleware.AuthMiddleware()) { d2Group.GET("/auth", controllers.GetUserListHandler) }
|
c.Set("currentUserID",claims.ID) 可以把认证结果放进Gin Context,后续的handler可以c.Get("currentUserID")获取它。