一、利用MQ自動取消未支付超時訂單最佳實踐
1、基于 RocketMQ 延遲消息
1.1:延遲消息
當消息寫入到 Broker 后,不會立刻被消費者消費,需要等待指定的時長后才可被消費處理的消息,稱為延時消息。
1.2:實現流程
(1)用戶創建訂單時,發送一個延遲消息到消息隊列,延時時間為訂單的超時時間。
(2)消息到期后,消費者接收到消息,檢查訂單狀態:
如果訂單未支付,則關閉訂單;
如果已支付,則忽略消息。
1.3:優點
高效,解耦,適合高并發場景。
失敗可重試,可靠性高。
1.4:缺點
需要引入消息隊列,增加系統復雜度。
2、RabbitMQ死信隊列
2.1:死信隊列
當 RabbitMQ 中的一條正常消息,因為過了存活時間(TTL 過期)、隊列長度超限、 被消費者拒絕等原因無法被消費時,就會被當成一條死信消息,投遞到死信隊列。
我們可以給消息設置一個 TTL ,然后故意不消費消息,等消息過期就 會進入死信隊列,我們再消費死信隊列即可。
通過這樣的方式,就可以達到同 RocketMQ 延遲消息一樣的效果。
2.2:優點
同 RocketMQ 一樣,RabbitMQ 同樣可以使業務解耦,基于其集群的擴展性, 也可以實現高可用、高性能的目標。
二、RabbitMQ死信隊列實現代碼
1、CancelOrderSender消息的發送者
/*** 取消訂單消息的發送者*/
@Component
public class CancelOrderSender {private static final Logger LOGGER = LoggerFactory.getLogger(CancelOrderSender.class);@Autowiredprivate AmqpTemplate amqpTemplate;public void sendMessage(Long orderId,final long delayTimes){//給延遲隊列發送消息amqpTemplate.convertAndSend(QueueEnum.QUEUE_TTL_ORDER_CANCEL.getExchange(), QueueEnum.QUEUE_TTL_ORDER_CANCEL.getRouteKey(), orderId, new MessagePostProcessor() {@Overridepublic Message postProcessMessage(Message message) throws AmqpException {//給消息設置延遲毫秒值message.getMessageProperties().setExpiration(String.valueOf(delayTimes));return message;}});LOGGER.info("send orderId:{}",orderId);}
}
2、CancelOrderReceiver消息的接收者
/*** 取消訂單消息的接收者*/
@Component
@RabbitListener(queues = "mall.order.cancel")
public class CancelOrderReceiver {private static final Logger LOGGER = LoggerFactory.getLogger(CancelOrderReceiver.class);@Autowiredprivate OmsPortalOrderService portalOrderService;@RabbitHandlerpublic void handle(Long orderId){portalOrderService.cancelOrder(orderId);LOGGER.info("process orderId:{}",orderId);}
}
3、QueueEnum消息隊列枚舉類
@Getter
public enum QueueEnum {/*** 消息通知隊列*/QUEUE_ORDER_CANCEL("mall.order.direct", "mall.order.cancel", "mall.order.cancel"),/*** 消息通知ttl隊列*/QUEUE_TTL_ORDER_CANCEL("mall.order.direct.ttl", "mall.order.cancel.ttl", "mall.order.cancel.ttl");/*** 交換名稱*/private final String exchange;/*** 隊列名稱*/private final String name;/*** 路由鍵*/private final String routeKey;QueueEnum(String exchange, String name, String routeKey) {this.exchange = exchange;this.name = name;this.routeKey = routeKey;}
}
4、OmsPortalOrderServiceImpl前臺訂單管理實現
這里核心是在創建訂單后,發送此訂單到死信隊列,用于后續MQ的監聽消費。
@Slf4j
@Service
public class OmsPortalOrderServiceImpl implements OmsPortalOrderService {@Overridepublic Map<String, Object> generateOrder(OrderParam orderParam) {List<OmsOrderItem> orderItemList = new ArrayList<>();//校驗收貨地址if(orderParam.getMemberReceiveAddressId()==null){Asserts.fail("請選擇收貨地址!");}//獲取購物車及優惠信息UmsMember currentMember = memberService.getCurrentMember();List<CartPromotionItem> cartPromotionItemList = cartItemService.listPromotion(currentMember.getId(), orderParam.getCartIds());for (CartPromotionItem cartPromotionItem : cartPromotionItemList) {//生成下單商品信息OmsOrderItem orderItem = new OmsOrderItem();orderItem.setProductId(cartPromotionItem.getProductId());orderItem.setProductQuantity(cartPromotionItem.getQuantity());orderItem.setProductSkuId(cartPromotionItem.getProductSkuId());orderItem.setProductSkuCode(cartPromotionItem.getProductSkuCode());orderItem.setProductCategoryId(cartPromotionItem.getProductCategoryId());orderItem.setPromotionAmount(cartPromotionItem.getReduceAmount());orderItem.setPromotionName(cartPromotionItem.getPromotionMessage());orderItem.setGiftIntegration(cartPromotionItem.getIntegration());orderItem.setGiftGrowth(cartPromotionItem.getGrowth());orderItemList.add(orderItem);}//判斷購物車中商品是否都有庫存if (!hasStock(cartPromotionItemList)) {Asserts.fail("庫存不足,無法下單");}//判斷使用使用了優惠券//計算order_item的實付金額//進行庫存鎖定//根據商品合計、運費、活動優惠、優惠券、積分計算應付金額OmsOrder order = new OmsOrder();order.setDiscountAmount(new BigDecimal(0));order.setTotalAmount(calcTotalAmount(orderItemList));order.setFreightAmount(new BigDecimal(0));order.setPromotionAmount(calcPromotionAmount(orderItemList));order.setPromotionInfo(getOrderPromotionInfo(orderItemList));if (orderParam.getCouponId() == null) {order.setCouponAmount(new BigDecimal(0));} else {order.setCouponId(orderParam.getCouponId());order.setCouponAmount(calcCouponAmount(orderItemList));}if (orderParam.getUseIntegration() == null) {order.setIntegration(0);order.setIntegrationAmount(new BigDecimal(0));} else {order.setIntegration(orderParam.getUseIntegration());order.setIntegrationAmount(calcIntegrationAmount(orderItemList));}order.setPayAmount(calcPayAmount(order));//轉化為訂單信息并插入數據庫order.setMemberId(currentMember.getId());order.setCreateTime(new Date());order.setMemberUsername(currentMember.getUsername());//支付方式:0->未支付;1->支付寶;2->微信order.setPayType(orderParam.getPayType());//訂單來源:0->PC訂單;1->app訂單order.setSourceType(1);//訂單狀態:0->待付款;1->待發貨;2->已發貨;3->已完成;4->已關閉;5->無效訂單order.setStatus(0);//訂單類型:0->正常訂單;1->秒殺訂單order.setOrderType(0);//收貨人信息:姓名、電話、郵編、地址UmsMemberReceiveAddress address = memberReceiveAddressService.getItem(orderParam.getMemberReceiveAddressId());order.setReceiverName(address.getName());order.setReceiverPhone(address.getPhoneNumber());order.setReceiverPostCode(address.getPostCode());order.setReceiverProvince(address.getProvince());order.setReceiverCity(address.getCity());order.setReceiverRegion(address.getRegion());order.setReceiverDetailAddress(address.getDetailAddress());//0->未確認;1->已確認order.setConfirmStatus(0);order.setDeleteStatus(0);//計算贈送積分order.setIntegration(calcGifIntegration(orderItemList));//計算贈送成長值order.setGrowth(calcGiftGrowth(orderItemList));//生成訂單號order.setOrderSn(generateOrderSn(order));//設置自動收貨天數List<OmsOrderSetting> orderSettings = orderSettingMapper.selectByExample(new OmsOrderSettingExample());if(CollUtil.isNotEmpty(orderSettings)){order.setAutoConfirmDay(orderSettings.get(0).getConfirmOvertime());}//插入order表和order_item表orderMapper.insert(order);for (OmsOrderItem orderItem : orderItemList) {orderItem.setOrderId(order.getId());orderItem.setOrderSn(order.getOrderSn());}orderItemDao.insertList(orderItemList);//刪除購物車中的下單商品deleteCartItemList(cartPromotionItemList, currentMember);//發送延遲消息取消訂單sendDelayMessageCancelOrder(order.getId());Map<String, Object> result = new HashMap<>();result.put("order", order);result.put("orderItemList", orderItemList);return result;}
}
具體的取消實現方法
@Overridepublic void cancelOrder(Long orderId) {//查詢未付款的取消訂單OmsOrderExample example = new OmsOrderExample();example.createCriteria().andIdEqualTo(orderId).andStatusEqualTo(0).andDeleteStatusEqualTo(0);List<OmsOrder> cancelOrderList = orderMapper.selectByExample(example);if (CollectionUtils.isEmpty(cancelOrderList)) {return;}OmsOrder cancelOrder = cancelOrderList.get(0);if (cancelOrder != null) {//修改訂單狀態為取消cancelOrder.setStatus(4);orderMapper.updateByPrimaryKeySelective(cancelOrder);OmsOrderItemExample orderItemExample = new OmsOrderItemExample();orderItemExample.createCriteria().andOrderIdEqualTo(orderId);List<OmsOrderItem> orderItemList = orderItemMapper.selectByExample(orderItemExample);//解除訂單商品庫存鎖定if (!CollectionUtils.isEmpty(orderItemList)) {for (OmsOrderItem orderItem : orderItemList) {int count = portalOrderDao.releaseStockBySkuId(orderItem.getProductSkuId(),orderItem.getProductQuantity());if(count==0){Asserts.fail("庫存不足,無法釋放!");}}}//修改優惠券使用狀態updateCouponStatus(cancelOrder.getCouponId(), cancelOrder.getMemberId(), 0);//返還使用積分if (cancelOrder.getUseIntegration() != null) {UmsMember member = memberService.getById(cancelOrder.getMemberId());memberService.updateIntegration(cancelOrder.getMemberId(), member.getIntegration() + cancelOrder.getUseIntegration());}}}