错误处理中间件必须最先注册,否则 panic 无法被捕获;需显式调用 c.next() 并用 defer+recover 拦截;要统一处理 apperror 和 panic,子应用须独立配置错误中间件。

中间件注册顺序决定错误能否被捕获
错误处理中间件必须在其他中间件之前注册,否则 panic 或 AppError 会在它执行前就终止流程。Fiber 按 app.Use() 的调用顺序执行中间件,先注册的先运行——这意味着日志、鉴权、参数解析等中间件都得排在错误处理中间件之后。
- ✅ 正确顺序:
app.Use(recoveryMiddleware)→app.Use(logger)→app.Use(auth) - ❌ 错误顺序:
app.Use(logger)→app.Use(recoveryMiddleware):logger 中 panic 就逃逸了,收不到 - ⚠️ 路由级中间件(如
app.Get("/api", auth, handler))会绕过全局Use链,需单独加 recovery 或统一用app.All()包裹
recoverMiddleware 必须显式调用 c.Next() 并拦截 panic
Fiber 默认不自动 recover panic,你得自己写一个能捕获 panic 并转为标准响应的中间件。核心是用 defer + recover(),并在 c.Next() 前后做状态兜底。
-
c.Next()是关键:它把控制权交给后续中间件/路由处理器,之后才能检查是否 panic - 恢复后要手动设置状态码和响应体,Fiber 不会自动重置
c.Response().StatusCode() - 别忘了清空已写入的响应头/体(
c.Response().ResetBody()),否则可能双写或冲突
func recoveryMiddleware(c *fiber.Ctx) error {
defer func() {
if r := recover(); r != nil {
c.Response().ResetBody()
c.Status(500).JSON(fiber.Map{
"code": 5000,
"message": "服务内部错误",
"status": 500,
})
}
}()
return c.Next()
}
统一返回结构需兼容 AppError 和 panic 两种错误源
真实场景中错误来自两处:显式 return &AppError{...} 和隐式 panic。错误处理中间件要能区分并归一化——但 Fiber 的 c.Next() 不会把 AppError 当 panic 抛出,所以得靠返回值判断。
- 在中间件末尾检查
err是否为*AppError类型,用类型断言判断 - 非
AppError的 error(比如数据库超时)也应映射到标准字段,避免裸露底层错误 - 注意
AppError的Status字段要赋给c.Status(),不能只塞进 JSON
func errorMiddleware(c *fiber.Ctx) error {
err := c.Next()
if err != nil {
var appErr *AppError
if errors.As(err, &appErr) {
c.Status(appErr.Status).JSON(fiber.Map{
"code": appErr.Code,
"message": appErr.Message,
"status": appErr.Status,
})
} else {
c.Status(500).JSON(fiber.Map{
"code": 5001,
"message": "未知错误",
"status": 500,
})
}
return nil // 阻止继续传递 err
}
return nil
}
局部错误处理容易漏掉子路由或挂载子应用
app.Use("/api", errorMiddleware) 看似覆盖了所有 /api 下路径,但若你用 app.Mount("/admin", adminApp) 挂载子应用,子应用内部的错误不会经过这个中间件——因为 Mount 是独立路由树,中间件不穿透。
- 子应用(
fiber.App实例)必须自己注册errorMiddleware,不能依赖父级 - 通配前缀如
app.Use("/*", ...)在 Fiber 中无效,Use只支持精确前缀或斜杠边界匹配 - 测试时务必覆盖挂载路径(如
/admin/users),否则上线才发现子应用错误没格式化
/admin 接口报错直接返回原始 panic 文本,既不安全也不符合统一规范。











