Beego v2 全局异常捕获必须通过 BConfig.RecoverFunc 实现,因中间件无法捕获 controller panic;需保存原函数并链式调用,自定义函数中优先处理 bresp.PageError 类型 panic,再回退至默认逻辑。

Beego v2 没有类似 Gin 的 Next() 或中间件级 defer recover() 机制,直接在中间件里写 panic 捕获无效——这是最常踩的坑。全局错误处理必须走 BConfig.RecoverFunc 这条路径,且需手动保存并链式调用原函数。
为什么中间件里 defer recover() 不生效
Beego v2 的中间件是「串行执行、各自独立」的,每个中间件的 goroutine 栈彼此隔离。你在中间件里写 defer func() { recover() }() 只能捕获该中间件自身 panic,无法捕获后续 controller 执行时的 panic。
- Controller 中 panic 后,控制权直接交还给 Beego 内部的 panic 恢复逻辑,跳过所有中间件的 defer
-
beego.BConfig.RecoverFunc是唯一被框架主动调用的恢复入口,它在请求生命周期末尾、HTTP 响应前被触发 - v2 默认值是未导出的
defaultRecoverPanic,你不能直接替换为匿名函数,必须保留原函数调用链
如何正确设置自定义 RecoverFunc
核心是「保存旧函数 + 替换新函数 + 在新函数里按需调用旧函数」。不能覆盖,只能增强。
- 定义一个包级变量存旧函数:
var oldFunc func(*context.Context, *web.Config) - 在
main()初始化阶段调用初始化函数(如HandleRecover(beego.BConfig)),把当前RecoverFunc保存下来,再赋值为你自己的RecoverFunc - 你的
RecoverFunc必须显式判断recover()结果类型:如果是bresp.PageError,就按业务规则返回 JSON 或状态码;否则交给oldFunc处理,默认日志+500响应
func RecoverFunc(ctx *context.Context, cfg *web.Config) {
if err := recover(); err != nil {
if e, ok := err.(bresp.PageError); ok {
ctx.Output.SetStatus(e.Code)
if e.Code == 200 {
ctx.JSONResp(bresp.RespMessage(e.Message))
} else {
ctx.WriteString(e.Message)
}
return
}
if oldFunc != nil {
oldFunc(ctx, cfg)
}
}
}
如何在 Controller 中主动触发全局错误处理
不要用 this.Abort("500") 或裸 panic("xxx")——前者只走错误页面路由,后者会被默认 RecoverFunc 当作未知 panic 处理,不带语义。
- 统一抛出自定义错误类型:
panic(bresp.NewPageError(400, "参数缺失"))或panic(&bresp.PageError{Code: 401, Message: "未登录"}) -
bresp.PageError是 Beego v2 内置的可识别错误类型,你的RecoverFunc能精准匹配并差异化响应 - 避免在
RecoverFunc里做复杂日志或监控上报——它只负责响应构造;真正需要记录的错误,应在 panic 前就用logs.Error()记一次
容易被忽略的兼容性细节
Beego v2 的 RecoverFunc 签名和 v1 不兼容,且 bresp.PageError 的字段在不同 patch 版本中微调过。如果你升级了 minor 版本(比如从 v2.1.x 升到 v2.2.x),务必检查 PageError 结构体是否新增了字段,否则 recover() 类型断言会失败,导致 fallback 到默认处理,掩盖真实错误。


















