我们来到MyOrderController ,这里就是统一用户操作订单的接口。
我们直接看一下取消订单接口核心代码:
@PutMapping("/cancel/{orderNumber}")
@Operation(summary = "根据订单编号取消订单" , description = "根据订单编号取消订单")
@Parameter(name = "orderNumber", description = "订单编号" , required = true)
public ServerResponseEntity<String> cancel(@PathVariable("orderNumber") String orderNumber) {
String userId = SecurityUtils.getUser().getUserId();
Order order = orderService.getOne(new LambdaQueryWrapper<Order>().eq(Order::getOrderNumber, orderNumber).eq(Order::getUserId, userId));
if (Objects.isNull(order)) {
eventPublisher.publishEvent(new SaveSeckillOrderEvent(orderNumber, userId));
eventPublisher.publishEvent(new RemoveSeckillOrderCacheEvent(orderNumber, userId));
order = orderService.getOrderByOrderNumberAndUserId(orderNumber, userId, true);
}
...
// 取消订单
orderService.cancelOrders(Collections.singletonList(order));
...
}
可以看到取消订单前先检查了订单是否在数据库中,没有的话就从缓存中获取并入库到数据库,目前只有秒杀订单在极少数情况下会存在这种情况。
再看看cancelOrders里面的代码:
@Override
@Transactional(rollbackFor = Exception.class)
public void cancelOrders(List<Order> orders) {
...
List<OrderItem> allOrderItems = new ArrayList<>();
List<String> orderNumberList = new ArrayList<>();
for (Order order : orders) {
List<OrderItem> orderItems = order.getOrderItems();
allOrderItems.addAll(orderItems);
orderNumberList.add(order.getOrderNumber());
eventPublisher.publishEvent(new CancelOrderEvent(order));
}
// 查询出微信支付并且为二维码支付的,失效掉对应支付二维码
invalidCode(orders);
// 如果是秒杀订单也不必还原库存
if (CollectionUtil.isNotEmpty(allOrderItems) && !OrderType.SECKILL.value().equals(orders.get(0).getOrderType())) {
// 判断处理关联卡券商品
List<SkuStockVO> stockList = handVoucherItemOrder(orders, orderNumberList);
// 解锁库存
skuStockLockService.unlockStock(orders);
// 先解锁库存后在更新商品库存
if (CollectionUtils.isNotEmpty(stockList)) {
stockManager.skuStock(stockList, LuaOperateEnum.SKU_SUB);
}
}
...
}
可以看到,在取消订单时,会失效掉对应的支付二维码,然后进行先解锁库存,最后更新商品库存。至于为什么不必还原秒杀订单的库存, 是因为这行会处理订单相关营销逻辑,如优惠券、团购、秒杀等等
eventPublisher.publishEvent(new CancelOrderEvent(order));
处理到秒杀时:
@EventListener(CancelOrderEvent.class)
@Order(CancelOrderOrder.SECKILL)
public void seckillCancelOrderEventListener(CancelOrderEvent event) {
String orderNumber = event.getOrder().getOrderNumber();
// 不是秒杀订单直接返回
if (!OrderType.SECKILL.value().equals(event.getOrder().getOrderType())) {
return;
}
// 还原秒杀sku库存
seckillOrderService.cancelUnpayOrderByOrderNumber(orderNumber);
}
可以看到在秒杀事件监听时进行回退秒杀库存了,所以后续代码判断中我们不必在回退秒杀的库存了。
首先我们来到OrderTask ,这里就是统一处理数据库订单定时任务相关的类。
我们直接看一下定时任务取消订单的代码:
@XxlJob("cancelOrder")
public void cancelOrder() {
logger.info("解锁创建失败订单的库存。。。");
orderService.unLockFailOrderStock();
// 获取x分钟之前未支付的订单
SysOrderConfig sysOrderConfig = sysConfigService.getSysConfigObject(Constant.ORDER_TIME_CONFIG, SysOrderConfig.class);
List<Order> orders = orderService.listUnRefundOrderAndOrderItems(OrderStatus.UNPAY.value(), null, DateUtil.offsetMinute(now, -sysOrderConfig.getOrderMaxTime()));
if (CollectionUtil.isEmpty(orders)) {
return;
}
List<Order> cancelOrderList = this.checkOrders(orders);
orderService.cancelOrders(cancelOrderList);
// 移除缓存
this.removeCache(cancelOrderList);
}
可以看到在checkOrders方法会检查订单是否已经在第三方支付过,如果已经支付则不取消订单进行支付成功操作。后续也是同用户主动取消订单用的同一个方法cancelOrders进行取消订单。
但是listUnRefundOrderAndOrderItems方法只查询了数据库中未支付的秒杀订单,有可能因为网络问题等导致redis中的秒杀订单还没有落库,此时我们用的是秒杀订单的定时任务来取消订单。
接着我们来到SeckillOrderTask,找到cancelSeckillOrder方法:
@XxlJob("cancelSeckillOrder")
public void cancelSeckillOrder() {
log.info("先从Redis中将订单数据存储到MySQL中。。。");
seckillCacheManager.saveSeckillOrderInfo();
log.info("取消超时未支付的秒杀订单...");
seckillOrderService.cancelOrderUnpayOrderByTime();
}
首先在saveSeckillOrderInfo可以看到我们将Redis中的订单数据保存到MySQL中:
// 保存订单到MySQL
eventPublisher.publishEvent(new SaveSeckillOrderEvent(multiDecrementBO.getOrderNumber(), multiDecrementBO.getUserId()));
接着在cancelOrderUnpayOrderByTime方法:
public void cancelOrderUnpayOrderByTime() {
SysOrderConfig sysOrderConfig = sysConfigService.getSysConfigObject(Constant.ORDER_TIME_CONFIG, SysOrderConfig.class);
Date cancelTime = DateUtil.offsetMinute(new Date(), -sysOrderConfig.getOrderMaxTime());
// 查询出微信or支付宝支付并且为二维码支付的,失效掉对应支付二维码
List<PayInfo> filterPayInfo = payInfoMapper.listByPayTypeAndTime(cancelTime);
// 失效二维码
if (CollectionUtils.isNotEmpty(filterPayInfo)) {
eventPublisher.publishEvent(new PayManagerEvent(filterPayInfo));
}
List<Order> orderList = orderMapper.listUnPayOrder(cancelTime);
if (CollUtil.isEmpty(orderList)) {
log.info("没有查询到超时未的支付秒杀订单");
return;
}
orderList.removeIf(order -> CollUtil.isEmpty(order.getOrderItems()));
List<String> orderNumbers = orderList.stream().map(Order::getOrderNumber).toList();
// 修改订单状态
DataBatchHandleUtil.batchSplitInsert(orderNumbers, orderMapper::batchCancelSeckillOrder);
// 还原库存
for (Order order : orderList) {
OrderItem orderItem = order.getOrderItems().get(0);
// 还原库存可以重复执行,lua有幂等处理,只会还原一次
stockManager.rollbackSecKillStock(orderItem.getActivityId(), orderItem.getSkuId(), orderItem.getProdCount(), orderItem.getOrderNumber(), orderItem.getUserId(), orderItem.getStockPointId());
}
}
我们可以看到逻辑跟取消订单定时任务一样,都是先进行失效第三方二维码和还原库存等操作,这里就不再赘述了。