中间件必须是 gin.handlerfunc 类型,否则注册直接 panic;鉴权中间件需前置注册并显式 c.set("user");jwt 过期须主动校验 valid;读写请求/响应体需缓存或包装 writer;中间件顺序至关重要,推荐 cors→recovery→auth→logger。

中间件函数必须是 gin.HandlerFunc 类型,否则注册直接 panic
Gin 对中间件类型有严格校验,传入非 gin.HandlerFunc 的函数(比如普通闭包、方法值、带额外参数的函数)会导致 router.Use() 或 group.Use() 调用时立即 panic,错误类似 panic: interface conversion: interface {} is func(), not gin.HandlerFunc。
- 正确写法:中间件必须签名完全匹配
func(*gin.Context),且显式赋值或定义为该类型 - 常见翻车点:用
func(c *gin.Context, extra string)带参函数,或试图在Use()里传authMiddleware("admin")返回值却不检查返回类型 - 安全写法示例:
func AuthMiddleware() gin.HandlerFunc { return func(c *gin.Context) { // ... c.Next() } }然后调用r.Use(AuthMiddleware()),不是r.Use(AuthMiddleware)(少括号会传函数地址而非执行结果)
鉴权中间件里拿不到 c.Get("user")?因为没按顺序注册或没 c.Set()
很多开发者在日志或业务 handler 里写 user, ok := c.Get("user"),结果 ok 总是 false——不是代码写错,而是中间件链根本没走到鉴权逻辑,或者鉴权中间件压根没存值。
- 必须确保鉴权中间件在
router.Use()或group.Use()中排在最前(至少在所有依赖"user"的中间件之前) - 鉴权成功后必须显式调用
c.Set("user", user);只解析 JWT、不Set,下游永远拿不到 - 别在鉴权中间件里写
c.Next()后再取c.Get("user")——这时c.Next()已执行完,c.Set()是上游的事,这里只是读 - JWT 过期检查不能省:
token.Claims.(jwt.MapClaims).Valid必须主动判断,否则过期 token 签名有效也能通过
想记录请求体或响应体?别直接读 c.Request.Body 或 c.Writer
日志中间件一读 c.Request.Body,后续 c.ShouldBindJSON() 就报 invalid character;想读响应体却发现 c.Writer 是个接口,直接类型断言失败或内容为空——这是 Gin 的流式设计导致的必然行为。
- 请求体缓存要手动重置:
bodyBytes, _ := io.ReadAll(c.Request.Body),再用c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))回填,但大文件慎用,会吃内存 - 响应体必须包装
ResponseWriter:自定义结构体实现Write()和WriteHeader(),把输出先写进bytes.Buffer,c.Next()后再读缓冲区 - 生产环境建议关掉体内容日志:
if gin.Mode() == gin.DebugMode才记录 body,否则只记路径、状态码、耗时、IP
多个中间件顺序写错,整个链就失效
中间件不是“加了就行”,执行顺序决定逻辑是否成立。比如把 Recovery 放在日志之后,panic 发生时日志根本刷不出来;把鉴权放日志后面,未授权请求照样被记日志还可能触发空指针 panic。
- 推荐全局顺序:
Cors()→Recovery()→Auth()→Logger()→业务路由 -
Cors()必须最前:否则 OPTIONS 预检请求会被Auth拦住,返回 401 而非 204,前端卡死 -
Recovery()必须在Logger()之前:保证 panic 时也能打出错误堆栈和请求上下文 - 路由组比全局更可控:
api := r.Group("/api"); api.Use(Auth(), Logger()),避免管理后台也被强鉴权
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











