典型重复请求源于双击、网络重发等,需用limiter或cache中间件在入口拦截;limiter按IP+路径+体哈希限流,cache可扩展支持POST去重;前后端需协同,前端禁用按钮为首要防线。

重复请求的典型表现和中间件定位点
用户快速双击按钮、网络抖动重发、前端未禁用提交按钮,都会导致同一请求在极短时间内重复抵达后端。Fiber 本身不自动去重,必须靠中间件在请求入口层拦截。关键不是“识别相同请求”,而是“对具备幂等特征的请求做短时窗口内拒绝”。重点应放在 POST、PUT、PATCH 等非安全方法上,GET 和 HEAD 天然幂等,无需处理。
用 limiter 中间件实现请求频控
最直接的方式是复用 Fiber 内置的 limiter,按客户端 IP + 请求路径 + 请求体哈希(可选)组合限流,把窗口设为秒级甚至毫秒级,让重复请求在到达业务逻辑前就被拦下。
- 单机部署可用内存计数:
limiter.New(limiter.Config{Max: 1, Window: 2 * time.Second})表示每 2 秒最多允许 1 次该路径请求 - 若需区分用户(如带有效
Authorizationheader),需自定义KeyFunc提取唯一标识,不能只依赖 IP - 注意
limiter.Config.Max是整数,Window单位是time.Duration;写成Window: 2会被当纳秒,实际窗口只有 2ns,等于永远拒绝 - 不要把
limiter放在static中间件之后——静态文件路由若没配好前缀,可能误触限流
用 cache 中间件拦截已响应过的重复请求
cache 中间件默认只缓存 GET/HEAD 响应,但你可以手动开启对 POST 的支持,并控制缓存键生成逻辑,实现“相同请求体 → 相同缓存键 → 命中即返回旧响应”的效果。
- 启用非 GET 方法缓存需显式设置
Next函数返回false以跳过默认拦截:Next: func(c fiber.Ctx) bool { return false } - 自定义
KeyGenerator,例如拼接c.Method() + c.Path() + sha256.Sum256(c.Body()).String(),确保相同请求体生成相同键 - 缓存时间不宜过长(建议
Expiration: 10 * time.Second),否则会掩盖真实业务失败 - 注意:若响应中含
Set-Cookie或状态码为3xx/4xx/5xx,cache默认跳过缓存,需在Next中主动放行
前端配合与后端兜底的边界问题
中间件防重只是后端最后一道防线。真正健壮的方案必须前后端协同,且明确中间件无法覆盖的场景。
- 前端必须加按钮 loading 状态 + 禁用提交,这是成本最低、效果最稳的一环
-
limiter和cache都无法防止两个**语义相同但请求体不同**的请求(比如仅 timestamp 字段变化),这种需业务层用唯一业务 ID(如X-Request-ID)+ DB 唯一索引兜底 - 超时请求未被中断时,
limiter只能限制新请求,旧请求仍在执行——必须搭配Timeout中间件 + 手动监听ctx.Context().Done()终止下游操作 - 所有防重中间件都应在路由组级别注册(如
api.Post("/order", ...)),避免全局生效干扰健康检查或静态资源


















