统一响应结构必须通过封装Ctx方法(如c.Success()/c.Error())实现,而非中间件劫持;HTTP状态码须真实反映语义,code为纯业务码,message不带技术细节,request_id用于链路追踪,且禁止在ResponseWriter上做缓冲。

统一响应结构必须用自定义 Context 方法封装,不能靠中间件“塞”
Fiber 的 Ctx 是请求生命周期的唯一载体,所有响应逻辑必须绑定到它身上。中间件只能做前置/后置拦截,没法改写 c.JSON() 的行为——你无法在中间件里“劫持”一个已经调用过的 c.Status(200).JSON(resp)。真正可控的入口只有一个:c.SendStatus()、c.JSON() 这些方法本身得被你重载。
常见错误是试图在中间件里统一包装返回值,结果业务 handler 仍直接调用 c.JSON(),导致部分接口格式合规、部分不合规。根本解法是:自己封装一个 c.Success() 和 c.Error() 方法,强制团队只用这两个出口。
- 在初始化 Fiber app 后,用
app.Use()注册一个“上下文增强中间件”,给每个Ctx注入新方法 - 新方法内部调用原生
c.Status().JSON(),但数据结构固定为{code, message, data, timestamp} - 避免修改
c的原始字段(如c.Response),只扩展方法;否则会干扰 Fiber 内部状态管理
标准响应体字段设计要兼顾前端解析和网关路由
别照搬 Gin 或 Spring 的习惯加 success: true 字段。Fiber 常部署在 Nginx 或 Traefik 后面,网关依赖 status 字段做熔断或重试——如果所有响应都返回 200,再加个 code: 5001,网关根本看不到错误,流量还会继续打进来。
正确做法是让 HTTP 状态码真实反映语义:
-
code:纯业务码(如1001参数校验失败,4001用户未登录),用于前端展示和埋点 -
status:必须与 HTTP 状态一致(400对应参数错,401对应鉴权失败,500对应服务异常) -
message:面向用户的简短提示,**不带技术细节**(禁止出现"pq: duplicate key") -
request_id:从c.Locals("request_id")取,确保日志、链路追踪能对齐
示例结构:
{"code":1001,"message":"用户名不能为空","data":null,"request_id":"req-abc123","timestamp":1728423456}
错误处理必须区分 panic、AppError 和 HTTP 错误码
Fiber 默认 panic 会返回 500 页面,但微服务场景下你得把三类错误收敛到同一套响应逻辑里:
- 显式
panic("xxx"):用app.Use(recover.New())拦住,转成500+code: 5000 - 业务错误(如参数校验失败):主动 new 一个
AppError{Code: 1001, Message: "...", Status: 400},然后c.Error(err) - HTTP 层错误(如路由不存在):Fiber 的
app.Use(func(c *fiber.Ctx) error { return c.SendStatus(fiber.StatusNotFound) })会走默认错误处理器,需重写app.Config().ErrorHandler
关键点:三者最终都进同一个 errorHandler 函数,统一序列化为标准 JSON,而不是各自写一套 c.Status().JSON()。
别在 ResponseWriter 上做缓冲,Fiber 的 fasthttp 不吃这一套
有人想“先收集响应体,再统一包壳”,于是去包装 c.Response().Body() 或用 io.MultiWriter。这在 net/http 下可行,但在 Fiber 底层的 fasthttp 中会触发 panic 或静默丢数据——因为 fasthttp 的 response buffer 是复用的,且不支持多次写入。
真正安全的做法只有两个:
- 所有响应走
c.JSON()或c.Status().SendString(),由你封装的方法一次性写完 - 如果真需要动态拼接(比如导出 CSV),用
c.Attachment()+c.SendFile(),绕过 JSON 流程
最易被忽略的是:Fiber v2 不支持 ResponseWriter 的 Hijack 或 Flush,任何试图“流式响应+中途加 header”的操作都会失败。标准响应结构的前提,就是接受“全量构造、一次写出”这个约束。











