基于 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 延迟消息,我们确保了整个流程的原子性和可靠性。

相关推荐
用户83071968408221 小时前
RabbitMQ vs RocketMQ 事务大对决:一个在“裸奔”,一个在“开挂”?
后端·rabbitmq·rocketmq
初次攀爬者2 天前
RabbitMQ的消息模式和高级特性
后端·消息队列·rabbitmq
初次攀爬者4 天前
ZooKeeper 实现分布式锁的两种方式
分布式·后端·zookeeper
让我上个超影吧5 天前
消息队列——RabbitMQ(高级)
java·rabbitmq
塔中妖5 天前
Windows 安装 RabbitMQ 详细教程(含 Erlang 环境配置)
windows·rabbitmq·erlang
断手当码农5 天前
Redis 实现分布式锁的三种方式
数据库·redis·分布式
初次攀爬者5 天前
Redis分布式锁实现的三种方式-基于setnx,lua脚本和Redisson
redis·分布式·后端
业精于勤_荒于稀5 天前
物流订单系统99.99%可用性全链路容灾体系落地操作手册
分布式
Ronin3055 天前
信道管理模块和异步线程模块
开发语言·c++·rabbitmq·异步线程·信道管理
Asher05095 天前
Hadoop核心技术与实战指南
大数据·hadoop·分布式