中间件函数签名必须是func(*gin.context),漏指针或错误返回值会导致panic或未注册;use()须在路由前调用;权限失败需c.abort()终止链;c.request.body只能读一次;c.next()位置决定前后置逻辑顺序。

中间件函数签名必须是 func(*gin.Context)
写错签名是新手最常踩的坑,比如漏掉指针符号写成 func(gin.Context),编译能过但运行时 panic;或者返回值写成 func() gin.HandlerFunc 却没调用它,导致中间件根本没注册进去。
正确写法只有两种等价形式:
-
func(c *gin.Context) { ... }(匿名函数直接传给Use()) -
func() gin.HandlerFunc { return func(c *gin.Context) { ... } }(闭包,适合带配置参数)
别用普通函数名直接传——r.Use(Logger) 要求 Logger 本身就得是 gin.HandlerFunc 类型,不是 func()。
必须在路由注册前调用 Use()
把 r.Use(mw) 放在 r.GET(...) 后面,中间件完全不生效。Gin 的中间件链是在路由匹配前构建好的,晚了就插不进去了。
常见错误场景:
- 全局中间件写在
r.Run()之后 - 分组中间件写在
v1 := r.Group("/api")之后、但没在v1上调用.Use() - 误用旧版
UseMiddleware(),新项目统一用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 } 是错的——后续中间件和 handler 还会继续执行。
必须显式调用 c.Abort() 或其变体:
-
c.Abort():终止链,不写响应体(状态码默认 200) -
c.AbortWithStatus(401):终止 + 设状态码 -
c.AbortWithStatusJSON(401, gin.H{...}):终止 + 设状态码 + 写 JSON 响应
调用 c.Abort() 后,c.Next() 不再有意义,也不该再调用。
c.Request.Body 只能读一次,别在中间件里反复解析
很多中间件想验签或记录原始 body,直接 c.ShouldBindJSON(&v) 或 ioutil.ReadAll(c.Request.Body) 会导致后续 handler 拿不到数据——因为 Body 是 io.ReadCloser,流已耗尽。
安全做法:
- 优先从
c.PostForm()、c.Query()、c.GetHeader()这类非消耗性方法取数据 - 真要读 body,用
c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))重置(注意内存和性能) - Gin v1.9+ 推荐用
c.Copy()复制上下文,或提前用c.Set("raw_body", data)传递解析结果
调试时发现 body 为空,八成是某个中间件已经读过了。
中间件链里c.Next() 的位置决定逻辑先后,前置处理写在它前面,后置处理写在它后面——这个顺序一错,日志时间、状态码、耗时统计全乱。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











