Spring Boot防重复提交需分层设防:前端按钮置灰+防抖降压;后端用Redis基于请求指纹短期去重;数据库加唯一索引兜底;分布式锁须在事务开始前获取、提交后释放。

Spring Boot 中处理重复提交异常,关键不是“捕获异常再兜底”,而是提前识别、拦截并拒绝重复请求。真正的重复提交问题一旦走到数据库插入阶段才抛异常,说明防线已经失守——订单已生成、库存已扣减、资金已划转,这时候再“处理异常”为时已晚。所以核心思路是:分层设防,前端控体验,后端做校验,数据库保底线。
前端按钮置灰 + 防抖是第一道软拦截
这不是可有可无的“锦上添花”,而是降低后端压力的基础动作。用户点一次按钮就发两三次请求,后端再强也扛不住流量放大。
- 提交按钮点击后立即禁用(
disabled=true),并显示 loading 状态 - 对表单提交事件加防抖(debounce),例如延迟 800ms 再触发,避免手速快导致的连续点击
- 移动端需额外处理回车、扫码枪、手势误触等非点击类重复触发源
后端基于请求指纹做短期去重校验
真正可靠的防重必须在后端完成,且不能依赖单机锁(集群环境下失效)。推荐用“请求指纹 + Redis 缓存”组合:
你是 TypeScript 项目架构专家,专注于脚手架搭建生产就绪的 Node.js 和前端应用。生成包含现代工具链(pnpm、Vite、Next.js)、类型
- 前端每次请求带上唯一标识,如
X-Request-ID(由 UUID 生成) - 后端提取关键维度拼成 key:例如
"submit:order:" + userId + ":" + orderId + ":" + requestId - 用
SET key value EX 5 NX原子写入 Redis,成功则放行,失败则直接返回“请勿重复提交” - 过期时间建议设为业务执行耗时的 1.5–2 倍(如下单接口平均 1.2s,设 3 秒),既防漏又不阻塞正常流程
数据库唯一约束是最后一道硬防线
无论前端多完善、Redis 多稳定,都可能因网络分区、服务重启、缓存击穿等原因短暂失效。此时唯一索引就是数据一致性的最终保障。
- 在订单表加唯一索引:
UNIQUE KEY uk_user_order (user_id, order_no) - 在优惠券领取表加:
UNIQUE KEY uk_user_coupon (user_id, coupon_id, biz_type) - 后端捕获
DuplicateKeyException,统一转换为友好的业务提示(如“您已领取过该优惠券”),而非 500 错误
别忽略事务与锁的顺序陷阱
很多团队用了 Redis 分布式锁,却仍在高并发下出现重复——问题常出在“锁释放时机”和“事务提交顺序”上。
- 锁必须在事务 开始前 获取,且在事务 提交后 才释放(不能在 try-finally 里提前释放)
- 否则可能出现:线程 A 拿锁 → 查询库存 → 提交事务 → 释放锁 → 线程 B 拿到锁 → 查询库存(此时库存还是旧值)→ 提交事务 → 重复扣减
- 推荐用 AOP + 自定义注解封装锁逻辑,并确保与
@Transactional协同生效(注意代理顺序)










