最稳妥的是用c.Request().URI().Path()获取原始URI路径并标准化处理;需跳过OPTIONS预检、截断API版本前缀、排除前端路由和静态资源;Redis原子递增配合时间切片Key实现高并发实时统计,数据库写入必须异步。

用全局中间件捕获所有请求路径
统计接口调用量的前提是能稳定拿到请求的原始路径(不含查询参数、不带版本前缀干扰),Fiber 的 c.Path() 返回的是路由匹配后的路径,而 c.OriginalURL() 又含 query string。最稳妥的是在中间件里用 c.Request().URI().Path() 获取原始 URI 路径,再做标准化处理(如统一小写、去除末尾斜杠)。
常见错误是直接用 c.Route().Path —— 它只对已注册路由有效,404 或静态资源请求会 panic;也别依赖 c.Method() + c.Path() 拼接,因为不同 HTTP 方法可能共用同一路径,但统计时通常要区分。
- 建议在中间件开头加判断:
if c.Method() == "OPTIONS" { c.Next(); return },跳过预检请求 - 若项目有 API 版本前缀(如
/v1/users),可提前截断:strings.TrimPrefix(path, "/v1"),避免同接口多版本重复计数 - 对 SPA 应用,
/、/login这类前端路由不应计入后端 API 统计,需排除static中间件已处理的路径(如.js、.css、index.html)
Redis 原子递增是最实用的实时方案
用 redis.Incr 或 Fiber 封装的 cache.Increment 是高并发下最轻量、线程安全的选择。Key 设计必须包含时间维度(如按小时或分钟切片),否则无法做“过去 N 分钟”统计。
例如统计最近 10 分钟访问量,Key 可设为 api:count:2026092014:10m:/user/profile,其中 2026092014 是当前小时戳,10m 表示窗口粒度。每分钟滚动更新 Key,旧 Key 自动 TTL 过期,无需手动清理。
- 别用
time.Now().Unix()当 Key 时间戳——它导致每秒一个 Key,爆炸式增长;改用time.Now().Truncate(10 * time.Minute).Unix() - Fiber 的
cache包默认使用 memory store,生产环境务必替换为 Redis:cache.New(cache.Config{Storage: redis.New(...)}) - 如果同时要 Top N,直接用 Redis
ZINCRBY把 API 路径作为 member、计数作为 score,ZREVRANGE api:top:10m 0 9 WITHSCORES一条命令拿前 10 名
数据库写入必须异步,否则拖垮接口
同步写数据库在 QPS > 100 时就会明显拖慢响应,尤其 MySQL 单条 INSERT 延迟常超 5ms。即使只是记日志,也应走消息队列或协程异步提交。
Fiber 中推荐用 go func() { ... }() 启动匿名协程,但要注意:不能直接传 c 上下文对象(它会在 handler 返回后被复用);必须提取所需字段(如 path、ip、status)后再传入。
- 错误写法:
go saveLog(c)→c已失效,读取c.IP()可能返回空或错误 IP - 正确写法:
ip := c.IP(); path := c.Path(); go saveLog(ip, path, time.Now()) - 更稳的做法是用
fiber.New()初始化时配置Config.DisableStartupMessage = true和Config.ErrorHandler,把异常日志单独打出来,避免和统计逻辑耦合
注意中间件挂载顺序和作用域
app.Use() 的路径前缀不是“过滤器”,而是“生效边界”。比如 app.Use("/api", logger.New()),那 /health、/metrics 就完全不会触发这个统计中间件——这常被忽略,导致看板数据偏低。
如果你需要统计全部 API(包括 /openapi.json、/healthz),中间件必须挂载在根路径:app.Use(statMiddleware),且放在所有 app.Get()、app.Post() 之前,否则路由匹配成功后就不再执行 Use() 链。
- 别把统计中间件和
cors、limiter挂在同一前缀下——比如app.Use("/api", cors.New(), limiter.New(), statMiddleware),一旦limiter拒绝请求,statMiddleware就根本不会运行 - 如果用了
static.New()且没设前缀(即app.Use(static.New("./public"))),所有请求(包括/login)都会先被 static 尝试匹配,统计中间件可能收不到真实 API 请求 - 上线前检查
app.Stack()输出,确认统计中间件在栈中位置靠前、无重复挂载
真正难的不是写几行计数代码,而是让统计不干扰主业务、不漏掉任何有效请求、不因中间件顺序或挂载方式产生盲区。尤其是当你的网关层做了路径重写、Nginx 加了 proxy_pass,c.Path() 和真实 API 路径可能已经对不上——这时候必须回溯到原始 header 或 X-Forwarded-Prefix 字段做校准。


















