gin中间件必须是func(*gin.context)类型函数,不返回值,通过c.next()、c.abort()控制流程;c.abort()终止后续中间件和handler,return仅退出当前函数;注册顺序决定执行顺序,c.keys需用自定义key类型防冲突。

中间件函数签名必须返回 func(*gin.Context)
Gin 中间件本质是函数,不是结构体或接口。写错签名会导致编译失败或 panic,比如写成 func() *gin.Context 或 func(http.ResponseWriter, *http.Request) 都不生效。
正确签名只有一种:func(*gin.Context)。它不返回值,靠修改传入的 *gin.Context 控制流程(比如调用 c.Next()、c.Abort() 或 c.JSON())。
- 必须接收且仅接收一个
*gin.Context参数 - 不能有返回值(void 函数)
- 不能直接用
net/http的 handler 签名混用 - 如果想复用已有 http middleware,得用
gin.WrapH()包一层
如何在中间件里提前终止请求(c.Abort() vs return)
很多人以为 return 就能跳出中间件,其实不行——Gin 的中间件链是靠 c.Next() 显式推进的,return 只结束当前函数执行,后续中间件仍会运行。
真正拦截请求要靠 c.Abort():它会清空 pending 的中间件栈,阻止后续所有中间件和最终 handler 执行。但注意,c.Abort() 不影响已经执行过的前置中间件(比如日志中间件已打完日志)。
- 鉴权失败时:先调
c.Abort(),再c.JSON(401, ...) - 不要只写
c.JSON()就return,否则后续中间件还会跑 -
c.AbortWithStatusJSON()是常用组合,内部已含Abort() - 调试时可在中间件开头加
fmt.Printf("in: %s\n", c.Request.URL.Path)观察是否被跳过
全局注册 vs 路由组注册:作用域差异直接影响行为
r.Use(mw1, mw2) 是全局中间件,所有路由都会走;而 rg := r.Group("/api"); rg.Use(mw3) 只对 /api 下的路由生效。但容易忽略的是:中间件注册顺序 = 执行顺序,且嵌套组会叠加。
比如 r.Use(A) + rg.Use(B) + rg.GET("/x", h),实际执行顺序是 A → B → h。如果 B 里没调 c.Next(),h 根本不会执行。
- 全局中间件适合日志、recover、CORS
- 路由组中间件适合权限控制(如 /admin 下才校验 admin role)
- 不要在中间件里依赖未初始化的路由参数(
c.Param("id")在GET /user/:id之外拿不到) - 用
c.FullPath()比c.Request.URL.Path更准,它包含路由定义的 pattern
中间件里读写 c.Keys 传参,但类型安全得自己兜底
Gin 用 map[interface{}]interface{} 实现 c.Keys,方便中间件之间传值(比如 JWT 解析出的 user ID),但它不校验 key 类型、也不防冲突。
常见坑是多个中间件用相同字符串 key 覆盖值,或者取值时类型断言失败 panic。建议统一用自定义 key 类型(如 type ctxKey string)避免字符串碰撞。
- 定义 key:
const UserKey ctxKey = "user" - 存:
c.Set(string(UserKey), userObj) - 取:
if u, ok := c.Get(string(UserKey)).(*User); ok { ... } - 别用
c.MustGet(),它 panic,线上环境慎用 - 敏感数据(如 token)别塞进
c.Keys,它生命周期等同于请求,无自动清理
c.Keys、c.Errors)和流程控制(Abort() / Next())稍有偏差,整个请求链就可能静默失效。最麻烦的是那种“看起来跑了,但 handler 没执行”的情况——八成是某个中间件忘了 c.Next() 或误用了 return。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











