本文已参与「新人创作礼」活动,一起开启掘金创作之路。
近期做一个项目中遇到需要使用多个邮箱以负载均衡的方式发邮件(单线程多账号发邮件),遇到一些问题在此做一个记录,主要解决 “501 mail from address must be same as authorization user” 这个错误。
场景
有A、B、C三个邮件账户,由于发信量限制原因,程序需要做到在每次向用户发邮件时,交替使用这三个邮箱,实际就是一种简单的负载均衡轮询。我将A、B、C三个账户设计为互为主备的策略,每次发信失败后,会自动切换为另外两个邮箱发信,都失败时,才会放弃发信,同时保存失败记录。
遇到的问题
切换到单个邮箱发信时,可以正常发送。但是,我将另外两个邮件账户的密码故意设置错误,然后再发信,却提示以下错误:
com.sun.mail.smtp.SMTPSendFailedException: 501 mail from address must be same as authorization user
解决办法
通过搜索资料,发现这是邮件会话设置的问题,是由于获取 Session 时的账号和 Message 中设置的邮箱地址 setFrom 不一致引起的,因此我们需要在获取邮件会话时,不能使用默认的 Session.getDefaultInstance,而是要使用 Session.getInstance,具体请参考以下代码片段:
/**
* 发送邮件
*/
public void 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.getDefaultInstance)
Session session = Session.getDefaultInstance(prop, new MyAuthenticator(ACCOUNT, PASSWORD));
// 开启调试模式(生产环境中请不要开启此项)
session.setDebug(true);
try {
MimeMessage mimeMessage = new MimeMessage(session);
// 设置发件人别名(如果未设置别名就默认为发件人邮箱)
if (fromAliasName != null && !fromAliasName.trim().isEmpty()) {
mimeMessage.setFrom(new InternetAddress(ACCOUNT, fromAliasName));
}
// 设置主题和收件人、发信时间等信息
mimeMessage.addRecipient(Message.RecipientType.TO, new InternetAddress(to));
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 {
mimeMessage.setText(content);
}
// 开始发信
mimeMessage.saveChanges();
Transport.send(mimeMessage);
} catch (MessagingException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
其他备注
使用多个邮箱发信时,不要在发信代码中 try catch 任何异常,而是将所有的异常抛出,让上一层调用者处理,这样才能够实现多账户交替发信,出错时切换其他账号发信的功能。