⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 juejin.cn/post/7527935518204837938 「天天摸鱼的java工程师」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言都将得到认真回复。甚至不知道如何读源码也可以请教噢。
  4. 新的源码解析文章实时收到通知。每周更新一篇左右。
  5. 认真的源码交流微信群。

一、背景介绍:为什么会产生重复提交?

在电商平台中,用户提交订单是一个非常敏感的动作。这通常涉及:

  • 库存扣减
  • 优惠券核销
  • 支付下单
  • 消息发送

但用户总喜欢:

  • 点两次“提交订单”按钮
  • 网络卡顿时刷新页面
  • 使用浏览器回退再次提交

结果就是:重复提交订单,造成资源浪费,甚至业务损失!

二、问题分析:重复提交的常见场景

场景 示例
用户行为 多次点击按钮、浏览器刷新
接口幂等性差 接口无幂等校验,每次都生成新订单
网络重试 客户端自动重发请求(如超时)
分布式系统 多个节点并发处理同一订单请求

三、防止重复提交的核心原则

要解决重复提交问题,必须从接口幂等性 + 请求唯一性 + 服务端锁控制三方面入手:

  • 控制请求的唯一标识(token/nonce)
  • 对订单操作进行幂等处理
  • 引入缓存或分布式锁限制重复提交

四、解决方案:基于Token机制 + Redis锁的防重复提交设计

✅ 设计思路:

  • 前端在创建订单前从服务端获取一个唯一 token(防重复提交标识)
  • 提交订单时将 token 附带传入
  • 后端验证 token 是否存在(Redis)
  • 校验通过 → 执行下单逻辑 → 删除 token
  • 若 token 已被使用 → 拒绝重复提交

五、代码实现(Spring Boot + Redis)

1. 前端获取防重复提交 Token 接口

@RestController
@RequestMapping("/api/order")
public class OrderTokenController {

@Autowired
private RedisTemplate<String, String> redisTemplate;

@GetMapping("/token")
public ResponseEntity<String> getToken() {
String token = UUID.randomUUID().toString();
String key = "order:token:" + token;

// 设置有效期5分钟
redisTemplate.opsForValue().set(key, "valid", Duration.ofMinutes(5));

return ResponseEntity.ok(token);
}
}

2. 提交订单接口(验证token + 删除token)

@RestController
@RequestMapping("/api/order")
public class OrderController {

@Autowired
private OrderService orderService;

@PostMapping("/submit")
public ResponseEntity<String> submitOrder(@RequestBody OrderRequest request,
@RequestHeader("X-Order-Token") String token) {
boolean success = orderService.submitOrder(request, token);
if (success) {
return ResponseEntity.ok("订单提交成功");
} else {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("请勿重复提交订单");
}
}
}

3. OrderService 实现防重复提交逻辑

@Service
public class OrderService {

@Autowired
private RedisTemplate<String, String> redisTemplate;

/**
* 提交订单接口,防止重复提交
*/
public boolean submitOrder(OrderRequest request, String token) {
String redisKey = "order:token:" + token;

// 利用 Redis 的 delete + check 保证幂等性(原子性)
Boolean result = redisTemplate.delete(redisKey);

if (Boolean.TRUE.equals(result)) {
// token存在并删除 → 第一次提交
// 执行正常订单创建逻辑
createOrder(request);
return true;
} else {
// token 不存在 → 重复提交
return false;
}
}

private void createOrder(OrderRequest request) {
// 实际业务处理:生成订单号、校验库存、扣减库存、写库、发MQ等
System.out.println("处理订单:" + request);
}
}

4. 请求对象 OrderRequest 示例

@Data
public class OrderRequest {
private Long userId;
private List<Long> productIds;
private BigDecimal totalAmount;
}

六、进阶优化建议

1. 使用 Lua 脚本保证 Redis 操作原子性

Redis delete 操作不是强原子性的,建议使用 Lua 脚本执行 “判断 + 删除” 逻辑。

// Lua 脚本实现原子删除
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
"return redis.call('del', KEYS[1]) else return 0 end";

DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
redisScript.setScriptText(script);
redisScript.setResultType(Long.class);

Long result = redisTemplate.execute(redisScript, Collections.singletonList(key), "valid");

2. 给订单接口加限流或熔断保护(如 Sentinel)

  • 防止恶意刷接口
  • 降低重复提交带来的系统压力

3. 数据库层幂等校验(双保险)

即便应用层失效,也可以通过数据库约束(如订单号唯一)+ INSERT IGNORE 或 ON DUPLICATE KEY 防止重复插入。

七、总结

面对用户重复提交订单的问题,我们不能只靠前端“禁用按钮”了,而是应该从后端保障:

  • 请求唯一性
  • 接口幂等性
  • 服务端锁机制

✅ 实战建议:

  • Redis 是处理幂等控制的利器
  • token机制简单实用,适用于下单、支付、秒杀等场景
  • 多层防御更安全:应用层 + 数据库层

最后

这篇文章分享了我在实际项目中防止订单重复提交的完整方案,希望对你有所帮助!

如果你也在做订单系统、支付系统,欢迎留言交流你的经验。

文章目录
  1. 1. 一、背景介绍:为什么会产生重复提交?
  2. 2. 二、问题分析:重复提交的常见场景
  3. 3. 三、防止重复提交的核心原则
  4. 4. 四、解决方案:基于Token机制 + Redis锁的防重复提交设计
    1. 4.0.0.1. ✅ 设计思路:
  • 5. 五、代码实现(Spring Boot + Redis)
    1. 5.0.1. 1. 前端获取防重复提交 Token 接口
    2. 5.0.2. 2. 提交订单接口(验证token + 删除token)
    3. 5.0.3. 3. OrderService 实现防重复提交逻辑
    4. 5.0.4. 4. 请求对象 OrderRequest 示例
  • 6. 六、进阶优化建议
    1. 6.0.1. 1. 使用 Lua 脚本保证 Redis 操作原子性
    2. 6.0.2. 2. 给订单接口加限流或熔断保护(如 Sentinel)
    3. 6.0.3. 3. 数据库层幂等校验(双保险)
  • 7. 七、总结
    1. 7.0.0.1. ✅ 实战建议:
  • 8. 最后