最近把项目迁移到阿里云,这几天观察后台日志,发现这么一段异常:
2020-04-09 20:51:39 ERROR MailUtils:228 : 发送邮件失败!com.sun.mail.util.MailConnectException: Couldn't connect to host, port: smtp.163.com, 25; timeout -1;
nested exception is:
java.net.ConnectException: Connection timed out (Connection timed out)
然后去调发送邮件的接口,还真的发不了。刚开始也是觉得很奇怪,之前项目在另一台服务器上运行好好的,在本地也可以正常的,为什么一迁移到阿里云服务器上就不行了呢?在阿里云官网上提交工单,他们的售后工程师说了一堆,为了防止垃圾邮件的泛滥,阿里加入了中国什么反垃圾邮件协会,禁用25端口。并给出两种解决方案:
其一:使用SSL安全端口465发送,
其二,使用他们阿里邮箱,他们将25重定向到80端口了。
果断排除第二种,这一来嘛,公司买了第三方邮箱的服务,未到期,重新买阿里邮箱,会造成资源浪费,这二来嘛,还需要修改各个项目涉及到邮件发送的代码。除了核心业务方面的代码,基础配置的代码我一向懒得去改。
所以就毅然决然踏上使用465端口这条路。首先找到项目中邮件发送的代码,可找来找去,没发现涉及到端口方面的配置,于是联系邮件服务提供方,他们说已经开放465端口了的,不信,你用下面的命令测试一下就知道了:
telnet smtp.163.com 465
打开终端,输入以上命令,确实出来welcome的欢迎语。那说明问题不在他们那,于是又找到阿里,阿里小姐姐可真敬业啊,大中午我在吃着午饭,她打来电话,此时已过北京时间12:30。电话里态度也是很好,说了一大堆,挂了电话一想,她话中“是,邮件提供方开放了465端口,但不代表他就使用默认的465端口来发邮件啊,万一他使用25呢”是解决问题的中心思想呀。又联系邮件服务提供方,他们明确告诉我,他们并不能指定使用哪个端口来发送邮件,使用哪个端口发送邮件在于客户端的设定。转了几圈,才猛地发现,原来需要自己编码手动指定邮件发送所使用的端口,如果不指定端口,那么系统将会使用25端口来发送。于是进行编码和测试,终于通了。下面是实现的源码:
/**
* 使用SSL 465安全方式发送
* @param toEmail 收件人邮箱
* @param content 邮件内容
* @param subject 邮件主题
*/
public static void sendSSLEmail(String toEmail, String content, String subject) {
boolean isSSL = true;
String host = "smtp.163.com";
int port = 465;
final String from = "smtp@163.com"; // 发件人的email
boolean isAuth = true;
final String password = "123456";
Properties props = new Properties();
props.put("mail.smtp.ssl.enable", isSSL); //指定使用SSL方式发送
props.put("mail.smtp.host", host); //邮件主机
props.put("mail.smtp.port", port); //指定使用465安全端口
props.put("mail.smtp.auth", isAuth); //需要验证
Session session = Session.getDefaultInstance(props, new Authenticator() {
/**
* 身份验证
*/
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(from , password);
}
});
/**
* 实现邮件发送功能
*/
try {
Message message = new MimeMessage(session);
message.setFrom(new InternetAddress(from));
message.addRecipient(Message.RecipientType.TO, new InternetAddress(toEmail));
message.setSubject(subject);
message.setText(content);
LOGGER.info("准备发送");
Transport.send(message);
LOGGER.info("发送完毕");
} catch (AddressException e) {
LOGGER.error(e.getMessage(),e);
} catch (MessagingException e) {
LOGGER.error(e.getMessage(),e);
}
}
注:文中涉及到host、账号和密码为虚构,使用时请替换为自己真实的信息。
在实际使用中可能还会遇到这样的异常:
使用465加密端口发邮件时,提示如下信息:
com.sun.mail.smtp.SMTPSendFailedException: 553 authentication is required
at com.sun.mail.smtp.SMTPTransport.issueSendCommand(SMTPTransport.java:1388)
at com.sun.mail.smtp.SMTPTransport.mailFrom(SMTPTransport.java:959)
at com.sun.mail.smtp.SMTPTransport.sendMessage(SMTPTransport.java:583)
at javax.mail.Transport.send0(Transport.java:169)
at javax.mail.Transport.send(Transport.java:98)
解决方法为:
props.put("mail.smtp.auth", "true"); /*是"true"不是true,需重点注意*/
更新到服务器终于能正常发送邮件了。
最近将项目部署到另一台阿里云服务器上,又遇到开头的问题,提示使用25端口号连接不到邮件服务器,可是代码里边明明指定的是465端口。百思不得其解之际,我想到这一行代码:
props.put("mail.smtp.auth", "true"); /*是"true"不是true,需重点注意*/
会不会跟声明的类型有关系呢,上面这行代码编码时写true也能编译通过,但是如果要加这个属性,只能传字符串类型,传boolean型就不可以。于是试着把port的类型由int改成String,重新运行后,终于能发送邮件了。这里我想是不是只能接收字符串类型,传递int类型接收不了,系统当作没传端口号使用默认的25端口号来处理了。