RabbitMq死信队列处理订单问题
前言
最近做一个新项目,碰到了有关的支付问题需要用到RabbitMq的一些特性去解决,简单记录一下自己的开发过程。
一、问题原因和分析
1)问题出现:
最近开发一个公众号的过程中,公司为了节省成本,并没有使用微信支付,而是对接了第三方支付。先看下第三方支付的基本流程:
重点在第五步,这里展示给用户的其实就是输入密码这一步:
从第三步开始,这时候订单信息已经是在第三方了,并且第三方系统里订单状态是处理中,用户在输入密码的时候可以选择支付,也可以选择不支付直接退出。那么就出现了,两种情况:
1)用户直接支付(扣款成功与否)
2)用户不支付,直接退出。
情况分析:
①用户支付成功,第三方也有了收款信息,回调地址返回支付成功标志,这是最正常的情况,一切原有逻辑走。
②用户支付成功,第三方扣款信息有延迟,调用我回调地址的支付标志并非最终状态,这就需要我自己主动去查询。
③用户直接退出不支付,这时候,第三方直接返回给我订单处理中,这时候其实我怎么查,订单都是处理中,一直到订单有效期结束以后,第三方才会判定支付失败。
后两种情况,不管成功或者失败,第三方都不会主动告知,因为回调地址只调一次。有人也许会说,那你把第三步的支付参数保存下来,订单有效期内重新唤起支付页面不就行了。很遗憾,第三方只回了我“重新调”。
没办法,想办法解决吧
2)解决方案:
第一种:定时任务固定去查,用户发起支付,并且调起支付页面的时候我就开始轮询去查,只要订单状态是未支付,我就开始每隔5,15,35,65,125(s)去查,第二天每天晚上十二点一过,重新查一遍前一天未支付订单
第二种:RabbitMq延时队列+死信队列
这里重要介绍一下第二种。
二、使用步骤
1.导包
代码如下(示例):
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
2.配置类
这里贴出逻辑代码:
TTLMQConfig类
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
/**
* @author zhangqingfeng
* @version 1.0
* @email zhangqingfeng95@icloud.com
* @date 2021/4/27 15:30
*/
@Configuration
public class TtlMQConfig {
//因为有冗余代码,这里
/**
* 支付结果队列延迟5秒
*/
public final static String TTL_PAY_QUEUE_5s = "pay.ttl.queue.5s";
/**
* 交换机名称
*/
public final static String TTL_PAY_EXCHANGE = "pay.ttl.exchange";
@Bean
public DirectExchange ttlDirect(){
return new DirectExchange(TTL_PAY_EXCHANGE,true,false);
}
/**
* 5秒
* @return
*/
@Bean
public Queue ttlQueue5s(){
Map<String,Object> args = new HashMap<>();
args.put("x-message-ttl",5000);
args.put("x-dead-letter-exchange","pay.dead.exchange");
args.put("x-dead-letter-routing-key","pay.dead.queue.5s");
return new Queue(TTL_PAY_QUEUE_5s,true,false,false,args);
}
@Bean
public Binding ttlDeadBinding5s(){
return BindingBuilder.bind(ttlQueue5s()).to(ttlDirect()).with(TTL_PAY_QUEUE_5s);
}
CommonMQConfig类:
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author zhangqingfeng
* @version 1.0
* @email zhangqingfeng95@icloud.com
* @date 2021/4/27 15:44
*/
@Configuration
public class CommonMQConfig {
/**
* 支付结果队列名称
*/
public final static String PAY_RESULT_QUEUE = "pay.result.queue";
/**
* 交换机名称
*/
public final static String PAY_RESULT_EXCHANGE = "pay.result.exchange";
/**
* 声明队列
* @return
*/
@Bean
public Queue resultQueue(){
return new Queue(PAY_RESULT_QUEUE);
}
/**
* 声明支付结果交换机
* @return
*/
@Bean
public TopicExchange resultExchange(){
return new TopicExchange(PAY_RESULT_EXCHANGE);
}
@Bean
public Binding resultBinding(){
return BindingBuilder.bind(resultQueue()).to(resultExchange()).with("pay.result.queue");
}
}
DeadMQConfig类
package com.kangjiu.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author zhangqingfeng
* @version 1.0
* @email zhangqingfeng95@icloud.com
* @date 2021/4/27 14:57
*/
@Configuration
public class DeadMQConfig {
/**
* 交换机名称
*/
public final static String PAY_DEAD_EXCHANGE = "pay.dead.exchange";
/**
* 死信队列5s
*/
public final static String PAY_DEAD_QUEUE_5s = "pay.dead.queue.5s";
@Bean
public DirectExchange deadDirect(){
return new DirectExchange(PAY_DEAD_EXCHANGE,true,false);
}
/**
* 5秒
* @return
*/
@Bean
public Queue deadQueue5s(){
return new Queue(PAY_DEAD_QUEUE_5s,true,false,false);
}
@Bean
public Binding deadBinding5s(){
return BindingBuilder.bind(deadQueue5s()).to(deadDirect()).with("pay.dead.queue.5s");
}
PayController类
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.math.BigDecimal;
/**
* @author: 张青峰
* @email: zhangqingfeng95@icloud.com
* @date: 2021/4/24 12:39 下午
* @description:
*/
@CrossOrigin
@RestController
@RequestMapping("/pay")
@Api(value = "支付接口", tags = {"支付接口"})
public class PayController {
@Autowired
private PayService payService;
@PostMapping("/unifiedOrder")
@ApiImplicitParams({
@ApiImplicitParam(value = "订单号",type = "String",name = "orderNo",paramType = "query",required = true),
@ApiImplicitParam(value = "金额",type = "BigDecimal",name = "amount",paramType = "query",required = true)
})
public Result unifiedOrder(
@RequestParam(value = "orderNo",required = false) String orderNo,
@RequestParam(value = "amount",required = false) BigDecimal amount){
return payService.unifiedOrder(orderNo,amount);
}
@PostMapping("/receivePayResult")
@ApiOperation(value = "支付回调")
public void receivePayResult(HttpServletRequest request, HttpServletResponse response){
payService.receivePayResult(request,response);
}
}
测试类
@Test
public void testSendTTL(){
Map<String,Object> resultMap = new HashMap<>();
resultMap.put("data","data");
resultMap.put("sign","sign");
rabbitTemplate.convertAndSend(TtlMQConfig.TTL_PAY_EXCHANGE,TtlMQConfig.TTL_PAY_QUEUE_5s, JSON.toJSONString(resultMap));
}
三、总结
以上内容仅仅是对自己学习和开发过程中的一些记录,并不是完整代码,如果有想一起讨论的可以评论留言。