gin默认recovery中间件仅打印panic日志并返回空白500响应,不调用c.abortwithstatusjson(),也不触发自定义错误处理;必须禁用默认recovery,手写中间件在defer+recover后立即调用c.abortwithstatusjson()返回结构化json响应。

Gin 默认的 recovery 中间件只打印 panic 日志并返回空白 500 响应,不走你写的错误处理逻辑,也不返回结构化 JSON;必须禁用默认 recovery,手写中间件,在 defer+recover() 后调用 c.AbortWithStatusJSON() 才能真正接管异常。
为什么默认 recovery 不返回你定义的错误 JSON
Gin 的 recovery.DefaultRecovery 在 panic 后仅调用 log.Printf 并写入空响应体,它不会执行 c.AbortWithStatusJSON(),也不会触发你注册的其他中间件或 handler 里的错误处理分支。请求链在 panic 发生后直接中断,后续逻辑全被跳过。
- 现象:前端收到
{"error": "Internal Server Error"}或完全空白响应,无法统一格式 - 根本原因:panic 发生在 handler 执行中,而默认 recovery 是“止血”而非“重建响应”
- 关键限制:
c.JSON()+c.Abort()组合不可靠——c.Abort()只阻断后续中间件,当前函数仍会继续执行,容易重复写响应
如何写一个真正可用的自定义 recovery 中间件
核心是用 defer+recover() 捕获 panic,立即构造并写出 JSON 响应,同时避免堆栈泄露和重复写入。
- 必须调用
c.AbortWithStatusJSON(500, ...),不能用c.JSON()+c.Abort() - 加防护:检查
c.Writer.Written(),防止 panic 后又被其他中间件二次写响应 - 脱敏处理:生产环境不要直接输出
err或debug.Stack(),开发环境可选加"stack"字段 - 示例精简版:
func CustomRecovery() gin.HandlerFunc {
return func(c *gin.Context) {
defer func() {
if err := recover(); err != nil {
if c.Writer.Written() {
return
}
c.AbortWithStatusJSON(500, gin.H{
"code": 500,
"msg": "服务暂时不可用",
"data": nil,
})
}
}()
c.Next()
}
}
哪些错误 recovery 根本捕不到,必须另加拦截
recovery 只捕获 panic,对 JSON 解析失败、数据库查询 error、字段校验失败等显式 error 类型完全无效——它们根本不会触发 panic,而是默默传进业务逻辑,最终可能引发二级 panic(比如 nil dereference),此时原始错误上下文已丢失。
- 典型场景:
c.ShouldBindJSON(&req)失败返回error,但你没检查,后续访问req.UserID→panic: invalid memory address - 正确做法:用解析中间件提前拦截,例如
BindJSONMiddleware,失败就c.AbortWithStatusJSON(400, ...) - 注意顺序:该中间件必须放在
CustomRecovery之后注册(否则 panic 会打断它),且不能复用c.Request.Body多次
中间件内部 panic 需要单独加 defer
Gin 的全局 CustomRecovery 能捕获路由 handler 的 panic,但捕获不了中间件自身的 panic(尤其当它启动 goroutine 时)。若某个鉴权中间件里发生 panic,全局 recovery 可能漏掉。
- 解决方案:高风险中间件(如 JWT 解析、RPC 调用)内部手动加
defer+recover() - 务必在
recover分支里调用c.Abort(),否则后续中间件仍会执行 - 示例片段:
func AuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
defer func() {
if err := recover(); err != nil {
c.AbortWithStatusJSON(401, gin.H{"code": 401, "msg": "认证失败"})
}
}()
// 实际鉴权逻辑...
c.Next()
}
}
最易被忽略的是:解析类错误(如 JSON/Protobuf 解码失败)和中间件内 panic 是两类独立问题,必须分别拦截;只靠一个 recovery 中间件,永远有盲区。











