一、利用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());}}}