SPRINGBOOT发送邮件出错和总结

SPRINGBOOT发送邮件出错和总结

doMore 988 2019-11-29

最近心血来潮想写一个练手的项目,考虑到异常处理这块,就想着在程序中出错的时候,能够及时的通知我,让我知道是什么时间出现的错误是什么,在找错误的的时候也能快速一点。

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