超时未支付订单,库存回滚:
- RabbitMQ延时队列
- 利用延时队列实现支付订单的监听,根据订单支付状况进行订单数据库回滚
1 秒杀流程

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
















