Beego中间件中recover不到panic,是因为中间件函数未手动添加defer+recover,而panic发生在独立goroutine中,全局或封装式recover无效,必须在每个中间件首行显式使用defer func(){if r:=recover();r!=nil{/处理/}}()。

Beego中间件里 panic 为什么 recover 不到
因为 Beego 的中间件函数本身不自动包裹 defer recover,而 panic 发生在中间件执行过程中时,若没手动加 recover,就会直接向上抛给框架默认的 recoverPanic(如果开启),导致你的日志、告警、自定义响应全部失效。
常见错误现象:ctx.Next() 执行期间 panic(比如调用未初始化的 c.Data 或解析空 session),但中间件里没写 defer func() { recover() }(),结果请求返回白屏或默认 500,且你完全收不到异常信号。
真正起作用的只有当前 goroutine 内的 recover(),而每个 HTTP 请求(含其中间件链)都在独立 goroutine 中运行 —— 所以你在 main() 或工具函数里写的全局 recover 完全无效。
- 必须在中间件函数体最开头就写
defer func() { if r := recover(); r != nil { /* 处理 */ } }() - 不要把
recover封装成独立函数再调用,recover()必须和defer直接绑定,否则返回nil - panic 后不能继续调
ctx.Next(),必须显式终止流程(如ctx.Abort(500))
如何在 BeforeStatic 中安全捕获 panic
beego.BeforeStatic 是唯一能拦截 /static/ 类路径的过滤点,但它有个硬限制:此时 ctx.Input.Param()、ctx.Controller、c.Data 全部为 nil,任何试图访问它们的操作都会 panic。
典型翻车场景:在 BeforeStatic 中写 ctx.Input.Session.Get("user_id") 或 ctx.Input.Param(":id"),结果直接崩溃,且因没 recover,整个静态响应失败。
- 所有参数提取必须手动做:
path := ctx.Request.URL.Path,再用strings.HasPrefix(path, "/static/private/")或正则匹配 - Session 检查要加判空:
if sess := ctx.Input.Session; sess != nil { ... } - 务必在函数第一行加
defer+recover,否则一次 panic 就会让所有静态资源不可访问 - 不要依赖
ctx.Abort(403)后还能写日志 ——Abort会立即中断链路,日志必须写在Abort前
中间件 panic 恢复后怎么统一返回 JSON 错误
Beego 默认 panic 恢复返回 HTML 白屏或框架内置页,但在 API 场景下,你希望无论哪层 panic 都返回 {"code":500,"msg":"Internal error"} 格式,且状态码正确。
关键难点在于:ctx.ResponseWriter 可能已被部分写入(比如前面中间件已写 header),直接 WriteHeader(500) 会 panic;同时 c.Ctx.ResponseWriter 是原始 http.ResponseWriter,支持多次调用 WriteHeader(Beego 2.3+ 已修复该行为)。
- 恢复后第一件事是检查是否已写入:
if !ctx.ResponseWriter.Header().WasWritten(),再调ctx.ResponseWriter.WriteHeader(500) - 用
json.NewEncoder(ctx.ResponseWriter).Encode(map[string]interface{}{"code":500,"msg":"Internal error"}) - 别用
c.Ctx.WriteString(),它会尝试设置 Content-Type=text/plain,和 JSON 冲突 - 若需带 requestID,得提前在
BeforeRouter中注入到ctx.Input.Data["request_id"],并在 recover 时读取
多个中间件嵌套时 recover 的执行顺序
Beego 过滤器是链式同步执行的:FilterA → FilterB → Controller → AfterExec。如果 FilterB panic,且只有 FilterA 有 recover,那 FilterA 能捕获,但 FilterB 后续逻辑(包括 ctx.Next())不会执行;反之,如果只有 FilterB 自己写了 recover,它就能拦住,不影响 FilterA 和后续。
容易忽略的细节:recover 只能捕获「本函数内」或「本函数调用栈中」发生的 panic。如果你在 FilterB 里调了一个外部工具函数,而那个函数 panic,只要 FilterB 有 defer recover,就能拿到。
- 推荐策略:每个中间件都独立加
defer recover,不要指望上游兜底 - recover 后不要 return,而是调
ctx.Abort(500),确保 Beego 不再往下走 - 如果中间件做了资源申请(如 open file、redis conn),recover 里必须显式释放,否则泄漏
- 日志中记录
debug.Stack(),但注意它返回的是字符串,不是error,别传给errors.Is()
Beego 中间件的异常捕获不是“配个开关”就能生效的事——每一层都得自己守好自己的 goroutine 边界,而且 recover 的位置、时机、后续响应处理,三者缺一不可。最常被跳过的,是 BeforeStatic 里连 c.Data 都不能碰这个事实。











