业务实战:订单超时处理
❤️弱水三千,只取一瓢饮❤️
🤞你好啊,我是小酥肉,欢迎阅读本博客👌
现在有一个需求:订单被成功创建后,若该订单超过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));
测试成功:
参考:
❤️弱水三千,只取一瓢饮❤️
🤞我是小酥肉 ,喜欢简单 ,期待您的留言👌