正确使用 recover 必须在 defer 中且紧挨 c.next() 前;panic 捕获后需检查 c.writer.written() 再响应;业务 error 与 panic 应分离处理,统一 handler 返回 error 类型并配合中间件兜底。

recover 必须在 defer 中且紧挨 c.Next() 前
Go 的 recover 只在当前 goroutine 的 defer 函数中有效,且仅能捕获同一函数内 panic 的后续调用。Gin 中常见错误是把 defer 放在 c.Next() 之后,或包裹在另一个函数里——此时 recover 永远不会触发。
正确顺序只有一种:defer 块必须直接包围 c.Next(),且不能有中间逻辑干扰执行流。
-
defer func() { ... }()要写在c.Next()之前 - 别在 defer 里加条件判断或异步操作(比如 go func() {...}())
- 如果用了
c.Abort()或提前返回,c.Next()不会执行,recover 就没机会运行
panic 捕获后必须检查 Writer 是否已写入
Gin 的 ResponseWriter 允许多次调用 WriteHeader,但实际 HTTP 协议只认第一次。如果 panic 发生在中间件链靠后位置(比如日志中间件已写 header),再调用 c.JSON(500, ...) 会 panic 报错 http: superfluous response.WriteHeader call。
安全做法是先判断:if !c.Writer.Written(),再写响应。
- 不加判断直接
c.JSON→ 生产环境可能二次 panic - 用
c.AbortWithStatusJSON更稳妥,它内部自带 written 检查 - debug.Stack() 要手动 import
"runtime/debug",否则编译失败
别把业务 error 和 panic 混为一谈
Gin 自带的 Recovery() 中间件只处理 panic,对 return errors.New("xxx") 或 c.Error(err) 完全无感。很多团队误以为装了 Recovery 就“全局兜底”了,结果 400/401 错误散落在各 handler 里,格式五花八门。
真正要统一的是两类出口:一是 panic(崩溃级),二是显式 error(业务级)。后者需额外设计中间件配合 handler 约定。
- handler 函数签名建议统一为
func(c *gin.Context) error - 中间件通过
c.Get("app_error")或自定义 context key 拿 error,而非依赖c.Errors(那是 Gin 内部调试用的) - 预定义
ErrNotFound、ErrInvalidInput等变量,避免字符串匹配错误码
堆栈信息绝不能直接返回给前端
开发时加 "stack": string(debug.Stack()) 很方便,但上线后等于把源码路径、变量名、依赖版本全暴露出去,属于高危信息泄露。
生产环境响应体里只保留可读性错误提示,堆栈必须进日志,且日志要带 traceID 关联请求。
- 用
log.Printf("[PANIC] %v | trace_id=%s", err, getTraceID(c)) - 前端看到的 message 字段应是运营语言,如
"系统正忙,请稍后再试",不是"nil pointer dereference" - 若需调试,可在 header 中加
X-Request-ID,日志系统按此 ID 检索完整上下文
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











