中间件函数签名必须是func(*gin.Context),写成func(gin.Context)或带返回值会导致运行时panic;须在路由前调用Use();权限失败需c.Abort()终止链;c.Request.Body只能读一次;c.Next()位置决定前后置逻辑顺序。

中间件函数签名必须是 func(*gin.Context)
写成 func(gin.Context) 或带返回值的变体,编译不报错但运行时直接 panic,错误类似 panic: interface conversion: interface {} is func(), not gin.HandlerFunc。Gin 在注册时做严格类型断言,不匹配就炸。
正确写法只有两种:
-
func(c *gin.Context) { c.Next() }—— 匿名函数直传,适合无配置场景 -
func() gin.HandlerFunc { return func(c *gin.Context) { ... } }—— 闭包,可接收配置参数,比如AuthMiddleware("admin")
别把函数名直接传给 r.Use():写 r.Use(Logger) 要求 Logger 本身已是 gin.HandlerFunc 类型;若定义为普通函数 func Logger() gin.HandlerFunc,必须加括号调用:r.Use(Logger()),漏括号会传函数地址而非中间件实例。
r.Use() 必须在路由注册前调用
把 r.Use(mw) 放在 r.GET("/api", handler) 后面,中间件完全不生效。Gin 的中间件链是在路由匹配前静态构建的,晚了插不进去。
常见翻车位置:
- 全局中间件写在
r.Run()之后 - 分组中间件写在
v1 := r.Group("/api")之后,但没对v1调用.Use() - 误用已废弃的
UseMiddleware()(1.9+ 统一用Use())
正确顺序示例:r.Use(gin.Recovery(), authMW); v1 := r.Group("/api"); v1.Use(logMW); v1.GET("/users", handler)。
权限失败必须调用 c.Abort(),不能只 return
鉴权失败时只写 if !valid { c.JSON(401, gin.H{"err": "unauthorized"}); return } 是错的——c.Next() 不会被跳过,后续中间件和 handler 仍会执行,可能造成越权或重复响应。
必须显式终止链:
-
c.Abort():终止,状态码默认 200,不写响应体 -
c.AbortWithStatus(401):终止 + 设状态码 -
c.AbortWithStatusJSON(401, gin.H{}):终止 + 状态码 + JSON 响应
调用 c.Abort() 后,c.Next() 不再有意义,也不该再调用;否则行为未定义。
读 c.Request.Body 只能一次,别在中间件里直接解析
很多中间件想验签或记录原始 body,直接 ioutil.ReadAll(c.Request.Body) 或 c.ShouldBindJSON(&v),会导致后续 handler 拿不到数据——因为 Body 是单次读取的 io.ReadCloser,流已耗尽。
安全做法:
- 优先用非消耗性方法:
c.PostForm()、c.Query()、c.GetHeader() - 真要读 body,先缓存再重置:
bodyBytes, _ := io.ReadAll(c.Request.Body); c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes)) - Gin v1.9+ 推荐用
c.Copy()复制上下文,避免污染原请求
大文件慎用缓存,内存吃紧;JWT 解析、日志记录这类轻量操作才适合中间件内处理。











