1.pom依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
2.配置(这里我使用了死讯队列,原理其实很简单,先发送到死讯队列并且设置时间,然后死讯队列到时会自动转发到对应真正的消费队列,3个列队,自动收货,2个自动取消订单)
**
* rabbitMQ采用路由模式
* 一个交换机对应多个队列
* bei
*/
@AllArgsConstructor
public class OrderDirectExchangeConfig {
/**
* 订单实际消费队列
*/
@Bean
public Queue orderQueue(){
return new Queue(QueueEnum.QUEUE_ORDER_CANCEL.getName());
}
/**
* 支付延时实际消费队列
*/
@Bean
public Queue payQueue(){
return new Queue(QueueEnum.QUEUE_PAY_CANCEL.getName());
}
/**
* 收货延时实际消费队列
*/
@Bean
public Queue receiveQueue(){
return new Queue(QueueEnum.QUEUE_AUTO_RECEIVE.getName());
}
/**
* 订单延迟队列(死信队列)
*/
@Bean
public Queue orderTtlQueue(){
return QueueBuilder
.durable(QueueEnum.QUEUE_TTL_ORDER_CANCEL.getName())
.withArgument("x-dead-letter-exchange", QueueEnum.QUEUE_ORDER_CANCEL.getExchange()) //到期后转发的交换机
.withArgument("x-dead-letter-routing-key", QueueEnum.QUEUE_ORDER_CANCEL.getRouteKey()) // 到期后转发的路由键
.build();
}
/**
* 支付延迟队列(死信队列)
*/
@Bean
public Queue payTtlQueue(){
return QueueBuilder
.durable(QueueEnum.QUEUE_TTL_PAY_CANCEL.getName())
.withArgument("x-dead-letter-exchange", QueueEnum.QUEUE_ORDER_CANCEL.getExchange()) //到期后转发的交换机
.withArgument("x-dead-letter-routing-key", QueueEnum.QUEUE_PAY_CANCEL.getRouteKey()) // 到期后转发的路由键
.build();
}
/**
* 收货延迟队列(死信队列)
*/
@Bean
public Queue receiveTtlQueue(){
return QueueBuilder
.durable(QueueEnum.QUEUE_TTL_AUTO_RECEIVE.getName())
.withArgument("x-dead-letter-exchange", QueueEnum.QUEUE_ORDER_CANCEL.getExchange()) //到期后转发的交换机
.withArgument("x-dead-letter-routing-key", QueueEnum.QUEUE_AUTO_RECEIVE.getRouteKey()) // 到期后转发的路由键
.build();
}
/**
* 订单消息实际消费绑定的交换机
*/
@Bean
DirectExchange orderExchange(){
return (DirectExchange) ExchangeBuilder.directExchange(QueueEnum.QUEUE_ORDER_CANCEL.getExchange())
// 开启持久化,如果服务器宕机消息依旧存在
.durable(true)
.build();
}
/**
* 订单队列绑定到交换机上
*/
@Bean
Binding orderBinding(DirectExchange orderExchange, Queue orderQueue){
return BindingBuilder
.bind(orderQueue)
.to(orderExchange)
.with(QueueEnum.QUEUE_ORDER_CANCEL.getRouteKey());
}
/**
* 支付队列绑定到交换机上
*/
@Bean
Binding payBinding(DirectExchange orderExchange, Queue payQueue){
return BindingBuilder
.bind(payQueue)
.to(orderExchange)
.with(QueueEnum.QUEUE_PAY_CANCEL.getRouteKey());
}
/**
* 支付队列绑定到交换机上
*/
@Bean
Binding receiveBinding(DirectExchange orderExchange, Queue receiveQueue){
return BindingBuilder
.bind(receiveQueue)
.to(orderExchange)
.with(QueueEnum.QUEUE_AUTO_RECEIVE.getRouteKey());
}
/**
* 订单延迟队列 绑定到交换机
*/
@Bean
Binding orderTtlBinding(DirectExchange orderExchange, Queue orderTtlQueue){
return BindingBuilder
.bind(orderTtlQueue)
.to(orderExchange)
.with(QueueEnum.QUEUE_TTL_ORDER_CANCEL.getRouteKey());
}
/**
* 支付延迟队列 绑定到交换机
*/
@Bean
Binding payTtlBinding(DirectExchange orderExchange, Queue payTtlQueue){
return BindingBuilder
.bind(payTtlQueue)
.to(orderExchange)
.with(QueueEnum.QUEUE_TTL_PAY_CANCEL.getRouteKey());
}
/**
* 收货延迟队列 绑定到交换机
*/
@Bean
Binding receiveTtlBinding(DirectExchange orderExchange, Queue receiveTtlQueue){
return BindingBuilder
.bind(receiveTtlQueue)
.to(orderExchange)
.with(QueueEnum.QUEUE_TTL_AUTO_RECEIVE.getRouteKey());
}
}
/**
* @author bei
* @2019/11/18 22:59
*/
public interface TimeConstants {
/**
* 延时任务默认延时时间 15分钟
*/
Long DEFAULT_DELAYED_TIME = 15 * 60 * 1000L;
/**
* 自动收货时间为7天
*/
Long AUTO_RECEIVE_TIME = 60 * 1000 * 60 * 24 * 7L;
// Long AUTO_RECEIVE_TIME = 1 * 60 * 1000L;
}
public enum QueueEnum {
/**
* 订单延时队列
*/
QUEUE_ORDER_CANCEL("mall.order.direct", "mall.order.cancel", "mall.order.cancel"),
/**
* 订单延时ttl队列
*/
QUEUE_TTL_ORDER_CANCEL("mall.order.direct", "mall.order.cancel.ttl", "mall.order.cancel.ttl"),
/**
* 订单延时队列
*/
QUEUE_PAY_CANCEL("mall.order.direct", "mall.pay.cancel", "mall.pay.cancel"),
/**
* 订单支付ttl队列
*/
QUEUE_TTL_PAY_CANCEL("mall.order.direct", "mall.pay.cancel.ttl", "mall.pay.cancel.ttl"),
/**
* 自动收货队列
*/
QUEUE_AUTO_RECEIVE("mall.order.direct", "mall.auto.receive", "mall.auto.receive"),
/**
* 自动收货ttl队列
*/
QUEUE_TTL_AUTO_RECEIVE("mall.order.direct", "mall.auto.receive.ttl", "mall.auto.receive.ttl");
/**
* 交换名称
*/
private String exchange;
/**
* 队列名称
*/
private String name;
/**
* 路由键
*/
private String routeKey;
QueueEnum(String exchange, String name, String routeKey) {
this.exchange = exchange;
this.name = name;
this.routeKey = routeKey;
}
public String getExchange() {
return exchange;
}
public String getName() {
return name;
}
public String getRouteKey() {
return routeKey;
}
}
3.yml
spring:
rabbitmq:
username: guest
password: guest
host: 127.0.0.1
port: 5672
4.生产者和消费者
/**
* 延时订单消费者
* bei
*/
@Component
@Slf4j
public class CancelOrderReceiver {
@Autowired
private MallOrderService mallOrderService;
@Autowired
MallProdSelfInfoService mallProdSelfInfoService;
/**
* 订单需要先确认是否还是未付款状态
*/
@RabbitListener(queues = "mall.order.cancel") // 设置队列路由键,和监听容器
public void handle(String orderId, Message message, Channel channel){
try {
MallOrder mallOrder = mallOrderService.getById(orderId);
if (ShopOrderConstants.WAITRECEIVE_ORDER_STATUS.equals(mallOrder.getStatus())){
MallOrder temp = new MallOrder();
temp.setId(mallOrder.getId());
log.info("订单超时自动取消,订单id为{}", mallOrder.getId());
mallOrderService.cancelOrder(orderId, "订单超时,系统自动取消");
}
// TODO 通知 MQ 消息已被成功消费,可以ACK了
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} catch (Exception e) {
// TODO 如果报错了,那么我们可以进行容错处理,比如转移当前消息进入其它队列
}
}
}
/**
* 订单延迟生产者
* bei
*/
@Component
public class CancelOrderSender {
private static Logger logger = LoggerFactory.getLogger(CancelOrderSender.class);
@Autowired
private RabbitTemplate rabbitTemplate;
public void sendMessage(String orderId, long delayTimes){
rabbitTemplate.convertAndSend(QueueEnum.QUEUE_TTL_ORDER_CANCEL.getExchange(),
QueueEnum.QUEUE_TTL_ORDER_CANCEL.getRouteKey(), orderId,
message -> {
message.getMessageProperties().setExpiration(String.valueOf(delayTimes));
// 当数据还没有持久化的时候,如果服务器宕机,设置此参数也可以恢复数据
message.getMessageProperties().setDeliveryMode(MessageProperties.DEFAULT_DELIVERY_MODE);
return message;
}
);
logger.info("订单生成者 订单编号:{}",orderId);
}
}
5.方法调用:
// 启动延时任务
cancelOrderSender.sendMessage(mallOrder.getId(), TimeConstants.DEFAULT_DELAYED_TIME);