Iris 框架不支持 Spring 风格的全局异常处理器,错误处理依赖显式中间件和上下文响应,禁止用 recover() 兜底 panic;应统一返回带状态码的 AppError 类型,并由中间件分类处理,HTTP 状态码必须手动设置。

Go 的 Iris 框架本身不提供类似 Spring 的全局异常处理器(如 @ControllerAdvice),它的错误处理是基于中间件 + 路由上下文的显式控制流,不是靠“捕获未处理 panic”来兜底。直接注册一个全局 panic 捕获器不仅不可靠,还会掩盖真实问题——Iris 明确要求你主动调用 ctx.StatusCode()、ctx.JSON() 或 ctx.Text() 来写响应,而不是依赖框架自动转换异常。
为什么不能用 recover() 做全局错误兜底
在 Iris 中,recover() 无法可靠拦截路由 handler 内部 panic:Iris 的请求生命周期由 http.Handler 封装,但其内部已对 panic 做了基础处理(记录日志 + 返回 500),且该行为不可覆盖;手动在自定义中间件里加 defer/recover 只能捕获该中间件内 panic,对后续 handler 无效;更关键的是,Iris 鼓励显式错误返回(如 return ctx.JSON(400, err)),而非抛 panic —— 把业务逻辑错误当 panic 处理,会混淆错误类型语义(比如把参数校验失败和数据库连接中断混为一谈)。
正确做法:用 error handler 中间件统一处理业务错误
所有业务逻辑应返回标准 error,再由中间件统一识别、分类、响应。核心是约定错误类型或错误码,而非依赖 panic 捕获。
- 定义带状态码的错误类型,例如:
type AppError struct { Code int Message string Err error } func (e *AppError) Error() string { return e.Message } - 在 handler 中主动返回:
if err := validate(req); err != nil { return ctx.JSON(400, &AppError{Code: 400, Message: "参数校验失败", Err: err}) } - 注册中间件统一包装非
AppError的底层错误(如 DB 错误):app.Use(func(ctx iris.Context) { ctx.Next() if err := ctx.GetErr(); err != nil { // 只处理未被 handler 显式响应过的 error if ctx.Response().StatusCode() == 0 { ctx.StatusCode(500) ctx.JSON(&AppError{Code: 500, Message: "服务内部错误"}) } } })
HTTP 状态码与错误响应格式必须由 handler 自己决定
Iris 不会根据 error 内容自动映射 HTTP 状态码。常见误区是期望 ctx.Application().SetErrors(...) 或类似方法生效——该方法仅用于模板渲染错误提示,对 API 响应无影响。必须显式调用:ctx.StatusCode(404)、ctx.JSON(...)、ctx.XML(...) 等。
- 400 类错误(参数缺失、格式错误):handler 内校验后立即返回
ctx.StatusCode(400).JSON(...) - 401/403:鉴权中间件中检测失败时直接终止链并响应,不交给后续 handler
- 500 类错误:只应在真正不可恢复的场景(如 DB 连接池耗尽)使用,且需记录完整堆栈(用
log.Printf("%+v", err))
日志与监控要分离错误处理逻辑
错误日志记录不应耦合在响应构造中。推荐用 app.Logger().Errorf() 单独记录,避免在 ctx.JSON() 前混入副作用。例如:
if err := db.QueryRow(...); err != nil {
app.Logger().Errorf("DB query failed: %+v", err) // 记录完整上下文
return ctx.StatusCode(500).JSON(&AppError{Code: 500, Message: "数据查询失败"})
}
真正的难点不在“怎么捕获”,而在于“哪些错误该暴露给前端、哪些该静默降级、哪些必须立刻告警”——这需要结合业务语义设计错误分类体系,而不是指望框架自动区分。Iris 把控制权完全交给你,这点恰恰是它轻量高效的前提。


















