目录
[1.1 为什么需要主动查询兜底](#1.1 为什么需要主动查询兜底)
[1.2 兜底方案设计 超时订单处理流程](#1.2 兜底方案设计 超时订单处理流程)
[1.3 订单超时处理方案对比](#1.3 订单超时处理方案对比)
[1.4 RabbitMQ延迟队列原理](#1.4 RabbitMQ延迟队列原理)
[1.5 常见状态补偿策略](#1.5 常见状态补偿策略)
[2.1 RabbitMQ延迟队列时序图](#2.1 RabbitMQ延迟队列时序图)
[2.2 延迟队列配置说明](#2.2 延迟队列配置说明)
[2.3 MQ配置实现](#2.3 MQ配置实现)
[2.4 订单创建时发送延迟消息](#2.4 订单创建时发送延迟消息)
[3.1 消费者交互时序图](#3.1 消费者交互时序图)
[3.2 MQ监听器实现](#3.2 MQ监听器实现)
[3.3 超时订单处理逻辑](#3.3 超时订单处理逻辑)
一、订单超时处理与状态补偿机制
1.1 为什么需要主动查询兜底
-
需求背景
-
微信支付回调可能因网络问题丢失,需要自动关闭长时间未支付的订单
-
用户创建订单后可能不支付,订单一直占用库存
-
回调处理可能因系统异常失败,需要确保订单状态最终一致性
-
-
核心作用
-
补偿机制:回调丢失时主动查询补全状态
-
一致性保障:确保订单状态与微信支付一致
-
异常恢复:系统异常后恢复订单状态
-
资源释放:超时未支付自动释放库存
-
1.2 兜底方案设计 超时订单处理流程

1.3 订单超时处理方案对比
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 定时任务 | 实现简单 | 精度低,压力大 | 小规模系统 |
| 延迟队列 | 精度高,实时性好 | 依赖消息队列 | 中大型系统 |
| Redis过期 | 性能好 | 可靠性差 | 高并发场景 |
1.4 RabbitMQ延迟队列原理
-
关键点:
-
延迟队列本身没有消费者,只做消息暂存
-
消息过期后自动变成"死信",路由到死信队列
-
死信队列有消费者监听,执行实际业务逻辑
-
通过 TTL + 死信交换机实现延迟功能(RabbitMQ 官方推荐方案)
-

1.5 常见状态补偿策略
| 微信状态 | 处理逻辑 |
|---|---|
| SUCCESS | 补偿更新为已支付状态 |
| USERPAYING | 暂不处理,等待下次扫描 |
| NOTPAY/CLOSED/REVOKED | 取消本地订单 |
| 查询失败 | 直接取消订单(保守策略) |
二、使用RabbitMQ延迟队列实现订单超时自动取消功能
2.1 RabbitMQ延迟队列时序图

2.2 延迟队列配置说明
| 组件 | 名称 | 作用 |
|---|---|---|
| TopicExchange | order.event.exchange |
消息交换机,负责路由消息 |
| 延迟队列 | order.close.delay.queue |
暂存消息,设置 TTL 过期时间,不被消费者监听 |
| 死信队列 | order.close.queue |
接收过期消息,被消费者监听处理 |
| TTL | 60秒(正常10分钟,方便测试才60秒) | 消息在延迟队列中的存活时间 |
2.3 MQ配置实现
java
package net.xdclass.config;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
@Configuration
@Slf4j
@Data
public class ProductOrderMQConfig {
// 订单交换机
private String orderEventExchange = "order.event.exchange";
// 延迟队列(不能被消费者监听)
private String orderCloseDelayQueue = "order.close.delay.queue";
// 死信队列(被消费者监听)
public static final String orderCloseQueue = "order.close.queue";
// 延迟队列路由key
private String orderCloseDelayRoutingKey = "order.close.delay.routing.key";
// 死信队列路由key
private String orderCloseRoutingKey = "order.close.delay.key";
// 过期时间:1分钟
private Integer ttl = 1000 * 60 ;
/**
* 配置 JSON 消息转换器,替代默认的 Java 原生序列化
* SpringAMQP3.x默认使用 Java 原生序列化时,会校验反序列化白名单,ProductOrderDTO 不在白名单中导致反序列化失败。
* 最佳解决方案是配置 Jackson2JsonMessageConverter,改用 JSON 序列化替代 Java 原生序列化。
*/
@Bean
public MessageConverter messageConverter() {
return new Jackson2JsonMessageConverter();
}
/**
* 配置 RabbitTemplate,使用 JSON 消息转换器
*/
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
rabbitTemplate.setMessageConverter(messageConverter());
return rabbitTemplate;
}
/**
* 创建交换机
*/
@Bean
public TopicExchange orderEventExchange() {
return new TopicExchange(orderEventExchange, true, false);
}
/**
* 延迟队列
*/
@Bean
public Queue orderCloseDelayQueue() {
Map<String, Object> args = new HashMap<>(3);
// 消息过期后转发的交换机
args.put("x-dead-letter-exchange", orderEventExchange);
// 消息过期后转发的路由key
args.put("x-dead-letter-routing-key", orderCloseRoutingKey);
// 消息过期时间
args.put("x-message-ttl", ttl);
return new Queue(orderCloseDelayQueue, true, false, false, args);
}
/**
* 死信队列(普通队列,被监听)
*/
@Bean
public Queue orderCloseQueue() {
return new Queue(orderCloseQueue, true, false, false);
}
/**
* 延迟队列绑定
*/
@Bean
public Binding orderCloseDelayBinding() {
return BindingBuilder.bind(orderCloseDelayQueue())
.to(orderEventExchange())
.with(orderCloseDelayRoutingKey);
}
/**
* 死信队列绑定
*/
@Bean
public Binding orderCloseBinding() {
return BindingBuilder.bind(orderCloseQueue())
.to(orderEventExchange())
.with(orderCloseRoutingKey);
}
}
2.4 订单创建时发送延迟消息
java
/**
* 根据支付方式路由到对应的支付逻辑
*/
private JsonData handlePayment(ProductOrderDO orderDO,String payType){
ProductOrderDTO productOrderDTO = SpringBeanUtil.copyProperties(orderDO, ProductOrderDTO.class);
rabbitTemplate.convertAndSend(rabbitMQConfig.getOrderEventExchange(), rabbitMQConfig.getOrderCloseDelayRoutingKey(), productOrderDTO);
if(PaymentEnum.WECHAT_PAY.name().equals(payType)){
//微信支付
return doWechatNativePay(orderDO);
}else if(PaymentEnum.ALI_PAY.name().equals(payType)){
return JsonData.buildError("支付宝支付暂时未实现");
}
return JsonData.buildError("不支持的支付方式");
}
三、超时支付订单MQ处理和关闭订单设计实战
需求:支付回调丢失导致的状态不一致
-
场景:用户支付成功,但微信支付回调因网络问题未到达系统
-
效果:即使回调丢失,超时检查时也能发现并补偿更新
-
通过主动查询补偿,确保本地与第三方支付状态一致
3.1 消费者交互时序图

3.2 MQ监听器实现
java
@Component
@Slf4j
@RabbitListener(queues = ProductOrderMQConfig.orderCloseQueue)
public class ProductOrderMQListener {
@Autowired
private ProductOrderService productOrderService;
@RabbitHandler
public void productOrderHandler(ProductOrderDTO productOrderDTO,
Message message, Channel channel) {
log.info("监听到超时未支付订单消息,订单号:{}",
productOrderDTO.getOutTradeNo());
try {
// 处理超时未支付订单
productOrderService.handleTimeoutOrder(productOrderDTO);
log.info("超时订单处理成功,订单号:{}",
productOrderDTO.getOutTradeNo());
// 手动ACK确认消息
channel.basicAck(message.getMessageProperties()
.getDeliveryTag(), false);
} catch (Exception e) {
log.error("处理超时订单失败,订单号:{}",
productOrderDTO.getOutTradeNo(), e);
try {
// 拒绝消息,不重新入队
channel.basicNack(message.getMessageProperties()
.getDeliveryTag(), false, false);
} catch (Exception ex) {
log.error("消息拒绝失败", ex);
}
}
}
}
3.3 超时订单处理逻辑
java
@Override
public void handleTimeoutOrder(ProductOrderDTO productOrderDTO) {
String outTradeNo = productOrderDTO.getOutTradeNo();
log.info("开始处理超时未支付订单,订单号:{}", outTradeNo);
try {
// 查询订单
ProductOrderDO orderDO = productOrderMapper.selectOne(
new LambdaQueryWrapper<ProductOrderDO>()
.eq(ProductOrderDO::getOutTradeNo, outTradeNo)
);
if (orderDO == null) {
log.warn("订单不存在,订单号:{}", outTradeNo);
return;
}
// 已支付订单直接返回
if (OrderStateEnum.PAY.name().equals(orderDO.getOrderState())) {
log.info("订单已支付,正常结束");
return;
}
// 已取消订单直接返回
if (OrderStateEnum.CANCEL.name().equals(orderDO.getOrderState())) {
log.info("订单已取消");
return;
}
// 查询第三方支付状态
if (PaymentEnum.WECHAT_PAY.name().equals(orderDO.getPayType())) {
handleWechatTimeoutOrder(orderDO);
}
} catch (Exception e) {
log.error("处理超时未支付订单失败", e);
}
}
/** 处理微信支付超时订单,主动查询第三方状态进行补偿 */
private void handleWechatTimeoutOrder(ProductOrderDO orderDO) {
String outTradeNo = orderDO.getOutTradeNo();
Transaction transaction = wechatPayUtil.queryOrderByOutTradeNo(outTradeNo);
Transaction.TradeStateEnum tradeState = transaction.getTradeState();
log.info("微信超时订单查询,订单号:{},状态:{}", outTradeNo, tradeState);
if (Transaction.TradeStateEnum.SUCCESS.equals(tradeState)) {
log.warn("回调异常补偿:第三方已支付,本地未更新,订单号:{}", outTradeNo);
int updated = updateOrderState(outTradeNo, transaction.getTransactionId(), OrderStateEnum.PAY.name());
if (updated<1) {
log.error("订单{}状态补偿更新失败", outTradeNo);
}
} else if (Transaction.TradeStateEnum.USERPAYING.equals(tradeState)) {
log.info("用户支付中,暂不处理,订单号:{}", outTradeNo);
} else {
// NOTPAY / CLOSED / REVOKED 等终态,直接取消
log.info("第三方未支付({}),取消本地订单,订单号:{}", tradeState, outTradeNo);
cancelOrder(orderDO);
}
}
/** 取消订单 */
private void cancelOrder(ProductOrderDO orderDO) {
wechatPayUtil.closeOrder(orderDO.getOutTradeNo());
orderDO.setOrderState(OrderStateEnum.CANCEL.name());
productOrderMapper.updateById(orderDO);
log.info("订单已取消,订单号:{}", orderDO.getOutTradeNo());
}