Gin中间件核心是func(*gin.Context)函数,必须显式调用c.Next()推进流程,否则请求卡住;c.Abort()终止后续处理,c.Set()/c.Get()安全传参。

直接上手写中间件,关键就三点:函数签名必须是 func(c *gin.Context)、用 c.Next() 控制流程、别忘了 c.Abort() 中断链。其他都是围绕这三点展开的细节。
为什么 c.Next() 必须显式调用?
Gin 不会自动把请求“推”给下一个中间件或 handler——它靠你手动触发 c.Next() 来往下走。漏掉这句,后续所有中间件和路由函数都不会执行,请求就卡在当前中间件里了。
- 常见错误现象:
curl一直挂起无响应,日志只打印前置逻辑,没看到后置或业务日志 - 本质原因:Gin 的 handler 链是惰性执行的,
c.Next()才是“继续”的信号 - 调试技巧:在中间件开头加
log.Printf("middleware A enter, index=%d", c.Index()),能直观看到执行到哪一层就停了
c.Abort() 和 c.AbortWithStatusJSON() 怎么选?
两者都终止后续流程,但语义和用途不同。
-
c.Abort()纯中断,不写响应体,适合做“守门人”但不返回内容(比如预检跨域时直接放行 OPTIONS 请求) -
c.AbortWithStatusJSON()自动设置状态码 + JSON 响应体 + 中断,适合鉴权失败、参数校验不通过等需明确反馈的场景 - 容易踩的坑:在
c.AbortWithStatusJSON(401, ...)后又写了c.JSON(200, ...)——后者不会生效,但可能引发 panic 或日志混乱
如何安全地在中间件间传数据?
用 c.Set() / c.Get() 是唯一推荐方式。不要用全局变量或闭包捕获,否则并发下会错乱。
- 典型用法:
c.Set("user_id", 123)在鉴权中间件里存用户 ID,后续中间件或 handler 用uid, ok := c.Get("user_id")取 - 注意类型断言:
c.Get()返回interface{},必须显式转成你需要的类型,比如uid := c.GetInt64("user_id") - 性能影响:底层是 map[string]interface{},读写开销极小,但别塞大对象(如整个 user struct),避免 GC 压力
全局中间件 vs 路由组中间件的注册时机差异
注册顺序决定执行顺序,但作用范围不同。
-
router.Use(mwA, mwB):全局注册,所有路由(包括未分组的)都会经过mwA → mwB -
v1 := router.Group("/v1"); v1.Use(mwC):仅/v1下的路由走mwC,且它会在全局中间件之后、路由 handler 之前执行(洋葱模型外层套内层) - 陷阱:如果在
Group().Use()里注册了日志中间件,而全局又注册了一个,会导致重复打日志;建议日志放全局,鉴权放 Group 级
最常被忽略的一点:中间件函数本身不能有长耗时阻塞操作(比如同步 HTTP 调用、大文件读取),否则会拖垮整个 handler 链的并发能力。真要 IO,优先考虑异步通知或缓存兜底,而不是在中间件里硬等。


















