依赖:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-amqpartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-mailartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-thymeleafartifactId>
dependency>
<dependency>
<groupId>com.xxxxgroupId>
<artifactId>yeb-serverartifactId>
<version>0.0.1-SNAPSHOTversion>
dependency>
配置文件:
server:
port: 8083
spring:
mail:
host: smtp.sina.com
protocol: smtps
password: xxxxxx
default-encoding: utf-8
username: [email protected]
port: 465
rabbitmq:
host: 192.168.10.100
port: 5672
username: guest
password: guest
#redis
redis:
host: 192.168.10.100
port: 6379
database: 0
timeout: 10000ms
password: root
lettuce:
pool:
max-active: 1024
max-wait: 10000ms
max-idle: 200
min-idle: 5
thymleaf模板:
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>入职欢迎邮件title>
head>
<body>
<h1 align="center">欢迎<p th:text="${name}">p>来到大佬h1>
您所在的部门:<div th:text="${departmentName}">div><br>
您所担任的职位:<div th:text="${jobName}">div><br>
地区:<div th:text="${nationName}">div><br>
<p>大佬软件有限公司©p>
body>
html>
redis配置
@Api("redis配置类")
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String,Object> getBean(LettuceConnectionFactory factory){
RedisTemplate<String, Object> template = new RedisTemplate();
template.setConnectionFactory(factory);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
return template;
}
}
/**
* @Desc 消息接收者
* @Author lsh
* @Create Time 2020/12/22 9:51
*/
@Component
public class MailReceiver {
private static final Logger LOGGER= LoggerFactory.getLogger(MailReceiver.class);
@Autowired
private JavaMailSender javaMailSender;
@Autowired
private MailProperties mailProperties;
@Autowired
private TemplateEngine templateEngine;
@Autowired
private RedisTemplate<String,Object> redisTemplate;
@RabbitListener(queues = MailConstants.MAIL_QUEUE_NAME)
public void handler(Message message, Channel channel){
EmpInfo empInfo = ((EmpInfo) message.getPayload());
MessageHeaders headers = message.getHeaders();
//消息序号
long tag = ((long) headers.get(AmqpHeaders.DELIVERY_TAG));
String msgId = ((String) headers.get("spring_returned_message_correlation"));
HashOperations hashOperations = redisTemplate.opsForHash();
try {
if (hashOperations.entries("mail_log").containsKey(msgId)){
LOGGER.error("消息已经被消费==========>{}",msgId);
//手动确认
channel.basicAck(tag,false);
return;
}
MimeMessage msg=javaMailSender.createMimeMessage();
MimeMessageHelper helper=new MimeMessageHelper(msg);
helper.setFrom(mailProperties.getUsername());
helper.setTo(empInfo.getEmail());
helper.setSubject("入职欢迎邮件");
helper.setSentDate(new Date());
//邮件内容
Context context=new Context();
context.setVariable("name",empInfo.getName());
context.setVariable("departmentName",empInfo.getDepartment().getName());
context.setVariable("jobName",empInfo.getJoblevel().getName());
context.setVariable("nationName",empInfo.getNation().getName());
//发送thymeleaf模板
String mail = templateEngine.process("mail", context);
helper.setText(mail,true);
javaMailSender.send(msg);
LOGGER.info("邮件发送成功");
//消息id存入redis中
hashOperations.put("mail_log",msgId,"ok");
//手动确认消息
channel.basicAck(tag,false);
} catch (Exception e) {
try {
/**
* 手动确认
* requeue 是否重回队列
*/
channel.basicNack(tag,false,true);
} catch (IOException ex) {
LOGGER.info("邮件发送失败!!"+ex.getMessage());
}
LOGGER.info("邮件发送失败!!"+e.getMessage());
}
}
}
/**
* @author lsh
*/
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class MailApplication {
public static void main(String[] args) {
SpringApplication.run(MailApplication.class, args);
}
@Bean
public Queue queue(){
return new Queue(MailConstants.MAIL_QUEUE_NAME);
}
}
配置文件:
#rabbitmq配置
rabbitmq:
host: 192.168.10.100
port: 5672
username: guest
password: guest
#消息确认回调
publisher-confirm-type: correlated
#消息失败回调
publisher-returns: true
#redis
redis:
host: 192.168.10.100
port: 6379
database: 0
timeout: 10000ms
password: root
lettuce:
pool:
max-active: 1024
max-wait: 10000ms
max-idle: 200
min-idle: 5
public class MailConstants {
//消息投递中
public static final Integer DELIVERING = 0;
//消息投成功
public static final Integer SUCCESS = 1;
//消息投递失败
public static final Integer FAILURE = 2;
//最大重试次数
public static final Integer MAX_TRY_COUNT = 2;
//消息超时时间
public static final Integer MSG_TIMEOUT = 1;
//队列
public static final String MAIL_QUEUE_NAME = "mail.queue";
//交换机
public static final String MAIL_EXCHANGE_NAME = "mail.exchange";
//路由键
public static final String MAIL_ROUTING_KEY_NAME = "mail.routing.key";
}
//消息落库
MailLog mailLog = new MailLog();
String msgId = UUID.randomUUID().toString();
mailLog.setMsgId(msgId);
mailLog.setEid(employee.getId());
mailLog.setStatus(MailConstants.DELIVERING);
mailLog.setTryTime(LocalDateTime.now().
plusMinutes(MailConstants.MSG_TIMEOUT));
mailLog.setCount(0);
mailLog.setExchange(MailConstants.MAIL_EXCHANGE_NAME);
mailLog.setRouteKey(MailConstants.MAIL_ROUTING_KEY_NAME);
mailLog.setCreateTime(LocalDateTime.now());
mailLog.setUpdateTime(LocalDateTime.now());
AssertUtil.isTrue(!mailLogService.save(mailLog) ,"落库失败");;
EmpInfo empInfo = employeeService.queryEmpInfoById(employee.getId());
//发送rabbitmq信息
rabbitTemplate.convertAndSend(MailConstants.MAIL_EXCHANGE_NAME,
MailConstants.MAIL_ROUTING_KEY_NAME
,empInfo,new CorrelationData(msgId));
rabbitmq配置文件:消息回调
@Component
public class RabbitMQConfig {
private static final Logger LOGGER= LoggerFactory.getLogger(RabbitMQConfig.class);
@Autowired
private CachingConnectionFactory cachingConnectionFactory;
@Autowired
private IMailLogService mailLogService;
@Bean
public RabbitTemplate rabbitTemplate(){
RabbitTemplate rabbitTemplate = new RabbitTemplate(cachingConnectionFactory);
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
/**
* 消息确认回调
* @param data 消息唯一标识
* @param ack 确认结果
* @param cause 失败原因
*/
@Override
public void confirm(CorrelationData data, boolean ack, String cause) {
String msgId = data.getId();
if (ack){
LOGGER.info("{}======>消息发送成功",msgId);
mailLogService.update(new UpdateWrapper<MailLog>()
.set("status",MailConstants.SUCCESS)
.eq("msgId",msgId));
}else{
LOGGER.error("{}======>消息发送失败",msgId);
}
}
});
rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
/**
* 消息失败回调
* @param message 消息主题
* @param replyCode 响应码
* @param replyText 响应描述
* @param exchange 交换机
* @param routingKey 路由键
*/
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
LOGGER.error("{}======>消息发送queue时失败",message.getBody());
}
});
return rabbitTemplate;
}
@Bean
public Queue queue(){
return new Queue(MailConstants.MAIL_QUEUE_NAME);
}
@Bean
public DirectExchange directExchange(){
return new DirectExchange(MailConstants.MAIL_EXCHANGE_NAME);
}
@Bean
public Binding binding(){
return BindingBuilder.bind(queue()).to(directExchange()).with(MailConstants.MAIL_ROUTING_KEY_NAME);
}
}
定时任务:
@Component
public class MailTask {
@Autowired
private IMailLogService mailLogService;
@Autowired
private EmployeeServiceImpl employeeService;
@Autowired
private RabbitTemplate rabbitTemplate;
@Scheduled(cron = "0/10 * * * * ?")
public void mailTask(){
List<MailLog> list = mailLogService.list(new QueryWrapper<MailLog>()
.eq("status", MailConstants.DELIVERING)
.lt("tryTime", LocalDateTime.now()));
//遍历要去发的信息,并且重试的时间到了
list.forEach(mailLog -> {
String msgId = mailLog.getMsgId();
//重试次数超过最大次数
if (mailLog.getCount()>= MailConstants.MAX_TRY_COUNT) {
mailLogService.update(new UpdateWrapper<MailLog>()
.eq("msgId",msgId)
.set("status",MailConstants.FAILURE)
.set("updateTime",LocalDateTime.now()));
}
mailLogService.update(new UpdateWrapper<MailLog>()
.eq("msgId",msgId)
.set("count",mailLog.getCount()+1)
.set("updateTime",LocalDateTime.now())
.set("tryTime",LocalDateTime.now().plusMinutes(MailConstants.MSG_TIMEOUT)));
EmpInfo empInfo = employeeService.queryEmpInfoById(mailLog.getEid());
//发送信息
rabbitTemplate.convertAndSend(MailConstants.MAIL_EXCHANGE_NAME,MailConstants.MAIL_ROUTING_KEY_NAME
,empInfo,new CorrelationData(msgId));
});
}
}
发送端可靠性发送:
使用消息落库的方式,将消息保存到数据库中,然后使用消息回调的机制,改变消息的状态,然后再使用定时任务,将需要重新发的信息进行重发,并改变其重发次数。
接收端幂等性判断:
接收信息的时候得到msgId,发送成功时,将msgId存入redis中,在此接收是需要判断该信息是否在redis中,如果在,则将该信息丢弃,或者重新回到队列
手动确认:
在逻辑代码执行后,在回复ack给消息中间件,保证了消息的正确消费
自动确认:
不建议使用,接受一个消息,自动向消息中间件确认了这个消息被消费了,而现实中就有可能消费者并没有正确消费,或者后续处理有异常导致此消息消费失败