项目
博客
文档
归档
资源链接
关于我
项目
博客
文档
归档
资源链接
关于我
35| 订单超时未支付-定时关单功能设计和开发
2024-09-12
·
YuanJs
·
原创
·
·
本文共 623个字,预计阅读需要 3分钟。
### 订单超时未支付-定时关单功能流程设计 * 业务流程梳理 * 消息消费-关单流程设计 ![](https://yn-blog.oss-cn-chengdu.aliyuncs.com/v_2023/2024-09-12/0c034884-ab34-4921-8ba0-30ee37ab4457.png) ### 定时关单RabbitMQ延迟消息交换机和队列配置 ![](https://yn-blog.oss-cn-chengdu.aliyuncs.com/v_2023/2024-09-12/33bd64eb-7276-417b-93ba-6333893ae751.png) * 配置文件 ```yml #自定义消息队列配置,发送锁定库存消息-》延迟exchange-》lock.queue-》死信exchange-》release.queue mq: config: #延迟队列,不能被监听消费 order_close_delay_queue: order.close.delay.queue #延迟队列的消息过期后转发的队列 order_close_queue: order.close.queue #交换机 order_event_exchange: order.event.exchange #进入延迟队列的路由key order_close_delay_routing_key: order.close.delay.routing.key #消息过期,进入释放队列的key,进入死信队列的key order_close_routing_key: order.close.routing.key #消息过期时间,毫秒,测试改为15秒 ttl: 15000 ``` * 配置类 ```java @Configuration @Data public class RabbitMQConfig { /** * 交换机 */ @Value("${mq.config.order_event_exchange}") private String eventExchange; /** * 延迟队列 */ @Value("${mq.config.order_close_delay_queue}") private String orderCloseDelayQueue; /** * 关单队列 */ @Value("${mq.config.order_close_queue}") private String orderCloseQueue; /** * 进入延迟队列的路由key */ @Value("${mq.config.order_close_delay_routing_key}") private String orderCloseDelayRoutingKey; /** * 进入死信队列的路由key */ @Value("${mq.config.order_close_routing_key}") private String orderCloseRoutingKey; /** * 过期时间 */ @Value("${mq.config.ttl}") private Integer ttl; /** * 消息转换器 * @return */ @Bean public MessageConverter messageConverter(){ return new Jackson2JsonMessageConverter(); } /** * 创建交换机 Topic类型,也可以用dirct路由 * 一般一个微服务一个交换机 * @return */ @Bean public Exchange orderEventExchange(){ return new TopicExchange(eventExchange,true,false); } /** * 延迟队列 */ @Bean public Queue orderCloseDelayQueue(){ Map
args = new HashMap<>(3); args.put("x-dead-letter-exchange",eventExchange); 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); } /** * 第一个队列,即延迟队列的绑定关系建立 * @return */ @Bean public Binding orderCloseDelayBinding(){ return new Binding(orderCloseDelayQueue,Binding.DestinationType.QUEUE,eventExchange,orderCloseDelayRoutingKey,null); } /** * 死信队列绑定关系建立 * @return */ @Bean public Binding orderCloseBinding(){ return new Binding(orderCloseQueue,Binding.DestinationType.QUEUE,eventExchange,orderCloseRoutingKey,null); } } ``` ### 订单微服务下单-延迟消息功能测试开发和注意事项 * 测试代码编写测试 ```java @RunWith(SpringRunner.class) @SpringBootTest(classes = OrderApplication.class) @Slf4j public class DemoApplicationTests { @Autowired private RabbitTemplate rabbitTemplate; @Test public void send(){ rabbitTemplate.convertAndSend("order.event.exchange","order.close.delay.routing.key","23342342"); } } ``` * 注意 * IOC容器存在不行,RabbitMQ默认是懒加载模式 * 只有微服务监听mq,才会创建对应的队列和交换机,所以编写测试方法或者写监听器就行 * 如果MQ已经存在对应的队列,则不会重新创建 * 修改配置后,需要删除队列重新建立生效 * 如果队列和交换机已经存在,重新启动项目会有错误警告,可以忽略 ### 订单超时未支付定时关单-发送消息功能开发 * 消息协议介绍 ```java @Data public class OrderMessage { /** * 消息队列id */ private Long messageId; /** * 订单号 */ private String outTradeNo; } ``` * 发送功能开发 ```java //MQ发送延迟队列 TODO,用于自动关单 OrderMessage orderMessage = new OrderMessage(); orderMessage.setOutTradeNo(orderOutTradeNo); rabbitTemplate.convertAndSend(rabbitConfig.getEventExchange(),rabbitConfig.getOrderCloseDelayRoutingKey(), orderMessage); ``` ### 订单微服务-订单超时未支付自动关单-消息监听处理 * 订单微服务消费MQ监听器开发 * 流程梳理 ![](https://yn-blog.oss-cn-chengdu.aliyuncs.com/v_2023/2024-09-12/2ea455af-c01e-47df-b999-9592fb317651.png) mq监听 ```java @Slf4j @Component @RabbitListener(queues = "${mq.config.order_close_queue}") public class ProductOrderMQListener { @Autowired private ProductOrderService productOrderService; /** * * 消费重复消息,幂等性保证 * 并发情况下如何保证安全 */ @RabbitHandler public void closeProductOrder(OrderMessage orderMessage, Message message, Channel channel) throws IOException { log.info("监听到消息:closeProductOrder:{}",orderMessage); long msgTag = message.getMessageProperties().getDeliveryTag(); try{ boolean flag = productOrderService.closeProductOrder(orderMessage); if(flag){ channel.basicAck(msgTag,false); }else { channel.basicReject(msgTag,true); } }catch (IOException e){ log.error("定时关单失败:",orderMessage); channel.basicReject(msgTag,true); } } } ``` 队列监听,定时关单操作 ```java @Override public boolean closeProductOrder(OrderMessage orderMessage) { ProductOrderEntity productOrderDO = baseMapper.selectOne(new QueryWrapper
() .eq("out_trade_no",orderMessage.getOutTradeNo())); if(productOrderDO == null){ //订单不存在 log.warn("直接确认消息,订单不存在:{}",orderMessage); return true; } if(productOrderDO.getState().equalsIgnoreCase(ProductOrderStateEnum.PAY.name())){ //已经支付 log.info("直接确认消息,订单已经支付:{}",orderMessage); return true; } //向第三方支付查询订单是否真的未支付 todo String payResult = ""; //结果为空,则未支付成功,本地取消订单 if(StringUtils.isBlank(payResult)){ baseMapper.updateOrderPayState(productOrderDO.getOutTradeNo(),ProductOrderStateEnum.CANCEL.name(),ProductOrderStateEnum.NEW.name()); log.info("结果为空,则未支付成功,本地取消订单:{}",orderMessage); return true; }else { //支付成功,主动的把订单状态改成UI就支付,造成该原因的情况可能是支付通道回调有问题 log.warn("支付成功,主动的把订单状态改成UI就支付,造成该原因的情况可能是支付通道回调有问题:{}",orderMessage); baseMapper.updateOrderPayState(productOrderDO.getOutTradeNo(),ProductOrderStateEnum.PAY.name(),ProductOrderStateEnum.NEW.name()); return true; } } ```