订单超时取消与库存回滚的完整实现(延迟任务 + 状态机)
这是一个非常典型的业务闭环下单后未支付自动取消并且把库存扣回去。目标超时未支付订单自动取消取消时库存回滚所有操作幂等、可重试一、订单状态机代码层约束publicenumOrderStatus{UNPAID,PAID,CANCELED}publicfinalclassOrderStateMachine{privatestaticfinalMapOrderStatus,SetOrderStatusALLOWEDMap.of(OrderStatus.UNPAID,Set.of(OrderStatus.PAID,OrderStatus.CANCELED),OrderStatus.PAID,Set.of(),// 已支付不可取消OrderStatus.CANCELED,Set.of()// 终态);publicstaticvoidassertCanTransfer(OrderStatusfrom,OrderStatusto){if(!ALLOWED.getOrDefault(from,Set.of()).contains(to)){thrownewIllegalStateException(状态不允许流转: from - to);}}}二、SQL 幂等更新核心安全点1) 下单预扣库存 UNPAIDUPDATEstockSETquantityquantity-1WHEREsku_id?ANDquantity0;INSERTINTOorders(id,user_id,status,create_time)VALUES(?,?,UNPAID,NOW());2) 超时取消只允许 UNPAID - CANCELEDUPDATEordersSETstatusCANCELED,cancel_timeNOW()WHEREid?ANDstatusUNPAID;3) 库存回滚必须幂等UPDATEstockSETquantityquantity1WHEREsku_id?;三、延迟任务定时取消你可以用 MQ 延迟消息也可以用定时任务。下面以 MQ 延迟为例。publicvoidcreateOrder(Orderorder){// 1) 创建订单 预扣库存createOrderAndDeductStock(order);// 2) 发送延迟消息30分钟后检查是否未支付rabbitTemplate.convertAndSend(order.delay.exchange,order.delay,order.getId());}消费端逻辑publicvoidhandleCancel(LongorderId){OrderorderorderMapper.selectById(orderId);if(ordernull)return;// 状态机校验OrderStateMachine.assertCanTransfer(order.getStatus(),OrderStatus.CANCELED);// 幂等取消introwsorderMapper.cancelIfUnpaid(orderId);if(rows1){stockMapper.rollback(order.getSkuId());}}四、幂等保护要点状态更新必须带上原状态条件库存回滚只在“真正取消成功”后执行MQ 重复消息不会导致二次扣库存最后总结订单超时取消的核心不是“定时任务”而是状态机约束SQL 层幂等延迟触发把这三件事组合起来才是真正稳定的业务闭环。