面试题:如何避免用户重复下单(多次下单未支付,占用库存)

在电商系统中,避免用户重复下单(多次下单未支付,占用库存)是一个常见的需求。以下是一些后端相关的解决方案,主要基于Java技术栈:

1. 使用分布式锁

在用户创建订单时,可以使用分布式锁(如Redis的SETNX命令)来确保同一用户在同一时间只能创建一个订单。这样可以防止用户并发提交多个订单。

   public boolean createOrder(String userId, String productId, int quantity) {
       String lockKey = "order_lock:" + userId;
       String requestId = UUID.randomUUID().toString();
       try {
           // 尝试获取分布式锁
           boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, requestId, 30, TimeUnit.SECONDS);
           if (!locked) {
               throw new RuntimeException("操作过于频繁,请稍后再试");
           }

           // 检查用户是否有未支付的订单
           if (orderService.hasUnpaidOrder(userId)) {
               throw new RuntimeException("您有未支付的订单,请先支付或取消");
           }

           // 创建订单
           Order order = orderService.createOrder(userId, productId, quantity);
           return true;
       } finally {
           // 释放锁
           if (requestId.equals(redisTemplate.opsForValue().get(lockKey))) {
               redisTemplate.delete(lockKey);
           }
       }
   }

2. 订单状态管理

在数据库中维护订单的状态(如待支付已支付已取消等),并在创建订单时检查用户是否有未支付的订单。如果有,则不允许创建新订单。

   public boolean createOrder(String userId, String productId, int quantity) {
       // 检查用户是否有未支付的订单
       if (orderRepository.existsByUserIdAndStatus(userId, OrderStatus.UNPAID)) {
           throw new RuntimeException("您有未支付的订单,请先支付或取消");
       }

       // 创建订单
       Order order = new Order();
       order.setUserId(userId);
       order.setProductId(productId);
       order.setQuantity(quantity);
       order.setStatus(OrderStatus.UNPAID);
       orderRepository.save(order);

       return true;
   }

3. 库存预占机制

在用户创建订单时,预占库存(即将库存从可用库存中扣除,并标记为预占状态)。如果订单超时未支付,则释放预占的库存。

   public boolean createOrder(String userId, String productId, int quantity) {
       // 预占库存
       if (!inventoryService.reserveStock(productId, quantity)) {
           throw new RuntimeException("库存不足,无法创建订单");
       }

       // 创建订单
       Order order = new Order();
       order.setUserId(userId);
       order.setProductId(productId);
       order.setQuantity(quantity);
       order.setStatus(OrderStatus.UNPAID);
       orderRepository.save(order);

       // 设置订单超时未支付的处理
       scheduleOrderTimeout(order.getId());

       return true;
   }

   private void scheduleOrderTimeout(String orderId) {
       // 使用定时任务或延迟队列处理超时未支付的订单
       // 例如,使用Redis的延迟队列或Spring的@Scheduled注解
   }

4. 幂等性处理

在创建订单的接口中,使用幂等性设计,确保同一请求多次提交不会产生多个订单。可以通过在请求中携带唯一标识(如requestId)来实现。

   public boolean createOrder(String userId, String productId, int quantity, String requestId) {
       // 检查是否已经处理过该请求
       if (orderRepository.existsByRequestId(requestId)) {
           throw new RuntimeException("重复请求,订单已创建");
       }

       // 创建订单
       Order order = new Order();
       order.setUserId(userId);
       order.setProductId(productId);
       order.setQuantity(quantity);
       order.setStatus(OrderStatus.UNPAID);
       order.setRequestId(requestId);
       orderRepository.save(order);

       return true;
   }

5. 消息队列异步处理

使用消息队列(如Kafka、RabbitMQ)异步处理订单创建和库存扣减操作。通过消息队列的幂等性和顺序性,确保订单创建的原子性。

   public void createOrderAsync(String userId, String productId, int quantity) {
       // 发送创建订单的消息到消息队列
       OrderMessage message = new OrderMessage(userId, productId, quantity);
       kafkaTemplate.send("order-create-topic", message);
   }

   @KafkaListener(topics = "order-create-topic")
   public void handleOrderCreation(OrderMessage message) {
       // 处理订单创建逻辑
       orderService.createOrder(message.getUserId(), message.getProductId(), message.getQuantity());
   }

6. 数据库唯一约束

在数据库层面,可以通过唯一约束来防止用户创建多个未支付的订单。例如,在订单表中添加userIdstatus的组合唯一索引。

   CREATE UNIQUE INDEX idx_user_unpaid_order ON orders (user_id, status) WHERE status = 'UNPAID';

在Java代码中,捕获数据库的唯一约束异常,并提示用户。

   public boolean createOrder(String userId, String productId, int quantity) {
       try {
           // 创建订单
           Order order = new Order();
           order.setUserId(userId);
           order.setProductId(productId);
           order.setQuantity(quantity);
           order.setStatus(OrderStatus.UNPAID);
           orderRepository.save(order);
           return true;
       } catch (DataIntegrityViolationException e) {
           throw new RuntimeException("您有未支付的订单,请先支付或取消");
       }
   }

总结

以上几种方法可以单独使用,也可以结合使用,具体取决于系统的复杂性和业务需求。分布式锁和幂等性处理是比较常见的解决方案,而库存预占机制和消息队列异步处理则适用于高并发场景。

THE END
点赞5 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容