Fiber 的 app.Group() 不自动补前缀,v1.Get("users", h) 注册为 /users 导致 /v1/users 404;正确写法是 v1.Get("/users", h) 注册为 /v1/users;版本分流需用中间件提取 API-Version 或 version 参数并存入 c.Locals["version"],再由 handler 分支处理。

Fiber 的 app.Group() 不自动补前缀,版本控制必须靠中间件提取 + c.Locals 传递,否则路由树里根本找不到“动态版本”这回事。
为什么 v1.Get("users", h) 一定 404
Fiber 的 Group 返回的是独立的 *fiber.Router 实例,它只提供作用域和中间件绑定能力,不改写路径逻辑。注册时路径拼接只发生在 Group 前缀和你传入的字符串之间,且仅在注册阶段执行。
-
v1 := app.Group("/v1")+v1.Get("users", h)→ 实际注册的是GET /users,请求/v1/users必然 404 -
v1.Get("/v1/users", h)→ 注册为GET /v1/v1/users,多了一层前缀,同样 404 - ✅ 正确写法:
v1.Get("/users", h)→ 注册为GET /v1/users,路径全量书写,无歧义
怎么用中间件做运行时版本分流
单纯靠 app.Group("/v1") 和 app.Group("/v2") 只能物理隔离路径,无法支持同一 URL 根据 API-Version 请求头或 version query 参数动态走不同逻辑。Fiber 的基数树在注册时就固化了路径,运行时不跳转节点。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 版本提取中间件必须放在
Group内注册,避免污染/health等非 API 路由 - 提取后必须存进
c.Locals["version"],供后续 handler 分支调用 - 示例中间件:
func versionMiddleware(c fiber.Ctx) error {
ver := c.Get("API-Version")
if ver == "" {
ver = c.QueryParam("version")
}
c.Locals["version"] = ver
return c.Next()
}
- 然后统一注册:
api.Use(versionMiddleware); api.Get("/users", usersHandler) -
usersHandler内部再根据c.Locals["version"]调用对应 service 层,Swagger 才能标注版本语义
app.Group() 嵌套和中间件挂载的致命误区
嵌套 app.Group("/v1").Group("/admin") 语法合法但工程危险:路径来源分散、调试难、重构易漏;group.Use() 也不是挂中间件的正确姿势。
立即学习“go语言免费学习笔记(深入)”;
- ❌
admin := app.Group("/admin"); admin.Use(authMiddleware):这会注册一条匹配/admin/*的USE路由,但它不保证与GET /admin同一执行链,甚至可能被跳过 - ✅ 正确方式:
admin := app.Group("/admin", authMiddleware),中间件作为参数传入,确保对所有/admin/*路由生效 - 多个中间件按顺序执行:
app.Group("/api", logger, jwtAuth)→ 先logger,再jwtAuth - 嵌套 Group 推荐替代:
v1admin := app.Group("/v1/admin"),路径和中间件集中管理,一目了然
StrictRouting 开启时尾斜杠不是风格问题,是路由契约
Fiber 默认 StrictRouting: true,/users 和 /users/ 是两条完全独立的路由节点。只注册前者,后者必 404;关掉它又会导致 /users/:id 被 /users 错误捕获,c.Params("id") 永远为空。
- 别全局关
StrictRouting:改配置fiber.New(&fiber.Config{StrictRouting: false})是最省事但最危险的做法 - 推荐统一重定向中间件,在所有路由前注册:
c.Redirect(strings.TrimSuffix(c.Path(), "/"), 301) - 前端、OpenAPI、Postman 全部约定用无尾斜杠路径,后端只注册一种形式,省去歧义
- 子域名路由(如
app.Host("api.example.com"))也必须配合StrictRouting: true,否则/users/123/中的c.Params("id")会拿到"123/"而非"123"


















