fiber.Recover()必须作为第一个中间件以捕获panic,404兜底中间件需置于所有路由之后,业务错误须提前return响应且结构体字段需大写加json tag。

Fiber 默认不处理错误,必须手动加 fiber.Recover() 和兜底中间件,否则 panic 直接 crash,404 返回空响应体。
为什么 fiber.Recover() 必须放在最前面
它负责捕获 panic,一旦 panic 发生在某个中间件里,而 fiber.Recover() 还没注册到执行链上,就无法拦截——Fiber 会直接终止请求并可能让进程退出。
常见翻车:把 fiber.Recover() 放在 logger 或 auth 中间件之后,结果 auth 里 panic 了,服务就挂了,连日志都来不及打。
正确顺序示例:
app.Use(fiber.Recover()) // 第一个
app.Use(logger.New())
app.Use(authMiddleware)
app.Get("/user/:id", handler)
-
fiber.Recover()必须是app.Use()注册的第一个中间件 - 它只捕获 panic,不处理 404 或业务错误
- recover 回调里不能再调
c.JSON()等写响应方法——如果前面 handler 已经 committed,再写会 panic
怎么写一个真正生效的 404 兜底中间件
它得放在所有路由注册之后、且是 app.Use() 链的最后一个,确保只有当所有 app.Get()/app.Post() 都没匹配上时才触发。
错误写法:把它放在 app.Use(fiber.Recover()) 前面,或插在 auth 和 logger 中间——它就永远不会执行。
正确写法:
app.Use(fiber.Recover())
// ... 其他中间件(logger、cors、auth 等)
app.Get("/health", healthHandler)
app.Post("/login", loginHandler)
// 所有路由注册完,再放兜底
app.Use(func(c *fiber.Ctx) error {
return c.Status(404).JSON(fiber.Map{"error": "not found", "path": c.Path()})
})
- 必须用
app.Use(),不能用app.All()或app.Get("*")——后者不匹配非 GET 方法 - 别在兜底中间件里调
c.Next(),它已经是终点 - 如果开了
StrictRouting: true,/user/123/和/user/123被视为不同路径,都要考虑进去
业务错误怎么统一包装,又不破坏 committed 状态
关键原则:响应只能写一次。很多开发者在中间件里判断失败后写了 c.Status(400).JSON(...),接着又写 c.Next(),以为后续日志或监控还能执行——实际全被跳过。
安全做法是「提前返回」:
app.Use(func(c *fiber.Ctx) error {
if !isValidToken(c.Get("Authorization")) {
return c.Status(401).JSON(fiber.Map{"error": "unauthorized"})
}
return c.Next() // 只有校验通过才放行
})
- 所有拦截逻辑必须在中间件开头做判断,不满足就 return 响应,绝不调
c.Next() - 结构体用于 JSON 输出时,每个字段必须带
jsontag,否则前端收到空对象:type Err struct { Msg string `json:"msg"` } - 避免在 handler 里混用
c.SendString()和c.JSON()——前者不设Content-Type,后者强制设为application/json,冲突会导致前端解析失败
自定义错误响应结构体容易被忽略的细节
你写了 c.JSON(MyError{Code: 400, Msg: "bad request"}),但前端始终收不到 Msg 字段——大概率是结构体字段没加 json tag,或者首字母小写导致未导出。
Go 的 json.Marshal 只序列化导出字段(首字母大写),且必须显式声明 tag:
type ErrorResponse struct {
Code int `json:"code"`
Msg string `json:"msg"`
Time string `json:"time,omitempty"`
}
- 字段名首字母小写 → 不导出 → JSON 里消失
- 有导出但没
json:"xxx"tag → 用字段名小写形式(msg→msg),但 Go 默认按原名导出(Msg→msg是巧合,不可依赖) - 生产环境别用
fiber.Map返回错误——类型不固定,契约难维护,Swagger 也生成不了准确 schema
最常被绕过的点:fiber.Recover() 捕获 panic 后,你得自己记录日志、构造响应、控制 status code;它不会帮你做任何业务层面的错误分类。真正的统一错误处理,是 recover + 404 中间件 + 每个业务 handler 内部的 error return 三层合力,缺一不可。

















