Gin 的 gin.Recovery() 默认不返回 JSON 错误,因其仅 recover panic 并打印日志,不修改响应格式;需自定义中间件调用 c.AbortWithStatusJSON() 才能返回结构化 JSON 错误。

为什么 Gin 的 gin.Recovery() 默认不返回 JSON 错误
Gin 自带的 gin.Recovery() 中间件只做一件事:recover panic 并打印日志,它**不会修改响应体格式**,也不会调用 c.AbortWithStatusJSON()。所以你看到的是浏览器原生 500 页面,而不是前端能解析的 JSON。
- 这是设计使然——Gin 把“捕获”和“渲染”解耦了,方便你自定义错误格式
- 如果你没手动替换或包装它,panic 就会走默认 fallback:纯文本响应 + 状态码 500
- 别在路由注册时直接写
r.Use(gin.Recovery())就以为万事大吉,那只是“日志有了”,错误体还是裸的
怎么写一个真正返回 JSON 的 recovery 中间件
核心是两步:recover 后构造 ErrorResponse,再用 c.AbortWithStatusJSON() 终止流程并写入响应。注意必须 return,否则后续逻辑可能二次写 header 导致 panic。
func CustomRecovery() gin.HandlerFunc {
return func(c *gin.Context) {
defer func() {
if err := recover(); err != nil {
log.Printf("PANIC: %+v", err)
c.AbortWithStatusJSON(http.StatusInternalServerError, map[string]interface{}{
"code": 5000,
"message": "服务器内部错误",
})
return // ⚠️ 必须加!否则可能触发 'header already written'
}
}()
c.Next()
}
}
- 别用
http.Error()或手写w.WriteHeader()—— Gin 的c.AbortWithStatusJSON()已封装好 content-type 和编码 - 返回的
code字段建议用业务码(如5000),和 HTTP 状态码(http.StatusInternalServerError)分开语义 - 如果用了
zap或logrus,记得把err传给Errorf()而不是Printf(),避免丢失堆栈
如何让业务错误(比如参数校验失败)也走统一格式
Gin 的 c.Error() 是个信号机制,它把错误存进 c.Errors 队列,但不会自动渲染——得靠你在 recovery 之后、c.Next() 结束前主动检查并处理。
- 在 handler 里别直接
c.JSON(400, ...),改用c.Error(errors.Join(ErrInvalidParam, err)) - 在全局中间件末尾加逻辑:
if len(c.Errors) > 0 { c.AbortWithStatusJSON(...); return } - 推荐预定义错误变量,比如
var ErrInvalidParam = errors.New("invalid param"),便于类型断言和日志归类 - 别依赖
err.Error()包含关键词来判断类型(如strings.Contains(err.Error(), "not found")),要用errors.Is()或类型断言
为什么 c.AbortWithStatusJSON() 有时不生效或报错
最常见原因是调用后没 return,或者混用了原生 http.ResponseWriter。这个方法只对 Gin 的上下文链有效,且假设你没提前写过 header。
立即学习“go语言免费学习笔记(深入)”;
- 如果 handler 里先调了
c.JSON()或c.String(),再调c.AbortWithStatusJSON(),就会 panic:“header already written” - 如果你在中间件里用了
c.Writer或c.Response直接操作底层 writer,Gin 的状态管理就失效了 - 传入的状态码必须是 4xx/5xx,传
200会导致前端误判为“成功”,而 JSON 里的code又是错误码,前后端语义撕裂 - 别在
defer里调它——defer是函数退出时执行,那时c可能已不可用或响应已提交
c.Error() 而不是 return 或 panic,并且所有错误类型都实现了统一接口。这点靠文档没用,得靠代码审查 + 单测覆盖错误路径。


















