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标准
+ * 发件人可以是以下形式:
+ *
+ *
+ * 1. user@xxx.xx
+ * 2. name <user@xxx.xx>
+ *
+ *
+ * @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}并设置:
+ *
+ * System.setProperty("mail.mime.charset", charset);
+ *
+ *
+ * @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(国内邮箱附件不支持切分的附件名)
+ * 注意此项为全局设置,此项会调用
+ *
+ * System.setProperty("mail.mime.splitlongparameters", true)
+ *
+ *
+ * @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}编码,此选项不会修改全局配置
+ * 如果此选项设置为{@code false},则是否编码取决于两个系统属性:
+ *
+ * - mail.mime.encodefilename 是否编码附件文件名
+ * - mail.mime.charset 编码文件名的编码
+ *
+ *
+ * @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 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 + "]";
+ }
+}
\ No newline at end of file
diff --git a/xueyi-common/xueyi-common-mail/src/main/java/com/xueyi/common/mail/utils/MailException.java b/xueyi-common/xueyi-common-mail/src/main/java/com/xueyi/common/mail/utils/MailException.java
new file mode 100644
index 0000000000000000000000000000000000000000..987b1e28ab73ca5056194b40091d1b6dffb9aed9
--- /dev/null
+++ b/xueyi-common/xueyi-common-mail/src/main/java/com/xueyi/common/mail/utils/MailException.java
@@ -0,0 +1,40 @@
+package com.xueyi.common.mail.utils;
+
+import cn.hutool.core.exceptions.ExceptionUtil;
+import cn.hutool.core.util.StrUtil;
+
+import java.io.Serial;
+
+/**
+ * 邮件异常
+ *
+ * @author kevin
+ */
+public class MailException extends RuntimeException {
+ @Serial
+ private static final long serialVersionUID = 8247610319171014183L;
+
+ public MailException(Throwable e) {
+ super(ExceptionUtil.getMessage(e), e);
+ }
+
+ public MailException(String message) {
+ super(message);
+ }
+
+ 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, boolean enableSuppression, boolean writableStackTrace) {
+ super(message, throwable, enableSuppression, writableStackTrace);
+ }
+
+ public MailException(Throwable throwable, String messageTemplate, Object... params) {
+ super(StrUtil.format(messageTemplate, params), throwable);
+ }
+}
\ No newline at end of file
diff --git a/xueyi-common/xueyi-common-mail/src/main/java/com/xueyi/common/mail/utils/MailUtils.java b/xueyi-common/xueyi-common-mail/src/main/java/com/xueyi/common/mail/utils/MailUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..8c9c496e07f81831fe1c62be1f7d02af966a49e0
--- /dev/null
+++ b/xueyi-common/xueyi-common-mail/src/main/java/com/xueyi/common/mail/utils/MailUtils.java
@@ -0,0 +1,467 @@
+package com.xueyi.common.mail.utils;
+
+import cn.hutool.core.collection.CollUtil;
+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 cn.hutool.extra.spring.SpringUtil;
+import jakarta.mail.Authenticator;
+import jakarta.mail.Session;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+import java.io.File;
+import java.io.InputStream;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+
+/**
+ * 邮件工具类
+ * @author kevin
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class MailUtils {
+
+ private static final MailAccount ACCOUNT = SpringUtil.getBean(MailAccount.class);
+
+ /**
+ * 获取邮件发送实例
+ */
+ public static MailAccount getMailAccount() {
+ return ACCOUNT;
+ }
+
+ /**
+ * 获取邮件发送实例 (自定义发送人以及授权码)
+ *
+ * @param user 发送人
+ * @param pass 授权码
+ */
+ public static MailAccount getMailAccount(String from, String user, String pass) {
+ ACCOUNT.setFrom(StrUtil.blankToDefault(from, ACCOUNT.getFrom()));
+ ACCOUNT.setUser(StrUtil.blankToDefault(user, ACCOUNT.getUser()));
+ ACCOUNT.setPass(StrUtil.blankToDefault(pass, ACCOUNT.getPass()));
+ return ACCOUNT;
+ }
+
+ /**
+ * 使用配置文件中设置的账户发送文本邮件,发送给单个或多个收件人
+ * 多个收件人可以使用逗号“,”分隔,也可以通过分号“;”分隔
+ *
+ * @param to 收件人
+ * @param subject 标题
+ * @param content 正文
+ * @param files 附件列表
+ * @return message-id
+ * @since 3.2.0
+ */
+ public static String sendText(String to, String subject, String content, File... files) {
+ return send(to, subject, content, false, files);
+ }
+
+ /**
+ * 使用配置文件中设置的账户发送HTML邮件,发送给单个或多个收件人
+ * 多个收件人可以使用逗号“,”分隔,也可以通过分号“;”分隔
+ *
+ * @param to 收件人
+ * @param subject 标题
+ * @param content 正文
+ * @param files 附件列表
+ * @return message-id
+ * @since 3.2.0
+ */
+ public static String sendHtml(String to, String subject, String content, File... files) {
+ return send(to, subject, content, true, files);
+ }
+
+ /**
+ * 使用配置文件中设置的账户发送邮件,发送单个或多个收件人
+ * 多个收件人可以使用逗号“,”分隔,也可以通过分号“;”分隔
+ *
+ * @param to 收件人
+ * @param subject 标题
+ * @param content 正文
+ * @param isHtml 是否为HTML
+ * @param files 附件列表
+ * @return message-id
+ */
+ public static String send(String to, String subject, String content, boolean isHtml, File... files) {
+ return send(splitAddress(to), subject, content, isHtml, files);
+ }
+
+ /**
+ * 使用配置文件中设置的账户发送邮件,发送单个或多个收件人
+ * 多个收件人、抄送人、密送人可以使用逗号“,”分隔,也可以通过分号“;”分隔
+ *
+ * @param to 收件人,可以使用逗号“,”分隔,也可以通过分号“;”分隔
+ * @param cc 抄送人,可以使用逗号“,”分隔,也可以通过分号“;”分隔
+ * @param bcc 密送人,可以使用逗号“,”分隔,也可以通过分号“;”分隔
+ * @param subject 标题
+ * @param content 正文
+ * @param isHtml 是否为HTML
+ * @param files 附件列表
+ * @return message-id
+ * @since 4.0.3
+ */
+ public static String send(String to, String cc, String bcc, String subject, String content, boolean isHtml, File... files) {
+ return send(splitAddress(to), splitAddress(cc), splitAddress(bcc), subject, content, isHtml, files);
+ }
+
+ /**
+ * 使用配置文件中设置的账户发送文本邮件,发送给多人
+ *
+ * @param tos 收件人列表
+ * @param subject 标题
+ * @param content 正文
+ * @param files 附件列表
+ * @return message-id
+ */
+ public static String sendText(Collection tos, String subject, String content, File... files) {
+ return send(tos, subject, content, false, files);
+ }
+
+ /**
+ * 使用配置文件中设置的账户发送HTML邮件,发送给多人
+ *
+ * @param tos 收件人列表
+ * @param subject 标题
+ * @param content 正文
+ * @param files 附件列表
+ * @return message-id
+ * @since 3.2.0
+ */
+ public static String sendHtml(Collection tos, String subject, String content, File... files) {
+ return send(tos, subject, content, true, files);
+ }
+
+ /**
+ * 使用配置文件中设置的账户发送邮件,发送给多人
+ *
+ * @param tos 收件人列表
+ * @param subject 标题
+ * @param content 正文
+ * @param isHtml 是否为HTML
+ * @param files 附件列表
+ * @return message-id
+ */
+ public static String send(Collection tos, String subject, String content, boolean isHtml, File... files) {
+ return send(tos, null, null, subject, content, isHtml, files);
+ }
+
+ /**
+ * 使用配置文件中设置的账户发送邮件,发送给多人
+ *
+ * @param tos 收件人列表
+ * @param ccs 抄送人列表,可以为null或空
+ * @param bccs 密送人列表,可以为null或空
+ * @param subject 标题
+ * @param content 正文
+ * @param isHtml 是否为HTML
+ * @param files 附件列表
+ * @return message-id
+ * @since 4.0.3
+ */
+ public static String send(Collection tos, Collection ccs, Collection bccs, String subject, String content, boolean isHtml, File... files) {
+ return send(getMailAccount(), true, tos, ccs, bccs, subject, content, null, isHtml, files);
+ }
+
+ // ------------------------------------------------------------------------------------------------------------------------------- Custom MailAccount
+
+ /**
+ * 发送邮件给多人
+ *
+ * @param mailAccount 邮件认证对象
+ * @param to 收件人,多个收件人逗号或者分号隔开
+ * @param subject 标题
+ * @param content 正文
+ * @param isHtml 是否为HTML格式
+ * @param files 附件列表
+ * @return message-id
+ * @since 3.2.0
+ */
+ public static String send(MailAccount mailAccount, String to, String subject, String content, boolean isHtml, File... files) {
+ return send(mailAccount, splitAddress(to), subject, content, isHtml, files);
+ }
+
+ /**
+ * 发送邮件给多人
+ *
+ * @param mailAccount 邮件帐户信息
+ * @param tos 收件人列表
+ * @param subject 标题
+ * @param content 正文
+ * @param isHtml 是否为HTML格式
+ * @param files 附件列表
+ * @return message-id
+ */
+ public static String send(MailAccount mailAccount, Collection tos, String subject, String content, boolean isHtml, File... files) {
+ return send(mailAccount, tos, null, null, subject, content, isHtml, files);
+ }
+
+ /**
+ * 发送邮件给多人
+ *
+ * @param mailAccount 邮件帐户信息
+ * @param tos 收件人列表
+ * @param ccs 抄送人列表,可以为null或空
+ * @param bccs 密送人列表,可以为null或空
+ * @param subject 标题
+ * @param content 正文
+ * @param isHtml 是否为HTML格式
+ * @param files 附件列表
+ * @return message-id
+ * @since 4.0.3
+ */
+ public static String send(MailAccount mailAccount, Collection tos, Collection ccs, Collection bccs, String subject, String content, boolean isHtml, File... files) {
+ return send(mailAccount, false, tos, ccs, bccs, subject, content, null, isHtml, files);
+ }
+
+ /**
+ * 使用配置文件中设置的账户发送HTML邮件,发送给单个或多个收件人
+ * 多个收件人可以使用逗号“,”分隔,也可以通过分号“;”分隔
+ *
+ * @param to 收件人
+ * @param subject 标题
+ * @param content 正文
+ * @param imageMap 图片与占位符,占位符格式为cid:$IMAGE_PLACEHOLDER
+ * @param files 附件列表
+ * @return message-id
+ * @since 3.2.0
+ */
+ public static String sendHtml(String to, String subject, String content, Map imageMap, File... files) {
+ return send(to, subject, content, imageMap, true, files);
+ }
+
+ /**
+ * 使用配置文件中设置的账户发送邮件,发送单个或多个收件人
+ * 多个收件人可以使用逗号“,”分隔,也可以通过分号“;”分隔
+ *
+ * @param to 收件人
+ * @param subject 标题
+ * @param content 正文
+ * @param imageMap 图片与占位符,占位符格式为cid:$IMAGE_PLACEHOLDER
+ * @param isHtml 是否为HTML
+ * @param files 附件列表
+ * @return message-id
+ */
+ public static String send(String to, String subject, String content, Map imageMap, boolean isHtml, File... files) {
+ return send(splitAddress(to), subject, content, imageMap, isHtml, files);
+ }
+
+ /**
+ * 使用配置文件中设置的账户发送邮件,发送单个或多个收件人
+ * 多个收件人、抄送人、密送人可以使用逗号“,”分隔,也可以通过分号“;”分隔
+ *
+ * @param to 收件人,可以使用逗号“,”分隔,也可以通过分号“;”分隔
+ * @param cc 抄送人,可以使用逗号“,”分隔,也可以通过分号“;”分隔
+ * @param bcc 密送人,可以使用逗号“,”分隔,也可以通过分号“;”分隔
+ * @param subject 标题
+ * @param content 正文
+ * @param imageMap 图片与占位符,占位符格式为cid:$IMAGE_PLACEHOLDER
+ * @param isHtml 是否为HTML
+ * @param files 附件列表
+ * @return message-id
+ * @since 4.0.3
+ */
+ public static String send(String to, String cc, String bcc, String subject, String content, Map imageMap, boolean isHtml, File... files) {
+ return send(splitAddress(to), splitAddress(cc), splitAddress(bcc), subject, content, imageMap, isHtml, files);
+ }
+
+ /**
+ * 使用配置文件中设置的账户发送HTML邮件,发送给多人
+ *
+ * @param tos 收件人列表
+ * @param subject 标题
+ * @param content 正文
+ * @param imageMap 图片与占位符,占位符格式为cid:$IMAGE_PLACEHOLDER
+ * @param files 附件列表
+ * @return message-id
+ * @since 3.2.0
+ */
+ public static String sendHtml(Collection tos, String subject, String content, Map imageMap, File... files) {
+ return send(tos, subject, content, imageMap, true, files);
+ }
+
+ /**
+ * 使用配置文件中设置的账户发送邮件,发送给多人
+ *
+ * @param tos 收件人列表
+ * @param subject 标题
+ * @param content 正文
+ * @param imageMap 图片与占位符,占位符格式为cid:$IMAGE_PLACEHOLDER
+ * @param isHtml 是否为HTML
+ * @param files 附件列表
+ * @return message-id
+ */
+ public static String send(Collection tos, String subject, String content, Map imageMap, boolean isHtml, File... files) {
+ return send(tos, null, null, subject, content, imageMap, isHtml, files);
+ }
+
+ /**
+ * 使用配置文件中设置的账户发送邮件,发送给多人
+ *
+ * @param tos 收件人列表
+ * @param ccs 抄送人列表,可以为null或空
+ * @param bccs 密送人列表,可以为null或空
+ * @param subject 标题
+ * @param content 正文
+ * @param imageMap 图片与占位符,占位符格式为cid:$IMAGE_PLACEHOLDER
+ * @param isHtml 是否为HTML
+ * @param files 附件列表
+ * @return message-id
+ * @since 4.0.3
+ */
+ public static String send(Collection tos, Collection ccs, Collection bccs, String subject, String content, Map imageMap, boolean isHtml, File... files) {
+ return send(getMailAccount(), true, tos, ccs, bccs, subject, content, imageMap, isHtml, files);
+ }
+
+ // ------------------------------------------------------------------------------------------------------------------------------- Custom MailAccount
+
+ /**
+ * 发送邮件给多人
+ *
+ * @param mailAccount 邮件认证对象
+ * @param to 收件人,多个收件人逗号或者分号隔开
+ * @param subject 标题
+ * @param content 正文
+ * @param imageMap 图片与占位符,占位符格式为cid:$IMAGE_PLACEHOLDER
+ * @param isHtml 是否为HTML格式
+ * @param files 附件列表
+ * @return message-id
+ * @since 3.2.0
+ */
+ public static String send(MailAccount mailAccount, String to, String subject, String content, Map imageMap, boolean isHtml, File... files) {
+ return send(mailAccount, splitAddress(to), subject, content, imageMap, isHtml, files);
+ }
+
+ /**
+ * 发送邮件给多人
+ *
+ * @param mailAccount 邮件帐户信息
+ * @param tos 收件人列表
+ * @param subject 标题
+ * @param content 正文
+ * @param imageMap 图片与占位符,占位符格式为cid:$IMAGE_PLACEHOLDER
+ * @param isHtml 是否为HTML格式
+ * @param files 附件列表
+ * @return message-id
+ * @since 4.6.3
+ */
+ public static String send(MailAccount mailAccount, Collection tos, String subject, String content, Map imageMap, boolean isHtml, File... files) {
+ return send(mailAccount, tos, null, null, subject, content, imageMap, isHtml, files);
+ }
+
+ /**
+ * 发送邮件给多人
+ *
+ * @param mailAccount 邮件帐户信息
+ * @param tos 收件人列表
+ * @param ccs 抄送人列表,可以为null或空
+ * @param bccs 密送人列表,可以为null或空
+ * @param subject 标题
+ * @param content 正文
+ * @param imageMap 图片与占位符,占位符格式为cid:$IMAGE_PLACEHOLDER
+ * @param isHtml 是否为HTML格式
+ * @param files 附件列表
+ * @return message-id
+ * @since 4.6.3
+ */
+ public static String send(MailAccount mailAccount, Collection tos, Collection ccs, Collection bccs, String subject, String content, Map imageMap,
+ boolean isHtml, File... files) {
+ return send(mailAccount, false, tos, ccs, bccs, subject, content, imageMap, isHtml, files);
+ }
+
+ /**
+ * 根据配置文件,获取邮件客户端会话
+ *
+ * @param mailAccount 邮件账户配置
+ * @param isSingleton 是否单例(全局共享会话)
+ * @return {@link Session}
+ * @since 5.5.7
+ */
+ public static Session getSession(MailAccount mailAccount, boolean isSingleton) {
+ Authenticator authenticator = null;
+ if (mailAccount.isAuth()) {
+ authenticator = new UserPassAuthenticator(mailAccount.getUser(), mailAccount.getPass());
+ }
+
+ return isSingleton ? Session.getDefaultInstance(mailAccount.getSmtpProps(), authenticator) //
+ : Session.getInstance(mailAccount.getSmtpProps(), authenticator);
+ }
+
+ // ------------------------------------------------------------------------------------------------------------------------ Private method start
+
+ /**
+ * 发送邮件给多人
+ *
+ * @param mailAccount 邮件帐户信息
+ * @param useGlobalSession 是否全局共享Session
+ * @param tos 收件人列表
+ * @param ccs 抄送人列表,可以为null或空
+ * @param bccs 密送人列表,可以为null或空
+ * @param subject 标题
+ * @param content 正文
+ * @param imageMap 图片与占位符,占位符格式为cid:${cid}
+ * @param isHtml 是否为HTML格式
+ * @param files 附件列表
+ * @return message-id
+ * @since 4.6.3
+ */
+ private static String send(MailAccount mailAccount, boolean useGlobalSession, Collection tos, Collection ccs, Collection bccs, String subject, String content,
+ Map imageMap, boolean isHtml, File... files) {
+ final Mail mail = Mail.create(mailAccount).setUseGlobalSession(useGlobalSession);
+
+ // 可选抄送人
+ if (CollUtil.isNotEmpty(ccs)) {
+ mail.setCcs(ccs.toArray(new String[0]));
+ }
+ // 可选密送人
+ if (CollUtil.isNotEmpty(bccs)) {
+ mail.setBccs(bccs.toArray(new String[0]));
+ }
+
+ mail.setTos(tos.toArray(new String[0]));
+ mail.setTitle(subject);
+ mail.setContent(content);
+ mail.setHtml(isHtml);
+ mail.setFiles(files);
+
+ // 图片
+ if (MapUtil.isNotEmpty(imageMap)) {
+ for (Map.Entry entry : imageMap.entrySet()) {
+ mail.addImage(entry.getKey(), entry.getValue());
+ // 关闭流
+ IoUtil.close(entry.getValue());
+ }
+ }
+
+ return mail.send();
+ }
+
+ /**
+ * 将多个联系人转为列表,分隔符为逗号或者分号
+ *
+ * @param addresses 多个联系人,如果为空返回null
+ * @return 联系人列表
+ */
+ private static List splitAddress(String addresses) {
+ if (StrUtil.isBlank(addresses)) {
+ return null;
+ }
+
+ List result;
+ if (StrUtil.contains(addresses, CharUtil.COMMA)) {
+ result = StrUtil.splitTrim(addresses, CharUtil.COMMA);
+ } else if (StrUtil.contains(addresses, ';')) {
+ result = StrUtil.splitTrim(addresses, ';');
+ } else {
+ result = CollUtil.newArrayList(addresses);
+ }
+ return result;
+ }
+ // ------------------------------------------------------------------------------------------------------------------------ Private method end
+
+}
\ No newline at end of file
diff --git a/xueyi-common/xueyi-common-mail/src/main/java/com/xueyi/common/mail/utils/UserPassAuthenticator.java b/xueyi-common/xueyi-common-mail/src/main/java/com/xueyi/common/mail/utils/UserPassAuthenticator.java
new file mode 100644
index 0000000000000000000000000000000000000000..9a3374401fa5a19d4c6186780630a337bb2e2680
--- /dev/null
+++ b/xueyi-common/xueyi-common-mail/src/main/java/com/xueyi/common/mail/utils/UserPassAuthenticator.java
@@ -0,0 +1,32 @@
+package com.xueyi.common.mail.utils;
+
+import jakarta.mail.Authenticator;
+import jakarta.mail.PasswordAuthentication;
+
+/**
+ * 用户名密码验证器
+ *
+ * @author kevin
+ */
+public class UserPassAuthenticator extends Authenticator {
+
+ private final String user;
+ private final String 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);
+ }
+
+}
\ No newline at end of file
diff --git a/xueyi-common/xueyi-common-mail/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/xueyi-common/xueyi-common-mail/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 0000000000000000000000000000000000000000..412273dcfb0ac881ddde7433f4fbafed1b281ff6
--- /dev/null
+++ b/xueyi-common/xueyi-common-mail/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1 @@
+com.xueyi.common.mail.config.MailConfiguration
\ No newline at end of file
diff --git a/xueyi-common/xueyi-common-sms/pom.xml b/xueyi-common/xueyi-common-sms/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..08f997c6e6ca899839f58c5fb5334ea1c2e3e12f
--- /dev/null
+++ b/xueyi-common/xueyi-common-sms/pom.xml
@@ -0,0 +1,33 @@
+
+
+
+ com.xueyi
+ xueyi-common
+ 3.0.0-Alpha4
+
+ 4.0.0
+
+ xueyi-common-sms
+
+
+ xueyi-common-sms 短信模块
+
+
+
+
+
+ org.dromara.sms4j
+ sms4j-spring-boot-starter
+
+
+
+ com.alibaba
+ fastjson
+
+
+
+
+
+
\ No newline at end of file
diff --git a/xueyi-common/xueyi-common-sms/src/main/java/com/ruoyi/common/sms/config/SmsAutoConfiguration.java b/xueyi-common/xueyi-common-sms/src/main/java/com/ruoyi/common/sms/config/SmsAutoConfiguration.java
new file mode 100644
index 0000000000000000000000000000000000000000..82f97759e07567100bcb7963e45ad69095d497e0
--- /dev/null
+++ b/xueyi-common/xueyi-common-sms/src/main/java/com/ruoyi/common/sms/config/SmsAutoConfiguration.java
@@ -0,0 +1,14 @@
+package com.ruoyi.common.sms.config;
+
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+
+/**
+ * 短信配置类
+ *
+ * @author Lion Li
+ * @version 4.2.0
+ */
+@AutoConfiguration
+public class SmsAutoConfiguration {
+
+}
\ No newline at end of file
diff --git a/xueyi-common/xueyi-common-sms/src/main/java/com/ruoyi/common/sms/config/properties/SmsProperties.java b/xueyi-common/xueyi-common-sms/src/main/java/com/ruoyi/common/sms/config/properties/SmsProperties.java
new file mode 100644
index 0000000000000000000000000000000000000000..c675dc3ca2235fd9261b28f1cc9de0887e54d3bd
--- /dev/null
+++ b/xueyi-common/xueyi-common-sms/src/main/java/com/ruoyi/common/sms/config/properties/SmsProperties.java
@@ -0,0 +1,18 @@
+package com.ruoyi.common.sms.config.properties;
+//
+//import lombok.Data;
+//import org.springframework.boot.context.properties.ConfigurationProperties;
+//import org.springframework.stereotype.Component;
+//
+///**
+// * SMS短信 配置属性
+// *
+// * @author kevin
+// */
+//@Data
+//@ConfigurationProperties(prefix = "sms")
+//public class SmsProperties {
+//
+// private Boolean enabled;
+//
+//}
\ No newline at end of file
diff --git a/xueyi-common/xueyi-common-sms/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/xueyi-common/xueyi-common-sms/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 0000000000000000000000000000000000000000..e4081a1fa1a353a48e83909b1f113ea07c11b407
--- /dev/null
+++ b/xueyi-common/xueyi-common-sms/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1 @@
+com.ruoyi.common.sms.config.SmsAutoConfiguration