中间件是请求生命周期的钩子,必须严格注册且顺序路径正确;Handler签名须为func(*fiber.Ctx) error,c.Locals是跨中间件通信唯一推荐方式,未调c.Next()将中断链路。

中间件不是插件,是请求生命周期的钩子;不注册就不存在,注册错顺序或路径就等于没写。
fiber.Handler签名必须严格匹配
编译能过但运行 panic 的最大雷区:函数签名漏掉 error 返回值。Fiber 不做类型转换,app.Use() 传入任何非 func(*fiber.Ctx) error 类型都会在启动时 panic。
- 错误写法:
func(c *fiber.Ctx)(无返回)、func(c *fiber.Ctx) string(类型错)、func(c fiber.Ctx)(少指针) - 正确结构必须是闭包返回:
func() fiber.Handler { return func(c *fiber.Ctx) error { ... } } - 即使只放行,也得写
return c.Next();提前终止必须显式return c.Status(401).SendString(...),不能只调c.Status(401)后跟return nil
app.Use() 的前缀和顺序决定中间件是否生效
中间件是否执行,不取决于你写了什么,而取决于它被挂在哪条路径、排在第几个位置。
-
app.Use(logger.New())影响所有路径,包括/favicon.ico和静态文件 -
app.Use("/api", jwt.New(), cors.New())只对/api/xxx生效,且jwt一定先于cors执行 - 把
static.New()放在无前缀app.Use()里,会导致每个请求(比如/login)都先查磁盘文件,严重拖慢路由匹配 - 子应用挂载如
app.Use("/oauth", oauthApp)是天然隔离作用域的方式,但oauthApp无法直接读主应用的c.Locals,需手动透传
c.Locals 是跨中间件通信的唯一推荐方式
别碰 c.Context().Value —— Fiber 不保证该 context 在整个生命周期中不被替换,c.Locals 才是稳定、高性能、框架感知的挂载点。
- 设值:
c.Locals("user_id", uint64(123));取值:userID, ok := c.Locals("user_id").(uint64) - 多个中间件用相同 key 会覆盖,建议加命名空间前缀,如
"auth.user_id"、"tenant.id" - 权限中间件从 JWT 解出
user_id写入c.Locals,后续业务 handler 才能安全读取,而不是每次重新解析 token - 多租户场景下,
TenantMiddleware必须尽早注册(比如全局app.Use()),否则下游 handler 拿不到c.Locals("tenant_id")
中间件组合不等于功能叠加,而是控制流编排
一个中间件内部没调 c.Next(),后续所有中间件和路由 handler 都收不到请求——这不是“跳过”,是链路彻底中断。
- 鉴权失败时,
return c.Status(403).JSON(...)后绝不能跟c.Next(),否则响应已发,Fiber 强制终止,后续逻辑无效 - 日志中间件想记录耗时,必须在
c.Next()前打开始时间,之后读c.Response().StatusCode和耗时差 - OPA 集成中间件(
gofiber/contrib/opa)执行 Rego 策略后,必须根据result.Allowed显式放行或拒绝,不能依赖策略“自动生效” - 缓存中间件若命中,应直接
return c.Status(200).Send(...)并终止;未命中才return c.Next()继续走业务逻辑
真正容易被忽略的是:中间件的「存在」和「执行」是两回事。注册了不代表跑得通,路径前缀、方法匹配、c.Next() 调用、响应是否已写入——任何一个环节卡住,整条链就断在那,而且不会报错,只会静默失效。


















