超时未支付订单,库存回滚:

  • RabbitMQ延时队列
  • 利用延时队列实现支付订单的监听,根据订单支付状况进行订单数据库回滚

1 秒杀流程

订单超时未支付自动取消和库存回滚_Redis

  1. 用户下单,经秒杀系统实现抢单,下单后会向MQ发个30min延时消息,包含抢单信息
  2. 启用延时消息监听,一旦监听到订单抢单信息,判断Redis缓存中是否存在订单信息,如存在,则回滚
  3. 启动支付回调信息监听,若:
  • 支付完成,则将订单持久化到MySQL
  • 没完成,清理排队信息回滚库存
  1. 每次秒杀下单后调用支付系统,创建二维码,若用户支付成功,微信系统会将支付信息发送给支付系统指定回调地址,支付系统收到信息后,将信息发送给MQ,step3就可监听到消息

延时消息实现订单关闭回滚库存

1.创建一个过期队列  Queue1
 2.接收消息的队列    Queue2
 3.中转交换机
 4.监听Queue2
   1)SeckillStatus->检查Redis中是否有订单信息
   2)如有订单信息,调用删除订单回滚库存->[需先关闭微信支付]
   3)如关闭订单时,用于已支付,修改订单状态即可
   4)如关闭订单时,发生别的错误,记录日志,人工处理

2 关闭支付

用户超时未支付,系统主动关闭支付订单,但关闭前,先关闭微信支付,防止中途用户支付。

修改支付微服务的WeixinPayService,添加关闭支付方法:

Map<String, String> closePay(Long orderId);

修改WeixinPayServiceImpl,实现关闭微信支付方法:

@Override
 public Map<String, String> closePay(Long orderId) {
     //参数设置
     Map<String,String> paramMap = new HashMap<String,String>();
     paramMap.put("appid",appid); // 应用ID
     paramMap.put("mch_id",partner); // 商户编号
     paramMap.put("nonce_str",WXPayUtil.generateNonceStr()); // 随机字符
     paramMap.put("out_trade_no",String.valueOf(orderId));   // 商家的唯一编号
 
     // 将Map数据转成XML字符
     String xmlParam = WXPayUtil.generateSignedXml(paramMap, partnerkey);
 
     // 确定url
     String url = "https://api.mch.weixin.qq.com/pay/closeorder";
 
     // 发送请求
     HttpClient httpClient = new HttpClient(url);
     // https
     httpClient.setHttps(true);
     // 提交参数
     httpClient.setXmlParam(xmlParam);
 
     // 提交
     httpClient.post();
 
     // 获取返回数据
     String content = httpClient.getContent();
 
     // 将返回数据解析成Map
     return  WXPayUtil.xmlToMap(content);
 }

3 关闭订单回滚库存

3.1 配置延时队列

队列信息配置:

# 位置支付交换机和队列
 mq:
   pay:
     exchange:
       order: exchange.order
     queue:
       order: queue.order
       seckillorder: queue.seckillorder
       seckillordertimer: queue.seckillordertimer
       seckillordertimerdelay: queue.seckillordertimerdelay
     routing:
       orderkey: queue.order
       seckillorderkey: queue.seckillorder

配置队列与交换机,在SeckillApplication中添加如下方法

/**
  * 到期数据队列
  */
 @Bean
 public Queue seckillOrderTimerQueue() {
     return new Queue(env.getProperty("mq.pay.queue.seckillordertimer"), true);
 }
 
 /**
  * 超时数据队列
  */
 @Bean
 public Queue delaySeckillOrderTimerQueue() {
     return QueueBuilder.durable(env.getProperty("mq.pay.queue.seckillordertimerdelay"))
             .withArgument("x-dead-letter-exchange", env.getProperty("mq.pay.exchange.order")) // 消息超时进入死信队列,绑定死信队列交换机
             .withArgument("x-dead-letter-routing-key", env.getProperty("mq.pay.queue.seckillordertimer")) // 绑定指定的routing-key
             .build();
 }
 
 /***
  * 交换机与队列绑定
  */
 @Bean
 public Binding basicBinding() {
     return BindingBuilder.bind(seckillOrderTimerQueue())
             .to(basicExchange())
             .with(env.getProperty("mq.pay.queue.seckillordertimer"));
 }

3.2 发送延时消息

MultiThreadingCreateOrder添加方法:

/***
  * 发送延时消息
  */
 public void sendTimerMessage(SeckillStatus seckillStatus) {
     rabbitTemplate.convertAndSend(env.getProperty("mq.pay.queue.seckillordertimerdelay"), (Object) JSON.toJSONString(seckillStatus), new MessagePostProcessor() {
         @Override
         public Message postProcessMessage(Message message) throws AmqpException {
             message.getMessageProperties().setExpiration("10000");
             return message;
         }
     });
 }

createOrder中调用上面方法:

// 发送延时消息到MQ
 sendTimerMessage(seckillStatus);

3.3 库存回滚

创建SeckillOrderDelayMessageListener实现监听消息,并回滚库存:

@Component
 @RabbitListener(queues = "${mq.pay.queue.seckillordertimer}")
 public class SeckillOrderDelayMessageListener {
 
     @Autowired
     private RedisTemplate redisTemplate;
 
     @Autowired
     private SeckillOrderService seckillOrderService;
 
     @Autowired
     private WeixinPayFeign weixinPayFeign;
 
     /***
      * 读取消息
      * 判断Redis中是否存在对应的订单
      * 如果存在,则关闭支付,再关闭订单
      */
     @RabbitHandler
     public void consumeMessage(@Payload String message) {
         SeckillStatus seckillStatus = JSON.parseObject(message,SeckillStatus.class);
 
         // 获取Redis中该用户的订单信息
         String username = seckillStatus.getUsername();
         SeckillOrder seckillOrder = (SeckillOrder) redisTemplate.boundHashOps("SeckillOrder").get(username);
 
         // 若Redis中有订单信息,说明用户未支付
         if(seckillOrder!=null) {
             System.out.println("准备回滚---"+seckillStatus);
             // 关闭支付
             Result closeResult = weixinPayFeign.closePay(seckillStatus.getOrderId());
             Map<String,String> closeMap = (Map<String, String>) closeResult.getData();
 
             if(closeMap!=null && closeMap.get("return_code").equalsIgnoreCase("success") &&
                     closeMap.get("result_code").equalsIgnoreCase("success") ){
                 // 关闭订单
                 seckillOrderService.closeOrder(username);
             }
         }
     }
 }