防重核心是服务端生成唯一请求标识并用Redis原子命令校验:基于请求体哈希+时间戳+随机数生成ID,通过SET NX EX实现原子存取,过期设2–5秒,配合Gin中间件封装,兼顾幂等性与客户端提示。

核心思路是:为每次请求生成唯一标识(如基于参数+时间戳+随机数的签名),在接口入口处用 Redis 缓存做“请求指纹”校验,已存在则拒绝处理,否则写入缓存并放行。关键在于标识的唯一性、缓存的原子性、以及过期时间的合理设置。
生成唯一请求标识(Request ID)
不依赖客户端传参(易伪造),推荐服务端生成或组合可信字段:
- 对请求 Body(JSON)做 SHA256 哈希,再截取前16位 + 时间戳毫秒 + 微秒级随机数,拼接后 Base64 编码,确保高区分度
- 若含用户 ID 和业务主键(如 order_id),可组合
userID:orderID:timestamp再哈希,天然绑定上下文,防跨用户重放 - 避免仅用 UUID 或纯时间戳——前者无业务语义,后者在高并发下易冲突
Redis 缓存校验(原子性是关键)
用 SET key value EX seconds NX 命令实现“设值并校验是否新增成功”,单命令保证原子性:
- 返回
OK→ 首次请求,正常执行后续逻辑 - 返回
(nil)→ 已存在,直接返回400 Bad Request或自定义错误码(如err: duplicate_request) - 过期时间建议 2–5 秒:太短可能误判(网络延迟、重试),太长影响高频操作(如秒杀下单)
结合 Gin 中间件统一拦截
封装成可复用中间件,自动提取 Body、计算指纹、调用 Redis 校验:
立即学习“go语言免费学习笔记(深入)”;
- 使用
c.Request.Body读取一次后需用gin.BindJSON或ioutil.ReadAll重置,避免 Body 被消耗导致下游解析失败 - 中间件内捕获 panic,兜底记录日志(如 Redis 连接异常时降级为只告警不阻断)
- 支持按路由或 method 白名单跳过(如 GET 查询类接口无需防重)
边界情况与增强建议
真实场景中还需考虑:
-
red">幂等性补充:防重只是第一层,业务层仍需用数据库唯一索引(如
user_id+order_no)或状态机校验(如“待支付”才允许创建) - 客户端友好提示:返回明确 message(如 “操作过于频繁,请稍后再试”),而非裸错误码,降低前端排查成本
-
缓存清理时机:成功处理后无需主动删 key —— 依靠过期自动清理;若需提前释放(如异步任务失败回滚),可用
DEL,但要加锁防竞态
基本上就这些。不复杂但容易忽略细节,比如 Body 读取顺序、Redis 命令原子性、过期时间粒度——踩过坑才明白为什么有些“防重”上线后还是有重复数据。










