场景:我的项目是一个内容管理中心,需要实现文章&广告的定时上下架功能。
实现可选方案有:
- 自己设计任务表进行定时轮询
- redis的过期事件
- rabbitMQ的TTL,通过设定消息的超时时间,交换机的x-dead-letter-exchange,超时后转移到待消费队列实现。
- 还有其他更多的方案,此处选择3
一图胜千言,方案设计图如下:
下面贴代码:
一、配置文件
配置rabbitmq,定义各交换机、队列,并根据routingkey进行绑定。为了更清晰不混乱,特意划分为几个配置文件:
RabbitConfigCommon:配置3个交换机、RabbitTemplate RabbitListenerContainerFactory 、定义各队列&交换机&路由建的名称。下面广告/公告上下架,每种消息一个配置文件:定义相应队列、路由键,绑定交换机,设置绑定策略和参数:
RabbitConfigForAdvOff、RabbitConfigForAdvOn 、RabbitConfigForArticleOff、RabbitConfigForArticleOn、RabbitConfigForDeathFinal(最终死信队列配置)
package com.pld.content.manager.config;
import org.springframework.amqp.core.AcknowledgeMode;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.listener.RabbitListenerContainerFactory;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author tlj
* @date 2019/12/31
*/
@Configuration
public class RabbitConfigCommon {
public static final String TRIGGER_REQ_EXCHANGE = "trigger.req.exchange" + "111";
public static final String TRIGGER_TIMED_EXCHANGE = "trigger.timed.exchange"+ "111";
public static final String TRIGGER_DEATH_FINAL_EXCHANGE = "trigger.death.exchange"+ "111";
public static final String TRIGGER_DEATH_FAILED_ROUTING_KEY = "trigger.death.routingkey"+ "111";
public static final String TRIGGER_REQ_ROUTING_KEY_ARTICLE_ON = "trigger.req.routingkey.article.on"+ "111";
public static final String TRIGGER_TIMED_ROUTING_KEY_ARTICLE_ON = "trigger.timed.routingkey.article.on"+ "111";
public static final String TRIGGER_REQ_ROUTING_KEY_ARTICLE_OFF = "trigger.req.routingkey.article.off"+ "111";
public static final String TRIGGER_TIMED_ROUTING_KEY_ARTICLE_OFF = "trigger.timed.routingkey.article.off"+ "111";
public static final String TRIGGER_REQ_ROUTING_KEY_ADV_ON = "trigger.req.routingkey.adv.on"+ "111";
public static final String TRIGGER_TIMED_ROUTING_KEY_ADV_ON = "trigger.timed.routingkey.adv.on"+ "111";
public static final String TRIGGER_REQ_ROUTING_KEY_ADV_OFF = "trigger.req.routingkey.adv.off"+ "111";
public static final String TRIGGER_TIMED_ROUTING_KEY_ADV_OFF = "trigger.timed.routingkey.adv.off"+ "111";
// 公共: 初始请求的延时交换器:包含内容上架,内容下架,文章上架,文章下架四种routingkey。每个消息的延迟时间由生产者对每条消息进行设定,到达延时时间后,转移到 TRIGGER_TIMED_EXCHANGE交换器
@Bean
public DirectExchange triggerReqExchange() {
return new DirectExchange(TRIGGER_REQ_EXCHANGE);
}
// 公共: TRIGGER_TIMED_EXCHANGE交换器,此交换器真正被消费者监听。用于转发已耗时消息
@Bean
public DirectExchange triggerTimedExchange() {
return new DirectExchange(TRIGGER_TIMED_EXCHANGE);
}
// 公共: TRIGGER_TIMED_FINAL_EXCHANGE交换器,此交换器真正的死信队列,存储消费不成功被拒绝的消息
@Bean
public DirectExchange triggerDeathFinalTopicExchange() {
return new DirectExchange(TRIGGER_DEATH_FINAL_EXCHANGE);
}
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate template = new RabbitTemplate(connectionFactory);
template.setMessageConverter(new Jackson2JsonMessageConverter());
return template;
}
@Bean
public RabbitListenerContainerFactory rabbitListenerContainerFactory(ConnectionFactory connectionFactory){
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
factory.setMessageConverter(new Jackson2JsonMessageConverter());
factory.setAcknowledgeMode(AcknowledgeMode.MANUAL); //开启手动 ack
return factory;
}
}
----------------------------------------
package com.pld.content.manager.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
/**广告下架:队列交换机配置
* @author tlj
* @date 2019/12/31
*/
@Configuration
@AutoConfigureAfter({RabbitConfigCommon.class})
public class RabbitConfigForAdvOff {
@Autowired
private RabbitConfigCommon rabbitConfigCommon;
/**
* 声明req queue 并配置下一流向交换机,并指定routingkey
* @return
*/
@Bean
public Queue advOffReqQueueAndConfigNextExchange() {
Map<String, Object> params = new HashMap<>();
// x-dead-letter-exchange 声明了队列里的死信转发到的DLX名称,
params.put("x-dead-letter-exchange", RabbitConfigCommon.TRIGGER_TIMED_EXCHANGE);
// x-dead-letter-routing-key 声明了这些死信在转发时携带的 routing-key 名称。
params.put("x-dead-letter-routing-key", RabbitConfigCommon.TRIGGER_TIMED_ROUTING_KEY_ADV_OFF);
return new Queue(RabbitConfigCommon.TRIGGER_REQ_ROUTING_KEY_ADV_OFF, true, false, false, params);
}
/**
* req queue 绑定到上游交换机(req exchange)并指定routinkey
* @return
*/
@Bean
public Binding bind2PrevQueueForAdvOffReq() {
return BindingBuilder.bind(advOffReqQueueAndConfigNextExchange()).to(rabbitConfigCommon.triggerReqExchange()).with(RabbitConfigCommon.TRIGGER_REQ_ROUTING_KEY_ADV_OFF);
}
/**
* timed queue 绑定到上游交换机(timed exchange)并指定routinkey
* @return
*/
@Bean
public Binding triggerTimedAdvOffBinding() {
// 如果要让延迟队列之间有关联,这里的 routingKey 和 绑定的交换机很关键
return BindingBuilder.bind(advOffTimedQueueAndConfigNextExchange()).to(rabbitConfigCommon.triggerTimedExchange()).with(RabbitConfigCommon.TRIGGER_TIMED_ROUTING_KEY_ADV_OFF);
}
/**
* 声明timed queue 并配置下一流向交换机,并指定routingkey
* @return
*/
@Bean
public Queue advOffTimedQueueAndConfigNextExchange() {
Map<String, Object> params = new HashMap<>();
// x-dead-letter-exchange 声明了队列里的死信转发到的DLX名称,
params.put("x-dead-letter-exchange", RabbitConfigCommon.TRIGGER_DEATH_FINAL_EXCHANGE);
// x-dead-letter-routing-key 声明了这些死信在转发时携带的 routing-key 名称。
params.put("x-dead-letter-routing-key", RabbitConfigCommon.TRIGGER_DEATH_FAILED_ROUTING_KEY);
return new Queue(RabbitConfigCommon.TRIGGER_TIMED_ROUTING_KEY_ADV_OFF, true, false, false, params);
}
}
--------------------------
package com.pld.content.manager.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
/**广告上架:队列交换机配置
* @author tlj
* @date 2019/12/31
*/
@Configuration
@AutoConfigureAfter({RabbitConfigCommon.class})
public class RabbitConfigForAdvOn {
@Autowired
private RabbitConfigCommon rabbitConfigCommon;
/**
* 声明req queue 并配置下一流向交换机,并指定routingkey
* @return
*/
@Bean
public Queue advOnReqQueueAndConfigNextExchange() {
Map<String, Object> params = new HashMap<>();
// x-dead-letter-exchange 声明了队列里的死信转发到的DLX名称,
params.put("x-dead-letter-exchange", RabbitConfigCommon.TRIGGER_TIMED_EXCHANGE);
// x-dead-letter-routing-key 声明了这些死信在转发时携带的 routing-key 名称。
params.put("x-dead-letter-routing-key", RabbitConfigCommon.TRIGGER_TIMED_ROUTING_KEY_ADV_ON);
return new Queue(RabbitConfigCommon.TRIGGER_REQ_ROUTING_KEY_ADV_ON, true, false, false, params);
}
/**
* req queue 绑定到上游交换机(req exchange)并指定routinkey
* @return
*/
@Bean
public Binding bind2PrevQueueForAdvOnReq() {
return BindingBuilder.bind(advOnReqQueueAndConfigNextExchange()).to(rabbitConfigCommon.triggerReqExchange()).with(RabbitConfigCommon.TRIGGER_REQ_ROUTING_KEY_ADV_ON);
}
/**
* timed queue 绑定到上游交换机(timed exchange)并指定routinkey
* @return
*/
@Bean
public Binding triggerDeathAdvOnBinding() {
// 如果要让延迟队列之间有关联,这里的 routingKey 和 绑定的交换机很关键
return BindingBuilder.bind(advOnTimedQueueAndConfigNextExchange()).to(rabbitConfigCommon.triggerTimedExchange()).with(RabbitConfigCommon.TRIGGER_TIMED_ROUTING_KEY_ADV_ON);
}
/**
* 声明timed queue 并配置下一流向交换机,并指定routingkey
* @return
*/
@Bean
public Queue advOnTimedQueueAndConfigNextExchange() {
Map<String, Object> params = new HashMap<>();
// x-dead-letter-exchange 声明了队列里的死信转发到的DLX名称,
params.put("x-dead-letter-exchange", RabbitConfigCommon.TRIGGER_DEATH_FINAL_EXCHANGE);
// x-dead-letter-routing-key 声明了这些死信在转发时携带的 routing-key 名称。
params.put("x-dead-letter-routing-key", RabbitConfigCommon.TRIGGER_DEATH_FAILED_ROUTING_KEY);
return new Queue(RabbitConfigCommon.TRIGGER_TIMED_ROUTING_KEY_ADV_ON, true, false, false, params);
}
}
-------------------------
package com.pld.content.manager.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
/**文章下架:队列交换机配置
* @author tlj
* @date 2019/12/31
*/
@Configuration
@AutoConfigureAfter({RabbitConfigCommon.class})
public class RabbitConfigForArticleOff {
@Autowired
private RabbitConfigCommon rabbitConfigCommon;
/**
* 声明req queue 并配置下一流向交换机,并指定routingkey
* @return
*/
@Bean
public Queue articleOffReqQueueAndConfigNextExchange() {
Map<String, Object> params = new HashMap<>();
// x-dead-letter-exchange 声明了队列里的死信转发到的DLX名称,
params.put("x-dead-letter-exchange", RabbitConfigCommon.TRIGGER_TIMED_EXCHANGE);
// x-dead-letter-routing-key 声明了这些死信在转发时携带的 routing-key 名称。
params.put("x-dead-letter-routing-key", RabbitConfigCommon.TRIGGER_TIMED_ROUTING_KEY_ARTICLE_OFF);
return new Queue(RabbitConfigCommon.TRIGGER_REQ_ROUTING_KEY_ARTICLE_OFF, true, false, false, params);
}
/**
* req queue 绑定到上游交换机(req exchange)并指定routinkey
* @return
*/
@Bean
public Binding bind2PrevQueueForArticleOffReq() {
return BindingBuilder.bind(articleOffReqQueueAndConfigNextExchange()).to(rabbitConfigCommon.triggerReqExchange()).with(RabbitConfigCommon.TRIGGER_REQ_ROUTING_KEY_ARTICLE_OFF);
}
/**
* timed queue 绑定到上游交换机(timed exchange)并指定routinkey
* @return
*/
@Bean
public Binding triggerDeathArticleOffBinding() {
// 如果要让延迟队列之间有关联,这里的 routingKey 和 绑定的交换机很关键
return BindingBuilder.bind(articleOffTimedQueueAndConfigNextExchange()).to(rabbitConfigCommon.triggerTimedExchange()).with(RabbitConfigCommon.TRIGGER_TIMED_ROUTING_KEY_ARTICLE_OFF);
}
/**
* 声明timed queue 并配置下一流向交换机,并指定routingkey
* @return
*/
@Bean
public Queue articleOffTimedQueueAndConfigNextExchange() {
Map<String, Object> params = new HashMap<>();
// x-dead-letter-exchange 声明了队列里的死信转发到的DLX名称,
params.put("x-dead-letter-exchange", RabbitConfigCommon.TRIGGER_DEATH_FINAL_EXCHANGE);
// x-dead-letter-routing-key 声明了这些死信在转发时携带的 routing-key 名称。
params.put("x-dead-letter-routing-key", RabbitConfigCommon.TRIGGER_DEATH_FAILED_ROUTING_KEY);
return new Queue(RabbitConfigCommon.TRIGGER_TIMED_ROUTING_KEY_ARTICLE_OFF, true, false, false, params);
}
}
---------------------
package com.pld.content.manager.config;
import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
/**文章上架:队列交换机配置
* @author tlj
* @date 2019/12/31
*/
@Configuration
@AutoConfigureAfter({RabbitConfigCommon.class})
public class RabbitConfigForArticleOn {
@Autowired
private RabbitConfigCommon rabbitConfigCommon;
/**
* 声明req queue 并配置下一流向交换机,并指定routingkey
* @return
*/
@Bean
public Queue articleOnReqQueueAndConfigNextExchange() {
Map<String, Object> params = new HashMap<>();
// x-dead-letter-exchange 声明了队列里的死信转发到的DLX名称,
params.put("x-dead-letter-exchange", RabbitConfigCommon.TRIGGER_TIMED_EXCHANGE);
// x-dead-letter-routing-key 声明了这些死信在转发时携带的 routing-key 名称。
params.put("x-dead-letter-routing-key", RabbitConfigCommon.TRIGGER_TIMED_ROUTING_KEY_ARTICLE_ON);
return new Queue(RabbitConfigCommon.TRIGGER_REQ_ROUTING_KEY_ARTICLE_ON, true, false, false, params);
}
/**
* req queue 绑定到上游交换机(req exchange)并指定routinkey
* @return
*/
@Bean
public Binding bind2PrevQueueForArticleOnReq() {
return BindingBuilder.bind(articleOnReqQueueAndConfigNextExchange()).to(rabbitConfigCommon.triggerReqExchange()).with(RabbitConfigCommon.TRIGGER_REQ_ROUTING_KEY_ARTICLE_ON);
}
/**
* timed queue 绑定到上游交换机(timed exchange)并指定routinkey
* @return
*/
@Bean
public Binding triggerDeathArticleOnBinding() {
// 如果要让延迟队列之间有关联,这里的 routingKey 和 绑定的交换机很关键
return BindingBuilder.bind(articleOnTimedQueueAndConfigNextExchange()).to(rabbitConfigCommon.triggerTimedExchange()).with(RabbitConfigCommon.TRIGGER_TIMED_ROUTING_KEY_ARTICLE_ON);
}
/**
* 声明timed queue 并配置下一流向交换机,并指定routingkey
* @return
*/
@Bean
public Queue articleOnTimedQueueAndConfigNextExchange() {
Map<String, Object> params = new HashMap<>();
// x-dead-letter-exchange 声明了队列里的死信转发到的DLX名称,
params.put("x-dead-letter-exchange", RabbitConfigCommon.TRIGGER_DEATH_FINAL_EXCHANGE);
// x-dead-letter-routing-key 声明了这些死信在转发时携带的 routing-key 名称。
params.put("x-dead-letter-routing-key", RabbitConfigCommon.TRIGGER_DEATH_FAILED_ROUTING_KEY);
return new Queue(RabbitConfigCommon.TRIGGER_TIMED_ROUTING_KEY_ARTICLE_ON, true, false, false, params);
}
}
-----------------------
package com.pld.content.manager.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
/**最终死信:队列交换机配置
* @author tlj
* @date 2019/12/31
*/
@Configuration
@AutoConfigureAfter({RabbitConfigCommon.class})
public class RabbitConfigForDeathFinal {
private static final long DEATH_FINAL_QUEUE_TTL_MS_TIME = 30*24*3600*1000;// 暂定存30天
/**
* fielded queue 绑定到上游交换机(death final exchange)并指定routinkey为#
* @return
*/
@Bean
public Binding triggerDeathAdvOnBinding(RabbitConfigCommon rabbitConfigCommon) {
return BindingBuilder.bind(fieldedQueue()).to(rabbitConfigCommon.triggerDeathFinalTopicExchange()).with(RabbitConfigCommon.TRIGGER_DEATH_FAILED_ROUTING_KEY);
}
@Bean
public Queue fieldedQueue() {
Map<String, Object> params = new HashMap<>();
// 最终死信队列的消息存活时间 暂定存30天
params.put("x-message-ttl", DEATH_FINAL_QUEUE_TTL_MS_TIME);
return new Queue(RabbitConfigCommon.TRIGGER_DEATH_FAILED_ROUTING_KEY, true, false, false, params);
}
}
二、生产者
package com.pld.content.manager.controller.mq;
import com.pld.content.manager.config.RabbitConfigCommon;
import org.springframework.amqp.core.MessageDeliveryMode;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* @author tlj
* @date 2019/12/31
*/
@Service
public class TriggerProducer {
@Autowired
private RabbitTemplate rabbitTemplate;
public void sendArticleOnTrigger(long expiration,TriggerTaskInfo taskInfo) {
rabbitTemplate.convertAndSend(RabbitConfigCommon.TRIGGER_REQ_EXCHANGE, RabbitConfigCommon.TRIGGER_REQ_ROUTING_KEY_ARTICLE_ON,taskInfo, message -> {
// 设置超时时间 毫秒
message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
message.getMessageProperties().setExpiration(String.valueOf(expiration));
return message;
});
}
public void sendArticleOffTrigger(long expiration,TriggerTaskInfo taskInfo) {
rabbitTemplate.convertAndSend(RabbitConfigCommon.TRIGGER_REQ_EXCHANGE, RabbitConfigCommon.TRIGGER_REQ_ROUTING_KEY_ARTICLE_OFF,taskInfo, message -> {
// 设置超时时间 毫秒
message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
message.getMessageProperties().setExpiration(String.valueOf(expiration));
return message;
});
}
public void sendAdvOnTrigger(long expiration,TriggerTaskInfo taskInfo) {
rabbitTemplate.convertAndSend(RabbitConfigCommon.TRIGGER_REQ_EXCHANGE, RabbitConfigCommon.TRIGGER_REQ_ROUTING_KEY_ADV_ON,taskInfo, message -> {
// 设置超时时间 毫秒
message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
message.getMessageProperties().setExpiration(String.valueOf(expiration));
return message;
});
}
public void sendAdvOffTrigger(long expiration,TriggerTaskInfo taskInfo) {
rabbitTemplate.convertAndSend(RabbitConfigCommon.TRIGGER_REQ_EXCHANGE, RabbitConfigCommon.TRIGGER_REQ_ROUTING_KEY_ADV_OFF,taskInfo, message -> {
// 设置超时时间 毫秒
message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
message.getMessageProperties().setExpiration(String.valueOf(expiration));
return message;
});
}
}
三、消费者
package com.pld.content.manager.controller.mq;
import com.alibaba.fastjson.JSON;
import com.pld.content.manager.common.enums.ContentResultCode;
import com.pld.content.manager.common.exceptions.PldContentDetailException;
import com.pld.content.manager.config.RabbitConfigCommon;
import com.pld.content.manager.domain.entity.AdvertisingEntity;
import com.pld.content.manager.domain.entity.UserArticleEntity;
import com.pld.content.manager.domain.repository.AdvertisingRepository;
import com.pld.content.manager.domain.repository.UserArticleRepository;
import com.pld.content.manager.domain.service.backend.AdvertisingService;
import com.pld.content.manager.domain.service.backend.UserArticleService;
import com.pld.content.manager.dto.advertising.req.AdvertisingOffDTO;
import com.pld.content.manager.dto.advertising.req.AdvertisingUpDTO;
import com.pld.content.manager.dto.article.req.ArticleOffDTO;
import com.pld.content.manager.dto.article.req.ArticleUpDTO;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.Date;
/**
* @author tlj
* @date 2019/12/31
*/
@Service
@Slf4j
public class TriggerConsumer {
@Autowired
private UserArticleService userArticleService;
@Autowired
private UserArticleRepository userArticleRepository;
@Autowired
private AdvertisingRepository advertisingRepository;
@Autowired
private AdvertisingService advertisingService;
private static final String TASK_CANCEL_LOG = "任务已撤销,不执行任务{}";
@RabbitListener(queues = RabbitConfigCommon.TRIGGER_TIMED_ROUTING_KEY_ARTICLE_ON, containerFactory = "rabbitListenerContainerFactory")
@RabbitHandler
public void processTriggerArticleOn(TriggerTaskInfo taskInfo, Channel channel, Message message) throws IOException {
log.info("消费processTriggerArticleOn:{}",taskInfo);
try{
UserArticleEntity userArticleEntity = userArticleRepository.findById(taskInfo.getId()).orElseThrow(()->new PldContentDetailException(ContentResultCode.ARTICLE_NO_EXIST,taskInfo.getId()));
// 验证trigger时间是否被修改过,如果修改过则撤销任务
if(!ifDateEqual(taskInfo.getTriggerTime(),userArticleEntity.getTriggerUpTime())){
log.warn(TASK_CANCEL_LOG, JSON.toJSONString(taskInfo));
// 任务被撤销:拒绝消息,流入最终死信队列
channel.basicReject(message.getMessageProperties().getDeliveryTag(),false);
return;
}
ArticleUpDTO dto = new ArticleUpDTO();
BeanUtils.copyProperties(taskInfo,dto);
// 只让当前的trigger时间生效
userArticleService.publishArticle(dto);
}catch (Exception e){
log.error("{}",e);
// 处理异常:拒绝消息,流入最终死信队列
channel.basicReject(message.getMessageProperties().getDeliveryTag(),false);
return;
}
// 正常流程:返回ack,删除消息
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
}
@RabbitListener(queues = RabbitConfigCommon.TRIGGER_TIMED_ROUTING_KEY_ARTICLE_OFF, containerFactory = "rabbitListenerContainerFactory")
@RabbitHandler
public void processTriggerArticleOff(TriggerTaskInfo taskInfo, Channel channel, Message message) throws IOException {
log.info("消费processTriggerArticleOff:{}",taskInfo);
try{
UserArticleEntity userArticleEntity = userArticleRepository.findById(taskInfo.getId()).orElseThrow(()->new PldContentDetailException(ContentResultCode.ARTICLE_NO_EXIST,taskInfo.getId()));
// 验证trigger时间是否被修改过,如果修改过则撤销任务
if(!ifDateEqual(taskInfo.getTriggerTime(),userArticleEntity.getTriggerOffTime())){
log.warn(TASK_CANCEL_LOG, JSON.toJSONString(taskInfo));
// 任务被撤销:拒绝消息,流入最终死信队列
channel.basicReject(message.getMessageProperties().getDeliveryTag(),false);
return;
}
ArticleOffDTO dto = new ArticleOffDTO();
BeanUtils.copyProperties(taskInfo,dto);
// 只让当前的trigger时间生效
userArticleService.putOffArticle(dto);
}catch (Exception e){
log.error("{}",e);
// 处理异常:拒绝消息,流入最终死信队列
channel.basicReject(message.getMessageProperties().getDeliveryTag(),false);
return;
}
// 正常流程:返回ack,删除消息
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
}
@RabbitListener(queues = RabbitConfigCommon.TRIGGER_TIMED_ROUTING_KEY_ADV_ON, containerFactory = "rabbitListenerContainerFactory")
@RabbitHandler
public void processTriggerAdvOn(TriggerTaskInfo taskInfo, Channel channel, Message message) throws IOException {
log.info("消费processTriggerAdvOn:{}",taskInfo);
try{
AdvertisingEntity advertisingEntity = advertisingRepository.findById(taskInfo.getId()).orElseThrow(()->new PldContentDetailException(ContentResultCode.ADVERTISING_NOT_EXIST,taskInfo.getId()));
// 验证trigger时间是否被修改过,如果修改过则撤销任务
if(!ifDateEqual(taskInfo.getTriggerTime(),advertisingEntity.getTriggerUpTime())){
log.warn(TASK_CANCEL_LOG, JSON.toJSONString(taskInfo));
// 任务被撤销:拒绝消息,流入最终死信队列
channel.basicReject(message.getMessageProperties().getDeliveryTag(),false);
return;
}
AdvertisingUpDTO dto = new AdvertisingUpDTO();
BeanUtils.copyProperties(taskInfo,dto);
// 只让当前的trigger时间生效
advertisingService.publishAdvertising(dto);
}catch (Exception e){
log.error("{}",e);
// 处理异常:拒绝消息,流入最终死信队列
channel.basicReject(message.getMessageProperties().getDeliveryTag(),false);
return;
}
// 正常流程:返回ack,删除消息
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
}
@RabbitListener(queues = RabbitConfigCommon.TRIGGER_TIMED_ROUTING_KEY_ADV_OFF, containerFactory = "rabbitListenerContainerFactory")
@RabbitHandler
public void processTriggerAdvOff(TriggerTaskInfo taskInfo, Channel channel, Message message) throws IOException {
try{
AdvertisingEntity advertisingEntity = advertisingRepository.findById(taskInfo.getId()).orElseThrow(()->new PldContentDetailException(ContentResultCode.ADVERTISING_NOT_EXIST,taskInfo.getId()));
// 验证trigger时间是否被修改过,如果修改过则撤销任务
if(!ifDateEqual(taskInfo.getTriggerTime(),advertisingEntity.getTriggerOffTime())){
log.warn(TASK_CANCEL_LOG, JSON.toJSONString(taskInfo));
// 任务被撤销:拒绝消息,流入最终死信队列
channel.basicReject(message.getMessageProperties().getDeliveryTag(),false);
return;
}
AdvertisingOffDTO dto = new AdvertisingOffDTO();
BeanUtils.copyProperties(taskInfo,dto);
// 只让当前的trigger时间生效
advertisingService.putOffAdvertising(dto);
}catch (Exception e){
log.error("{}",e);
// 处理异常:拒绝消息,流入最终死信队列
channel.basicReject(message.getMessageProperties().getDeliveryTag(),false);
return;
}
// 正常流程:返回ack,删除消息
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
}
private boolean ifDateEqual(Date date1,Date date2){
if( date1==null ){
throw new PldContentDetailException(ContentResultCode.CONTENT_NEED_PARAM_EXCEPTION,"triggerTime不能为空");
}
if(date2==null){
return false;
}
return date1.compareTo(date2)==0;
}
}
四、测试类
package com.pld.content.manager.controller.mq;
import com.pld.common.util.MsgResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
/**mqtest
* @author tlj
* @date 2019/9/29
*/
@RestController
//@Api(value = "mqtest", tags = "mqtest")
public class MqTestController {
@Autowired
private TriggerProducer triggerProducer;
public String sendOrder(){
System.out.println("消息发送时间:"+System.currentTimeMillis());
/**orderProducer.sendArticleOnTrigger();
orderProducer.sendArticleOffTrigger();
orderProducer.sendAdvOnTrigger();*/
TriggerTaskInfo req = new TriggerTaskInfo();
req.setId("03d7cb2c75044dfcac0be830aa17dda2");
req.setUserId("admin");
long duration = 10000*1L;
Date triggerTime = new Date(System.currentTimeMillis()+duration);
req.setTriggerTime(triggerTime);
triggerProducer.sendAdvOffTrigger(duration,req);
return "sendOrder";
}
@GetMapping(value = "/backend/mqtest")
// @ApiOperation(value = "mqtest", notes = "mqtest")
public MsgResult<String> mqtest() {
return MsgResult.successStatic( sendOrder());
}
}
五、其他
任务bean:
/**
* @author tlj
* @date 2019/12/31
*/
@Data
public class TriggerTaskInfo implements Serializable{
// 业务表主键
private String id;
// 操作人id
private String userId;
private Date triggerTime;
}
其他业务service略...........