Gin中间件必须是func(*gin.Context)类型,需显式调用c.Next()或c.Abort()控制流程,通过c.Set()/c.MustGet()跨中间件传值,注册顺序决定执行顺序,异步操作须用c.Copy()。

中间件函数必须是 gin.HandlerFunc 类型
不是任意函数都能当 Gin 中间件用——它必须严格满足 func(*gin.Context) 签名,没有返回值。写成 func() error 或带额外参数的函数,编译会直接报错:cannot use xxx (type func() error) as type gin.HandlerFunc。
常见错误是把处理逻辑和中间件混在一起,比如在中间件里直接调用 c.JSON() 后忘了调用 c.Next() 或 c.Abort(),导致后续 handler 被跳过或重复执行。
-
c.Next()表示“继续走下一个中间件或最终 handler”,必须显式调用,否则流程就卡住了 -
c.Abort()表示“终止流程,不再往下传”,适合鉴权失败、参数校验不通过等场景 - 想在中间件里提前返回响应(比如 401),得先设状态码、再写响应体,最后调
c.Abort(),否则 Gin 可能覆盖你的输出
全局注册用 Use(),路由级注册直接塞进 GET/POST 参数列表
r.Use(m1, m2) 是全局中间件,所有路由(包括之后加的)都会经过;而 r.GET("/user", m1, handler) 是局部中间件,只对这一条生效。别误以为 Use() 只能放最前面——它支持多次调用,且顺序影响执行链。
容易踩的坑:用 gin.Default() 时,Logger 和 Recovery 已默认启用;若改用 gin.New(),这两个就得手动 Use(),否则看不到请求日志、panic 也会直接崩进程。
立即学习“go语言免费学习笔记(深入)”;
- 调试阶段建议保留
Logger,但上线后高并发下它可能成为性能瓶颈(格式化+IO) -
Recovery必须放在Use()链靠前位置,否则 panic 发生在它之前,就捕获不到 - 多个中间件注册顺序 = 执行顺序 = 退出顺序(类似栈),
m1进入 →m2进入 → handler →m2退出 →m1退出
c.Set() 和 c.MustGet() 是跨中间件传值的关键
中间件之间不能靠闭包变量共享数据,得用 c.Set(key, value) 存,再用 c.MustGet(key) 或 c.Get(key) 取。类型断言必须自己做,MustGet 会在 key 不存在时 panic,生产环境慎用。
典型场景是登录鉴权中间件解析 token 后存用户 ID:c.Set("uid", 123),后面 handler 就能安全读取,不用重复解析。
- key 建议用常量定义,避免字符串拼写错误(如
const CtxUserID = "uid") - 不要往 context 里塞大对象(如原始图片字节),有内存和 GC 压力
-
c.Copy()在 goroutine 场景下才需要,普通同步 handler 不用管
耗时统计中间件必须用 time.Now() + c.Next() 包裹
想测整个请求链路耗时,得在 c.Next() 前记开始时间,之后算差值。写成 defer time.Since(start) 是错的——defer 在函数返回时才执行,而 handler 可能早于中间件函数结束就写了响应。
更隐蔽的问题是:如果某个中间件调了 c.Abort(),c.Next() 就不会执行,那后续的计时代码就永远不跑,导致日志缺失或监控断点。
- 正确写法是把计时逻辑放在
c.Next()前后,用defer仅包裹打印动作(确保一定执行) - 高精度场景建议用
time.Now().UnixNano(),避免time.Since()在纳秒级误差累积 - 别在中间件里直接打全量日志(如 dump body),body 可能已被读过一次,再次读会失败
*gin.Context 上,一旦 request 生命周期结束,里面存的所有东西就自动清理。很多人试图在中间件里启动 goroutine 并访问 c,结果拿到的是已失效的 context —— 这类异步操作必须用 c.Copy() 显式复制。


















