这是一个非常典型的业务闭环:下单后未支付自动取消,并且把库存扣回去。
目标
- 超时未支付订单自动取消
- 取消时库存回滚
- 所有操作幂等、可重试
一、订单状态机(代码层约束)
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());
}
}
四、幂等保护要点
- 状态更新必须带上原状态条件
- 库存回滚只在"真正取消成功"后执行
- MQ 重复消息不会导致二次扣库存
最后总结
订单超时取消的核心不是"定时任务",而是:
- 状态机约束
- SQL 层幂等
- 延迟触发
把这三件事组合起来,才是真正稳定的业务闭环。