Spring boot 发邮件攻略

Spring boot 发邮件攻略

项目场景:

给用户发送带附件的邮件,附件为本地资源或者可访问URL资源。


问题描述:

由于我们是定时任务发送邮件,突然发现发送邮件的线程处于无限等待的状态。导致新申请的邮件一直发送不出去。


原因分析:

通过排查代码发现没有任何地方指定sendAttachmentsMail函数的执行超时时间。

直接上代码。核心代码,可自行封装优化,测试

@Autowired
private JavaMailSender mailSender;
@Autowired
private MailProperties mailProperties;

public boolean sendAttachmentsMail(String to, String subject, String content, String filePath, String fileName) {
        MimeMessage message = mailSender.createMimeMessage();
        try {
            MimeMessageHelper helper = new MimeMessageHelper(message, true);
            // 指定from 获取 配置文件中的userName
            String username = mailProperties.getUsername();

            helper.setFrom(new InternetAddress(username));
            helper.setTo(to);
            helper.setSubject(subject);
            helper.setText(content, true);
            //如果是本地文件 File file = new File("/Users/a.txt");
			//helper.addAttachment(fileName, new FileSystemResource(file));
			
			//远程文件 可访问URL文件 使用getMultipart()
            message.setContent(getMultipart(content, filePath, fileName));
            mailSender.send(message);
            log.info("带附件的邮件发送成功");
            return true;
        } catch (Exception e) {
            log.error("带附件的邮件发送失败,异常信息:", e);
            return false;
        }
    }

/**
     * 获取Multipart对象,包含正文内容、附件、文件名
     */
    private static Multipart getMultipart(String content, String remoteFile, String fileName) throws Exception {
        Multipart multipart = new MimeMultipart();
        // 向multipart中添加正文
        BodyPart contentBodyPart = new MimeBodyPart();
        contentBodyPart.setContent(content, "text/html;charset=UTF-8");
        multipart.addBodyPart(contentBodyPart);

        // 向multipart中添加远程附件
        multipart.addBodyPart(getBodyPart(encodeUri(remoteFile), fileName));

        return multipart;
    }

    /**
     * 对文件fileUrl进行编码 类似于浏览器的encodeURI编码 例子:编码前:http://www.baidu.com/api/resource/robot/word/2/婚前协议.doc
     * 编码后:http://www.baidu.com/api/resource/robot/word/2/%E5%A9%9A%E5%89%8D%E5%8D%8F%E8%AE%AE.doc
     */
    public static String encodeUri(String url) throws UnsupportedEncodingException {
        String encode = URLEncoder.encode(url, "UTF-8");
        return encode.replace("%3A", ":").replace("%2F", "/");
    }

    /**
     * 获取 bodyPart
     *
     * @param path     文件路径
     * @param fileName 文件名称
     */
    public static BodyPart getBodyPart(String path, String fileName) throws Exception {
        BodyPart bodyPart = new MimeBodyPart();
        // 根据附件路径获取文件,
        DataHandler dh = new DataHandler(new URLDataSource(new URL(path)));
        bodyPart.setDataHandler(dh);
        bodyPart.setFileName(fileName);
        return bodyPart;
    }

application.yml

spring: 
 mail:
    protocol: smtps
    port: 465
    host: smtp.mxhichina.com
    username: XXX
    password: XXX
    default-encoding: UTF-8
    properties:
      mail:
        smtp:
          connectiontimeout: 5000
          timeout: 5000
          writetimeout: 5000
          auth: true
          starttls:
            required: true
            enable: true

重点解决方案:

  • connectiontimeout: 5000
  • timeout: 5000
  • writetimeout: 5000

配置文件中的超时时间是重点。以下内容摘自spring 官方文档。

  1. Sending Email
    The Spring Framework provides an easy abstraction for sending email by using the JavaMailSender interface, and Spring Boot provides auto-configuration for it as well as a starter module.

    See the reference documentation.
    for a detailed explanation of how you can use JavaMailSender.

    If spring.mail.host and the relevant libraries (as defined by spring-boot-starter-mail) are available, a default JavaMailSender is created if none exists. The sender can be further customized by configuration items from the spring.mail namespace. See MailProperties for more details.

    In particular, certain default timeout values are infinite, and you may want to change that to avoid having a thread blocked by an unresponsive mail server, as shown in the following example:
spring.mail.properties.mail.smtp.connectiontimeout=5000
spring.mail.properties.mail.smtp.timeout=3000
spring.mail.properties.mail.smtp.writetimeout=5000

In particular, certain default timeout values are infinite
特别是,某些默认超时值是无限的.这是个坑。/糗大了

更新一下:2021-09-14
上面的配置文件不生效:依赖版本如下

<dependency>
      <groupId>org.springframework.bootgroupId>
      <artifactId>spring-boot-starter-mailartifactId>
      <version>2.2.5.RELEASEversion>
dependency>

yaml 配置如下(不生效):

spring: 
   mail:
    protocol: smtps
    port: 465
    host: smtp.mxhichina.com
    username: XXX
    password: XXX
    default-encoding: UTF-8
    properties:
      mail:
        smtp:
          connectiontimeout: 5000
          timeout: 5000
          writetimeout: 5000
          auth: true
          starttls:
            required: true
            enable: true

点击:spring最新文档链接

spring:
 mail:
  properties:
   "[mail.smtp.connectiontimeout]": 5000
   "[mail.smtp.timeout]": 3000
   "[mail.smtp.writetimeout]": 5000

这么写就没问题了。注意依赖版本哦。

mail.properties 在源码中是一个Map 所以在配置文件yml中properties属性都要采用如上配置方式。
package org.springframework.boot.autoconfigure.mail;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

import org.springframework.boot.context.properties.ConfigurationProperties;

/**
 * Configuration properties for email support.
 *
 * @author Oliver Gierke
 * @author Stephane Nicoll
 * @author Eddú Meléndez
 * @since 1.2.0
 */
@ConfigurationProperties(prefix = "spring.mail")
public class MailProperties {

	private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;

	/**
	 * SMTP server host. For instance, `smtp.example.com`.
	 */
	private String host;

	/**
	 * SMTP server port.
	 */
	private Integer port;

	/**
	 * Login user of the SMTP server.
	 */
	private String username;

	/**
	 * Login password of the SMTP server.
	 */
	private String password;

	/**
	 * Protocol used by the SMTP server.
	 */
	private String protocol = "smtp";

	/**
	 * Default MimeMessage encoding.
	 */
	private Charset defaultEncoding = DEFAULT_CHARSET;

	/**
	 * Additional JavaMail Session properties.
	 */
	private Map<String, String> properties = new HashMap<>();

	/**
	 * Session JNDI name. When set, takes precedence over other Session settings.
	 */
	private String jndiName;

你可能感兴趣的:(个人学习笔记,spring,spring,boot,java,email)