背景
订单创建成功之后,超过一定时间不支付,就需要关闭订单,这里方案有很多,比如用 MQ、定时任务、Redis 等等。
我们选择了一种相对轻量并且也比较可靠的方案,那就是 XXL-JOB 定时任务,并且为了减少 XXL-JOB 关单的延迟问题,我们同时引入了被动关单的逻辑。
被动关单
所谓被动关单,就是说当用户想要操作这个订单的时候,我们去检查下是不是已经超时了,如果超时了,并且状态还不是已关闭的话,那就执行一次关单操作。
这种被动关单,我们在两个地方都加了:
- 1、用户对订单支付时
- 2、用户查看订单详情时
订单支付时关单
用户可以通过订单列表唤起收银台进行支付,在支付接口中,我们进行了判断:
@PostMapping("/pay")
public Result<PayOrderVO> pay(@Valid @RequestBody PayParam payParam) {
String userId = (String) StpUtil.getLoginId();
SingleResponse<TradeOrderVO> singleResponse = orderFacadeService.getTradeOrder(payParam.getOrderId(), userId);
TradeOrderVO tradeOrderVO = singleResponse.getData();
if (tradeOrderVO == null) {
throw new TradeException(TradeErrorCode.GOODS_NOT_EXIST);
}
if (tradeOrderVO.getOrderState() != TradeOrderState.CONFIRM) {
throw new TradeException(TradeErrorCode.ORDER_IS_CANNOT_PAY);
}
if (tradeOrderVO.getTimeout()) {
doAsyncTimeoutOrder(tradeOrderVO);
throw new TradeException(TradeErrorCode.ORDER_IS_CANNOT_PAY);
}
//支付逻辑省略
}
这里,针对状态为 CONFIRM 并且已经达到了关单时间的订单,进行一次异步的订单关闭操作。异步关单逻辑如下:
private void doAsyncTimeoutOrder(TradeOrderVO tradeOrderVO) {
if (tradeOrderVO.getOrderState() != TradeOrderState.CLOSED) {
Thread.ofVirtual().start(() -> {
OrderTimeoutRequest cancelRequest = new OrderTimeoutRequest();
cancelRequest.setOperatorType(PLATFORM);
cancelRequest.setOperator(PLATFORM.getDesc());
cancelRequest.setOrderId(tradeOrderVO.getOrderId());
cancelRequest.setOperateTime(new Date());
cancelRequest.setIdentifier(UUID.randomUUID().toString());
orderFacadeService.timeout(cancelRequest);
});
}
}
这里用了虚拟线程,发起关单操作,这里通过异步实现,不阻塞主线程,主线程直接返回告诉用户订单状态无法支付即可,等他再次刷新时,异步线程执行完了,这个订单也就被关闭了。
用户查看订单详情时关单
除了在列表页的支付发起我们会进行被动关单,还会在用户主动查看订单详情的时候进行这个关单动作。
@GetMapping("/orderDetail")
public Result<TradeOrderVO> orderDetail(@NotNull String orderId) {
String userId = (String) StpUtil.getLoginId();
SingleResponse<TradeOrderVO> singleResponse = orderFacadeService.getTradeOrder(orderId, userId);
if (singleResponse.getSuccess()) {
TradeOrderVO tradeOrderVO = singleResponse.getData();
if (tradeOrderVO.getTimeout() && tradeOrderVO.getOrderState() == TradeOrderState.CONFIRM) {
//如果订单已经超时,并且尚未关闭,则执行一次关单后再返回数据
OrderTimeoutRequest cancelRequest = new OrderTimeoutRequest();
cancelRequest.setOperatorType(PLATFORM);
cancelRequest.setOperator(PLATFORM.getDesc());
cancelRequest.setOrderId(tradeOrderVO.getOrderId());
cancelRequest.setOperateTime(new Date());
cancelRequest.setIdentifier(UUID.randomUUID().toString());
orderFacadeService.timeout(cancelRequest);
singleResponse = orderFacadeService.getTradeOrder(orderId, userId);
}
return Result.success(singleResponse.getData());
} else {
return Result.error(singleResponse.getResponseCode(), singleResponse.getResponseMessage());
}
}
这里。同样是针对状态为 CONFIRM 并且已经达到了关单时间的订单,进行一次同步的订单关闭操作。
这里为啥就不是异步而是同步了呢?因为这里需要在前台准确的返回最新的订单信息,所以需要同步执行。
主动关单
除了以上2个被动关单场景外,我们还使用XXL-JOB定义了定时任务进行定时执行,进行批量的订单超时关闭,之后会详细说明。