gin.Default()的Recovery中间件不返回JSON错误,因其仅打印堆栈并返回空白500响应,不调用c.AbortWithStatusJSON(),也不触发自定义逻辑;必须禁用默认Recovery,手写中间件才能返回带code/msg/data的结构化JSON。

为什么 gin.Default() 的 Recovery 中间件不返回 JSON 错误
因为 gin.Default() 注册的 Recovery() 中间件只做两件事:打印 panic 堆栈、调用 c.Abort() 并返回空白 500 响应体,它**不调用 c.JSON() 或 c.AbortWithStatusJSON()**,也不触发你写的其他中间件逻辑。前端收到的是 {"error": "Internal Server Error"} 或纯空响应,无法统一格式,也无法携带业务字段。
必须禁用默认 Recovery,手写中间件才能返回结构化 JSON
直接在 gin.New() 后注册自定义中间件,并确保不调用 gin.Default() —— 否则默认 Recovery() 会先执行并中止流程,你的中间件根本没机会运行。
- 用
defer+recover()捕获 panic,仅限当前 goroutine(Gin handler 是单 goroutine,安全) - 务必调用
c.AbortWithStatusJSON(),而不是c.JSON()+c.Abort();后者可能被后续中间件再次处理,导致重复写响应或 panic - 对
err类型做简单判断,避免panic(42)或panic(nil)导致类型断言失败 - 生产环境禁止直接输出
debug.Stack()到响应体,日志里记全量堆栈即可,响应只返回脱敏信息
如何让 panic 返回带 code/msg/data 的标准 JSON
核心是把错误分类映射到统一响应结构。不要在中间件里硬编码字符串,而是复用项目已定义的响应封装函数(如 response.Fail()),保持前后端协议一致。
- HTTP 状态码和业务 code 必须分离:
500表示请求处理失败,code: 50001表示具体服务端错误类型 - panic 通常对应
http.StatusInternalServerError,但某些场景(如数据库连接中断)可映射为503 - 若 panic 来自自定义 error(如
errors.New("db timeout")),可在recover()后做类型断言,提取更精准的msg和code - 示例片段:
if e, ok := err.(error); ok && strings.Contains(e.Error(), "timeout") {<br> c.AbortWithStatusJSON(http.StatusServiceUnavailable, response.Fail(c, http.StatusServiceUnavailable, 50002, "service unavailable"))<br>}
404 和 panic 的错误页不能混用同一套逻辑
r.NoRoute() 处理的是路由未匹配,属于正常控制流;而 panic 是运行时崩溃,必须靠 recover() 拦截。两者触发时机、堆栈深度、可观测性都不同,强行合并会导致 404 日志里塞满 panic 堆栈,或 panic 响应被当成普通页面渲染。
-
NoRoute适合返回 HTML 页面(如c.HTML(404, "404.html", ...)),但 API 服务建议统一走 JSON - panic 中间件里不要调用
c.HTML(),避免 Content-Type 冲突和模板渲染失败二次 panic - 如果真要 HTML 错误页,需提前用
r.LoadHTMLGlob()加载模板,并在中间件中显式判断请求是否为 HTML(看Acceptheader),否则 REST 客户端会收到 HTML 字符串


















