Gin全局错误处理需组合panic捕获与c.Error()注入两套正交逻辑:必须手写recover中间件置于r.Use()最前,禁用默认gin.Recovery();业务错误须显式调用c.Error()并由后续中间件检查c.Errors统一响应。

Gin 没有“注册一个函数就全局生效”的错误处理机制,所谓全局错误处理,其实是两套正交逻辑的组合:一是 recover() 捕获 panic 并统一响应,二是通过 c.Error() 主动注入业务错误、再由中间件检查 c.Errors 列表并渲染。缺一不可。
panic 捕获必须自己写中间件,不能依赖 gin.Recovery()
默认的 gin.Recovery() 只打印日志 + 返回固定文本 500 响应,不透出 panic 值、不调用 c.AbortWithStatusJSON()、也不执行你定义的错误结构。它会抢在你自定义 recover 之前吞掉 panic,导致你写的 defer+recover 完全无效。
- 开发时务必禁用它:
gin.SetMode(gin.DebugMode),否则连原始 panic 堆栈都看不到 - 生产环境必须手写中间件,且要放在
r.Use()链最前面(比如r.Use(PanicRecovery(), gin.Logger(), ...)) -
defer必须包住c.Next(),recover()后必须立刻调用c.AbortWithStatusJSON()或c.Abort() + c.JSON(),否则后续 handler 可能重复写 body 或 panic - 堆栈要用
debug.Stack()获取,别只打err;日志里存stack=string(debug.Stack()),但绝不返回给前端
业务错误要用 c.Error() 注入,不是直接 c.JSON()
Gin 不会自动识别你 return errors.New("xxx"),也不会把 error 返回值转成 HTTP 响应——它只认你显式调用的 c.Error(err)。这是和 panic 处理完全独立的一条路径。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 所有 handler 内部,参数校验失败、DB 查询出错等,优先写
c.Error(&AppError{Code: 4001, Message: "参数缺失"}),而不是c.JSON(400, ...) -
c.Error()只是把错误塞进c.Errors队列,不会自动终止流程或写响应,必须靠后续中间件检查并处理 - 错误类型建议预定义(如
var ErrNotFound = errors.New("not found")),方便用errors.Is(err, ErrNotFound)判断,避免字符串匹配 - 别在 handler 里用
switch err.Error(),既脆弱又难测试
统一响应必须在中间件末尾检查 c.Errors
你写了 c.Error(),不代表错误就“被处理了”。得在请求生命周期末尾(即 c.Next() 执行完后)主动读取 c.Errors,构造响应并中断流程。
- 典型模式:
c.Next()后立即判断if len(c.Errors) > 0,然后根据错误类型做c.AbortWithStatusJSON() - 推荐用类型断言或
errors.As()提取自定义错误,比如if e, ok := err.(*AppError); ok { c.AbortWithStatusJSON(e.HTTPStatus, gin.H{"code": e.Code, "message": e.Message}) } - HTTP 状态码(如
http.StatusBadRequest)和业务错误码(如1001)要分开,前者控制协议层,后者供前端解析 - 别漏掉
c.Abort()—— 否则可能触发 “header already written” panic
真正麻烦的不是写代码,而是两套路径(panic / 业务 error)必须同时覆盖、且顺序不能错:recover 中间件必须最前,error 处理中间件必须在 c.Next() 之后;c.Error() 和 recover() 的语义也完全不同,混用会导致错误静默丢失或响应体冲突。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










