订单超时取消与库存回滚的完整实现(延迟任务 + 状态机)

这是一个非常典型的业务闭环:下单后未支付自动取消,并且把库存扣回去。

目标

  1. 超时未支付订单自动取消
  2. 取消时库存回滚
  3. 所有操作幂等、可重试

一、订单状态机(代码层约束)

java 复制代码
public enum OrderStatus {
    UNPAID, PAID, CANCELED
}

public final class OrderStateMachine {
    private static final Map<OrderStatus, Set<OrderStatus>> ALLOWED = Map.of(
        OrderStatus.UNPAID, Set.of(OrderStatus.PAID, OrderStatus.CANCELED),
        OrderStatus.PAID, Set.of(),        // 已支付不可取消
        OrderStatus.CANCELED, Set.of()     // 终态
    );

    public static void assertCanTransfer(OrderStatus from, OrderStatus to) {
        if (!ALLOWED.getOrDefault(from, Set.of()).contains(to)) {
            throw new IllegalStateException("状态不允许流转: " + from + " -> " + to);
        }
    }
}

二、SQL 幂等更新(核心安全点)

1) 下单(预扣库存 + UNPAID)

sql 复制代码
UPDATE stock
SET quantity = quantity - 1
WHERE sku_id = ? AND quantity > 0;

INSERT INTO orders (id, user_id, status, create_time)
VALUES (?, ?, 'UNPAID', NOW());

2) 超时取消(只允许 UNPAID -> CANCELED)

sql 复制代码
UPDATE orders
SET status = 'CANCELED', cancel_time = NOW()
WHERE id = ? AND status = 'UNPAID';

3) 库存回滚(必须幂等)

sql 复制代码
UPDATE stock
SET quantity = quantity + 1
WHERE sku_id = ?;

三、延迟任务(定时取消)

你可以用 MQ 延迟消息,也可以用定时任务。下面以 MQ 延迟为例。

java 复制代码
public void createOrder(Order order) {
    // 1) 创建订单 + 预扣库存
    createOrderAndDeductStock(order);

    // 2) 发送延迟消息(30分钟后检查是否未支付)
    rabbitTemplate.convertAndSend("order.delay.exchange", "order.delay", order.getId());
}

消费端逻辑:

java 复制代码
public void handleCancel(Long orderId) {
    Order order = orderMapper.selectById(orderId);
    if (order == null) return;

    // 状态机校验
    OrderStateMachine.assertCanTransfer(order.getStatus(), OrderStatus.CANCELED);

    // 幂等取消
    int rows = orderMapper.cancelIfUnpaid(orderId);
    if (rows == 1) {
        stockMapper.rollback(order.getSkuId());
    }
}

四、幂等保护要点

  1. 状态更新必须带上原状态条件
  2. 库存回滚只在"真正取消成功"后执行
  3. MQ 重复消息不会导致二次扣库存

最后总结

订单超时取消的核心不是"定时任务",而是:

  1. 状态机约束
  2. SQL 层幂等
  3. 延迟触发

把这三件事组合起来,才是真正稳定的业务闭环。

相关推荐
咩咩啃树皮6 小时前
第40篇:Vue3组件化开发精讲——组件拆分、复用、父子通信、工程化架构
java·前端·架构
灯澜忆梦6 小时前
GO_并发编程---定时器
开发语言·后端·golang
鱟鲥鳚6 小时前
Spring Boot 集成 LangChain4j:从模型调用到 Tool Calling(Demo版)
java·spring boot
-银雾鸢尾-6 小时前
C#中的StringBuilder相关方法
开发语言·c#
-银雾鸢尾-6 小时前
C#中结构体与类的区别;抽象类与接口的区别
开发语言·c#
大模型码小白7 小时前
【Python零基础教程】继承、多态与魔法函数:面向对象编程三大核心特性详解
java·大数据·开发语言·人工智能·python·ai编程
腾渊信息科技公司8 小时前
Spring Boot对接MES实战:视觉检测数据自动同步方案
java·人工智能·spring boot·后端·计算机视觉·ai·软件需求
爱笑的源码基地9 小时前
高并发 Redis 缓存门诊HIS系统源码,含财务统计药房进销存
java·程序·门诊系统·诊所系统·云诊所源码
wuqingshun3141599 小时前
TCP超时重传机制是为了解决什么问题?
java
段一凡-华北理工大学9 小时前
向量数据库实战:选型、调优与落地~系列文章12:文本分块策略实战:chunk_size 怎么选?重叠多少?
开发语言·数据库·后端·oracle·rust·工业智能体·高炉智能化