热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

SpringBoot+RabbitMQ实现订单超时自动取消功能

场景:在京东下单,订单创建成功,等待支付,一般会给30分钟的时间,开始倒计时。如果在这段时间内用户没有支付,则默认订单取消。如何订单超时实现?定时任务redission延时任务ra

场景:在京东下单,订单创建成功,等待支付,一般会给30分钟的时间,开始倒计时。如果在这段时间内
用户没有支付,则默认订单取消。

如何订单超时实现?
  • 定时任务
  • redission延时任务
  • rabbitmq死信队列

本文将以rabbitmq死信队列展开做讲解,因为定时任务的方式,是有点问题的,原本业务系统希望10分钟后,如果订单未支付,就马上取消订单,并释放商品库存。但是一旦数据量大的话,就会加长获取未支付订单数据的时间,部分订单就做不到10分钟后取消了,可能是15分钟,20分钟之类的。这样的话,库存就无法及时得到释放,也就会影响成单数。而使用rabbitmq死信队列,在定义业务队列时可以考虑指定一个死信交换机,并绑定一个死信队列。当消息变成死信时,该消息就会被发送到该死信队列上,再取出订单信息进行判断订单是否已支付,如未支付则讲订单状态修改为取消状态,这样也是可以达到订单超时取消的需求的。

软件准备

erlang

请参考Win10下安装erlang

https://blog.csdn.net/linsongbin1/article/details/80170487

RabbitMQ

https://blog.csdn.net/linsongbin1/article/details/80170567

启动RabbitMQ,然后添加一个用户, 并给用户设置权限。

# 后台启动
rabbitmq-server -detached
# 添加用户
rabbitmqctl add_user root 123456
# 设置用户权限:
rabbitmqctl set_permissions -p "/" root ".*" ".*" ".*"

接下来创建一个springboot工程,并集成RabbitMQ。



    4.0.0
    
        org.springframework.boot
        spring-boot-starter-parent
        2.2.8.RELEASE
         
    
    com.lagou
    rabbitmq-work
    0.0.1-SNAPSHOT
    rabbitmq-work
    Demo project for Spring Boot
    
        1.8
    
    
        
            org.springframework.boot
            spring-boot-starter-amqp
        
        
            org.springframework.boot
            spring-boot-starter-data-redis
        
        
            org.springframework.boot
            spring-boot-starter-web
        
        
            mysql
            mysql-connector-java
            5.1.47
            runtime
        
        
        
            com.baomidou
            mybatis-plus-boot-starter
            3.3.2
        

        
            org.springframework.boot
            spring-boot-starter-test
            test
        

    

    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
            
        
    


接下来在application.yml配置文件中假如rabbitmq配置

spring:
  application:
    name: rabbit-work
  # rabbitmq配置
  rabbitmq:
    host: localhost
    virtual-host: /
    username: root
    password: 123456
  # redis配置
  redis:
    timeout: 6000
    host: localhost
    port: 6379
    database: 0
  # 数据源配置
  datasource:
    url: jdbc:mysql://localhost:3306/order?useUnicode=true&characterEncoding=utf8&useSSL=false
    driver-class-name: com.mysql.jdbc.Driver
    username: root
    password: 123456
    hikari:
      minimum-idle: 3
      maximum-pool-size: 5
      max-lifetime: 30000
      connection-init-sql: SELECT 1
# mybatis-plus配置
mybatis-plus:
  mapper-locations: classpath:com.lagou.*.mapper/*.xml
  type-aliases-package: com.lagou.*.domain

定义RabbitConfig

@Configuration
public class RabbitConfig {

    /**
     * 订单队列
     *
     * @return {@link Queue}
     */
    @Bean
    public Queue orderQueue() {
        Map argments = new HashMap<>();
        argments.put("x-message-ttl", 60000);
        argments.put("x-dead-letter-exchange", RabbitConstants.ORDER_DLX_EXCHANGE);
        argments.put("x-dead-letter-routing-key", RabbitConstants.ORDER_DLX_ROUTING_KEY);
        Queue queue = new Queue(RabbitConstants.ORDER_QUEUE, true, false, false, argments);
        return queue;
    }

    /**
     * 订单交换机
     *
     * @return {@link Exchange}
     */
    @Bean
    public Exchange orderExchange() {
        return new DirectExchange(RabbitConstants.ORDER_EXCHANGE, true, false, null);
    }

    /**
     * 订单路由键
     *
     * @return {@link Binding}
     */
    @Bean
    public Binding orderRouting() {
        return BindingBuilder.bind(orderQueue()).to(orderExchange()).with(RabbitConstants.ORDER_ROUTING_KEY).noargs();
    }

    /**
     * 订单死信队列
     *
     * @return {@link Queue}
     */
    @Bean
    public Queue orderDlxQueue() {
        Queue queue = new Queue(RabbitConstants.ORDER_DLX_QUEUE, true, false, false);
        return queue;
    }

    /**
     * 订单死信交换机
     *
     * @return {@link Exchange}
     */
    @Bean
    public Exchange orderDlxExchange() {
        return new DirectExchange(RabbitConstants.ORDER_DLX_EXCHANGE, true, false, null);
    }

    /**
     * 订单死信路由键
     *
     * @return {@link Binding}
     */
    @Bean
    public Binding orderDlxRouting() {
        return BindingBuilder.bind(orderDlxQueue()).to(orderDlxExchange()).with(RabbitConstants.ORDER_DLX_ROUTING_KEY).noargs();
    }

    /**
     * 库存队列
     *
     * @return {@link Queue}
     */
    @Bean
    public Queue stockQueue() {
        return new Queue(RabbitConstants.STOCK_QUEUE, true, false, false, null);
    }

    /**
     * 库存交换机
     *
     * @return {@link Exchange}
     */
    @Bean
    public Exchange stockExchange() {
        return new DirectExchange(RabbitConstants.STOCK_EXCHANGE, true, false, null);
    }

    /**
     * 库存路由键
     *
     * @return {@link Binding}
     */
    @Bean
    public Binding stockRouting() {
        return BindingBuilder.bind(stockQueue()).to(stockExchange()).with(RabbitConstants.STOCK_ROUTING_KEY).noargs();
    }

}

实现消息发送

@RequestMapping(value = "/submit", produces = "application/json;charset=UTF-8")
public R submit(@RequestBody OrderVo orderVo) throws UnsupportedEncodingException {
	Order order = orderService.createOrder(orderVo);
	System.out.println("OrderController.submit... createOrder");

	// 放入死信队列
	amqpTemplate.convertAndSend(RabbitConstants.ORDER_EXCHANGE,
			RabbitConstants.ORDER_ROUTING_KEY,
			(order.getId() + ""));
	System.out.println("OrderController.submit... sendMessage to orderExchange");
	return R.ok(order);
}

消息消费者监听

@Component
public class OrderHandler {

    @Autowired
    private OrderService orderService;

    @RabbitListener(queues = RabbitConstants.ORDER_DLX_QUEUE, ackMode = "MANUAL")
    public void onMessage(Message message, Channel channel) throws IOException {
        System.out.println("消息进入死信队列...");
        String s = new String(message.getBody());
        Order o = orderService.getById(Long.parseLong(s));
        if (o != null && OrderConstants.TOPAID.getCode().equals(o.getOrderState())) {
            Order order = new Order();
            order.setId(o.getId());
            order.setOrderState(OrderConstants.CANCEL.getCode());
            order.setGmtModified(new Date());
            orderService.updateById(order);
        }
        // 手动ack
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
    }
}

启动Spring Boot应用进行测试

Spring Boot + RabbitMQ实现订单超时自动取消功能

点击其中一个有库存的商品进行购买,先演示一个成功支付的。

Spring Boot + RabbitMQ实现订单超时自动取消功能

接下来再演示支付失败的,并注意控制台日志打印。

Spring Boot + RabbitMQ实现订单超时自动取消功能

Spring Boot + RabbitMQ实现订单超时自动取消功能

通过监听死信队列,消息在进入死信队列之后就可以做一系列业务逻辑处理,比如,消息如果还是未支付状态,将其修改为取消支付。


推荐阅读
  • RabbitMq之发布确认高级部分1.为什么会需要发布确认高级部分?在生产环境中由于一些不明原因,导致rabbitmq重启,在RabbitMQ重启期间生产者消息投递失败,导致消息丢 ... [详细]
  • 开发笔记:(002)spring容器中bean初始化销毁时执行的方法及其3种实现方式
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了(002)spring容器中bean初始化销毁时执行的方法及其3种实现方式相关的知识,希望对你有一定的参考价值。 ... [详细]
  • 03Spring使用注解方式注入
    基于注解的DI注入1.导包环境搭建:导入aop包(spring-aop-4.1.6.RELEASE.jar)2.创建类3.创建spring.xml配置文件(必须在src目录下)该配 ... [详细]
  • 6(自)、交换机之关键字模式
    上一节中的我们的日志系统将所有消息广播给所有消费者,对此我们想做一些改变,例如我们希望将日志消息写入磁盘的程序仅接收严重错误(error),而不存储那些警告(warnning)或者 ... [详细]
  • php网站设计实验报告,php网站开发实训报告
    本文目录一览:1、php动态网站设计的关键技术有哪些软件,及搭建步骤需要哪些页面,分别完成 ... [详细]
  • 本文介绍了Python高级网络编程及TCP/IP协议簇的OSI七层模型。首先简单介绍了七层模型的各层及其封装解封装过程。然后讨论了程序开发中涉及到的网络通信内容,主要包括TCP协议、UDP协议和IPV4协议。最后还介绍了socket编程、聊天socket实现、远程执行命令、上传文件、socketserver及其源码分析等相关内容。 ... [详细]
  • 本文讨论了Alink回归预测的不完善问题,指出目前主要针对Python做案例,对其他语言支持不足。同时介绍了pom.xml文件的基本结构和使用方法,以及Maven的相关知识。最后,对Alink回归预测的未来发展提出了期待。 ... [详细]
  • 在Xamarin XAML语言中如何在页面级别构建ControlTemplate控件模板
    本文介绍了在Xamarin XAML语言中如何在页面级别构建ControlTemplate控件模板的方法和步骤,包括将ResourceDictionary添加到页面中以及在ResourceDictionary中实现模板的构建。通过本文的阅读,读者可以了解到在Xamarin XAML语言中构建控件模板的具体操作步骤和语法形式。 ... [详细]
  • 分享css中提升优先级属性!important的用法总结
    web前端|css教程css!importantweb前端-css教程本文分享css中提升优先级属性!important的用法总结微信门店展示源码,vscode如何管理站点,ubu ... [详细]
  • Activiti7流程定义开发笔记
    本文介绍了Activiti7流程定义的开发笔记,包括流程定义的概念、使用activiti-explorer和activiti-eclipse-designer进行建模的方式,以及生成流程图的方法。还介绍了流程定义部署的概念和步骤,包括将bpmn和png文件添加部署到activiti数据库中的方法,以及使用ZIP包进行部署的方式。同时还提到了activiti.cfg.xml文件的作用。 ... [详细]
  • 一次上线事故,30岁+的程序员踩坑经验之谈
    本文主要介绍了一位30岁+的程序员在一次上线事故中踩坑的经验之谈。文章提到了在双十一活动期间,作为一个在线医疗项目,他们进行了优惠折扣活动的升级改造。然而,在上线前的最后一天,由于大量数据请求,导致部分接口出现问题。作者通过部署两台opentsdb来解决问题,但读数据的opentsdb仍然经常假死。作者只能查询最近24小时的数据。这次事故给他带来了很多教训和经验。 ... [详细]
  • 解决IDEA配置xml文件头报错的方法
    本文介绍了解决IDEA配置xml文件头报错的方法,包括了具体的解决方案和步骤。通过本文的指导,读者可以轻松解决这个问题并正常使用IDEA进行开发工作。 ... [详细]
  • RabbitMQ的消息持久化处理
    1、RabbitMQ的消息持久化处理,消息的可靠性是RabbitMQ的一大特色,那么RabbitMQ是如何保证消息可靠性的呢——消息持久化。2、auto ... [详细]
  • 讨伐Java多线程与高并发——MQ篇
    本文是学习Java多线程与高并发知识时做的笔记。这部分内容比较多,按照内容分为5个部分:多线程基础篇JUC篇同步容器和并发容器篇线程池篇MQ篇本篇 ... [详细]
  • 使用Packet Tracer对不同网段组网模拟
    @[toc]1.不同网段通信处于同于一个网段的电脑通过集线器或者交换机可以实现互相通信,处于不同网段的电脑就需要通过路由器来实现,这里可以通过Cisco的PacketTracer软 ... [详细]
author-avatar
浅小影HLGC_215
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有