Gin中间件必须返回func(*gin.Context)类型函数,注册须用Use()且顺序执行,c.Next()控制流程、c.Abort()终止后续,Body只能读一次,panic捕获需将gin.Recovery()置于最外层。

gin.HandlerFunc 是自定义中间件的唯一入口类型,所有中间件必须返回它 —— 这不是可选项,而是 Gin 的执行模型硬性要求。
中间件函数签名必须是 func(*gin.Context)
Gin 不接受任何其他签名。常见错误包括:
- 漏写
*gin.Context参数,或误写为gin.Context(没加指针) - 返回值不是
gin.HandlerFunc,比如直接返回error或bool - 试图在中间件里写
return后继续执行逻辑(c.Next()之后的代码仍会运行,但需注意上下文状态)
正确写法只有一种模式:func() gin.HandlerFunc { return func(c *gin.Context) { ... } }。闭包封装不是为了炫技,而是为了支持参数化配置(比如日志级别、白名单路径前缀)。
c.Next() 决定中间件是“前置”还是“前后置”
调用 c.Next() 的位置,直接决定逻辑执行时机:
- 放在开头:只做请求前处理(如鉴权拦截),后续逻辑不执行时必须配
c.Abort() - 放在中间:先执行前置逻辑,再进路由,最后执行后置逻辑(如耗时统计、响应日志)
- 不调用
c.Next():该中间件彻底阻断链路,后续所有中间件和 handler 都不会触发
典型陷阱是忘记 c.Abort() 就直接 c.JSON() 返回,导致后续中间件仍被执行,可能引发 panic(如重复写 header)或逻辑冲突。
立即学习“go语言免费学习笔记(深入)”;
全局注册 vs 路由组注册:影响范围完全不同
r.Use(...) 和 group.Use(...) 看似只是调用位置不同,但行为差异极大:
-
r.Use(mw):所有路由(包括/ping、/health)都经过该中间件,哪怕你只想要保护 API 路径 -
v1 := r.Group("/v1"); v1.Use(mw):仅/v1/xxx下的路由生效,静态文件、管理后台等路径不受影响 - 多个中间件按
Use()调用顺序入栈,先注册的先执行;c.Next()是“进入下一层”,不是“跳到下一个中间件”
调试时如果发现某个中间件没触发,第一反应不该是检查函数体,而是确认它是否被注册到了目标路由组 —— 尤其当项目拆分了 routers 包时,容易漏掉 Use() 调用。
中间件里读取请求 Body 会破坏原始流
想记录请求 body?小心 c.Request.Body 只能读一次。常见错误写法:
body, _ := io.ReadAll(c.Request.Body) c.Request.Body = io.NopCloser(bytes.NewReader(body)) // 必须重置,否则下游 handler 读不到
更安全的做法是用 c.GetRawData()(它内部已做缓冲),但要注意大 body 会吃内存;生产环境建议只对调试接口或小数据接口开启 body 日志。


















