【JavaMail】JavaMail整合RabbitMq发送邮件案例
前言
Linux安装RabbitMQ:https://www.cnblogs.com/jxd283465/p/11975094.html
SpringBoot整合RabbitMQ:https://www.cnblogs.com/jxd283465/p/11975136.html
流程
代码
数据库表
CREATE TABLE `msg_log` (
`msg_id` varchar(255) NOT NULL DEFAULT '' COMMENT '消息唯一标识',
`msg` text COMMENT '消息体, json格式化',
`exchange` varchar(255) NOT NULL DEFAULT '' COMMENT '交换机',
`routing_key` varchar(255) NOT NULL DEFAULT '' COMMENT '路由键',
`status` int(11) NOT NULL DEFAULT '' COMMENT '状态: 0投递中 1投递成功 2投递失败 3已消费',
`try_count` int(11) NOT NULL DEFAULT '' COMMENT '重试次数',
`next_try_time` datetime DEFAULT NULL COMMENT '下一次重试时间',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`msg_id`),
UNIQUE KEY `unq_msg_id` (`msg_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='消息投递日志';
邮件发送类
package cc.mrbird.febs.common.utils;
/**
* 邮件发送工具类。
* 以下邮件中的配置参数,请在实际环境中,根据需要采取合适的配置方式。
* 发送邮件依赖 com.sun.mail(1.6.1) 包、javax.mail(1.5.0-b01) 包。
* 如果使用 Idea 运行,请将这两个包(可以直接到Maven目录下面去找)添加到项目的 Libraries 里面(快捷键:Ctrl + Alt + Shift + S)
*
* @author Zebe
*/
public class SendEmailUtil { /**
* 发件人别名(可以为空)
*/
private final static String fromAliasName = "***"; /**
* 登录用户名
*/
private String ACCOUNT; /**
* 登录密码
*/
private String PASSWORD; /**
* 邮件服务器地址
*/
//QQ企业邮箱:smtp.exmail.qq.com
//网易企业邮箱:smtphz.qiye.163.com
private String HOST; /**
* 发信端口
*/
//QQ企业邮箱:465
//网易企业邮箱:994
private String PORT; /**
* 发信协议
*/
private final static String PROTOCOL = "ssl"; /**
* 收件人
*/
private String to; /**
* 收件人名称
*/
private String toName; /**
* 主题
*/
private String subject; /**
* 内容
*/
private String content; /**
* 附件列表(可以为空)
*/
private List<String> attachFileList; /**
* 构造器
*
* @param attachFileList 附件列表
*/
public SendEmailUtil(MailTemplate mailTemplate, List<String> attachFileList) {
this.to = mailTemplate.getTo();
this.toName = mailTemplate.getToName();
this.subject = mailTemplate.getSubject();
this.content = mailTemplate.getContent();
this.attachFileList = attachFileList;
this.ACCOUNT = mailTemplate.getAccount();
this.PASSWORD = mailTemplate.getPassword();
switch (mailTemplate.getSendType()) {
case "qq":
this.HOST = "smtp.exmail.qq.com";
this.PORT = "465";
break;
case "163":
this.HOST = "smtp.ym.163.com";
this.PORT = "994";
break;
}
} /**
* 认证信息
*/
static class MyAuthenticator extends Authenticator { /**
* 用户名
*/
String username = null; /**
* 密码
*/
String password = null; /**
* 构造器
*
* @param username 用户名
* @param password 密码
*/
public MyAuthenticator(String username, String password) {
this.username = username;
this.password = password;
} @Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(username, password);
}
} /**
* 发送邮件
*/
public boolean send() {
// 设置邮件属性
Properties prop = new Properties();
prop.setProperty("mail.transport.protocol", PROTOCOL);
prop.setProperty("mail.smtp.host", HOST);
prop.setProperty("mail.smtp.port", PORT);
prop.setProperty("mail.smtp.auth", "true");
MailSSLSocketFactory sslSocketFactory = null;
try {
sslSocketFactory = new MailSSLSocketFactory();
sslSocketFactory.setTrustAllHosts(true);
} catch (GeneralSecurityException e1) {
e1.printStackTrace();
}
if (sslSocketFactory == null) {
System.err.println("开启 MailSSLSocketFactory 失败");
} else {
prop.put("mail.smtp.ssl.enable", "true");
prop.put("mail.smtp.ssl.socketFactory", sslSocketFactory);
// 创建邮件会话(注意,如果要在一个进程中切换多个邮箱账号发信,应该用 Session.getInstance)
Session session = Session.getDefaultInstance(prop, new MyAuthenticator(ACCOUNT, PASSWORD));
// 开启调试模式(生产环境中请不要开启此项)
session.setDebug(true);
try {
MimeMessage mimeMessage = new MimeMessage(session);
// 设置发件人别名(如果未设置别名就默认为发件人邮箱)
mimeMessage.setFrom(new InternetAddress(ACCOUNT, fromAliasName));
// 设置主题和收件人、发信时间等信息
mimeMessage.addRecipient(Message.RecipientType.TO, new InternetAddress(to, toName));
mimeMessage.setSubject(subject);
mimeMessage.setSentDate(new Date());
// 如果有附件信息,则添加附件
if (!attachFileList.isEmpty()) {
Multipart multipart = new MimeMultipart();
MimeBodyPart body = new MimeBodyPart();
body.setContent(content, "text/html; charset=UTF-8");
multipart.addBodyPart(body);
// 添加所有附件(添加时判断文件是否存在)
for (String filePath : attachFileList) {
if (Files.exists(Paths.get(filePath))) {
MimeBodyPart tempBodyPart = new MimeBodyPart();
tempBodyPart.attachFile(filePath);
multipart.addBodyPart(tempBodyPart);
}
}
mimeMessage.setContent(multipart);
} else {
Multipart multipart = new MimeMultipart();
MimeBodyPart body = new MimeBodyPart();
body.setContent(content, "text/html; charset=UTF-8");
multipart.addBodyPart(body);
mimeMessage.setContent(multipart);
//mimeMessage.setText(content);
}
// 开始发信
mimeMessage.saveChanges();
Transport.send(mimeMessage);
return true;
} catch (MessagingException | IOException e) {
e.printStackTrace();
return false;
}
}
return false;
}
}
邮件模板
@Data
@NoArgsConstructor
public class MailTemplate implements Serializable { private String msgId;
/**
* 收件人
*/
private String to; /**
* 收件人名称
*/
private String toName; /**
* 主题
*/
private String subject; /**
* 内容
*/
private String content; /**
* 附件列表
*/
private List<String> attachFileList; /**
* 邮箱账号
*/
private String account; /**
* 邮箱密码
*/
private String password; /**
* 邮箱类型
*/
private String sendType; /**
* 构造器
*
* @param to 收件人
* @param subject 主题
* @param content 内容
*/
public MailTemplate(String account, String password, String sendType, String to, String toName, String subject, String content) {
this.account = account;
this.password = password;
this.sendType = sendType;
this.to = to;
this.toName = toName;
this.subject = subject;
this.content = content;
} /**
* 构造器
*
* @param to 收件人
* @param subject 主题
* @param content 内容
* @param attachFileList 附件列表
*/
public MailTemplate(String account, String password, String sendType, String to, String toName, String subject, String content, List<String> attachFileList) {
this(account, password, sendType, to, toName, subject, content);
this.attachFileList = attachFileList;
}
}
rabbit mq配置类
@Configuration
@Slf4j
public class RabbitConfig { // 发送邮件
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";
public final static Integer MAIL_DELIVER_SUCCESS = 1;
public final static Integer MAIL_DELIVER_FAIL = 2;
public final static Integer MAIL_CONSUMED_SUCCESS = 3;
public static boolean ENABLE_SCHEDULED = false;
private final CachingConnectionFactory connectionFactory; @Autowired
private IMsgLogService iMsgLogService; public RabbitConfig(CachingConnectionFactory connectionFactory) {
this.connectionFactory = connectionFactory;
} @Bean
public RabbitTemplate rabbitTemplate() {
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
rabbitTemplate.setMessageConverter(converter()); // 消息是否成功发送到Exchange
rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
if (ack) {
log.info("消息成功发送到Exchange");
String msgId = correlationData.getId();
UpdateWrapper<MsgLog> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("msg_id",msgId);
MsgLog msgLog = new MsgLog();
msgLog.setStatus(MAIL_DELIVER_SUCCESS);
iMsgLogService.update(msgLog, updateWrapper);
} else {
log.info("消息发送到Exchange失败, {}, cause: {}", correlationData, cause);
}
}); // 触发setReturnCallback回调必须设置mandatory=true, 否则Exchange没有找到Queue就会丢弃掉消息, 而不会触发回调
rabbitTemplate.setMandatory(true);
// 消息是否从Exchange路由到Queue, 注意: 这是一个失败回调, 只有消息从Exchange路由到Queue失败才会回调这个方法
rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> {
log.info("消息从Exchange路由到Queue失败: exchange: {}, route: {}, replyCode: {}, replyText: {}, message: {}", exchange, routingKey, replyCode, replyText, message);
}); return rabbitTemplate;
} @Bean
public Jackson2JsonMessageConverter converter() {
return new Jackson2JsonMessageConverter();
} @Bean
public Queue mailQueue() {
return new Queue(MAIL_QUEUE_NAME, true);
} @Bean
public DirectExchange mailExchange() {
return new DirectExchange(MAIL_EXCHANGE_NAME, true, false);
} @Bean
public Binding mailBinding() {
return BindingBuilder.bind(mailQueue()).to(mailExchange()).with(MAIL_ROUTING_KEY_NAME);
} }
生产者
@RestController
@RequestMapping("mail")
public class MailController { @Autowired
private IMsgLogService iMsgLogService;
@Autowired
private RabbitTemplate rabbitTemplate; @PostMapping("/test")
public void test(String account, String password, String sendType) {
try {
for (int j = 1; j <= 3; j++) {
for (int i = 1; i <= 15; i++) {
for (int num = 1; num <= 73; num++) {
// 设置发信参数
final String toName = "我是" + num + "号";
final String to = "test" + num + "@forexgwg.com";
String subject = num + " 第" + num + "次发送测试邮件标题";
final String content = "<p style='color:red'>这是邮件内容正文。</p></br>";
MailTemplate mailTemplate = new MailTemplate();
String msgId = UUID.randomUUID().toString();
mailTemplate.setMsgId(msgId);
mailTemplate.setAccount(account);
mailTemplate.setPassword(password);
mailTemplate.setSendType(sendType);
mailTemplate.setToName(toName);
mailTemplate.setTo(to);
mailTemplate.setSubject(subject);
mailTemplate.setContent(content);
mailTemplate.setAttachFileList(new ArrayList<>()); MsgLog msgLog = new MsgLog(msgId, JSON.toJSONString(mailTemplate), RabbitConfig.MAIL_EXCHANGE_NAME, RabbitConfig.MAIL_ROUTING_KEY_NAME, LocalDateTime.now());
iMsgLogService.save(msgLog);
CorrelationData correlationData = new CorrelationData(msgId);
Thread.sleep(1000);
rabbitTemplate.convertAndSend(RabbitConfig.MAIL_EXCHANGE_NAME, RabbitConfig.MAIL_ROUTING_KEY_NAME, JSON.toJSONString(mailTemplate), correlationData);// 发送消息
}
}
}
RabbitConfig.ENABLE_SCHEDULED = true;
} catch (Exception e) {
System.out.println("错误: " + e);
}
}
}
消费者
@Component
@Slf4j
public class MailConsumer { @Autowired
private IMsgLogService iMsgLogService; @RabbitListener(queues = RabbitConfig.MAIL_QUEUE_NAME)
public void consume(Message message, Channel channel) throws IOException {
String msg = new String(message.getBody());
msg = msg.replaceAll("\\\\", "");
msg = msg.substring(1, msg.length() - 1);
MailTemplate mailTemplate = JSON.parseObject(msg, MailTemplate.class);
log.info("收到消息: {}", mailTemplate.toString()); String msgId = mailTemplate.getMsgId();
QueryWrapper<MsgLog> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("msg_id", msgId);
UpdateWrapper<MsgLog> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("msg_id", msgId);
MsgLog msgLog = iMsgLogService.getOne(queryWrapper); // 消费幂等性
if (null == msgLog || msgLog.getStatus().equals(RabbitConfig.MAIL_CONSUMED_SUCCESS)) {
log.info("重复消费, msgId: {}", msgId);
return;
} msgLog.setStatus(3);
msgLog.setUpdateTime(LocalDateTime.now());
iMsgLogService.update(msgLog, updateWrapper); MessageProperties properties = message.getMessageProperties();
long tag = properties.getDeliveryTag(); boolean success = new SendEmailUtil(mailTemplate, new ArrayList<>()).send();
if (success) {
msgLog.setStatus(RabbitConfig.MAIL_CONSUMED_SUCCESS);
msgLog.setUpdateTime(LocalDateTime.now());
iMsgLogService.update(msgLog, updateWrapper);
log.info("消费成功!");
channel.basicAck(tag, false);// 消费确认
} else {
channel.basicNack(tag, false, true);
}
}
}
重新发送
@Component
@Slf4j
public class ResendMsg { @Autowired
private IMsgLogService iMsgLogService; @Autowired
private RabbitTemplate rabbitTemplate; // 最大投递次数
private static final int MAX_TRY_COUNT = 3; /**
* 每30s拉取投递失败的消息, 重新投递
*/
@Scheduled(cron = "0 0/1 * * * ?")
public void resend() {
log.info("开始执行定时任务(重新投递消息)");
UpdateWrapper<MsgLog> updateWrapper = new UpdateWrapper<>();
QueryWrapper<MsgLog> queryWrapper = new QueryWrapper<>();
queryWrapper.ne("status", RabbitConfig.MAIL_DELIVER_FAIL).ne("status", RabbitConfig.MAIL_CONSUMED_SUCCESS); List<MsgLog> msgLogs = iMsgLogService.list(queryWrapper);
if (msgLogs.size() == 0){
RabbitConfig.ENABLE_SCHEDULED = false;
}
msgLogs.forEach(msgLog -> {
String msgId = msgLog.getMsgId();
updateWrapper.eq("msg_id", msgId);
if (msgLog.getTryCount() >= MAX_TRY_COUNT) {
msgLog.setStatus(RabbitConfig.MAIL_DELIVER_FAIL);
msgLog.setUpdateTime(LocalDateTime.now());
iMsgLogService.update(msgLog, updateWrapper);
log.info("超过最大重试次数, 消息投递失败, msgId: {}", msgId);
} else {
msgLog.setTryCount(msgLog.getTryCount() + 1);
msgLog.setUpdateTime(LocalDateTime.now());
msgLog.setNextTryTime(LocalDateTime.now().plusSeconds(60));
iMsgLogService.update(msgLog, updateWrapper);// 投递次数+1 CorrelationData correlationData = new CorrelationData(msgId);
rabbitTemplate.convertAndSend(msgLog.getExchange(), msgLog.getRoutingKey(), msgLog.getMsg(), correlationData);// 重新投递
log.info("第 " + (msgLog.getTryCount() + 1) + " 次重新投递消息");
}
});
log.info("定时任务执行结束(重新投递消息)");
} }
使用不同的兩個帳戶发送email时,第一个账户可以发送成功,但到第二个账户的时候就报出了501 mail from address must be same as authorization user的错误。
Session session = Session.getDefaultInstance(props, auth);
以上改成
Session session = Session.getInstance(props, auth);
【JavaMail】JavaMail整合RabbitMq发送邮件案例的更多相关文章
- JavaWeb学习总结(五十二)——使用JavaMail创建邮件和发送邮件
一.RFC882文档简单说明 RFC882文档规定了如何编写一封简单的邮件(纯文本邮件),一封简单的邮件包含邮件头和邮件体两个部分,邮件头和邮件体之间使用空行分隔. 邮件头包含的内容有: from字段 ...
- (转载)JavaWeb学习总结(五十二)——使用JavaMail创建邮件和发送邮件
博客源地址:http://www.cnblogs.com/xdp-gacl/p/4216311.html 一.RFC882文档简单说明 RFC882文档规定了如何编写一封简单的邮件(纯文本邮件),一封 ...
- spring利用javamail,quartz定时发送邮件 <转>
原文地址:spring利用javamail,quartz定时发送邮件 <转>作者:物是人非 spring提供的定时发送邮件功能,下面是一个简单的例子以供大家参考,首先从spring配置文件 ...
- 使用JavaMail创建邮件和发送邮件
参考https://www.cnblogs.com/xdp-gacl/p/4216311.html,写的真好,知识在于分享,备份留着看 一.RFC882文档简单说明 RFC882文档规定了如何编写一封 ...
- JavaMail实现邮箱之间发送邮件功能
package com.minstone.message.util; import java.util.Date; import java.util.Properties; import javax. ...
- 一篇学习完rabbitmq基础知识,springboot整合rabbitmq
一 rabbitmq 介绍 MQ全称为Message Queue,即消息队列, RabbitMQ是由erlang语言开发,基于AMQP(Advanced MessageQueue 高级消息队列协议 ...
- Spring Boot 整合 rabbitmq
一.消息中间件的应用场景 异步处理 场景:用户注册,信息写入数据库后,需要给用户发送注册成功的邮件,再发送注册成功的邮件. 1.同步调用:注册成功后,顺序执行发送邮件方法,发送短信方法,最后响应用户 ...
- java框架之SpringBoot(12)-消息及整合RabbitMQ
前言 概述 大多数应用中,可通过消息服务中间件来提升系统异步通信.扩展解耦的能力. 消息服务中两个重要概念:消息代理(message broker)和目的地(destination).当消息发送者发送 ...
- Spring Boot (5) 整合 RabbitMQ
一.前言 RabbitMQ是实现了AMQP(高级消息队列协议)的开源消息中间件,RabbitMQ服务器是用Erlang(面向并发的编程语言)编写的. RabbitMQ官网下载地址:https://ww ...
随机推荐
- Our growth depends not on how many experiences we devour, but on how manywe digest.
rot. v/n. 腐烂 vibration.n. 震动 charcoal. n 木炭 wrinkle. v. 长皱纹 geometry. n. 几何学 walnut.n. 核桃 tailor. n. ...
- 005 gcc 的简单使用
0. 前言 本文主要讲关于 gcc 的几种编译方式 不妨设文件名为 test.c 1. 方法一 $ gcc test.c (Windows OS)编译成功的话,没有回馈,在 test.c 所在的文件夹 ...
- 【转】MySQL查询缓存详解
[转]MySQL查询缓存详解 转自:https://www.cnblogs.com/Alight/p/3981999.html 相关文章:http://www.zsythink.net/archive ...
- 【烦人的字符集】linux字符集问题,中文乱码
[1]快速修改命令 [2]locale 查看现在服务器的字符 [root@Master ~]# localeLANG=en_US.UTF-8LC_CTYPE="zh_CN.UTF-8&quo ...
- 【JZOJ 3910】Idiot 的间谍网络
题面: Description 作为一名高级特工,Idiot 苦心经营多年,终于在敌国建立起一张共有n 名特工的庞大间谍网络. 当然,出于保密性的要求,间谍网络中的每名特工最多只会有一名直接领导.现在 ...
- P1168 中位数 堆
题目描述 给出一个长度为NN的非负整数序列A_iAi,对于所有1 ≤ k ≤ (N + 1) / 21≤k≤(N+1)/2,输出A_1, A_3, …, A_{2k - 1}A1,A3,…,A2 ...
- php配置伪静态如何将.htaccess文件转换 nginx伪静态文件
php通常设置伪静态三种情况,.htaccess文件,nginx伪静态文件,Web.Config文件得形式,如何将三种伪静态应用到项目中呢, 1,.htaccess文件 实例 <IfModule ...
- spark 在启动的时候出现JAVA_HOME not set
解决方法:在sbin目录下的spark-config.sh 中添加对应的jdk 路径,然后使用scp -r 命令复制到各个worker节点
- 值不能为null.参数名: viewInfo,如何解决
有蓝队网络服务器租用客户反映在一台服务器上使用数据库管理工具时弹出了如下错误 :值不能为null.参数名: viewInfo (Microsoft.SqlServer.Management.SqlSt ...
- nsswitch.conf - 系统数据库及名字服务开关配置文件
DESCRIPTION C 程序库里很多函数都需要配置以便能在本地环境正常工作, 习惯上是使用文件(例如`/etc/passwd') 来完成这一任务. 但别的名字服务, 如网络信息服务NIS, 还有域 ...