Fiber中间件必须用ctx.Next()而非return,否则跳过defer清理、日志及鉴权;Redis防重需原子设置key与过期时间;提取请求ID应一次性读body并保留原始流;Token须绑定用户上下文且成功后清理。

为什么 Fiber 的中间件必须用 ctx.Next() 而不是 return
在 Fiber 中,中间件执行流程依赖于显式调用 ctx.Next() 向下传递控制权。如果在防重逻辑里直接 return(比如校验失败就提前退出),后续中间件和路由处理器不会执行——这看似合理,但会跳过 defer 清理、日志记录、监控埋点等关键逻辑。更隐蔽的问题是:若你后续加了 JWT 鉴权中间件,它可能因没被调用而让非法请求“绕过鉴权”直抵业务层。
正确做法是校验失败时调用 ctx.Status(400).SendString("重复提交"),然后仍执行 ctx.Next() —— 但需确保下游处理器能识别已响应状态,避免二次写 body。Fiber 官方推荐用 ctx.Response().Committed 判断是否已发送响应。
redis.SetNX + redis.Expire 必须合并为原子操作
常见错误写法是先 redis.SetNX(key, "1"),再 redis.Expire(key, 30)。这两步非原子:若进程在 SetNX 成功后、Expire 前崩溃,key 将永久存在,导致该请求 ID 永久失效,用户无法重试。
应改用 redis.Set(key, "1", 30*time.Second)(即带过期时间的 set),或使用 redis.SetNX(key, "1", 30*time.Second)(仅当 key 不存在时设置并设过期)。后者更安全,因为能明确区分“首次设置成功”和“已存在”。
注意:Fiber 默认的 redis.Client 实例(如基于 go-redis)需确认是否启用 pipeline 或是否支持 SetNX 原子命令;若用旧版 redigo,需手动拼 SET key val EX 30 NX。
如何从请求体提取唯一标识而不破坏原始数据流
Fiber 的 ctx.Body() 是一次性读取,多次调用会返回空。防重中间件需在解析前拿到原始 body,否则后续 ctx.BodyParser() 会失败。
推荐做法:
- 调用
body := ctx.Body()一次,保存为局部变量 - 用
json.Unmarshal(body, &req)解析结构体,同时从中提取req.RequestID或req.OrderNo - 若需保留原始 body 给下游使用,可调用
ctx.Request().ResetBody() → ctx.Request().SetBody(body)(仅限内存足够时) - 更稳妥的是:只提取必要字段(如
request_id),不全量解析;业务层仍走标准ctx.BodyParser()
别用 ctx.FormValue() 或 ctx.QueryParam() 替代——表单或 query 里的 request_id 易被篡改,且不适用于 JSON 请求体。
Token 生成与校验必须绑定用户上下文
单纯用 UUID 生成 token 不足以防重:攻击者可批量请求 /token 接口获取一堆有效 token,再并发提交。必须绑定用户维度,例如:
- 对登录用户:token =
sha256(fmt.Sprintf("%s:%s", userID, time.Now().UnixNano())),并存入 Redis 时加 user_id 前缀:redis.Set("user:123:token:"+token, "1", 10*time.Minute) - 对未登录用户:用设备指纹(如 UA+IP 的哈希)代替 userID,但需接受一定误判率
- 校验时严格比对
userID + request_id组合,而非仅request_id
漏掉用户绑定,等于把分布式锁退化成全局锁,高并发下 Redis 成为瓶颈,且无法防止同一用户多端重复提交。
最易被忽略的是清理时机:token 必须在业务处理成功后才删除;若业务失败(如库存不足),token 应保留,允许用户修正后重试。强行在中间件末尾无条件 Del,会导致失败请求无法重试。


















