基于 RabbitMQ 实现延迟消息的订单处理流程

文章目录

      • 订单创建流程
        • [1. 商品查询与订单数据初始化](#1. 商品查询与订单数据初始化)
        • [2. 总价计算与订单保存](#2. 总价计算与订单保存)
        • [3. 扣减库存与购物车清理](#3. 扣减库存与购物车清理)
        • [4. 延迟消息与支付状态检测](#4. 延迟消息与支付状态检测)
      • 订单延迟消息监听器
      • 支付成功与订单取消
        • [1. 订单支付成功](#1. 订单支付成功)
        • [2. 订单取消与库存恢复](#2. 订单取消与库存恢复)
      • 总结

在现代电商系统中,订单处理是一个复杂且关键的环节。从商品查询到库存扣减、订单生成、支付确认等多个步骤,任何一个环节出现问题都可能导致用户体验下降甚至订单失败。今天,我们将通过一个典型的 Java Spring Boot 订单处理流程,结合 RabbitMQ 延迟消息 和全局事务管理,确保每一步都能顺利执行。

订单创建流程

在这个系统中,我们主要关注的功能是 createOrder 方法,它实现了从订单创建到支付确认的全过程。让我们一起来看看代码背后都发生了什么。

java 复制代码
@Override
@GlobalTransactional
public Long createOrder(OrderFormDTO orderFormDTO) {
    // 1.订单数据
    Order order = new Order();
    // 1.1.查询商品
    List<OrderDetailDTO> detailDTOS = orderFormDTO.getDetails();
    // 1.2.获取商品id和数量的Map
    Map<Long, Integer> itemNumMap = detailDTOS.stream().collect(
            Collectors.toMap(OrderDetailDTO::getItemId, OrderDetailDTO::getNum));
    Set<Long> itemIds = itemNumMap.keySet();
    // 1.3.查询商品
    List<ItemDTO> items = itemClient.queryItemByIds(itemIds);
    if (items == null || items.size() < itemIds.size()) {
        throw new BadRequestException("商品不存在");
    }
    // 1.4.基于商品价格、购买数量计算商品总价:totalFee
    int total = 0;
    for (ItemDTO item : items) {
        total += item.getPrice() * itemNumMap.get(item.getId());
    }
    order.setTotalFee(total);
    // 1.5.其它属性
    order.setPaymentType(orderFormDTO.getPaymentType());
    order.setUserId(UserContext.getUser());
    order.setStatus(1);
    // 1.6.将Order写入数据库order表中
    save(order);

    // 2.保存订单详情
    List<OrderDetail> details = buildDetails(order.getId(), items, itemNumMap);
    detailService.saveBatch(details);

    // 3.扣减库存
    try {
        itemClient.deductStock(detailDTOS);
    } catch (Exception e) {
        throw new RuntimeException("库存不足!");
    }

    // 4.清理购物车商品
    cartClient.deleteCartItemByIds(itemIds);

    // 5.发送延迟消息,检测订单支付状态
    rabbitTemplate.convertAndSend(MQConstants.DELAY_EXCHANGE_NAME, MQConstants.DELAY_ORDER_KEY, order.getId(),
                                  message -> {
                                      message.getMessageProperties().setDelay(15 * 60 * 1000); // 15分钟延迟
                                      return message;
                                  });
    return order.getId();
}
1. 商品查询与订单数据初始化

首先,我们从用户提交的订单数据中提取商品 ID 和数量,并通过 itemClient.queryItemByIds 方法批量查询商品信息。如果查询结果中商品数量与请求的商品数量不符,则抛出异常提示"商品不存在"。

2. 总价计算与订单保存

接下来,系统根据商品单价与购买数量计算总价,并将相关信息写入数据库。在这一过程中,我们利用 Java 的流处理特性,对商品列表进行遍历与总价计算。

3. 扣减库存与购物车清理

订单数据保存成功后,系统会尝试扣减相应库存,并清理用户购物车中的相关商品。如果库存不足,系统会抛出异常并回滚整个事务。

4. 延迟消息与支付状态检测

最后,系统通过 RabbitMQ 发送一个延迟消息,以便在未来某个时间点检查订单支付状态。这一步保证了即便用户在支付过程中遇到问题,系统也能够及时响应并处理未支付的订单。这里,我们将延迟消息的时间设置为 15 分钟

订单延迟消息监听器

接下来,我们来看看如何利用消息队列处理订单支付状态的异步检测。

java 复制代码
@Component
@RequiredArgsConstructor
public class OrderDelayMessageListener {
    private final IOrderService orderService;
    private final PayClient payClient;

    @RabbitListener(bindings = @QueueBinding(value = @Queue(MQConstants.DELAY_ORDER_QUEUE_NAME),
                                             exchange = @Exchange(name = MQConstants.DELAY_EXCHANGE_NAME,
                                                                  delayed = "true"), key = MQConstants.DELAY_ORDER_KEY))
    public void listenOrderDelayMessage(Long orderId) {
        // 1.查询订单
        Order order = orderService.getById(orderId);
        // 2.判断订单状态,是否已支付
        if (order == null || order.getStatus() != 1) {
            // 不做处理
            return;
        }
        // 3.未支付,需要查询支付流水状态
        PayOrderDTO payOrder = payClient.queryPayOrderByBizOrderNo(orderId);
        // 4.判断是否支付
        if (payOrder != null && payOrder.getStatus() == 3) {
            // 4.1.已经支付,标记订单状态为已支付
            orderService.markOrderPaySuccess(orderId);
        } else {
            // 4.2.未支付,取消订单,回复库存
            orderService.cancelOrder(orderId);
        }
    }
}

在上述代码中,我们利用了 RabbitMQ 的延迟队列特性。当订单延迟消息被监听器捕获后,系统会根据订单当前状态和支付流水状态决定接下来的处理步骤。如果支付成功,订单状态将被更新为已支付;否则,订单将被取消,并恢复相应库存。

支付成功与订单取消

java 复制代码
@Override
public void markOrderPaySuccess(Long orderId) {
    Order order = new Order();
    order.setId(orderId);
    order.setStatus(2);
    order.setPayTime(LocalDateTime.now());
    updateById(order);
}

@Override
@GlobalTransactional
public void cancelOrder(Long orderId) {
    // 标记订单为已关闭
    Order order = getById(orderId);
    if (order != null && order.getStatus() != 5) {
        order.setStatus(5);
        updateById(order);
    }
    // 恢复库存
    List<OrderDetail> details = detailService.lambdaQuery().eq(OrderDetail::getOrderId, orderId).list();
    List<OrderDetailDTO> orderDetailDTOS = BeanUtils.copyList(details, OrderDetailDTO.class);
    itemClient.restoreStock(orderDetailDTOS);
}
1. 订单支付成功

markOrderPaySuccess 方法中,系统将订单状态更新为已支付,并记录支付时间。这一操作确保用户和系统对订单状态有准确的认知。

2. 订单取消与库存恢复

如果订单在延迟消息检测中被认定为未支付,则调用 cancelOrder 方法取消订单,并通过 itemClient.restoreStock 恢复相应库存。由于这些操作可能涉及多个服务,因此通过 @GlobalTransactional 注解确保其在全局事务中执行。

总结

在本篇博客中,我们深入探讨了一个完整的订单处理流程。从商品查询到库存管理,再到支付状态检测,每一步都经过精心设计与实现。通过 Spring Boot 的全局事务管理和 RabbitMQ 延迟消息,我们确保了整个流程的原子性和可靠性。

相关推荐
ZHOU西口21 分钟前
微服务实战系列之玩转Docker(十八)
分布式·docker·云原生·架构·数据安全·etcd·rbac
zmd-zk31 分钟前
kafka+zookeeper的搭建
大数据·分布式·zookeeper·中间件·kafka
yx9o6 小时前
Kafka 源码 KRaft 模式本地运行
分布式·kafka
Gemini19956 小时前
分布式和微服务的区别
分布式·微服务·架构
G丶AEOM6 小时前
分布式——BASE理论
java·分布式·八股
P.H. Infinity12 小时前
【RabbitMQ】03-交换机
分布式·rabbitmq
龙哥·三年风水15 小时前
群控系统服务端开发模式-应用开发-个人资料
分布式·php·群控系统
funnyZpC17 小时前
quartz集群增强版🎉
java·分布式·开源·集群·定时任务
明达技术17 小时前
工业4.0时代下的分布式IO模块
分布式
天冬忘忧19 小时前
Spark 程序开发与提交:本地与集群模式全解析
大数据·分布式·spark