Java防止重复提交订单的实现示例

 更新时间:2025年11月12日 09:33:33   作者:天天摸鱼的java工程师  
本文主要介绍了Java防止重复提交订单的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

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

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

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

但用户总喜欢:

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

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

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

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

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

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

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

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

✅ 设计思路:

  1. 前端在创建订单前从服务端获取一个唯一 token(防重复提交标识)
  2. 提交订单时将 token 附带传入
  3. 后端验证 token 是否存在(Redis)
  4. 校验通过 → 执行下单逻辑 → 删除 token
  5. 若 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 IGNOREON DUPLICATE KEY 防止重复插入。

七、总结

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

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

✅ 实战建议:

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

📌 最后

到此这篇关于Java防止重复提交订单的实现示例的文章就介绍到这了,更多相关Java防止重复提交订单内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java面向程序的三大特性之封装、继承、多态详解(最新整理)

    Java面向程序的三大特性之封装、继承、多态详解(最新整理)

    面向对象程序三大特性:封装、继承、多态,而类和对象阶段,主要研究的就是封装特性,何为封装呢?简单来说就是套壳屏蔽细节,本文给大家介绍Java面向程序的三大特性之封装、继承、多态,感兴趣的朋友一起看看吧
    2025-06-06
  • Java递归算法经典实例(经典兔子问题)

    Java递归算法经典实例(经典兔子问题)

    本文主要对经典的兔子案例分析,来进一步更好的理解和学习java递归算法,具有很好的参考价值,需要的朋友一起来看下吧
    2016-12-12
  • Springboot项目瘦身之如何将jar包与lib依赖分开打包

    Springboot项目瘦身之如何将jar包与lib依赖分开打包

    这篇文章主要介绍了Springboot项目瘦身之如何将jar包与lib依赖分开打包问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2025-04-04
  • Java实现swf图片转pdf的示例代码

    Java实现swf图片转pdf的示例代码

    本文介绍了一种将SWF文件中的多张图片提取并转换为PDF文件的方法,首先,通过pom依赖提取SWF文件中的图片,然后将这些图片转换为PDF格式,整个过程中,详细测试了该方法的可行性和有效性,需要的朋友可以参考下
    2026-05-05
  • springboot接口接收数组及多个参数的问题及解决

    springboot接口接收数组及多个参数的问题及解决

    这篇文章主要介绍了springboot接口接收数组及多个参数的问题及解决,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-11-11
  • SpringBoot整合MOTT动态读取数据库连接信息并连接MQTT服务端

    SpringBoot整合MOTT动态读取数据库连接信息并连接MQTT服务端

    MQTT是一种轻量级的消息传输协议(Message Queuing Telemetry Transport),旨在实现设备之间的低带宽和高延迟的通信,本文给大家介绍了SpringBoot整合MOTT动态读取数据库连接信息并连接MQTT服务端,需要的朋友可以参考下
    2024-04-04
  • Java中Integer.equals的用法与特殊情况

    Java中Integer.equals的用法与特殊情况

    Java中Integer.equals比较数值而非对象地址,自动装箱处理int参数,-128~127范围内使用==安全,超出范围或null需用Objects.equals,compareTo用于大小比较,equals仅判断相等性,注意类型不匹配会导致false
    2025-07-07
  • Java实战小技巧之数组与list互转

    Java实战小技巧之数组与list互转

    在Java中,经常遇到需要List与数组互相转换的场景,下面这篇文章主要给大家介绍了关于Java实战小技巧之数组与list互转的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考下
    2021-08-08
  • SpringBoot集合Mybatis过程解析

    SpringBoot集合Mybatis过程解析

    这篇文章主要介绍了SpringBoot集合Mybatis过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-10-10
  • IDEA类存在但找不到的解决办法

    IDEA类存在但找不到的解决办法

    本文主要介绍了IDEA类存在但找不到的解决办法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-07-07

最新评论