围绕下单场景整理接口幂等与防重复提交方案,包含唯一约束、幂等 token 与状态机控制。
接口防重复提交怎么做?一次幂等设计入门实践h1
这篇起因很简单h2
我刚写后端接口时,总觉得“用户点一次按钮,后端就处理一次”是理所当然的。
但后来发现,真实场景里重复请求很常见:
- 用户连点两次提交按钮
- 前端超时后自动重试
- 网络抖动导致客户端重复发起请求
如果后端没有做控制,就可能出现:
- 重复下单
- 重复扣款
- 重复发券
直到一次联调里同一个订单被提交了两次,我才意识到“接口能跑”不等于“业务结果一定对”。
所以我后来开始认真补幂等这块。
什么是幂等h2
我的理解是:
同一个请求,无论执行一次还是多次,最终结果都应该符合预期,不应该产生重复副作用。
比如“查询接口”天然就比较幂等,而“创建订单”“支付回调”这类写操作,就需要额外设计。
一个典型问题场景h2
假设有个下单接口:
@PostMapping("/order")public Result<Long> createOrder(@RequestBody OrderDTO dto) { return Result.ok(orderService.createOrder(dto));}如果用户因为网络卡顿,连续点了两次“提交订单”,后端可能就创建出两笔完全一样的订单。
问题不在于代码报错,而在于:业务结果错了。
我先说我现在的做法h2
防重复提交常见有 4 类思路:
- 前端防抖 / 按钮置灰
- 唯一索引做兜底
- Token / 幂等号机制
- Redis 分布式锁或状态校验
如果是面试场景,我一般会这样回答:
- 前端先做基础防重复
- 后端必须做真正兜底
- 核心写操作通常用“幂等号 + 唯一约束”更稳
方案一:前端按钮置灰h2
这是最表层的一层防护。
比如用户点击“提交订单”后,按钮马上置灰,等接口返回后再恢复。
优点h3
- 实现简单
- 用户体验直观
缺点h3
- 只能防正常用户操作
- 防不了网络重试、脚本请求、回放请求
所以它只能算“第一层”,不能当最终方案。
方案二:数据库唯一索引兜底h2
如果业务上有天然唯一标识,可以直接加唯一索引。
比如一个用户同一场活动只能领取一次优惠券:
CREATE UNIQUE INDEX uk_user_couponON user_coupon(user_id, coupon_id);这样即使并发下多次插入,也只有一条能成功。
我的理解h3
数据库唯一约束是最靠谱的兜底手段之一,因为它最终守住了数据层。
适用场景h3
- 领取优惠券
- 用户签到
- 用户报名
- 某种“同一个人只能成功一次”的业务
方案三:幂等 Token / 幂等号h2
这个方案在写操作里很常见。
思路h3
- 客户端发起请求前,先拿一个唯一幂等号
- 提交时带上这个幂等号
- 服务端判断这个幂等号是否已处理过
- 如果处理过,直接返回之前结果或拒绝重复处理
示例流程h3
客户端先获取 idempotentToken-> 提交订单时携带 token-> 服务端校验 token 是否已消费-> 未消费:执行业务并标记已消费-> 已消费:拒绝重复提交简化实现思路(Redis)h3
String key = "order:token:" + token;Boolean success = stringRedisTemplate.opsForValue() .setIfAbsent(key, "1", 5, TimeUnit.MINUTES);if (Boolean.FALSE.equals(success)) { throw new IllegalArgumentException("请勿重复提交");}这里本质上是利用 Redis 的原子性,把“是否第一次提交”这个判断做掉。
优点h3
- 适合创建类接口
- 逻辑清晰
- 和前后端分离场景兼容得很好
注意点h3
- Token 要有有效期
- 成功和失败的业务定义要统一
- 最好配合数据库唯一约束,不要只靠 Redis
方案四:基于业务状态做幂等控制h2
有些场景不一定用 token,而是直接根据业务状态判断。
比如支付回调:
- 如果订单状态已经是“已支付”
- 那么重复回调就直接忽略
if (order.getStatus() == OrderStatus.PAID) { return;}这种方式的本质是:同一个业务只能从某状态推进一次。
适合场景h3
- 支付回调
- MQ 消费去重
- 状态机类业务
我现在会用的组合方案h2
如果让我现在设计“下单防重复提交”,我会这么做:
- 前端按钮置灰,减少无效重复点击
- 请求带幂等 token
- 后端 Redis 校验 token 只消费一次
- 数据库关键字段加唯一约束兜底
这样就不是单点防护,而是多层保护。
我踩过的坑h2
坑 1:只做前端防抖h3
前端只能防“普通用户重复点击”,防不了重试和并发。
坑 2:只做 Redis,不做数据库兜底h3
如果极端情况下 Redis 逻辑有问题,数据库层没有约束,还是可能写脏数据。
坑 3:把“重复请求”与“幂等请求”混为一谈h3
重复请求不一定安全,只有你设计过后,它才真正幂等。
联调前我会这样自测h2
- 连续点击两次提交,是否只生成一笔订单
- 请求超时后重试,是否会重复处理
- 并发提交下,数据库是否还能守住唯一性
- 重复请求时,接口返回是否清晰可理解
面试里我会这样描述h2
可以按这个结构讲:
- 问题:创建类接口可能因为重复点击、网络重试导致重复写入
- 方案:前端防抖 + 后端幂等 token + 数据库唯一约束
- 关键点:前端减少重复,后端真正兜底,数据库保证最终一致性
- 适用场景:下单、支付、发券、回调处理
这篇我的结论h2
接口防重复提交的核心不是“拦请求”,而是:
即使同一个请求来了多次,后端也只能安全地处理一次。
评论