Fiber 中用 app.Group("/prefix") 创建带前缀的路由分组,再调用 group.Use(mw) 绑定中间件,该中间件仅对分组内路由生效;多个中间件按链式调用顺序执行;需在中间件中显式 return 终止流程,JWT 认证后应通过 c.Locals 存储用户 ID 供 Casbin 权限校验使用。

怎么用 Group() 绑定中间件到特定路由前缀
Fiber 没有“某个路由单独配中间件”的语法糖,真正能实现路由级拦截的只有 app.Group()。它不是装饰器,而是创建一个带路径前缀的新路由作用域,所有挂载其上的中间件只对这个分组内注册的路由生效。
常见错误是试图用 app.Use("/api/users", middleware) 去限定路径——这实际注册的是一个匹配路径前缀的全局中间件,/api/users/delete 会命中,但 /api/users 本身(无尾部斜杠)也可能因 fasthttp 路径匹配逻辑被误判,且无法控制是否跳过子路径。
- 正确写法:
api := app.Group("/api"),然后api.Use(authMiddleware),再api.Get("/users", handler) - 路径匹配以
Group()的第一个参数为准,app.Group("/api/v1")下的Get("/users")最终注册为/api/v1/users,中间件只对这个完整前缀生效 - 多个中间件可链式调用:
api.Use(logging, rateLimit, auth),执行顺序严格按书写顺序 - 别把
Group()和Use()混用:先app.Use(m0)再api := app.Group("/api", m1),访问/api/users时执行链是m0 → m1 → handler,不是“仅 m1”
如何动态排除某些子路径(比如 /api/public)
Group() 本身不支持 exclude,必须在中间件内部做路径判断。靠 c.Path() 或 c.OriginalURL() 做字符串检查是最轻量、最可控的方式,避免引入正则造成高频请求下的性能损耗。
例如,在 authMiddleware 中跳过公开接口:
开箱即用的技能链路由引擎。13 条预定义链覆盖搜索、开发、审查、MLOps、法律、创意等场景,三层路由架构(触发词→SAD反馈→DAG编排),recall@10=96.97%。配置驱动(chains.yaml),零代码扩展。pip install skill-weave-chains 一键安装。
func authMiddleware(c *fiber.Ctx) error {
path := c.Path()
if strings.HasPrefix(path, "/api/public/") || path == "/api/public" {
return c.Next()
}
// 实际校验逻辑...
if !validToken(c) {
return c.Status(401).SendString("unauthorized")
}
return c.Next()
}
-
c.Path()是解析后的路径(不含 query),适合做前缀判断;c.OriginalURL()含完整 URL,适合需要协议/域名参与判断的场景 - 不要用
regexp.MatchString对每个请求做路径匹配,v3 版本下高并发时易成瓶颈 - 静态资源路径如
/public/favicon.ico应单独挂载,不放进受保护 group,否则会被拦截
为什么中间件写了却没生效?关键看 return 和 c.Next()
所有 Fiber 中间件都必须显式控制流程:放行才调 c.Next(),拦截就必须立即返回响应并 return。漏掉 return 是最常见失效原因——函数继续往下走,哪怕前面已调了 c.Status(403).SendString(),后续 c.Next() 还是会执行。
- 错误写法:
c.Status(403).SendString("forbidden"); c.Next()→ 请求仍进 handler - 正确写法:
return c.Status(403).SendString("forbidden")→ 立即终止 - 别在中间件里用
defer补c.Next(),它无法中断已开始的流程 - 如果中间件要复用,推荐封装成闭包传参,而不是在每个
Use()里重复写条件分支
JWT 认证后怎么让 Casbin 权限校验拿到用户 ID
Casbin 不自动从上下文取用户信息,它只返回 bool。你得确保认证中间件(如 JWT)已把用户标识写入 c.Locals,权限中间件才能读出来。
典型配合链:
// JWT 中间件
func jwtMiddleware(c *fiber.Ctx) error {
token := c.Get("Authorization")
userID, err := parseAndValidate(token)
if err != nil {
return c.Status(401).SendString("invalid token")
}
c.Locals["userID"] = userID // 必须这一步
return c.Next()
}
// Casbin 中间件
func CasbinMiddleware(e *casbin.Enforcer) fiber.Handler {
return func(c *fiber.Ctx) error {
userID, ok := c.Locals["userID"].(string)
if !ok {
return c.Status(401).SendString("unauthorized")
}
obj := c.Route().Path // 注意:用 Route().Path,不是 c.Path()
act := c.Method()
if !e.Enforce(userID, obj, act) {
return c.Status(403).SendString("forbidden")
}
return c.Next()
}
}
-
c.Route().Path返回注册时的路由模板(如/api/v1/users/:id),Casbin 需要这个做 keyMatch2 匹配;c.Path()是实际请求路径(如/api/v1/users/123),直接比对会失败 - 模型文件中必须启用
keyMatch2,否则/user/:id和/user/123被视为不同obj - 别传
int或结构体给e.Enforce()的第一个参数,它只接受string,传错会静默返回false


















