提示:使用任何邮箱都一样,这里使用qq邮箱进行测试,获取授权码的过程也大同小异。
1.引入rabbitmq,redis,mail依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starteramqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-startermail</artifactId>
</dependency>
2.配置文件(yml与properties均可)
注意:使用到的各种参数按照自己的实际情况填写即可,一定要开启rabbitmq的发布确认模式CORRELATED值是发布消息成功到交换器后会触发回调方法。
注意:我按照跟人的开发习惯把发送邮件的模块抽出来了,方便以后使用,下面是mail的配置。不同邮箱的端口是不一样的 感兴趣的可以去了解一下
3.代码展示
3.1:配置rabbitmq,为了保证邮件百分百被发送开启手动确认
@Configuration
public class RabbitMqConfig {
private static final Logger log = LoggerFactory.getLogger(AFRabbitMqConfig.class);
@Autowired
private MailSendLogService mailSendLogService;
@Autowired
private CachingConnectionFactory cachingConnectionFactory;
@Bean
Queue mainQuene(){
return new Queue(MailConstants.MAIL_QUEUE_NAME,true);
}
@Bean
DirectExchange mailExchange(){
return new DirectExchange(MailConstants.MAIL_EXCHANGE_NAME, true, false);
}
@Bean
Binding mailBind(){
return BindingBuilder.bind(mainQuene()).to(mailExchange()).with(MailConstants.MAIL_ROUTING_KEY_NAME);
}
@Bean
RabbitTemplate rabbitTemplate() {
RabbitTemplate rabbitTemplate = new RabbitTemplate(cachingConnectionFactory);
//邮件发送失败监控
rabbitTemplate.setConfirmCallback((data, ack, cause) -> {
String msgId = data.getId();
if (ack) {
log.info("邮件发送成功" + msgId);
mailSendLogService.updateMailSendLogStatus(msgId, 1);
} else {
log.info("邮件发送失败" + msgId);
}
});
//rabbit自身错误监控
rabbitTemplate.setReturnCallback((msg, repCode, repText, exchange, routingkey) -> {
log.info("Rabblimq自身原因邮件发送失败" + msg);
});
return rabbitTemplate;
}
}
3.2:创建邮件发送日志表,记录发送情况,并创建实体类
CREATE TABLE `mail_send_log` (
`msgId` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
`empId` int(11) DEFAULT NULL,
`status` int(11) DEFAULT 0 COMMENT '0发送中,1发送成功,2发送失败',
`routeKey` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
`exchange` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
`count` int(11) DEFAULT NULL COMMENT '重试次数',
`tryTime` date DEFAULT NULL COMMENT '第一次重试时间',
`createTime` date DEFAULT NULL,
`updateTime` date DEFAULT NULL
)
public class MailSendLog {
private String msgId;
private Integer empId;
//0 消息投递中 1 投递成功 2投递失败
private Integer status;
private String routeKey;
private String exchange;
private Integer count;
private Date tryTime;
private Date createTime;
private Date updateTime;
public String getMsgId() {
return msgId;
}
public void setMsgId(String msgId) {
this.msgId = msgId;
}
public Integer getEmpId() {
return empId;
}
public void setEmpId(Integer empId) {
this.empId = empId;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public String getRouteKey() {
return routeKey;
}
public void setRouteKey(String routeKey) {
this.routeKey = routeKey;
}
public String getExchange() {
return exchange;
}
public void setExchange(String exchange) {
this.exchange = exchange;
}
public Integer getCount() {
return count;
}
public void setCount(Integer count) {
this.count = count;
}
public Date getTryTime() {
return tryTime;
}
public void setTryTime(Date tryTime) {
this.tryTime = tryTime;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
public Date getUpdateTime() {
return updateTime;
}
public void setUpdateTime(Date updateTime) {
this.updateTime = updateTime;
}
}
3.3:生产者发送邮件
//开始发送邮件
MailSendLog mailSendLog = new MailSendLog();
mailSendLog.setEmpId(emp.getId());
String msgId = UUID.randomUUID().toString().replaceAll("-", "");
mailSendLog.setMsgId(msgId);
mailSendLog.setCreateTime(new Date());
mailSendLog.setExchange(MailConstants.MAIL_EXCHANGE_NAME);
mailSendLog.setRouteKey(MailConstants.MAIL_ROUTING_KEY_NAME);
//默认重试时间为一分钟以后
mailSendLog.setTryTime(new Date(System.currentTimeMillis() + 1000 * 60));
mailSendLogService.insert(mailSendLog);
//发给rabbitmq CorrelationData为唯一标识
rabbitTemplate.convertAndSend(MailConstants.MAIL_EXCHANGE_NAME, MailConstants.MAIL_ROUTING_KEY_NAME
, new CorrelationData(msgId));
3.4:消费者进行邮件处理,发送还使用了thymleaf,比较简单,就是简单的内容填充,这里不再过多赘述
@Autowired
JavaMailSender javaMailSender;
@Autowired
MailProperties mailProperties;
@Autowired
TemplateEngine templateEngine;
@Autowired
StringRedisTemplate redisTemplate;
@RabbitListener(queues = MailConstants.MAIL_QUEUE_NAME)
public void handler(Message message, Channel channel) throws IOException {
MessageHeaders headers = message.getHeaders();
Long tag = (Long) headers.get(AmqpHeaders.DELIVERY_TAG);
String msgId = (String) headers.get("spring_returned_message_correlation");
//解决幂等性
if (redisTemplate.opsForHash().entries("mail_log").containsKey(msgId)) {
//redis 中包含该 key,说明该消息已经被消费过
logger.info(msgId + ":消息已经被消费");
channel.basicAck(tag, false);//确认消息已消费
return;
}
//收到消息,发送邮件
MimeMessage msg = javaMailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(msg);
try {
helper.setTo("收件人");
helper.setFrom("发件人");
helper.setSubject("测试主题");
helper.setSentDate(new Date());
Context context = new Context();
context.setVariable("title", "标题");
context.setVariable("content", "内容");
//填充模板内容
String mail = templateEngine.process("mail", context);
helper.setText(mail, true);
javaMailSender.send(msg);
redisTemplate.opsForHash().put("mail_log", msgId, "mail_log");
//已消费
channel.basicAck(tag, false);
logger.info(msgId + ":邮件发送成功");
} catch (MessagingException e) {
//回到队列里面 等待下一次消费
channel.basicNack(tag, false, true);
e.printStackTrace();
logger.error("邮件发送失败:" + e.getMessage());
}
}
3.5:创建定时对失败发送进行轮循,根据业务需要书写cron表达式,规定轮循三次以后还失败的不再轮询。
@Autowired
MailSendLogService mailSendLogService;
@Autowired
RabbitTemplate rabbitTemplate;
@Scheduled(cron = "*/5 * * * * ?")
public void mailResendTask() {
List<MailSendLog> logs = mailSendLogService.getMailSendLogsByStatus();
if (logs == null || logs.size() == 0) {
return;
}
logs.forEach(mailSendLog -> {
if (mailSendLog.getCount() >= 3) {
mailSendLogService.updateMailSendLogStatus(mailSendLog.getMsgId(), 2);//直接设置该条消息发送失败
} else {
mailSendLogService.updateCount(mailSendLog.getMsgId(), new Date());
rabbitTemplate.convertAndSend(MailConstants.MAIL_EXCHANGE_NAME, MailConstants.MAIL_ROUTING_KEY_NAME, new CorrelationData(mailSendLog.getMsgId()));
}
});
}