自定义中间件必须严格满足gin.HandlerFunc类型,即签名func(*gin.Context),漏指针、多参数或带返回值均导致r.Use()注册时panic;正确写法仅限匿名函数直传或闭包返回,且须注意调用括号。

自定义中间件不是“写个函数塞进去就行”,它必须严格满足 gin.HandlerFunc 类型,否则 r.Use() 会直接 panic,而不是静默失败。
中间件函数签名必须是 func(*gin.Context)
这是最常翻车的点:漏指针、多参数、带返回值都会导致运行时 panic。Gin 在注册时做类型断言,错误信息类似 panic: interface conversion: interface {} is func(), not gin.HandlerFunc。
- ❌ 错误写法:
func(c gin.Context)(少*)、func(c *gin.Context) error(多返回值)、func(c *gin.Context, cfg Config)(多参数) - ✅ 正确写法只有两种:
– 匿名函数直传:r.Use(func(c *gin.Context) { c.Next() })
– 闭包返回:func AuthMiddleware(role string) gin.HandlerFunc { return func(c *gin.Context) { ... } },然后调用r.Use(AuthMiddleware("admin")) - ⚠️ 注意括号:写成
r.Use(AuthMiddleware)是传函数地址,不会执行闭包;必须加括号r.Use(AuthMiddleware("admin"))才拿到真正的gin.HandlerFunc
c.Next() 和 c.Abort() 控制流程,不能混用
c.Next() 是同步阻塞调用,它会等整个后续链(包括其他中间件和最终 handler)全部执行完,才继续执行当前中间件里 c.Next() 后面的代码。而 c.Abort() 一调就跳过所有后续环节,但当前函数体仍会继续往下跑。
- 权限校验失败时,只写
c.JSON(401, ...); return是错的——c.Next()没被调,但后续中间件和 handler 还会执行 - 必须配对使用:
c.AbortWithStatusJSON(401, gin.H{"err": "unauthorized"}),它内部已含c.Abort() -
c.Next()后不能再写响应(比如又调一次c.JSON()),会 panic:”write header after body written”
中间件顺序决定行为是否生效
Gin 中间件按 r.Use() 注册顺序入链,执行是洋葱模型:先入者后出。顺序错一位,日志可能漏记、panic 可能没被捕获、用户信息可能拿不到。
- 全局
Recovery必须在最外层(r.Use(gin.Recovery())),否则 panic 会崩掉进程;别把它塞进某个Group().Use()里 - 鉴权中间件要放在
Recovery之后、业务 handler 之前;如果放在Logger后面,认证失败的请求就不会出现在日志里 - 读
c.Request.Body的中间件(如验签、审计)必须在所有依赖ShouldBindJSON的 handler 之前,且需手动缓存重置:bodyBytes, _ := io.ReadAll(c.Request.Body); c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
goroutine 里用 c.Copy(),别直接传原始 *gin.Context
*gin.Context 不是线程安全的,一旦进入 goroutine,原上下文可能在 handler 返回后就被回收。这时候再读 c.Get("user") 或写响应,大概率 panic 或返回空值。
- 需要异步处理(如发 MQ、写审计日志)时,必须先调
ctx := c.Copy(),再把ctx传进 goroutine -
c.Copy()会深拷贝关键字段(如Values、Request头部),但不会复制Body流,所以 Body 缓存逻辑仍需单独处理 - 别图省事在 goroutine 里直接用
c—— 看似能跑通,压测或高并发时必现竞态
真正难的不是写中间件,而是想清楚它在哪一层介入、谁依赖它的输出、谁会被它中断。一个 c.Set("user") 调得再漂亮,如果中间件注册顺序靠后,下游永远 c.Get("user") 不到。











