中间件注册顺序即执行顺序,use()调用先后直接决定a→b→handler的链式流程;漏调c.next()将阻断后续,c.abort()用于终止流程,跨中间件传值须用c.set()/c.get()。

中间件注册顺序决定执行顺序
中间件不是“自动串联”的,Use() 调用的顺序直接决定链中执行顺序:先 Use(A),再 Use(B),最终调用链就是 A → B → handler。漏掉某个 Use() 或位置写错,中间件就彻底不生效。
常见错误包括:
- 误把全局中间件写成单路由形式:
r.GET("/api", AuthMiddleware(), handler)—— 这只对/api生效,且AuthMiddleware实际上成了前置 handler,不是真正意义上的链式中间件 - 用
gin.New()启动但忘记手动Use()日志和恢复中间件 —— 导致无日志、panic 直接崩溃 - 在
Group()链式调用后没保存引用:r.Group("/v1").Use(M1).GET("/x", h)——GET()注册到了根路由,M1白注册
c.Next() 是链式流转的核心开关
c.Next() 不是“调用下一个函数”,而是让当前 Context 的索引 c.index 自增,并触发链表中下一个 HandlerFunc 执行。它不返回值,也不阻塞,但位置极其关键。
典型结构必须是:
- 前置逻辑(如鉴权校验、上下文注入)
-
c.Next()—— 缺失则后续所有中间件和 handler 全部跳过 - 后置逻辑(如耗时统计、响应头追加)—— 即使
c.Next()内 panic,这部分仍会执行
想中断流程(比如 token 失效),必须显式调用 c.Abort() 或 c.AbortWithStatusJSON(),否则 c.Next() 之后的代码照常运行,容易造成重复响应或状态错乱。
路由组中间件要绑定到分组实例上
给子路径加中间件,不能靠“感觉”链式调用。正确做法只有两种:
- 参数式:
api := r.Group("/api", AuthMiddleware(), LoggerMiddleware()) -
Use()式:api := r.Group("/api"); api.Use(AuthMiddleware()).Use(LoggerMiddleware())
二者等价,但后者更可控——支持 if 分支动态挂载,例如:
if env == "prod" {
api.Use(otelgin.Middleware("my-app"))
}
注意:Group() 返回的是新分组指针,每次 Use() 都作用于该分组,而不是根引擎。混用 r.Use() 和 group.Use() 会导致中间件挂错层级,调试时很难定位。
跨中间件传数据只能靠 c.Set()/c.Get()
Gin 没有隐式上下文继承,每个中间件的局部变量彼此隔离。想把用户 ID、请求 ID、权限角色等透传给下游 handler 或其他中间件,唯一可靠方式是显式存取 Context:
- 存:
c.Set("user_id", 123) - 取:
uid, ok := c.Get("user_id")(注意类型断言)
别用闭包捕获变量、也别依赖全局 map——并发下会出竞态;c.MustGet() 在 key 不存在时 panic,生产环境慎用;c.GetString() 等快捷方法只适用于 string 类型,通用场景坚持用 Get() + 断言。
链路管理真正的复杂点不在注册语法,而在于各中间件之间隐式依赖的梳理:哪个中间件必须在哪个之前?哪些 key 必须由谁 set?这些契约一旦松动,c.Get() 就会返回 nil,错误可能延迟到业务 handler 才暴露。











