最近心血来潮想写一个练手的项目,考虑到异常处理这块,就想着在程序中出错的时候,能够及时的通知我,让我知道是什么时间出现的错误是什么,在找错误的的时候也能快速一点。
1.技术:SPRINGBOOT-boot-start-mail 和 FREEMARKER
spring整合的邮件发送方便简洁,直接在yml中配置即可。 FREEMARKER 在spring项目的 start 中也帮我们进行了定制化的配置,如果没有特殊的需要,可以直接使用。 freemarker整合详情也可自行去官网查看。
spring:
mail:
host: smtp.qq.com
port: 465 // 端口没有特殊配置使用 25
username: 1054656114@qq.com
password: mzbbzkmqulmwbfcc
protocol: smtp
test-connection: true //1.这是一个测试连接是否正常
default-encoding: UTF-8
properties:
mail.smtp.auth: true
mail.smtp.starttls.enable: true
mail.smtp.starttls.required: true
mail.smtp.ssl.enable: true // 2.使用ssl安全传输
mail.display.sendmail: xkcoding
//当1和2同时为true时,并且发送邮件的请求不是https 则会报错
// javax.net.ssl.SSLException: Unrecognized SSL message, plaintext connection?
// javax.mail.MessagingException: Could not connect to SMTP host: smtp.qq.com, port: 587
TIPS:此处使用qq邮箱发送。 在申请授权码的地方能够看到,qq邮箱建议使用端口456和587。这两个端口都不是ssl安全访问的默认端口。
2.配置完成,撸代码
EmailServiceImpl.class
private final FreeMarkerConfigurer freeMarker;
@Resource
private JavaMailSenderImpl javaMailSender;
@Override
public String sendErrorReport() throws IOException {
// 注意:loadConfig方法,就是对javaSender进行 进一步配置。
// 特别说明: 本处是为了不再配置文件中暴露自己邮箱信息,所以使用了JavaMailSender实现类,改接口没有将诸多配置字段暴露出去,只提供发送的方法。
loadConfig();
User user = new User();
user.setId(123L);
user.setUsername("javaboy>>>>" + "123123123123123");
user.setAddress("www.javaboy.org>>>>" + "123123123123123");
MimeMessage message = javaMailSender.createMimeMessage();
MimeMessageHelper helper = null;
try {
//利用 freemarker 获取模板 默认地址 resources/templates.这里使用文件全称
Template template = freeMarker.getConfiguration().getTemplate("email.ftl");
//用产生的值将freemarker模板中的表达式替换
String text = FreeMarkerTemplateUtils.processTemplateIntoString(template, user);
helper = new MimeMessageHelper(message, true);
helper.setFrom("1054656114@qq.com");
helper.setTo("suxingkang@163.com");
helper.setSubject("测试系统错误邮件发送");
// 第一个参数,需要发送的内容。第二个参数,发送的是不是html(默认是false)
helper.setText(text, true);
javaMailSender.send(message);
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
3.发送原理
@Override
public void send(MimeMessage... mimeMessages) throws MailException {
doSend(mimeMessages, null);
}
protected void doSend(MimeMessage[] mimeMessages, @Nullable Object[] originalMessages) throws MailException {
Map<Object, Exception> failedMessages = new LinkedHashMap<>();
Transport transport = null;
try {
for (int i = 0; i < mimeMessages.length; i++) {
// Check transport connection first...
if (transport == null || !transport.isConnected()) {
if (transport != null) {
try {
transport.close();
}
catch (Exception ex) {
// Ignore - we're reconnecting anyway
}
transport = null;
}
try {
// 这一步就是获取连接对象,之前在yml中的properties,在这里会进行配置
transport = connectTransport();
}
catch (AuthenticationFailedException ex) {
throw new MailAuthenticationException(ex);
}
catch (Exception ex) {
// Effectively, all remaining messages failed...
for (int j = i; j < mimeMessages.length; j++) {
Object original = (originalMessages != null ? originalMessages[j] : mimeMessages[j]);
failedMessages.put(original, ex);
}
throw new MailSendException("Mail server connection failed", ex, failedMessages);
}
}
// Send message via current transport...
MimeMessage mimeMessage = mimeMessages[i];
try {
if (mimeMessage.getSentDate() == null) {
mimeMessage.setSentDate(new Date());
}
String messageId = mimeMessage.getMessageID();
mimeMessage.saveChanges();
if (messageId != null) {
// Preserve explicitly specified message id...
mimeMessage.setHeader(HEADER_MESSAGE_ID, messageId);
}
Address[] addresses = mimeMessage.getAllRecipients();
// 这里进行邮件发送
transport.sendMessage(mimeMessage, (addresses != null ? addresses : new Address[0]));
}
catch (Exception ex) {
Object original = (originalMessages != null ? originalMessages[i] : mimeMessage);
failedMessages.put(original, ex);
}
}
}
finally {
try {
// 关闭传输
if (transport != null) {
transport.close();
}
}
catch (Exception ex) {
if (!failedMessages.isEmpty()) {
throw new MailSendException("Failed to close server connection after message failures", ex,
failedMessages);
}
else {
throw new MailSendException("Failed to close server connection after message sending", ex);
}
}
}
if (!failedMessages.isEmpty()) {
throw new MailSendException(failedMessages);
}
}
发短信过程会依赖javax.mail-api.jar,并且会通过 javax.mail.Session 选择不同协议的传输对象(此处com.sun.mail.smtp.SMTPTransport),再其中初始化大量的配置属性(比如:是否启用ssl安全发送,是否使用严格邮箱地址校验等等。)
4.列举properties常见属性及其默认值:
mail.stmp.ssl.enable=false
mail.stmp.quitwait=true
mail.stmp.reportsuccess=false // 在发送成功时抛出异常,因为send 时void 无法获取状态
// 是否必须使用 starttls 命令
// 特别说明:STARTTLS,是一种明文通信协议的扩展,能够让明文的通信连线直接成为加密连线(使用SSL或TLS加密),而不需要使用另一个特别的端口来进行加密通信,属于机会性加密。
mail.stmp.starttls.required=false
// 使用rset而不是noop 对于连接 noop 无操作,服务器但会 ok;rset 重置当前会话
mail.stmp.userset=false
mail.stmp.noop.strict=true //使用严格校验邮箱格式
mail.stmp.sasl.enable=false // 使用 ssl 安全传输模式
mail.stmp.sasl.usecanonicalhostname