一、利用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);
@Autowired
private 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() {
@Override
public 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);
@Autowired
private OmsPortalOrderService portalOrderService;
@RabbitHandler
public 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 {
@Override
public 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;
}
}
具体的取消实现方法
@Override
public 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());
}
}
}