ソースを参照

update 优化 邮件工具代码格式

疯狂的狮子li 2 年 前
コミット
44a796ed57

+ 31 - 32
ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/GlobalMailAccount.java

@@ -6,42 +6,41 @@ import cn.hutool.core.io.IORuntimeException;
  * 全局邮件帐户,依赖于邮件配置文件{@link MailAccount#MAIL_SETTING_PATHS}
  *
  * @author looly
- *
  */
 public enum GlobalMailAccount {
-	INSTANCE;
+    INSTANCE;
 
-	private final MailAccount mailAccount;
+    private final MailAccount mailAccount;
 
-	/**
-	 * 构造
-	 */
-	GlobalMailAccount() {
-		mailAccount = createDefaultAccount();
-	}
+    /**
+     * 构造
+     */
+    GlobalMailAccount() {
+        mailAccount = createDefaultAccount();
+    }
 
-	/**
-	 * 获得邮件帐户
-	 *
-	 * @return 邮件帐户
-	 */
-	public MailAccount getAccount() {
-		return this.mailAccount;
-	}
+    /**
+     * 获得邮件帐户
+     *
+     * @return 邮件帐户
+     */
+    public MailAccount getAccount() {
+        return this.mailAccount;
+    }
 
-	/**
-	 * 创建默认帐户
-	 *
-	 * @return MailAccount
-	 */
-	private MailAccount createDefaultAccount() {
-		for (String mailSettingPath : MailAccount.MAIL_SETTING_PATHS) {
-			try {
-				return new MailAccount(mailSettingPath);
-			} catch (IORuntimeException ignore) {
-				//ignore
-			}
-		}
-		return null;
-	}
+    /**
+     * 创建默认帐户
+     *
+     * @return MailAccount
+     */
+    private MailAccount createDefaultAccount() {
+        for (String mailSettingPath : MailAccount.MAIL_SETTING_PATHS) {
+            try {
+                return new MailAccount(mailSettingPath);
+            } catch (IORuntimeException ignore) {
+                //ignore
+            }
+        }
+        return null;
+    }
 }

+ 83 - 82
ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/InternalMailUtil.java

@@ -13,95 +13,96 @@ import java.util.List;
 
 /**
  * 邮件内部工具类
+ *
  * @author looly
  * @since 3.2.3
  */
 public class InternalMailUtil {
 
-	/**
-	 * 将多个字符串邮件地址转为{@link InternetAddress}列表<br>
-	 * 单个字符串地址可以是多个地址合并的字符串
-	 *
-	 * @param addrStrs 地址数组
-	 * @param charset 编码(主要用于中文用户名的编码)
-	 * @return 地址数组
-	 * @since 4.0.3
-	 */
-	public static InternetAddress[] parseAddressFromStrs(String[] addrStrs, Charset charset) {
-		final List<InternetAddress> resultList = new ArrayList<>(addrStrs.length);
-		InternetAddress[] addrs;
-		for (String addrStr : addrStrs) {
-			addrs = parseAddress(addrStr, charset);
-			if (ArrayUtil.isNotEmpty(addrs)) {
-				Collections.addAll(resultList, addrs);
-			}
-		}
-		return resultList.toArray(new InternetAddress[0]);
-	}
+    /**
+     * 将多个字符串邮件地址转为{@link InternetAddress}列表<br>
+     * 单个字符串地址可以是多个地址合并的字符串
+     *
+     * @param addrStrs 地址数组
+     * @param charset  编码(主要用于中文用户名的编码)
+     * @return 地址数组
+     * @since 4.0.3
+     */
+    public static InternetAddress[] parseAddressFromStrs(String[] addrStrs, Charset charset) {
+        final List<InternetAddress> resultList = new ArrayList<>(addrStrs.length);
+        InternetAddress[] addrs;
+        for (String addrStr : addrStrs) {
+            addrs = parseAddress(addrStr, charset);
+            if (ArrayUtil.isNotEmpty(addrs)) {
+                Collections.addAll(resultList, addrs);
+            }
+        }
+        return resultList.toArray(new InternetAddress[0]);
+    }
 
-	/**
-	 * 解析第一个地址
-	 *
-	 * @param address 地址字符串
-	 * @param charset 编码,{@code null}表示使用系统属性定义的编码或系统编码
-	 * @return 地址列表
-	 */
-	public static InternetAddress parseFirstAddress(String address, Charset charset) {
-		final InternetAddress[] internetAddresses = parseAddress(address, charset);
-		if (ArrayUtil.isEmpty(internetAddresses)) {
-			try {
-				return new InternetAddress(address);
-			} catch (AddressException e) {
-				throw new MailException(e);
-			}
-		}
-		return internetAddresses[0];
-	}
+    /**
+     * 解析第一个地址
+     *
+     * @param address 地址字符串
+     * @param charset 编码,{@code null}表示使用系统属性定义的编码或系统编码
+     * @return 地址列表
+     */
+    public static InternetAddress parseFirstAddress(String address, Charset charset) {
+        final InternetAddress[] internetAddresses = parseAddress(address, charset);
+        if (ArrayUtil.isEmpty(internetAddresses)) {
+            try {
+                return new InternetAddress(address);
+            } catch (AddressException e) {
+                throw new MailException(e);
+            }
+        }
+        return internetAddresses[0];
+    }
 
-	/**
-	 * 将一个地址字符串解析为多个地址<br>
-	 * 地址间使用" "、","、";"分隔
-	 *
-	 * @param address 地址字符串
-	 * @param charset 编码,{@code null}表示使用系统属性定义的编码或系统编码
-	 * @return 地址列表
-	 */
-	public static InternetAddress[] parseAddress(String address, Charset charset) {
-		InternetAddress[] addresses;
-		try {
-			addresses = InternetAddress.parse(address);
-		} catch (AddressException e) {
-			throw new MailException(e);
-		}
-		//编码用户名
-		if (ArrayUtil.isNotEmpty(addresses)) {
-			final String charsetStr = null == charset ? null : charset.name();
-			for (InternetAddress internetAddress : addresses) {
-				try {
-					internetAddress.setPersonal(internetAddress.getPersonal(), charsetStr);
-				} catch (UnsupportedEncodingException e) {
-					throw new MailException(e);
-				}
-			}
-		}
+    /**
+     * 将一个地址字符串解析为多个地址<br>
+     * 地址间使用" "、","、";"分隔
+     *
+     * @param address 地址字符串
+     * @param charset 编码,{@code null}表示使用系统属性定义的编码或系统编码
+     * @return 地址列表
+     */
+    public static InternetAddress[] parseAddress(String address, Charset charset) {
+        InternetAddress[] addresses;
+        try {
+            addresses = InternetAddress.parse(address);
+        } catch (AddressException e) {
+            throw new MailException(e);
+        }
+        //编码用户名
+        if (ArrayUtil.isNotEmpty(addresses)) {
+            final String charsetStr = null == charset ? null : charset.name();
+            for (InternetAddress internetAddress : addresses) {
+                try {
+                    internetAddress.setPersonal(internetAddress.getPersonal(), charsetStr);
+                } catch (UnsupportedEncodingException e) {
+                    throw new MailException(e);
+                }
+            }
+        }
 
-		return addresses;
-	}
+        return addresses;
+    }
 
-	/**
-	 * 编码中文字符<br>
-	 * 编码失败返回原字符串
-	 *
-	 * @param text 被编码的文本
-	 * @param charset 编码
-	 * @return 编码后的结果
-	 */
-	public static String encodeText(String text, Charset charset) {
-		try {
-			return MimeUtility.encodeText(text, charset.name(), null);
-		} catch (UnsupportedEncodingException e) {
-			// ignore
-		}
-		return text;
-	}
+    /**
+     * 编码中文字符<br>
+     * 编码失败返回原字符串
+     *
+     * @param text    被编码的文本
+     * @param charset 编码
+     * @return 编码后的结果
+     */
+    public static String encodeText(String text, Charset charset) {
+        try {
+            return MimeUtility.encodeText(text, charset.name(), null);
+        } catch (UnsupportedEncodingException e) {
+            // ignore
+        }
+        return text;
+    }
 }

+ 448 - 448
ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/Mail.java

@@ -32,452 +32,452 @@ public class Mail implements Builder<MimeMessage> {
     @Serial
     private static final long serialVersionUID = 1L;
 
-	/**
-	 * 邮箱帐户信息以及一些客户端配置信息
-	 */
-	private final MailAccount mailAccount;
-	/**
-	 * 收件人列表
-	 */
-	private String[] tos;
-	/**
-	 * 抄送人列表(carbon copy)
-	 */
-	private String[] ccs;
-	/**
-	 * 密送人列表(blind carbon copy)
-	 */
-	private String[] bccs;
-	/**
-	 * 回复地址(reply-to)
-	 */
-	private String[] reply;
-	/**
-	 * 标题
-	 */
-	private String title;
-	/**
-	 * 内容
-	 */
-	private String content;
-	/**
-	 * 是否为HTML
-	 */
-	private boolean isHtml;
-	/**
-	 * 正文、附件和图片的混合部分
-	 */
-	private final Multipart multipart = new MimeMultipart();
-	/**
-	 * 是否使用全局会话,默认为false
-	 */
-	private boolean useGlobalSession = false;
-
-	/**
-	 * debug输出位置,可以自定义debug日志
-	 */
-	private PrintStream debugOutput;
-
-	/**
-	 * 创建邮件客户端
-	 *
-	 * @param mailAccount 邮件帐号
-	 * @return Mail
-	 */
-	public static Mail create(MailAccount mailAccount) {
-		return new Mail(mailAccount);
-	}
-
-	/**
-	 * 创建邮件客户端,使用全局邮件帐户
-	 *
-	 * @return Mail
-	 */
-	public static Mail create() {
-		return new Mail();
-	}
-
-	// --------------------------------------------------------------- Constructor start
-
-	/**
-	 * 构造,使用全局邮件帐户
-	 */
-	public Mail() {
-		this(GlobalMailAccount.INSTANCE.getAccount());
-	}
-
-	/**
-	 * 构造
-	 *
-	 * @param mailAccount 邮件帐户,如果为null使用默认配置文件的全局邮件配置
-	 */
-	public Mail(MailAccount mailAccount) {
-		mailAccount = (null != mailAccount) ? mailAccount : GlobalMailAccount.INSTANCE.getAccount();
-		this.mailAccount = mailAccount.defaultIfEmpty();
-	}
-	// --------------------------------------------------------------- Constructor end
-
-	// --------------------------------------------------------------- Getters and Setters start
-
-	/**
-	 * 设置收件人
-	 *
-	 * @param tos 收件人列表
-	 * @return this
-	 * @see #setTos(String...)
-	 */
-	public Mail to(String... tos) {
-		return setTos(tos);
-	}
-
-	/**
-	 * 设置多个收件人
-	 *
-	 * @param tos 收件人列表
-	 * @return this
-	 */
-	public Mail setTos(String... tos) {
-		this.tos = tos;
-		return this;
-	}
-
-	/**
-	 * 设置多个抄送人(carbon copy)
-	 *
-	 * @param ccs 抄送人列表
-	 * @return this
-	 * @since 4.0.3
-	 */
-	public Mail setCcs(String... ccs) {
-		this.ccs = ccs;
-		return this;
-	}
-
-	/**
-	 * 设置多个密送人(blind carbon copy)
-	 *
-	 * @param bccs 密送人列表
-	 * @return this
-	 * @since 4.0.3
-	 */
-	public Mail setBccs(String... bccs) {
-		this.bccs = bccs;
-		return this;
-	}
-
-	/**
-	 * 设置多个回复地址(reply-to)
-	 *
-	 * @param reply 回复地址(reply-to)列表
-	 * @return this
-	 * @since 4.6.0
-	 */
-	public Mail setReply(String... reply) {
-		this.reply = reply;
-		return this;
-	}
-
-	/**
-	 * 设置标题
-	 *
-	 * @param title 标题
-	 * @return this
-	 */
-	public Mail setTitle(String title) {
-		this.title = title;
-		return this;
-	}
-
-	/**
-	 * 设置正文<br>
-	 * 正文可以是普通文本也可以是HTML(默认普通文本),可以通过调用{@link #setHtml(boolean)} 设置是否为HTML
-	 *
-	 * @param content 正文
-	 * @return this
-	 */
-	public Mail setContent(String content) {
-		this.content = content;
-		return this;
-	}
-
-	/**
-	 * 设置是否是HTML
-	 *
-	 * @param isHtml 是否为HTML
-	 * @return this
-	 */
-	public Mail setHtml(boolean isHtml) {
-		this.isHtml = isHtml;
-		return this;
-	}
-
-	/**
-	 * 设置正文
-	 *
-	 * @param content 正文内容
-	 * @param isHtml  是否为HTML
-	 * @return this
-	 */
-	public Mail setContent(String content, boolean isHtml) {
-		setContent(content);
-		return setHtml(isHtml);
-	}
-
-	/**
-	 * 设置文件类型附件,文件可以是图片文件,此时自动设置cid(正文中引用图片),默认cid为文件名
-	 *
-	 * @param files 附件文件列表
-	 * @return this
-	 */
-	public Mail setFiles(File... files) {
-		if (ArrayUtil.isEmpty(files)) {
-			return this;
-		}
-
-		final DataSource[] attachments = new DataSource[files.length];
-		for (int i = 0; i < files.length; i++) {
-			attachments[i] = new FileDataSource(files[i]);
-		}
-		return setAttachments(attachments);
-	}
-
-	/**
-	 * 增加附件或图片,附件使用{@link DataSource} 形式表示,可以使用{@link FileDataSource}包装文件表示文件附件
-	 *
-	 * @param attachments 附件列表
-	 * @return this
-	 * @since 4.0.9
-	 */
-	public Mail setAttachments(DataSource... attachments) {
-		if (ArrayUtil.isNotEmpty(attachments)) {
-			final Charset charset = this.mailAccount.getCharset();
-			MimeBodyPart bodyPart;
-			String nameEncoded;
-			try {
-				for (DataSource attachment : attachments) {
-					bodyPart = new MimeBodyPart();
-					bodyPart.setDataHandler(new DataHandler(attachment));
-					nameEncoded = attachment.getName();
-					if (this.mailAccount.isEncodefilename()) {
-						nameEncoded = InternalMailUtil.encodeText(nameEncoded, charset);
-					}
-					// 普通附件文件名
-					bodyPart.setFileName(nameEncoded);
-					if (StrUtil.startWith(attachment.getContentType(), "image/")) {
-						// 图片附件,用于正文中引用图片
-						bodyPart.setContentID(nameEncoded);
-					}
-					this.multipart.addBodyPart(bodyPart);
-				}
-			} catch (MessagingException e) {
-				throw new MailException(e);
-			}
-		}
-		return this;
-	}
-
-	/**
-	 * 增加图片,图片的键对应到邮件模板中的占位字符串,图片类型默认为"image/jpeg"
-	 *
-	 * @param cid         图片与占位符,占位符格式为cid:${cid}
-	 * @param imageStream 图片文件
-	 * @return this
-	 * @since 4.6.3
-	 */
-	public Mail addImage(String cid, InputStream imageStream) {
-		return addImage(cid, imageStream, null);
-	}
-
-	/**
-	 * 增加图片,图片的键对应到邮件模板中的占位字符串
-	 *
-	 * @param cid         图片与占位符,占位符格式为cid:${cid}
-	 * @param imageStream 图片流,不关闭
-	 * @param contentType 图片类型,null赋值默认的"image/jpeg"
-	 * @return this
-	 * @since 4.6.3
-	 */
-	public Mail addImage(String cid, InputStream imageStream, String contentType) {
-		ByteArrayDataSource imgSource;
-		try {
-			imgSource = new ByteArrayDataSource(imageStream, ObjectUtil.defaultIfNull(contentType, "image/jpeg"));
-		} catch (IOException e) {
-			throw new IORuntimeException(e);
-		}
-		imgSource.setName(cid);
-		return setAttachments(imgSource);
-	}
-
-	/**
-	 * 增加图片,图片的键对应到邮件模板中的占位字符串
-	 *
-	 * @param cid       图片与占位符,占位符格式为cid:${cid}
-	 * @param imageFile 图片文件
-	 * @return this
-	 * @since 4.6.3
-	 */
-	public Mail addImage(String cid, File imageFile) {
-		InputStream in = null;
-		try {
-			in = FileUtil.getInputStream(imageFile);
-			return addImage(cid, in, FileTypeMap.getDefaultFileTypeMap().getContentType(imageFile));
-		} finally {
-			IoUtil.close(in);
-		}
-	}
-
-	/**
-	 * 设置字符集编码
-	 *
-	 * @param charset 字符集编码
-	 * @return this
-	 * @see MailAccount#setCharset(Charset)
-	 */
-	public Mail setCharset(Charset charset) {
-		this.mailAccount.setCharset(charset);
-		return this;
-	}
-
-	/**
-	 * 设置是否使用全局会话,默认为true
-	 *
-	 * @param isUseGlobalSession 是否使用全局会话,默认为true
-	 * @return this
-	 * @since 4.0.2
-	 */
-	public Mail setUseGlobalSession(boolean isUseGlobalSession) {
-		this.useGlobalSession = isUseGlobalSession;
-		return this;
-	}
-
-	/**
-	 * 设置debug输出位置,可以自定义debug日志
-	 *
-	 * @param debugOutput debug输出位置
-	 * @return this
-	 * @since 5.5.6
-	 */
-	public Mail setDebugOutput(PrintStream debugOutput) {
-		this.debugOutput = debugOutput;
-		return this;
-	}
-	// --------------------------------------------------------------- Getters and Setters end
-
-	@Override
-	public MimeMessage build() {
-		try {
-			return buildMsg();
-		} catch (MessagingException e) {
-			throw new MailException(e);
-		}
-	}
-
-	/**
-	 * 发送
-	 *
-	 * @return message-id
-	 * @throws MailException 邮件发送异常
-	 */
-	public String send() throws MailException {
-		try {
-			return doSend();
-		} catch (MessagingException e) {
-			if (e instanceof SendFailedException) {
-				// 当地址无效时,显示更加详细的无效地址信息
-				final Address[] invalidAddresses = ((SendFailedException) e).getInvalidAddresses();
-				final String msg = StrUtil.format("Invalid Addresses: {}", ArrayUtil.toString(invalidAddresses));
-				throw new MailException(msg, e);
-			}
-			throw new MailException(e);
-		}
-	}
-
-	// --------------------------------------------------------------- Private method start
-
-	/**
-	 * 执行发送
-	 *
-	 * @return message-id
-	 * @throws MessagingException 发送异常
-	 */
-	private String doSend() throws MessagingException {
-		final MimeMessage mimeMessage = buildMsg();
-		Transport.send(mimeMessage);
-		return mimeMessage.getMessageID();
-	}
-
-	/**
-	 * 构建消息
-	 *
-	 * @return {@link MimeMessage}消息
-	 * @throws MessagingException 消息异常
-	 */
-	private MimeMessage buildMsg() throws MessagingException {
-		final Charset charset = this.mailAccount.getCharset();
-		final MimeMessage msg = new MimeMessage(getSession());
-		// 发件人
-		final String from = this.mailAccount.getFrom();
-		if (StrUtil.isEmpty(from)) {
-			// 用户未提供发送方,则从Session中自动获取
-			msg.setFrom();
-		} else {
-			msg.setFrom(InternalMailUtil.parseFirstAddress(from, charset));
-		}
-		// 标题
-		msg.setSubject(this.title, (null == charset) ? null : charset.name());
-		// 发送时间
-		msg.setSentDate(new Date());
-		// 内容和附件
-		msg.setContent(buildContent(charset));
-		// 收件人
-		msg.setRecipients(MimeMessage.RecipientType.TO, InternalMailUtil.parseAddressFromStrs(this.tos, charset));
-		// 抄送人
-		if (ArrayUtil.isNotEmpty(this.ccs)) {
-			msg.setRecipients(MimeMessage.RecipientType.CC, InternalMailUtil.parseAddressFromStrs(this.ccs, charset));
-		}
-		// 密送人
-		if (ArrayUtil.isNotEmpty(this.bccs)) {
-			msg.setRecipients(MimeMessage.RecipientType.BCC, InternalMailUtil.parseAddressFromStrs(this.bccs, charset));
-		}
-		// 回复地址(reply-to)
-		if (ArrayUtil.isNotEmpty(this.reply)) {
-			msg.setReplyTo(InternalMailUtil.parseAddressFromStrs(this.reply, charset));
-		}
-
-		return msg;
-	}
-
-	/**
-	 * 构建邮件信息主体
-	 *
-	 * @param charset 编码,{@code null}则使用{@link MimeUtility#getDefaultJavaCharset()}
-	 * @return 邮件信息主体
-	 * @throws MessagingException 消息异常
-	 */
-	private Multipart buildContent(Charset charset) throws MessagingException {
-		final String charsetStr = null != charset ? charset.name() : MimeUtility.getDefaultJavaCharset();
-		// 正文
-		final MimeBodyPart body = new MimeBodyPart();
-		body.setContent(content, StrUtil.format("text/{}; charset={}", isHtml ? "html" : "plain", charsetStr));
-		this.multipart.addBodyPart(body);
-
-		return this.multipart;
-	}
-
-	/**
-	 * 获取默认邮件会话<br>
-	 * 如果为全局单例的会话,则全局只允许一个邮件帐号,否则每次发送邮件会新建一个新的会话
-	 *
-	 * @return 邮件会话 {@link Session}
-	 */
-	private Session getSession() {
-		final Session session = MailUtils.getSession(this.mailAccount, this.useGlobalSession);
-
-		if (null != this.debugOutput) {
-			session.setDebugOut(debugOutput);
-		}
-
-		return session;
-	}
-	// --------------------------------------------------------------- Private method end
+    /**
+     * 邮箱帐户信息以及一些客户端配置信息
+     */
+    private final MailAccount mailAccount;
+    /**
+     * 收件人列表
+     */
+    private String[] tos;
+    /**
+     * 抄送人列表(carbon copy)
+     */
+    private String[] ccs;
+    /**
+     * 密送人列表(blind carbon copy)
+     */
+    private String[] bccs;
+    /**
+     * 回复地址(reply-to)
+     */
+    private String[] reply;
+    /**
+     * 标题
+     */
+    private String title;
+    /**
+     * 内容
+     */
+    private String content;
+    /**
+     * 是否为HTML
+     */
+    private boolean isHtml;
+    /**
+     * 正文、附件和图片的混合部分
+     */
+    private final Multipart multipart = new MimeMultipart();
+    /**
+     * 是否使用全局会话,默认为false
+     */
+    private boolean useGlobalSession = false;
+
+    /**
+     * debug输出位置,可以自定义debug日志
+     */
+    private PrintStream debugOutput;
+
+    /**
+     * 创建邮件客户端
+     *
+     * @param mailAccount 邮件帐号
+     * @return Mail
+     */
+    public static Mail create(MailAccount mailAccount) {
+        return new Mail(mailAccount);
+    }
+
+    /**
+     * 创建邮件客户端,使用全局邮件帐户
+     *
+     * @return Mail
+     */
+    public static Mail create() {
+        return new Mail();
+    }
+
+    // --------------------------------------------------------------- Constructor start
+
+    /**
+     * 构造,使用全局邮件帐户
+     */
+    public Mail() {
+        this(GlobalMailAccount.INSTANCE.getAccount());
+    }
+
+    /**
+     * 构造
+     *
+     * @param mailAccount 邮件帐户,如果为null使用默认配置文件的全局邮件配置
+     */
+    public Mail(MailAccount mailAccount) {
+        mailAccount = (null != mailAccount) ? mailAccount : GlobalMailAccount.INSTANCE.getAccount();
+        this.mailAccount = mailAccount.defaultIfEmpty();
+    }
+    // --------------------------------------------------------------- Constructor end
+
+    // --------------------------------------------------------------- Getters and Setters start
+
+    /**
+     * 设置收件人
+     *
+     * @param tos 收件人列表
+     * @return this
+     * @see #setTos(String...)
+     */
+    public Mail to(String... tos) {
+        return setTos(tos);
+    }
+
+    /**
+     * 设置多个收件人
+     *
+     * @param tos 收件人列表
+     * @return this
+     */
+    public Mail setTos(String... tos) {
+        this.tos = tos;
+        return this;
+    }
+
+    /**
+     * 设置多个抄送人(carbon copy)
+     *
+     * @param ccs 抄送人列表
+     * @return this
+     * @since 4.0.3
+     */
+    public Mail setCcs(String... ccs) {
+        this.ccs = ccs;
+        return this;
+    }
+
+    /**
+     * 设置多个密送人(blind carbon copy)
+     *
+     * @param bccs 密送人列表
+     * @return this
+     * @since 4.0.3
+     */
+    public Mail setBccs(String... bccs) {
+        this.bccs = bccs;
+        return this;
+    }
+
+    /**
+     * 设置多个回复地址(reply-to)
+     *
+     * @param reply 回复地址(reply-to)列表
+     * @return this
+     * @since 4.6.0
+     */
+    public Mail setReply(String... reply) {
+        this.reply = reply;
+        return this;
+    }
+
+    /**
+     * 设置标题
+     *
+     * @param title 标题
+     * @return this
+     */
+    public Mail setTitle(String title) {
+        this.title = title;
+        return this;
+    }
+
+    /**
+     * 设置正文<br>
+     * 正文可以是普通文本也可以是HTML(默认普通文本),可以通过调用{@link #setHtml(boolean)} 设置是否为HTML
+     *
+     * @param content 正文
+     * @return this
+     */
+    public Mail setContent(String content) {
+        this.content = content;
+        return this;
+    }
+
+    /**
+     * 设置是否是HTML
+     *
+     * @param isHtml 是否为HTML
+     * @return this
+     */
+    public Mail setHtml(boolean isHtml) {
+        this.isHtml = isHtml;
+        return this;
+    }
+
+    /**
+     * 设置正文
+     *
+     * @param content 正文内容
+     * @param isHtml  是否为HTML
+     * @return this
+     */
+    public Mail setContent(String content, boolean isHtml) {
+        setContent(content);
+        return setHtml(isHtml);
+    }
+
+    /**
+     * 设置文件类型附件,文件可以是图片文件,此时自动设置cid(正文中引用图片),默认cid为文件名
+     *
+     * @param files 附件文件列表
+     * @return this
+     */
+    public Mail setFiles(File... files) {
+        if (ArrayUtil.isEmpty(files)) {
+            return this;
+        }
+
+        final DataSource[] attachments = new DataSource[files.length];
+        for (int i = 0; i < files.length; i++) {
+            attachments[i] = new FileDataSource(files[i]);
+        }
+        return setAttachments(attachments);
+    }
+
+    /**
+     * 增加附件或图片,附件使用{@link DataSource} 形式表示,可以使用{@link FileDataSource}包装文件表示文件附件
+     *
+     * @param attachments 附件列表
+     * @return this
+     * @since 4.0.9
+     */
+    public Mail setAttachments(DataSource... attachments) {
+        if (ArrayUtil.isNotEmpty(attachments)) {
+            final Charset charset = this.mailAccount.getCharset();
+            MimeBodyPart bodyPart;
+            String nameEncoded;
+            try {
+                for (DataSource attachment : attachments) {
+                    bodyPart = new MimeBodyPart();
+                    bodyPart.setDataHandler(new DataHandler(attachment));
+                    nameEncoded = attachment.getName();
+                    if (this.mailAccount.isEncodefilename()) {
+                        nameEncoded = InternalMailUtil.encodeText(nameEncoded, charset);
+                    }
+                    // 普通附件文件名
+                    bodyPart.setFileName(nameEncoded);
+                    if (StrUtil.startWith(attachment.getContentType(), "image/")) {
+                        // 图片附件,用于正文中引用图片
+                        bodyPart.setContentID(nameEncoded);
+                    }
+                    this.multipart.addBodyPart(bodyPart);
+                }
+            } catch (MessagingException e) {
+                throw new MailException(e);
+            }
+        }
+        return this;
+    }
+
+    /**
+     * 增加图片,图片的键对应到邮件模板中的占位字符串,图片类型默认为"image/jpeg"
+     *
+     * @param cid         图片与占位符,占位符格式为cid:${cid}
+     * @param imageStream 图片文件
+     * @return this
+     * @since 4.6.3
+     */
+    public Mail addImage(String cid, InputStream imageStream) {
+        return addImage(cid, imageStream, null);
+    }
+
+    /**
+     * 增加图片,图片的键对应到邮件模板中的占位字符串
+     *
+     * @param cid         图片与占位符,占位符格式为cid:${cid}
+     * @param imageStream 图片流,不关闭
+     * @param contentType 图片类型,null赋值默认的"image/jpeg"
+     * @return this
+     * @since 4.6.3
+     */
+    public Mail addImage(String cid, InputStream imageStream, String contentType) {
+        ByteArrayDataSource imgSource;
+        try {
+            imgSource = new ByteArrayDataSource(imageStream, ObjectUtil.defaultIfNull(contentType, "image/jpeg"));
+        } catch (IOException e) {
+            throw new IORuntimeException(e);
+        }
+        imgSource.setName(cid);
+        return setAttachments(imgSource);
+    }
+
+    /**
+     * 增加图片,图片的键对应到邮件模板中的占位字符串
+     *
+     * @param cid       图片与占位符,占位符格式为cid:${cid}
+     * @param imageFile 图片文件
+     * @return this
+     * @since 4.6.3
+     */
+    public Mail addImage(String cid, File imageFile) {
+        InputStream in = null;
+        try {
+            in = FileUtil.getInputStream(imageFile);
+            return addImage(cid, in, FileTypeMap.getDefaultFileTypeMap().getContentType(imageFile));
+        } finally {
+            IoUtil.close(in);
+        }
+    }
+
+    /**
+     * 设置字符集编码
+     *
+     * @param charset 字符集编码
+     * @return this
+     * @see MailAccount#setCharset(Charset)
+     */
+    public Mail setCharset(Charset charset) {
+        this.mailAccount.setCharset(charset);
+        return this;
+    }
+
+    /**
+     * 设置是否使用全局会话,默认为true
+     *
+     * @param isUseGlobalSession 是否使用全局会话,默认为true
+     * @return this
+     * @since 4.0.2
+     */
+    public Mail setUseGlobalSession(boolean isUseGlobalSession) {
+        this.useGlobalSession = isUseGlobalSession;
+        return this;
+    }
+
+    /**
+     * 设置debug输出位置,可以自定义debug日志
+     *
+     * @param debugOutput debug输出位置
+     * @return this
+     * @since 5.5.6
+     */
+    public Mail setDebugOutput(PrintStream debugOutput) {
+        this.debugOutput = debugOutput;
+        return this;
+    }
+    // --------------------------------------------------------------- Getters and Setters end
+
+    @Override
+    public MimeMessage build() {
+        try {
+            return buildMsg();
+        } catch (MessagingException e) {
+            throw new MailException(e);
+        }
+    }
+
+    /**
+     * 发送
+     *
+     * @return message-id
+     * @throws MailException 邮件发送异常
+     */
+    public String send() throws MailException {
+        try {
+            return doSend();
+        } catch (MessagingException e) {
+            if (e instanceof SendFailedException) {
+                // 当地址无效时,显示更加详细的无效地址信息
+                final Address[] invalidAddresses = ((SendFailedException) e).getInvalidAddresses();
+                final String msg = StrUtil.format("Invalid Addresses: {}", ArrayUtil.toString(invalidAddresses));
+                throw new MailException(msg, e);
+            }
+            throw new MailException(e);
+        }
+    }
+
+    // --------------------------------------------------------------- Private method start
+
+    /**
+     * 执行发送
+     *
+     * @return message-id
+     * @throws MessagingException 发送异常
+     */
+    private String doSend() throws MessagingException {
+        final MimeMessage mimeMessage = buildMsg();
+        Transport.send(mimeMessage);
+        return mimeMessage.getMessageID();
+    }
+
+    /**
+     * 构建消息
+     *
+     * @return {@link MimeMessage}消息
+     * @throws MessagingException 消息异常
+     */
+    private MimeMessage buildMsg() throws MessagingException {
+        final Charset charset = this.mailAccount.getCharset();
+        final MimeMessage msg = new MimeMessage(getSession());
+        // 发件人
+        final String from = this.mailAccount.getFrom();
+        if (StrUtil.isEmpty(from)) {
+            // 用户未提供发送方,则从Session中自动获取
+            msg.setFrom();
+        } else {
+            msg.setFrom(InternalMailUtil.parseFirstAddress(from, charset));
+        }
+        // 标题
+        msg.setSubject(this.title, (null == charset) ? null : charset.name());
+        // 发送时间
+        msg.setSentDate(new Date());
+        // 内容和附件
+        msg.setContent(buildContent(charset));
+        // 收件人
+        msg.setRecipients(MimeMessage.RecipientType.TO, InternalMailUtil.parseAddressFromStrs(this.tos, charset));
+        // 抄送人
+        if (ArrayUtil.isNotEmpty(this.ccs)) {
+            msg.setRecipients(MimeMessage.RecipientType.CC, InternalMailUtil.parseAddressFromStrs(this.ccs, charset));
+        }
+        // 密送人
+        if (ArrayUtil.isNotEmpty(this.bccs)) {
+            msg.setRecipients(MimeMessage.RecipientType.BCC, InternalMailUtil.parseAddressFromStrs(this.bccs, charset));
+        }
+        // 回复地址(reply-to)
+        if (ArrayUtil.isNotEmpty(this.reply)) {
+            msg.setReplyTo(InternalMailUtil.parseAddressFromStrs(this.reply, charset));
+        }
+
+        return msg;
+    }
+
+    /**
+     * 构建邮件信息主体
+     *
+     * @param charset 编码,{@code null}则使用{@link MimeUtility#getDefaultJavaCharset()}
+     * @return 邮件信息主体
+     * @throws MessagingException 消息异常
+     */
+    private Multipart buildContent(Charset charset) throws MessagingException {
+        final String charsetStr = null != charset ? charset.name() : MimeUtility.getDefaultJavaCharset();
+        // 正文
+        final MimeBodyPart body = new MimeBodyPart();
+        body.setContent(content, StrUtil.format("text/{}; charset={}", isHtml ? "html" : "plain", charsetStr));
+        this.multipart.addBodyPart(body);
+
+        return this.multipart;
+    }
+
+    /**
+     * 获取默认邮件会话<br>
+     * 如果为全局单例的会话,则全局只允许一个邮件帐号,否则每次发送邮件会新建一个新的会话
+     *
+     * @return 邮件会话 {@link Session}
+     */
+    private Session getSession() {
+        final Session session = MailUtils.getSession(this.mailAccount, this.useGlobalSession);
+
+        if (null != this.debugOutput) {
+            session.setDebugOut(debugOutput);
+        }
+
+        return session;
+    }
+    // --------------------------------------------------------------- Private method end
 }

+ 636 - 636
ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/MailAccount.java

@@ -18,642 +18,642 @@ import java.util.Properties;
  * @author Luxiaolei
  */
 public class MailAccount implements Serializable {
-	@Serial
+    @Serial
     private static final long serialVersionUID = -6937313421815719204L;
 
-	private static final String MAIL_PROTOCOL = "mail.transport.protocol";
-	private static final String SMTP_HOST = "mail.smtp.host";
-	private static final String SMTP_PORT = "mail.smtp.port";
-	private static final String SMTP_AUTH = "mail.smtp.auth";
-	private static final String SMTP_TIMEOUT = "mail.smtp.timeout";
-	private static final String SMTP_CONNECTION_TIMEOUT = "mail.smtp.connectiontimeout";
-	private static final String SMTP_WRITE_TIMEOUT = "mail.smtp.writetimeout";
-
-	// SSL
-	private static final String STARTTLS_ENABLE = "mail.smtp.starttls.enable";
-	private static final String SSL_ENABLE = "mail.smtp.ssl.enable";
-	private static final String SSL_PROTOCOLS = "mail.smtp.ssl.protocols";
-	private static final String SOCKET_FACTORY = "mail.smtp.socketFactory.class";
-	private static final String SOCKET_FACTORY_FALLBACK = "mail.smtp.socketFactory.fallback";
-	private static final String SOCKET_FACTORY_PORT = "smtp.socketFactory.port";
-
-	// System Properties
-	private static final String SPLIT_LONG_PARAMS = "mail.mime.splitlongparameters";
-	//private static final String ENCODE_FILE_NAME = "mail.mime.encodefilename";
-	//private static final String CHARSET = "mail.mime.charset";
-
-	// 其他
-	private static final String MAIL_DEBUG = "mail.debug";
-
-	public static final String[] MAIL_SETTING_PATHS = new String[]{"config/mail.setting", "config/mailAccount.setting", "mail.setting"};
-
-	/**
-	 * SMTP服务器域名
-	 */
-	private String host;
-	/**
-	 * SMTP服务端口
-	 */
-	private Integer port;
-	/**
-	 * 是否需要用户名密码验证
-	 */
-	private Boolean auth;
-	/**
-	 * 用户名
-	 */
-	private String user;
-	/**
-	 * 密码
-	 */
-	private String pass;
-	/**
-	 * 发送方,遵循RFC-822标准
-	 */
-	private String from;
-
-	/**
-	 * 是否打开调试模式,调试模式会显示与邮件服务器通信过程,默认不开启
-	 */
-	private boolean debug;
-	/**
-	 * 编码用于编码邮件正文和发送人、收件人等中文
-	 */
-	private Charset charset = CharsetUtil.CHARSET_UTF_8;
-	/**
-	 * 对于超长参数是否切分为多份,默认为false(国内邮箱附件不支持切分的附件名)
-	 */
-	private boolean splitlongparameters = false;
-	/**
-	 * 对于文件名是否使用{@link #charset}编码,默认为 {@code true}
-	 */
-	private boolean encodefilename = true;
-
-	/**
-	 * 使用 STARTTLS安全连接,STARTTLS是对纯文本通信协议的扩展。它将纯文本连接升级为加密连接(TLS或SSL), 而不是使用一个单独的加密通信端口。
-	 */
-	private boolean starttlsEnable = false;
-	/**
-	 * 使用 SSL安全连接
-	 */
-	private Boolean sslEnable;
-
-	/**
-	 * SSL协议,多个协议用空格分隔
-	 */
-	private String sslProtocols;
-
-	/**
-	 * 指定实现javax.net.SocketFactory接口的类的名称,这个类将被用于创建SMTP的套接字
-	 */
-	private String socketFactoryClass = "javax.net.ssl.SSLSocketFactory";
-	/**
-	 * 如果设置为true,未能创建一个套接字使用指定的套接字工厂类将导致使用java.net.Socket创建的套接字类, 默认值为true
-	 */
-	private boolean socketFactoryFallback;
-	/**
-	 * 指定的端口连接到在使用指定的套接字工厂。如果没有设置,将使用默认端口
-	 */
-	private int socketFactoryPort = 465;
-
-	/**
-	 * SMTP超时时长,单位毫秒,缺省值不超时
-	 */
-	private long timeout;
-	/**
-	 * Socket连接超时值,单位毫秒,缺省值不超时
-	 */
-	private long connectionTimeout;
-	/**
-	 * Socket写出超时值,单位毫秒,缺省值不超时
-	 */
-	private long writeTimeout;
-
-	/**
-	 * 自定义的其他属性,此自定义属性会覆盖默认属性
-	 */
-	private final Map<String, Object> customProperty = new HashMap<>();
-
-	// -------------------------------------------------------------- Constructor start
-
-	/**
-	 * 构造,所有参数需自行定义或保持默认值
-	 */
-	public MailAccount() {
-	}
-
-	/**
-	 * 构造
-	 *
-	 * @param settingPath 配置文件路径
-	 */
-	public MailAccount(String settingPath) {
-		this(new Setting(settingPath));
-	}
-
-	/**
-	 * 构造
-	 *
-	 * @param setting 配置文件
-	 */
-	public MailAccount(Setting setting) {
-		setting.toBean(this);
-	}
-
-	// -------------------------------------------------------------- Constructor end
-
-	/**
-	 * 获得SMTP服务器域名
-	 *
-	 * @return SMTP服务器域名
-	 */
-	public String getHost() {
-		return host;
-	}
-
-	/**
-	 * 设置SMTP服务器域名
-	 *
-	 * @param host SMTP服务器域名
-	 * @return this
-	 */
-	public MailAccount setHost(String host) {
-		this.host = host;
-		return this;
-	}
-
-	/**
-	 * 获得SMTP服务端口
-	 *
-	 * @return SMTP服务端口
-	 */
-	public Integer getPort() {
-		return port;
-	}
-
-	/**
-	 * 设置SMTP服务端口
-	 *
-	 * @param port SMTP服务端口
-	 * @return this
-	 */
-	public MailAccount setPort(Integer port) {
-		this.port = port;
-		return this;
-	}
-
-	/**
-	 * 是否需要用户名密码验证
-	 *
-	 * @return 是否需要用户名密码验证
-	 */
-	public Boolean isAuth() {
-		return auth;
-	}
-
-	/**
-	 * 设置是否需要用户名密码验证
-	 *
-	 * @param isAuth 是否需要用户名密码验证
-	 * @return this
-	 */
-	public MailAccount setAuth(boolean isAuth) {
-		this.auth = isAuth;
-		return this;
-	}
-
-	/**
-	 * 获取用户名
-	 *
-	 * @return 用户名
-	 */
-	public String getUser() {
-		return user;
-	}
-
-	/**
-	 * 设置用户名
-	 *
-	 * @param user 用户名
-	 * @return this
-	 */
-	public MailAccount setUser(String user) {
-		this.user = user;
-		return this;
-	}
-
-	/**
-	 * 获取密码
-	 *
-	 * @return 密码
-	 */
-	public String getPass() {
-		return pass;
-	}
-
-	/**
-	 * 设置密码
-	 *
-	 * @param pass 密码
-	 * @return this
-	 */
-	public MailAccount setPass(String pass) {
-		this.pass = pass;
-		return this;
-	}
-
-	/**
-	 * 获取发送方,遵循RFC-822标准
-	 *
-	 * @return 发送方,遵循RFC-822标准
-	 */
-	public String getFrom() {
-		return from;
-	}
-
-	/**
-	 * 设置发送方,遵循RFC-822标准<br>
-	 * 发件人可以是以下形式:
-	 *
-	 * <pre>
-	 * 1. user@xxx.xx
-	 * 2.  name &lt;user@xxx.xx&gt;
-	 * </pre>
-	 *
-	 * @param from 发送方,遵循RFC-822标准
-	 * @return this
-	 */
-	public MailAccount setFrom(String from) {
-		this.from = from;
-		return this;
-	}
-
-	/**
-	 * 是否打开调试模式,调试模式会显示与邮件服务器通信过程,默认不开启
-	 *
-	 * @return 是否打开调试模式,调试模式会显示与邮件服务器通信过程,默认不开启
-	 * @since 4.0.2
-	 */
-	public boolean isDebug() {
-		return debug;
-	}
-
-	/**
-	 * 设置是否打开调试模式,调试模式会显示与邮件服务器通信过程,默认不开启
-	 *
-	 * @param debug 是否打开调试模式,调试模式会显示与邮件服务器通信过程,默认不开启
-	 * @return this
-	 * @since 4.0.2
-	 */
-	public MailAccount setDebug(boolean debug) {
-		this.debug = debug;
-		return this;
-	}
-
-	/**
-	 * 获取字符集编码
-	 *
-	 * @return 编码,可能为{@code null}
-	 */
-	public Charset getCharset() {
-		return charset;
-	}
-
-	/**
-	 * 设置字符集编码,此选项不会修改全局配置,若修改全局配置,请设置此项为{@code null}并设置:
-	 * <pre>
-	 * 	System.setProperty("mail.mime.charset", charset);
-	 * </pre>
-	 *
-	 * @param charset 字符集编码,{@code null} 则表示使用全局设置的默认编码,全局编码为mail.mime.charset系统属性
-	 * @return this
-	 */
-	public MailAccount setCharset(Charset charset) {
-		this.charset = charset;
-		return this;
-	}
-
-	/**
-	 * 对于超长参数是否切分为多份,默认为false(国内邮箱附件不支持切分的附件名)
-	 *
-	 * @return 对于超长参数是否切分为多份
-	 */
-	public boolean isSplitlongparameters() {
-		return splitlongparameters;
-	}
-
-	/**
-	 * 设置对于超长参数是否切分为多份,默认为false(国内邮箱附件不支持切分的附件名)<br>
-	 * 注意此项为全局设置,此项会调用
-	 * <pre>
-	 * System.setProperty("mail.mime.splitlongparameters", true)
-	 * </pre>
-	 *
-	 * @param splitlongparameters 对于超长参数是否切分为多份
-	 */
-	public void setSplitlongparameters(boolean splitlongparameters) {
-		this.splitlongparameters = splitlongparameters;
-	}
-
-	/**
-	 * 对于文件名是否使用{@link #charset}编码,默认为 {@code true}
-	 *
-	 * @return 对于文件名是否使用{@link #charset}编码,默认为 {@code true}
-	 * @since 5.7.16
-	 */
-	public boolean isEncodefilename() {
-
-		return encodefilename;
-	}
-
-	/**
-	 * 设置对于文件名是否使用{@link #charset}编码,此选项不会修改全局配置<br>
-	 * 如果此选项设置为{@code false},则是否编码取决于两个系统属性:
-	 * <ul>
-	 *     <li>mail.mime.encodefilename  是否编码附件文件名</li>
-	 *     <li>mail.mime.charset         编码文件名的编码</li>
-	 * </ul>
-	 *
-	 * @param encodefilename 对于文件名是否使用{@link #charset}编码
-	 * @since 5.7.16
-	 */
-	public void setEncodefilename(boolean encodefilename) {
-		this.encodefilename = encodefilename;
-	}
-
-	/**
-	 * 是否使用 STARTTLS安全连接,STARTTLS是对纯文本通信协议的扩展。它将纯文本连接升级为加密连接(TLS或SSL), 而不是使用一个单独的加密通信端口。
-	 *
-	 * @return 是否使用 STARTTLS安全连接
-	 */
-	public boolean isStarttlsEnable() {
-		return this.starttlsEnable;
-	}
-
-	/**
-	 * 设置是否使用STARTTLS安全连接,STARTTLS是对纯文本通信协议的扩展。它将纯文本连接升级为加密连接(TLS或SSL), 而不是使用一个单独的加密通信端口。
-	 *
-	 * @param startttlsEnable 是否使用STARTTLS安全连接
-	 * @return this
-	 */
-	public MailAccount setStarttlsEnable(boolean startttlsEnable) {
-		this.starttlsEnable = startttlsEnable;
-		return this;
-	}
-
-	/**
-	 * 是否使用 SSL安全连接
-	 *
-	 * @return 是否使用 SSL安全连接
-	 */
-	public Boolean isSslEnable() {
-		return this.sslEnable;
-	}
-
-	/**
-	 * 设置是否使用SSL安全连接
-	 *
-	 * @param sslEnable 是否使用SSL安全连接
-	 * @return this
-	 */
-	public MailAccount setSslEnable(Boolean sslEnable) {
-		this.sslEnable = sslEnable;
-		return this;
-	}
-
-	/**
-	 * 获取SSL协议,多个协议用空格分隔
-	 *
-	 * @return SSL协议,多个协议用空格分隔
-	 * @since 5.5.7
-	 */
-	public String getSslProtocols() {
-		return sslProtocols;
-	}
-
-	/**
-	 * 设置SSL协议,多个协议用空格分隔
-	 *
-	 * @param sslProtocols SSL协议,多个协议用空格分隔
-	 * @since 5.5.7
-	 */
-	public void setSslProtocols(String sslProtocols) {
-		this.sslProtocols = sslProtocols;
-	}
-
-	/**
-	 * 获取指定实现javax.net.SocketFactory接口的类的名称,这个类将被用于创建SMTP的套接字
-	 *
-	 * @return 指定实现javax.net.SocketFactory接口的类的名称, 这个类将被用于创建SMTP的套接字
-	 */
-	public String getSocketFactoryClass() {
-		return socketFactoryClass;
-	}
-
-	/**
-	 * 设置指定实现javax.net.SocketFactory接口的类的名称,这个类将被用于创建SMTP的套接字
-	 *
-	 * @param socketFactoryClass 指定实现javax.net.SocketFactory接口的类的名称,这个类将被用于创建SMTP的套接字
-	 * @return this
-	 */
-	public MailAccount setSocketFactoryClass(String socketFactoryClass) {
-		this.socketFactoryClass = socketFactoryClass;
-		return this;
-	}
-
-	/**
-	 * 如果设置为true,未能创建一个套接字使用指定的套接字工厂类将导致使用java.net.Socket创建的套接字类, 默认值为true
-	 *
-	 * @return 如果设置为true, 未能创建一个套接字使用指定的套接字工厂类将导致使用java.net.Socket创建的套接字类, 默认值为true
-	 */
-	public boolean isSocketFactoryFallback() {
-		return socketFactoryFallback;
-	}
-
-	/**
-	 * 如果设置为true,未能创建一个套接字使用指定的套接字工厂类将导致使用java.net.Socket创建的套接字类, 默认值为true
-	 *
-	 * @param socketFactoryFallback 如果设置为true,未能创建一个套接字使用指定的套接字工厂类将导致使用java.net.Socket创建的套接字类, 默认值为true
-	 * @return this
-	 */
-	public MailAccount setSocketFactoryFallback(boolean socketFactoryFallback) {
-		this.socketFactoryFallback = socketFactoryFallback;
-		return this;
-	}
-
-	/**
-	 * 获取指定的端口连接到在使用指定的套接字工厂。如果没有设置,将使用默认端口
-	 *
-	 * @return 指定的端口连接到在使用指定的套接字工厂。如果没有设置,将使用默认端口
-	 */
-	public int getSocketFactoryPort() {
-		return socketFactoryPort;
-	}
-
-	/**
-	 * 指定的端口连接到在使用指定的套接字工厂。如果没有设置,将使用默认端口
-	 *
-	 * @param socketFactoryPort 指定的端口连接到在使用指定的套接字工厂。如果没有设置,将使用默认端口
-	 * @return this
-	 */
-	public MailAccount setSocketFactoryPort(int socketFactoryPort) {
-		this.socketFactoryPort = socketFactoryPort;
-		return this;
-	}
-
-	/**
-	 * 设置SMTP超时时长,单位毫秒,缺省值不超时
-	 *
-	 * @param timeout SMTP超时时长,单位毫秒,缺省值不超时
-	 * @return this
-	 * @since 4.1.17
-	 */
-	public MailAccount setTimeout(long timeout) {
-		this.timeout = timeout;
-		return this;
-	}
-
-	/**
-	 * 设置Socket连接超时值,单位毫秒,缺省值不超时
-	 *
-	 * @param connectionTimeout Socket连接超时值,单位毫秒,缺省值不超时
-	 * @return this
-	 * @since 4.1.17
-	 */
-	public MailAccount setConnectionTimeout(long connectionTimeout) {
-		this.connectionTimeout = connectionTimeout;
-		return this;
-	}
-
-	/**
-	 * 设置Socket写出超时值,单位毫秒,缺省值不超时
-	 *
-	 * @param writeTimeout Socket写出超时值,单位毫秒,缺省值不超时
-	 * @return this
-	 * @since 5.8.3
-	 */
-	public MailAccount setWriteTimeout(long writeTimeout) {
-		this.writeTimeout = writeTimeout;
-		return this;
-	}
-
-	/**
-	 * 获取自定义属性列表
-	 *
-	 * @return 自定义参数列表
-	 * @since 5.6.4
-	 */
-	public Map<String, Object> getCustomProperty() {
-		return customProperty;
-	}
-
-	/**
-	 * 设置自定义属性,如mail.smtp.ssl.socketFactory
-	 *
-	 * @param key   属性名,空白被忽略
-	 * @param value 属性值, null被忽略
-	 * @return this
-	 * @since 5.6.4
-	 */
-	public MailAccount setCustomProperty(String key, Object value) {
-		if (StrUtil.isNotBlank(key) && ObjectUtil.isNotNull(value)) {
-			this.customProperty.put(key, value);
-		}
-		return this;
-	}
-
-	/**
-	 * 获得SMTP相关信息
-	 *
-	 * @return {@link Properties}
-	 */
-	public Properties getSmtpProps() {
-		//全局系统参数
-		System.setProperty(SPLIT_LONG_PARAMS, String.valueOf(this.splitlongparameters));
-
-		final Properties p = new Properties();
-		p.put(MAIL_PROTOCOL, "smtp");
-		p.put(SMTP_HOST, this.host);
-		p.put(SMTP_PORT, String.valueOf(this.port));
-		p.put(SMTP_AUTH, String.valueOf(this.auth));
-		if (this.timeout > 0) {
-			p.put(SMTP_TIMEOUT, String.valueOf(this.timeout));
-		}
-		if (this.connectionTimeout > 0) {
-			p.put(SMTP_CONNECTION_TIMEOUT, String.valueOf(this.connectionTimeout));
-		}
-		// issue#2355
-		if (this.writeTimeout > 0) {
-			p.put(SMTP_WRITE_TIMEOUT, String.valueOf(this.writeTimeout));
-		}
-
-		p.put(MAIL_DEBUG, String.valueOf(this.debug));
-
-		if (this.starttlsEnable) {
-			//STARTTLS是对纯文本通信协议的扩展。它将纯文本连接升级为加密连接(TLS或SSL), 而不是使用一个单独的加密通信端口。
-			p.put(STARTTLS_ENABLE, "true");
-
-			if (null == this.sslEnable) {
-				//为了兼容旧版本,当用户没有此项配置时,按照starttlsEnable开启状态时对待
-				this.sslEnable = true;
-			}
-		}
-
-		// SSL
-		if (null != this.sslEnable && this.sslEnable) {
-			p.put(SSL_ENABLE, "true");
-			p.put(SOCKET_FACTORY, socketFactoryClass);
-			p.put(SOCKET_FACTORY_FALLBACK, String.valueOf(this.socketFactoryFallback));
-			p.put(SOCKET_FACTORY_PORT, String.valueOf(this.socketFactoryPort));
-			// issue#IZN95@Gitee,在Linux下需自定义SSL协议版本
-			if (StrUtil.isNotBlank(this.sslProtocols)) {
-				p.put(SSL_PROTOCOLS, this.sslProtocols);
-			}
-		}
-
-		// 补充自定义属性,允许自定属性覆盖已经设置的值
-		p.putAll(this.customProperty);
-
-		return p;
-	}
-
-	/**
-	 * 如果某些值为null,使用默认值
-	 *
-	 * @return this
-	 */
-	public MailAccount defaultIfEmpty() {
-		// 去掉发件人的姓名部分
-		final String fromAddress = InternalMailUtil.parseFirstAddress(this.from, this.charset).getAddress();
-
-		if (StrUtil.isBlank(this.host)) {
-			// 如果SMTP地址为空,默认使用smtp.<发件人邮箱后缀>
-			this.host = StrUtil.format("smtp.{}", StrUtil.subSuf(fromAddress, fromAddress.indexOf('@') + 1));
-		}
-		if (StrUtil.isBlank(user)) {
-			// 如果用户名为空,默认为发件人(issue#I4FYVY@Gitee)
-			//this.user = StrUtil.subPre(fromAddress, fromAddress.indexOf('@'));
-			this.user = fromAddress;
-		}
-		if (null == this.auth) {
-			// 如果密码非空白,则使用认证模式
-			this.auth = (false == StrUtil.isBlank(this.pass));
-		}
-		if (null == this.port) {
-			// 端口在SSL状态下默认与socketFactoryPort一致,非SSL状态下默认为25
-			this.port = (null != this.sslEnable && this.sslEnable) ? this.socketFactoryPort : 25;
-		}
-		if (null == this.charset) {
-			// 默认UTF-8编码
-			this.charset = CharsetUtil.CHARSET_UTF_8;
-		}
-
-		return this;
-	}
-
-	@Override
-	public String toString() {
-		return "MailAccount [host=" + host + ", port=" + port + ", auth=" + auth + ", user=" + user + ", pass=" + (StrUtil.isEmpty(this.pass) ? "" : "******") + ", from=" + from + ", startttlsEnable="
-				+ starttlsEnable + ", socketFactoryClass=" + socketFactoryClass + ", socketFactoryFallback=" + socketFactoryFallback + ", socketFactoryPort=" + socketFactoryPort + "]";
-	}
+    private static final String MAIL_PROTOCOL = "mail.transport.protocol";
+    private static final String SMTP_HOST = "mail.smtp.host";
+    private static final String SMTP_PORT = "mail.smtp.port";
+    private static final String SMTP_AUTH = "mail.smtp.auth";
+    private static final String SMTP_TIMEOUT = "mail.smtp.timeout";
+    private static final String SMTP_CONNECTION_TIMEOUT = "mail.smtp.connectiontimeout";
+    private static final String SMTP_WRITE_TIMEOUT = "mail.smtp.writetimeout";
+
+    // SSL
+    private static final String STARTTLS_ENABLE = "mail.smtp.starttls.enable";
+    private static final String SSL_ENABLE = "mail.smtp.ssl.enable";
+    private static final String SSL_PROTOCOLS = "mail.smtp.ssl.protocols";
+    private static final String SOCKET_FACTORY = "mail.smtp.socketFactory.class";
+    private static final String SOCKET_FACTORY_FALLBACK = "mail.smtp.socketFactory.fallback";
+    private static final String SOCKET_FACTORY_PORT = "smtp.socketFactory.port";
+
+    // System Properties
+    private static final String SPLIT_LONG_PARAMS = "mail.mime.splitlongparameters";
+    //private static final String ENCODE_FILE_NAME = "mail.mime.encodefilename";
+    //private static final String CHARSET = "mail.mime.charset";
+
+    // 其他
+    private static final String MAIL_DEBUG = "mail.debug";
+
+    public static final String[] MAIL_SETTING_PATHS = new String[]{"config/mail.setting", "config/mailAccount.setting", "mail.setting"};
+
+    /**
+     * SMTP服务器域名
+     */
+    private String host;
+    /**
+     * SMTP服务端口
+     */
+    private Integer port;
+    /**
+     * 是否需要用户名密码验证
+     */
+    private Boolean auth;
+    /**
+     * 用户名
+     */
+    private String user;
+    /**
+     * 密码
+     */
+    private String pass;
+    /**
+     * 发送方,遵循RFC-822标准
+     */
+    private String from;
+
+    /**
+     * 是否打开调试模式,调试模式会显示与邮件服务器通信过程,默认不开启
+     */
+    private boolean debug;
+    /**
+     * 编码用于编码邮件正文和发送人、收件人等中文
+     */
+    private Charset charset = CharsetUtil.CHARSET_UTF_8;
+    /**
+     * 对于超长参数是否切分为多份,默认为false(国内邮箱附件不支持切分的附件名)
+     */
+    private boolean splitlongparameters = false;
+    /**
+     * 对于文件名是否使用{@link #charset}编码,默认为 {@code true}
+     */
+    private boolean encodefilename = true;
+
+    /**
+     * 使用 STARTTLS安全连接,STARTTLS是对纯文本通信协议的扩展。它将纯文本连接升级为加密连接(TLS或SSL), 而不是使用一个单独的加密通信端口。
+     */
+    private boolean starttlsEnable = false;
+    /**
+     * 使用 SSL安全连接
+     */
+    private Boolean sslEnable;
+
+    /**
+     * SSL协议,多个协议用空格分隔
+     */
+    private String sslProtocols;
+
+    /**
+     * 指定实现javax.net.SocketFactory接口的类的名称,这个类将被用于创建SMTP的套接字
+     */
+    private String socketFactoryClass = "javax.net.ssl.SSLSocketFactory";
+    /**
+     * 如果设置为true,未能创建一个套接字使用指定的套接字工厂类将导致使用java.net.Socket创建的套接字类, 默认值为true
+     */
+    private boolean socketFactoryFallback;
+    /**
+     * 指定的端口连接到在使用指定的套接字工厂。如果没有设置,将使用默认端口
+     */
+    private int socketFactoryPort = 465;
+
+    /**
+     * SMTP超时时长,单位毫秒,缺省值不超时
+     */
+    private long timeout;
+    /**
+     * Socket连接超时值,单位毫秒,缺省值不超时
+     */
+    private long connectionTimeout;
+    /**
+     * Socket写出超时值,单位毫秒,缺省值不超时
+     */
+    private long writeTimeout;
+
+    /**
+     * 自定义的其他属性,此自定义属性会覆盖默认属性
+     */
+    private final Map<String, Object> customProperty = new HashMap<>();
+
+    // -------------------------------------------------------------- Constructor start
+
+    /**
+     * 构造,所有参数需自行定义或保持默认值
+     */
+    public MailAccount() {
+    }
+
+    /**
+     * 构造
+     *
+     * @param settingPath 配置文件路径
+     */
+    public MailAccount(String settingPath) {
+        this(new Setting(settingPath));
+    }
+
+    /**
+     * 构造
+     *
+     * @param setting 配置文件
+     */
+    public MailAccount(Setting setting) {
+        setting.toBean(this);
+    }
+
+    // -------------------------------------------------------------- Constructor end
+
+    /**
+     * 获得SMTP服务器域名
+     *
+     * @return SMTP服务器域名
+     */
+    public String getHost() {
+        return host;
+    }
+
+    /**
+     * 设置SMTP服务器域名
+     *
+     * @param host SMTP服务器域名
+     * @return this
+     */
+    public MailAccount setHost(String host) {
+        this.host = host;
+        return this;
+    }
+
+    /**
+     * 获得SMTP服务端口
+     *
+     * @return SMTP服务端口
+     */
+    public Integer getPort() {
+        return port;
+    }
+
+    /**
+     * 设置SMTP服务端口
+     *
+     * @param port SMTP服务端口
+     * @return this
+     */
+    public MailAccount setPort(Integer port) {
+        this.port = port;
+        return this;
+    }
+
+    /**
+     * 是否需要用户名密码验证
+     *
+     * @return 是否需要用户名密码验证
+     */
+    public Boolean isAuth() {
+        return auth;
+    }
+
+    /**
+     * 设置是否需要用户名密码验证
+     *
+     * @param isAuth 是否需要用户名密码验证
+     * @return this
+     */
+    public MailAccount setAuth(boolean isAuth) {
+        this.auth = isAuth;
+        return this;
+    }
+
+    /**
+     * 获取用户名
+     *
+     * @return 用户名
+     */
+    public String getUser() {
+        return user;
+    }
+
+    /**
+     * 设置用户名
+     *
+     * @param user 用户名
+     * @return this
+     */
+    public MailAccount setUser(String user) {
+        this.user = user;
+        return this;
+    }
+
+    /**
+     * 获取密码
+     *
+     * @return 密码
+     */
+    public String getPass() {
+        return pass;
+    }
+
+    /**
+     * 设置密码
+     *
+     * @param pass 密码
+     * @return this
+     */
+    public MailAccount setPass(String pass) {
+        this.pass = pass;
+        return this;
+    }
+
+    /**
+     * 获取发送方,遵循RFC-822标准
+     *
+     * @return 发送方,遵循RFC-822标准
+     */
+    public String getFrom() {
+        return from;
+    }
+
+    /**
+     * 设置发送方,遵循RFC-822标准<br>
+     * 发件人可以是以下形式:
+     *
+     * <pre>
+     * 1. user@xxx.xx
+     * 2.  name &lt;user@xxx.xx&gt;
+     * </pre>
+     *
+     * @param from 发送方,遵循RFC-822标准
+     * @return this
+     */
+    public MailAccount setFrom(String from) {
+        this.from = from;
+        return this;
+    }
+
+    /**
+     * 是否打开调试模式,调试模式会显示与邮件服务器通信过程,默认不开启
+     *
+     * @return 是否打开调试模式,调试模式会显示与邮件服务器通信过程,默认不开启
+     * @since 4.0.2
+     */
+    public boolean isDebug() {
+        return debug;
+    }
+
+    /**
+     * 设置是否打开调试模式,调试模式会显示与邮件服务器通信过程,默认不开启
+     *
+     * @param debug 是否打开调试模式,调试模式会显示与邮件服务器通信过程,默认不开启
+     * @return this
+     * @since 4.0.2
+     */
+    public MailAccount setDebug(boolean debug) {
+        this.debug = debug;
+        return this;
+    }
+
+    /**
+     * 获取字符集编码
+     *
+     * @return 编码,可能为{@code null}
+     */
+    public Charset getCharset() {
+        return charset;
+    }
+
+    /**
+     * 设置字符集编码,此选项不会修改全局配置,若修改全局配置,请设置此项为{@code null}并设置:
+     * <pre>
+     * 	System.setProperty("mail.mime.charset", charset);
+     * </pre>
+     *
+     * @param charset 字符集编码,{@code null} 则表示使用全局设置的默认编码,全局编码为mail.mime.charset系统属性
+     * @return this
+     */
+    public MailAccount setCharset(Charset charset) {
+        this.charset = charset;
+        return this;
+    }
+
+    /**
+     * 对于超长参数是否切分为多份,默认为false(国内邮箱附件不支持切分的附件名)
+     *
+     * @return 对于超长参数是否切分为多份
+     */
+    public boolean isSplitlongparameters() {
+        return splitlongparameters;
+    }
+
+    /**
+     * 设置对于超长参数是否切分为多份,默认为false(国内邮箱附件不支持切分的附件名)<br>
+     * 注意此项为全局设置,此项会调用
+     * <pre>
+     * System.setProperty("mail.mime.splitlongparameters", true)
+     * </pre>
+     *
+     * @param splitlongparameters 对于超长参数是否切分为多份
+     */
+    public void setSplitlongparameters(boolean splitlongparameters) {
+        this.splitlongparameters = splitlongparameters;
+    }
+
+    /**
+     * 对于文件名是否使用{@link #charset}编码,默认为 {@code true}
+     *
+     * @return 对于文件名是否使用{@link #charset}编码,默认为 {@code true}
+     * @since 5.7.16
+     */
+    public boolean isEncodefilename() {
+
+        return encodefilename;
+    }
+
+    /**
+     * 设置对于文件名是否使用{@link #charset}编码,此选项不会修改全局配置<br>
+     * 如果此选项设置为{@code false},则是否编码取决于两个系统属性:
+     * <ul>
+     *     <li>mail.mime.encodefilename  是否编码附件文件名</li>
+     *     <li>mail.mime.charset         编码文件名的编码</li>
+     * </ul>
+     *
+     * @param encodefilename 对于文件名是否使用{@link #charset}编码
+     * @since 5.7.16
+     */
+    public void setEncodefilename(boolean encodefilename) {
+        this.encodefilename = encodefilename;
+    }
+
+    /**
+     * 是否使用 STARTTLS安全连接,STARTTLS是对纯文本通信协议的扩展。它将纯文本连接升级为加密连接(TLS或SSL), 而不是使用一个单独的加密通信端口。
+     *
+     * @return 是否使用 STARTTLS安全连接
+     */
+    public boolean isStarttlsEnable() {
+        return this.starttlsEnable;
+    }
+
+    /**
+     * 设置是否使用STARTTLS安全连接,STARTTLS是对纯文本通信协议的扩展。它将纯文本连接升级为加密连接(TLS或SSL), 而不是使用一个单独的加密通信端口。
+     *
+     * @param startttlsEnable 是否使用STARTTLS安全连接
+     * @return this
+     */
+    public MailAccount setStarttlsEnable(boolean startttlsEnable) {
+        this.starttlsEnable = startttlsEnable;
+        return this;
+    }
+
+    /**
+     * 是否使用 SSL安全连接
+     *
+     * @return 是否使用 SSL安全连接
+     */
+    public Boolean isSslEnable() {
+        return this.sslEnable;
+    }
+
+    /**
+     * 设置是否使用SSL安全连接
+     *
+     * @param sslEnable 是否使用SSL安全连接
+     * @return this
+     */
+    public MailAccount setSslEnable(Boolean sslEnable) {
+        this.sslEnable = sslEnable;
+        return this;
+    }
+
+    /**
+     * 获取SSL协议,多个协议用空格分隔
+     *
+     * @return SSL协议,多个协议用空格分隔
+     * @since 5.5.7
+     */
+    public String getSslProtocols() {
+        return sslProtocols;
+    }
+
+    /**
+     * 设置SSL协议,多个协议用空格分隔
+     *
+     * @param sslProtocols SSL协议,多个协议用空格分隔
+     * @since 5.5.7
+     */
+    public void setSslProtocols(String sslProtocols) {
+        this.sslProtocols = sslProtocols;
+    }
+
+    /**
+     * 获取指定实现javax.net.SocketFactory接口的类的名称,这个类将被用于创建SMTP的套接字
+     *
+     * @return 指定实现javax.net.SocketFactory接口的类的名称, 这个类将被用于创建SMTP的套接字
+     */
+    public String getSocketFactoryClass() {
+        return socketFactoryClass;
+    }
+
+    /**
+     * 设置指定实现javax.net.SocketFactory接口的类的名称,这个类将被用于创建SMTP的套接字
+     *
+     * @param socketFactoryClass 指定实现javax.net.SocketFactory接口的类的名称,这个类将被用于创建SMTP的套接字
+     * @return this
+     */
+    public MailAccount setSocketFactoryClass(String socketFactoryClass) {
+        this.socketFactoryClass = socketFactoryClass;
+        return this;
+    }
+
+    /**
+     * 如果设置为true,未能创建一个套接字使用指定的套接字工厂类将导致使用java.net.Socket创建的套接字类, 默认值为true
+     *
+     * @return 如果设置为true, 未能创建一个套接字使用指定的套接字工厂类将导致使用java.net.Socket创建的套接字类, 默认值为true
+     */
+    public boolean isSocketFactoryFallback() {
+        return socketFactoryFallback;
+    }
+
+    /**
+     * 如果设置为true,未能创建一个套接字使用指定的套接字工厂类将导致使用java.net.Socket创建的套接字类, 默认值为true
+     *
+     * @param socketFactoryFallback 如果设置为true,未能创建一个套接字使用指定的套接字工厂类将导致使用java.net.Socket创建的套接字类, 默认值为true
+     * @return this
+     */
+    public MailAccount setSocketFactoryFallback(boolean socketFactoryFallback) {
+        this.socketFactoryFallback = socketFactoryFallback;
+        return this;
+    }
+
+    /**
+     * 获取指定的端口连接到在使用指定的套接字工厂。如果没有设置,将使用默认端口
+     *
+     * @return 指定的端口连接到在使用指定的套接字工厂。如果没有设置,将使用默认端口
+     */
+    public int getSocketFactoryPort() {
+        return socketFactoryPort;
+    }
+
+    /**
+     * 指定的端口连接到在使用指定的套接字工厂。如果没有设置,将使用默认端口
+     *
+     * @param socketFactoryPort 指定的端口连接到在使用指定的套接字工厂。如果没有设置,将使用默认端口
+     * @return this
+     */
+    public MailAccount setSocketFactoryPort(int socketFactoryPort) {
+        this.socketFactoryPort = socketFactoryPort;
+        return this;
+    }
+
+    /**
+     * 设置SMTP超时时长,单位毫秒,缺省值不超时
+     *
+     * @param timeout SMTP超时时长,单位毫秒,缺省值不超时
+     * @return this
+     * @since 4.1.17
+     */
+    public MailAccount setTimeout(long timeout) {
+        this.timeout = timeout;
+        return this;
+    }
+
+    /**
+     * 设置Socket连接超时值,单位毫秒,缺省值不超时
+     *
+     * @param connectionTimeout Socket连接超时值,单位毫秒,缺省值不超时
+     * @return this
+     * @since 4.1.17
+     */
+    public MailAccount setConnectionTimeout(long connectionTimeout) {
+        this.connectionTimeout = connectionTimeout;
+        return this;
+    }
+
+    /**
+     * 设置Socket写出超时值,单位毫秒,缺省值不超时
+     *
+     * @param writeTimeout Socket写出超时值,单位毫秒,缺省值不超时
+     * @return this
+     * @since 5.8.3
+     */
+    public MailAccount setWriteTimeout(long writeTimeout) {
+        this.writeTimeout = writeTimeout;
+        return this;
+    }
+
+    /**
+     * 获取自定义属性列表
+     *
+     * @return 自定义参数列表
+     * @since 5.6.4
+     */
+    public Map<String, Object> getCustomProperty() {
+        return customProperty;
+    }
+
+    /**
+     * 设置自定义属性,如mail.smtp.ssl.socketFactory
+     *
+     * @param key   属性名,空白被忽略
+     * @param value 属性值, null被忽略
+     * @return this
+     * @since 5.6.4
+     */
+    public MailAccount setCustomProperty(String key, Object value) {
+        if (StrUtil.isNotBlank(key) && ObjectUtil.isNotNull(value)) {
+            this.customProperty.put(key, value);
+        }
+        return this;
+    }
+
+    /**
+     * 获得SMTP相关信息
+     *
+     * @return {@link Properties}
+     */
+    public Properties getSmtpProps() {
+        //全局系统参数
+        System.setProperty(SPLIT_LONG_PARAMS, String.valueOf(this.splitlongparameters));
+
+        final Properties p = new Properties();
+        p.put(MAIL_PROTOCOL, "smtp");
+        p.put(SMTP_HOST, this.host);
+        p.put(SMTP_PORT, String.valueOf(this.port));
+        p.put(SMTP_AUTH, String.valueOf(this.auth));
+        if (this.timeout > 0) {
+            p.put(SMTP_TIMEOUT, String.valueOf(this.timeout));
+        }
+        if (this.connectionTimeout > 0) {
+            p.put(SMTP_CONNECTION_TIMEOUT, String.valueOf(this.connectionTimeout));
+        }
+        // issue#2355
+        if (this.writeTimeout > 0) {
+            p.put(SMTP_WRITE_TIMEOUT, String.valueOf(this.writeTimeout));
+        }
+
+        p.put(MAIL_DEBUG, String.valueOf(this.debug));
+
+        if (this.starttlsEnable) {
+            //STARTTLS是对纯文本通信协议的扩展。它将纯文本连接升级为加密连接(TLS或SSL), 而不是使用一个单独的加密通信端口。
+            p.put(STARTTLS_ENABLE, "true");
+
+            if (null == this.sslEnable) {
+                //为了兼容旧版本,当用户没有此项配置时,按照starttlsEnable开启状态时对待
+                this.sslEnable = true;
+            }
+        }
+
+        // SSL
+        if (null != this.sslEnable && this.sslEnable) {
+            p.put(SSL_ENABLE, "true");
+            p.put(SOCKET_FACTORY, socketFactoryClass);
+            p.put(SOCKET_FACTORY_FALLBACK, String.valueOf(this.socketFactoryFallback));
+            p.put(SOCKET_FACTORY_PORT, String.valueOf(this.socketFactoryPort));
+            // issue#IZN95@Gitee,在Linux下需自定义SSL协议版本
+            if (StrUtil.isNotBlank(this.sslProtocols)) {
+                p.put(SSL_PROTOCOLS, this.sslProtocols);
+            }
+        }
+
+        // 补充自定义属性,允许自定属性覆盖已经设置的值
+        p.putAll(this.customProperty);
+
+        return p;
+    }
+
+    /**
+     * 如果某些值为null,使用默认值
+     *
+     * @return this
+     */
+    public MailAccount defaultIfEmpty() {
+        // 去掉发件人的姓名部分
+        final String fromAddress = InternalMailUtil.parseFirstAddress(this.from, this.charset).getAddress();
+
+        if (StrUtil.isBlank(this.host)) {
+            // 如果SMTP地址为空,默认使用smtp.<发件人邮箱后缀>
+            this.host = StrUtil.format("smtp.{}", StrUtil.subSuf(fromAddress, fromAddress.indexOf('@') + 1));
+        }
+        if (StrUtil.isBlank(user)) {
+            // 如果用户名为空,默认为发件人(issue#I4FYVY@Gitee)
+            //this.user = StrUtil.subPre(fromAddress, fromAddress.indexOf('@'));
+            this.user = fromAddress;
+        }
+        if (null == this.auth) {
+            // 如果密码非空白,则使用认证模式
+            this.auth = (false == StrUtil.isBlank(this.pass));
+        }
+        if (null == this.port) {
+            // 端口在SSL状态下默认与socketFactoryPort一致,非SSL状态下默认为25
+            this.port = (null != this.sslEnable && this.sslEnable) ? this.socketFactoryPort : 25;
+        }
+        if (null == this.charset) {
+            // 默认UTF-8编码
+            this.charset = CharsetUtil.CHARSET_UTF_8;
+        }
+
+        return this;
+    }
+
+    @Override
+    public String toString() {
+        return "MailAccount [host=" + host + ", port=" + port + ", auth=" + auth + ", user=" + user + ", pass=" + (StrUtil.isEmpty(this.pass) ? "" : "******") + ", from=" + from + ", startttlsEnable="
+            + starttlsEnable + ", socketFactoryClass=" + socketFactoryClass + ", socketFactoryFallback=" + socketFactoryFallback + ", socketFactoryPort=" + socketFactoryPort + "]";
+    }
 }

+ 21 - 20
ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/MailException.java

@@ -7,33 +7,34 @@ import java.io.Serial;
 
 /**
  * 邮件异常
+ *
  * @author xiaoleilu
  */
-public class MailException extends RuntimeException{
-	@Serial
+public class MailException extends RuntimeException {
+    @Serial
     private static final long serialVersionUID = 8247610319171014183L;
 
-	public MailException(Throwable e) {
-		super(ExceptionUtil.getMessage(e), e);
-	}
+    public MailException(Throwable e) {
+        super(ExceptionUtil.getMessage(e), e);
+    }
 
-	public MailException(String message) {
-		super(message);
-	}
+    public MailException(String message) {
+        super(message);
+    }
 
-	public MailException(String messageTemplate, Object... params) {
-		super(StrUtil.format(messageTemplate, params));
-	}
+    public MailException(String messageTemplate, Object... params) {
+        super(StrUtil.format(messageTemplate, params));
+    }
 
-	public MailException(String message, Throwable throwable) {
-		super(message, throwable);
-	}
+    public MailException(String message, Throwable throwable) {
+        super(message, throwable);
+    }
 
-	public MailException(String message, Throwable throwable, boolean enableSuppression, boolean writableStackTrace) {
-		super(message, throwable, enableSuppression, writableStackTrace);
-	}
+    public MailException(String message, Throwable throwable, boolean enableSuppression, boolean writableStackTrace) {
+        super(message, throwable, enableSuppression, writableStackTrace);
+    }
 
-	public MailException(Throwable throwable, String messageTemplate, Object... params) {
-		super(StrUtil.format(messageTemplate, params), throwable);
-	}
+    public MailException(Throwable throwable, String messageTemplate, Object... params) {
+        super(StrUtil.format(messageTemplate, params), throwable);
+    }
 }

+ 4 - 5
ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/MailUtils.java

@@ -5,13 +5,12 @@ import cn.hutool.core.io.IoUtil;
 import cn.hutool.core.map.MapUtil;
 import cn.hutool.core.util.CharUtil;
 import cn.hutool.core.util.StrUtil;
-import org.dromara.common.core.utils.SpringUtils;
-import org.dromara.common.core.utils.StringUtils;
-import lombok.AccessLevel;
-import lombok.NoArgsConstructor;
-
 import jakarta.mail.Authenticator;
 import jakarta.mail.Session;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.core.utils.StringUtils;
 
 import java.io.File;
 import java.io.InputStream;

+ 16 - 16
ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/UserPassAuthenticator.java

@@ -11,23 +11,23 @@ import jakarta.mail.PasswordAuthentication;
  */
 public class UserPassAuthenticator extends Authenticator {
 
-	private final String user;
-	private final String pass;
+    private final String user;
+    private final String pass;
 
-	/**
-	 * 构造
-	 *
-	 * @param user 用户名
-	 * @param pass 密码
-	 */
-	public UserPassAuthenticator(String user, String pass) {
-		this.user = user;
-		this.pass = pass;
-	}
+    /**
+     * 构造
+     *
+     * @param user 用户名
+     * @param pass 密码
+     */
+    public UserPassAuthenticator(String user, String pass) {
+        this.user = user;
+        this.pass = pass;
+    }
 
-	@Override
-	protected PasswordAuthentication getPasswordAuthentication() {
-		return new PasswordAuthentication(this.user, this.pass);
-	}
+    @Override
+    protected PasswordAuthentication getPasswordAuthentication() {
+        return new PasswordAuthentication(this.user, this.pass);
+    }
 
 }