RabbitMQ实现下单超时自动取消支付

文章内容输出来源:拉勾教育Java高薪训练营;

问题场景

在淘宝或者京东下单,订单创建成功,等待支付,一般会给30分钟的时间,开始倒计时。如果在这段时间内用户没有支付,则默认订单取消,该如何实现?

  1. 定期轮询(数据库等) 用户下单成功,将订单信息放入数据库,同时将支付状态放入数据库,用户付款更改数据库状态。定期轮询数据库支付状态,如果超过30分钟就将该订单取消。
    优点: 设计实现简单
    缺点: 需要对数据库进行大量的IO操作,效率低下。
  2. 基于Timer
    缺点: Timers没有持久化机制.,Timers不灵活 (只可以设置开始时间和重复间隔,对等待支付貌似够用),Timers 不能利用线程池,一个timer一个线程,Timers没有真正的管理计划
  3. ScheduledExecutorService
    优点: 可以多线程执行,一定程度上避免任务间互相影响,单个任务异常不影响其它任务。
    缺点: 在高并发的情况下,不建议使用定时任务去做,因为太浪费服务器性能
  4. RabbitMQ,使用TTL,消息过期投递到死信队列

实现原理

用户下单,将订单存放到交换机ex.order(过期时间为下单超时时间30分钟),消息到q_order队列中,不设置该队列的消费者(故此消息一直未消费).,同时指定一个死信交换机ex.order.dlx,并绑定一个死信队列q.order.dlx,当消息超过30分钟过期变成死信时,该消就会被发送到该死信队列上,由死信消费者消费,判断订单id是否支付,如果未支付则修改为支付超时订单过期。

代码

声明订单和死信队列和交换机,并绑定,(订单消息过期时间这里为了方便测试,设置为10s)

@Configuration
public class RabbitConfig {
    @Bean
    public Queue queue() {
        Map<String, Object> props = new HashMap<>();
        // 消息的生存时间 10s
        props.put("x-message-ttl", 10000);
        // 设置该队列所关联的死信交换器(当队列消息到期后依然没有消费,则加入死信 队列)
        props.put("x-dead-letter-exchange", "ex.order.dlx");
        // 设置该队列所关联的死信交换器的routingKey,如果没有特殊指定,使用原队列的routingKey
        props.put("x-dead-letter-routing-key", "order.dlx");
        Queue queue = new Queue("q.order", true, false, false, props);
        return queue;
    }
    @Bean
    public Queue queueDlx() {
        Queue queue = new Queue("q.order.dlx", true, false, false);
        return queue;
    }
    @Bean
    public Exchange exchange() {
        DirectExchange exchange = new DirectExchange("ex.order", true,false, null);
        return exchange;
    }
    /**
     * 死信交换器
     * @return
     */
    @Bean
    public Exchange exchangeDlx() {
        DirectExchange exchange = new DirectExchange("ex.order.dlx", true,false, null);
        return exchange;
    }
    @Bean
    public Binding binding() {
        return BindingBuilder.bind(queue()).to(exchange()).with("order").noargs();
    }

    @Bean
    public Binding bindingDlx() {
        return BindingBuilder.bind(queueDlx()).to(exchangeDlx()).with("order.dlx").noargs();
    }
}

订单contoller

@RestController
public class OrderController {

    @Autowired
    private AmqpTemplate rabbitTemplate;
    @Autowired
    private OrderInfoMapper orderInfoMapper;


    /**
     * 提交订单
     * @return
     */
    @RequestMapping("/submit")
    public Integer submitOrder() {
		//添加订单信息到mysql
        OrderInfo orderInfo = new OrderInfo();
        orderInfo.setStatus(0);
        orderInfoMapper.insertSelective(orderInfo);
		//将消息发送到order队列
        rabbitTemplate.convertAndSend("ex.order", "order", orderInfo.getId());

        return orderInfo.getId();
    }

    /**
     * 支付
     * @return
     */
    @RequestMapping("/pay/{id}")
    public Integer pay(@PathVariable Integer id) {

        OrderInfo orderInfo = orderInfoMapper.selectByPrimaryKey(id);

        //订单过期
        if(orderInfo.getStatus().equals(2)){
            //跳转用户历史账单中查看因付款超时而取消的订单
            return 2;
        }else{
            orderInfo.setStatus(1);
            orderInfoMapper.updateByPrimaryKey(orderInfo);
            //跳转到付款成功页面
            return 1;

        }
    }

    @RequestMapping("/orderList")
    public List<OrderInfo> orderList() {

        List<OrderInfo> orderInfos = orderInfoMapper.selectAll();

        return orderInfos;
    }

}

死信消费者监听q.order.dlx死信队列,消费过期消息

@Component
public class TimeoutOrderListener {

    @Autowired
    OrderInfoMapper orderInfoMapper;

    @RabbitListener(queues = "q.order.dlx")
    public void onMessage(String orderId)  {
		//通过从死信队列中获取的订单号,查询订单
        OrderInfo orderInfo = orderInfoMapper.selectByPrimaryKey(Integer.valueOf(orderId));

        //判断订单状态如果为未支付,则修改状态为过期
        if(orderInfo.getStatus().equals(0)) {
            orderInfo.setStatus(2);
            orderInfoMapper.updateByPrimaryKey(orderInfo);
        }

    }

}

效果演示

提交订单
在这里插入图片描述
页面跳转至支付
在这里插入图片描述
观察订单信息进入到订单队列
在这里插入图片描述
10s之后由于一直没有点支付,订单队列中的消息一直未被消费,消息过期,进入到q.order.dlx死信队列,死信队列消费者消费到消息,修改订单状态为过期(由于消费者直接监听到进行消费,所以可能看不到死信队列消息)
在这里插入图片描述
此时点击支付后,由于订单已经过期,直接跳转至过期
在这里插入图片描述

代码地址

https://github.com/Nicococococo/rabbitmq-order

写在最后

工作几年,一直都没有去体系化的学习,很多东西没有复杂的工作场景经验,去年综合几家机构,最后还是决定报了拉勾的高薪训练营,在这里也是实实在在的学习到了很多,学完掌握程度也比之前深了很多,而且还有定期的内推,多了更多的机会,真的对我有了很大的帮助提升。

猜你喜欢

转载自blog.csdn.net/qq_23830637/article/details/107869123