自定义中间件必须返回gin.handlerfunc类型且显式调用c.next()或c.abort();漏c.next()则后续handler不执行;签名须为func(*gin.context),闭包传参是主流写法;c.next()同步阻塞执行链,决定洋葱模型流程。

自定义中间件必须返回 gin.HandlerFunc 类型,且内部必须显式调用 c.Next() 或 c.Abort();漏写 c.Next() 会导致后续 handler 完全不执行,这是最常踩的坑。
中间件函数必须是 gin.HandlerFunc 类型
Gin 不接受任意函数签名——只有形如 func(*gin.Context) 的函数才能被注册。闭包传参是主流写法,因为真实项目总要带配置:
- 错误写法:
func AuthMiddleware(role string) { ... }—— 返回值不是gin.HandlerFunc,r.Use()会静默失败或 panic - 正确写法:
func AuthMiddleware(requiredRole string) gin.HandlerFunc { return func(c *gin.Context) { ... } } - 调用时:
r.Use(AuthMiddleware("admin"))或api := r.Group("/admin").Use(AuthMiddleware("admin"))
c.Next() 是流程控制的关键,不是“可选”
它不是自动跳转,而是同步阻塞地执行后续所有中间件和最终 handler,等它们全部返回后,才继续执行 c.Next() 后面的代码。洋葱模型就靠它撑着:
开箱即用的技能链路由引擎。13 条预定义链覆盖搜索、开发、审查、MLOps、法律、创意等场景,三层路由架构(触发词→SAD反馈→DAG编排),recall@10=96.97%。配置驱动(chains.yaml),零代码扩展。pip install skill-weave-chains 一键安装。
- 漏写
c.Next():整个链路在该中间件就断掉,handler 永远不会运行 - 错放位置(比如写在
c.Abort()后):逻辑失效,可能造成鉴权绕过或日志缺失 - 典型结构应为:
preCheck(); if failed { c.Abort(); return }; c.Next(); postLog()
注册方式决定作用域,错用等于没配
中间件不会“自动生效”,它的作用范围完全由注册方式决定:
- 全局生效:
r.Use(middleware)—— 必须作用于*gin.Engine实例(即r),gin.New()后若不手动加gin.Logger()和gin.Recovery(),连日志和 panic 恢复都没有 -
路由组生效:
api := r.Group("/api", middleware)或api := r.Group("/api"); api.Use(middleware)—— 注意不能写成r.Group("/api").Use(...).GET(...),链式调用未保存分组引用,GET()实际注册到根路由 - 单路由生效:
r.POST("/upload", rateLimit(), uploadHandler)—— 中间件只对该接口起作用,适合调试钩子、接口级限流等场景
多个中间件顺序敏感,Use() 调用顺序即执行顺序
Gin 按 Use() 的调用先后把中间件追加进 slice,请求进来时从前到后执行,响应返回时从后到前收尾:
-
r.Use(A, B, C)→ 请求阶段:A→B→C→handler;响应阶段:C→B→A - 常见陷阱:把
Recovery()放在AuthMiddleware()后面,一旦鉴权 panic,Recovery()来不及捕获 - 日志耗时统计必须包裹整个链:
start := time.Now(); c.Next(); log.Printf("cost: %v", time.Since(start))
真正容易被忽略的是:中间件里读 c.Request.Body 只能一次,二次读就是空;如果业务 handler 和中间件都需要 body 内容,必须提前用 ioutil.ReadAll() 缓存,并用 io.NopCloser() 替换回去——这个细节在鉴权+解析 JSON 的场景下几乎必踩。










