RabbitMQ消息队列(二):业务实战订单超时处理

业务实战:订单超时处理

❤️弱水三千,只取一瓢饮❤️
🤞你好啊,我是小酥肉,欢迎阅读本博客👌

现在有一个需求:订单被成功创建后,若该订单超过1天没有被处理,则手动处理该超时订单

  • 对“成功创建”的定义:
    • 用户点击《确认购买》后,调用mvc的purchaseItem接口,隐藏该商品,创建该order,填充order_detail,返回卖家信息
  • 对“处理”的定义
    • 如果该订单1天之内没有被商家答复,买家取消,则默认该订单超时
  • 如果没有超时:
    • 若订单1天之内被处理了(商家答复,买家取消),则订单状态更新(已取消?已完成?)
  • 如果超时了?
    • 超时了则需将订单设置为“已超时”,商品需要用户重新上架

事实上还有其他的解决方案:

  • java层:JUC
  • 中间件+java操作:redis
  • 数据库+java操作:轮询
  • 算法:时间片轮转

可以参考 订单超时业务参考

业务流程图

下面是业务流程图

消息队列解决架构图

本文主要讲解RabbitMq如何处理超时业务,默认您已经对消息队列RabbitMq已经有了基础了解

可以看我之前写的博客
RabbitMQ消息队列(一):快速上手和理解消息队列

在这里插入图片描述

对于TTL的设置

TTL
我们可以配置队列TTL或者消息TTL。

  • 队列配置TTL:
    • 生产者A发送一条消息,经过延时exchange,转发给延时队列,设置该队列的消息存活时间TTL,超时后将其根据routeKey转发给死信exchange,死信exchange将死信转发给死信队列,等待监听的消费者去消费消息
  • 消息配置TTL:
    • 消息配置TTL后,处理更加灵活,更符合这里的业务需求,所以本文采用消息配置TTL的方式

实现

声明一个延时交换机,一个死信交换机,一个延时队列,一个处理死信队列,并绑定

/**
 * @Author:xsr
 * @Date:2023/11/11 15:18
 * 订单 mq配置
 */
@Configuration
public class RabbitMQConfiguration {
    // 延迟交换机
    public static final String ORDER_DELAY_EXCHANGE = "order.delay.exchange";
    // 延迟队列
    public static final String ORDER_DELAY_QUEUE = "order.delay.queue";
    // 延迟队列路由Key
    public static final String ORDER_DELAY_QUEUE_ROUTING_KEY = "order.delay.queue.routingkey";
    // 死信交换机
    public static final String ORDER_DEAD_LETTER_EXCHANGE = "order.dead.letter.exchange";
    // 死信队列
    public static final String ORDER_DEAD_LETTER_QUEUE = "order.dead.letter.queue";
    // 死信队列路由Key
    public static final String ORDER_DEAD_LETTER_QUEUE_ROUTING_KEY = "order.dead.letter.routingkey";
    // 声明延迟交换机
    @Bean("orderDelayExchange")
    public DirectExchange orderDelayExchange() {
        return new DirectExchange(ORDER_DELAY_EXCHANGE);
    }
    // 声明死信交换机
    @Bean("orderDeadLetterExchange")
    public DirectExchange orderDeadLetterExchange() {
        return new DirectExchange(ORDER_DEAD_LETTER_EXCHANGE);
    }
    // 声明延迟队列,不设置消息TTL存活时间,并绑定到对应的死信交换机
    @Bean("orderDelayQueue")
    public Queue orderDelayQueue() {
        Map<String, Object> args = new HashMap<>();
        // x-dead-letter-exchange 声明队列绑定的死信交换机
        args.put("x-dead-letter-exchange", ORDER_DEAD_LETTER_EXCHANGE);
        // x-dead-letter-routing-key 声明队列的死信路由Key
        args.put("x-dead-letter-routing-key", ORDER_DEAD_LETTER_QUEUE_ROUTING_KEY);
        return QueueBuilder.durable(ORDER_DELAY_QUEUE).withArguments(args).build();
    }
    // 声明延迟队列的绑定关系
    @Bean
    public Binding delayBinding(@Qualifier("orderDelayQueue") Queue queue,
                                @Qualifier("orderDelayExchange") DirectExchange exchange) {
        return BindingBuilder.bind(queue).to(exchange).with(ORDER_DELAY_QUEUE_ROUTING_KEY);
    }
    // 声明死信队列
    @Bean("orderDeadLetterQueue")
    public Queue orderDeadLetterQueue() {
        return new Queue(ORDER_DEAD_LETTER_QUEUE);
    }
    // 声明死信队列的绑定关系
    @Bean
    public Binding deadLetterBinding(@Qualifier("orderDeadLetterQueue") Queue queue,
                                     @Qualifier("orderDeadLetterExchange") DirectExchange exchange) {
        return BindingBuilder.bind(queue).to(exchange).with(ORDER_DEAD_LETTER_QUEUE_ROUTING_KEY);
    }
}

声明一个生产者,一个消费者,并绑定

/**
 * @Author:xsr
 * @Date:2023/11/11 15:37
 * 订单 延迟消息 生产者
 */
@Slf4j
@Component
public class OrderDelayMessageProducer {

    @Resource
    private RabbitTemplate rabbitTemplate;
    //todo 生产者 dalayTime String
    public void send(String message, String delayTime) {
        try{
            rabbitTemplate.convertAndSend(ORDER_DELAY_EXCHANGE, ORDER_DELAY_QUEUE_ROUTING_KEY, message, msg -> {
                // 设置消息的到期时间
                msg.getMessageProperties().setExpiration(delayTime);
                return msg;
            });
        }catch (Exception e){
            throw new BusinessException(ErrorCode.OPERATION_ERROR,"订单创建消息发送失败");
        }

    }
}
/**
 * @Author:xsr
 * @Date:2023/11/11 15:37
 * 订单 死信消息 消费者
 */
@Slf4j
@Component
public class OrderDeadLetterQueueConsumer {
    @Resource
    private OrdersService ordersService;
    @Resource
    private DetailOrderService detailOrderService;
    @Resource
    private UserInfoService userInfoService;
    @Resource
    private UserMapper userMapper;
    // 监听死信队列
    @RabbitListener(queues = ORDER_DEAD_LETTER_QUEUE)
    @Transactional(rollbackFor=BusinessException.class)
    public void receive(Message message, Channel channel) {
        //检测该订单消息是否没有被处理
        String msg = new String(message.getBody());
        log.info("当前时间:{},死信队列收到消息:{}", LocalDateTime.now(), msg);
        // 使用正则表达式提取订单编号和延时时间
        Pattern pattern = Pattern.compile("订单编号:(\\d+),延时时间:(\\d+)ms");
        Matcher matcher = pattern.matcher(msg);

        if (matcher.find()) {
            String orderNumber = matcher.group(1);
            String delayTime = matcher.group(2);
            log.info("死信队列消费者提取到订单编号:{},延时时间:{}", orderNumber, delayTime);
            // 在这里进行处理,例如执行相应的业务逻辑
            //查出该order和order_detail
            Order order = ordersService.getById(orderNumber);
            DetailOrder detailOrder = detailOrderService.getById(orderNumber);
            if(order==null||detailOrder==null){
                throw new BusinessException(ErrorCode.NOT_FOUND_ERROR,"订单不存在");
            }
            //检验:订单答复时间需为null,订单状态需为“待处理”
            if (detailOrder.getConfirmTime()!=null|| !Objects.equals(detailOrder.getOrderStatus(), OrderStatus.WaitProcessed.getText())) {
                throw new BusinessException(ErrorCode.OPERATION_ERROR,"订单已结束");
            }
//             若通过检验,将该订单状态置为“已超时”,定义为卖家责任,卖家信誉分减少-5
            detailOrder.setOrderStatus(OrderStatus.HasExpire.getText());

            boolean saveDetailOrder = detailOrderService.save(detailOrder);
            if(!saveDetailOrder){
                throw new BusinessException(ErrorCode.OPERATION_ERROR,"更新订单内容失败");
            }
            Long salerId = order.getSalerId();
            User saler = userInfoService.getUserInfo(salerId);
            saler.setReputation(saler.getReputation()-decreaseSalerReputation);
            int salerSave = userMapper.updateById(saler);
            if(salerSave==0){
                throw new BusinessException(ErrorCode.OPERATION_ERROR,"更新卖家信誉分失败");
            }

        } else {
            log.warn("未能从消息中提取订单编号和延时时间");
        }
    }

}

在控制器注入生产者,发送消息

        //todo 发送消息格式:一条订单被创建 orderId:xxx
        String orderCreateMessage="一条顶点被创建,订单编号:"+orderNumber+",延时时间:"+OrderExpireMillions+"ms";
        orderDelayMessageProducer.send(orderCreateMessage, String.valueOf(OrderExpireMillions));

测试成功:
在这里插入图片描述
参考:

为什么我推荐你使用 RabbitMQ 实现订单超时处理

订单超时业务实现方案

❤️弱水三千,只取一瓢饮❤️
🤞我是小酥肉 ,喜欢简单 ,期待您的留言👌

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

编程就是n踢r

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值