gin异常处理依赖defer+recover拦截panic而非返回error,recovery中间件仅捕获panic并返回500,需用结构体apperror封装业务错误以便类型断言,noroute/nomethod不经过recovery,handler中return error被忽略须手动处理。

Gin 的异常处理不是靠“自动捕获 error 返回值”,而是依赖 defer + recover() 拦截 panic,再配合中间件顺序和错误类型判断做分层响应。不手动 panic 就不会触发 Recovery 逻辑,handler 里 return error 完全被忽略。
gin.Recovery 中间件到底捕什么
它只捕 panic,不处理任何 return err。Gin 默认启用的 gin.Recovery() 本质是调用 RecoveryWithWriter(os.Stderr),遇到 panic 时:打印堆栈到终端、返回 HTTP 500、中断后续中间件执行。
- 它会识别
net.OpError类型的 broken pipe / connection reset,避免误打日志 - 但对自定义 panic(比如
panic("db timeout"))一律当字符串处理,不区分业务含义 - 如果你替换为自定义 Recovery,必须调用
c.AbortWithStatusJSON(),不能用c.JSON() + c.Abort(),否则可能被后续中间件再次处理
如何让 panic 包含可识别的业务错误信息
直接 panic("xxx") 或 panic(errors.New("xxx")) 会导致类型判断困难,建议统一用结构体封装:
type AppError struct {
Code int
Message string
HTTPCode int // 如 400、500
}
func (e *AppError) Error() string { return e.Message }
- 在 handler 中
panic(&AppError{Code: 1001, Message: "user not found", HTTPCode: 404}) - 在 Recovery 中用
if appErr, ok := err.(*AppError); ok做类型断言,精准提取Code和HTTPCode - 避免用
err.(type)切 string/error/default 分支——这种写法掩盖了真实错误类型,也难扩展
NoRoute 和 NoMethod 怎么配合错误页面
r.NoRoute() 捕的是「路径完全不匹配」,r.NoMethod() 捕的是「路径匹配但 HTTP 方法不支持」,两者都发生在路由阶段,早于中间件链,所以它们不走 Recovery。
- 必须在
r.LoadHTMLGlob("templates/**.html")之后注册,否则c.HTML()找不到模板 -
NoRoute处理器里调用c.HTML(404, "errors/404.html", ...)是安全的,状态码写 404 没问题,浏览器能正确识别 - 不要在
NoRoute里 panic —— Recovery 不生效,错误会直接透出到客户端,且无日志
为什么 handler 显式 return error 总是被忽略
Gin 的 Context 接口没有 error 返回约定,所有 Bind、Render 等方法的 error 都只是函数返回值,框架不会自动 abort 或响应。你得自己判断:
if err := c.ShouldBind(&req); err != nil { Fail(c, http.StatusBadRequest, 1002, "参数校验失败"); return }- 别写
c.ShouldBind(&req); if err != nil { ... }——err未声明,编译不过 - 统一响应封装(如
Fail(c, ...))要主动调用c.Abort(),防止后续逻辑继续执行
最易忽略的一点:panic 后的 Recovery 中若没调用 c.AbortWithStatusJSON() 或等效中断操作,请求会继续往下走,可能触发重复响应或 panic 再次发生。这个细节在调试时很难复现,但上线后容易导致 500 响应体为空或内容错乱。











