疯狂的狮子li
2023-04-14 44a796ed57e87a386cf212d6e750343faf9affdc
update 优化 邮件工具代码格式
已修改7个文件
2274 ■■■■ 文件已修改
ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/GlobalMailAccount.java 63 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/InternalMailUtil.java 165 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/Mail.java 824 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/MailAccount.java 1140 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/MailException.java 41 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/MailUtils.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/UserPassAuthenticator.java 32 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/GlobalMailAccount.java
@@ -6,42 +6,41 @@
 * 全局邮件帐户,依赖于邮件配置文件{@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;
    }
}
ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/InternalMailUtil.java
@@ -13,95 +13,96 @@
/**
 * 邮件内部工具类
 *
 * @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;
    }
}
ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/Mail.java
@@ -32,452 +32,452 @@
    @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;
    /**
     * 邮箱帐户信息以及一些客户端配置信息
     */
    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;
    /**
     * debug输出位置,可以自定义debug日志
     */
    private PrintStream debugOutput;
    /**
     * 创建邮件客户端
     *
     * @param mailAccount 邮件帐号
     * @return Mail
     */
    public static Mail create(MailAccount mailAccount) {
        return new Mail(mailAccount);
    }
    /**
     * 创建邮件客户端
     *
     * @param mailAccount 邮件帐号
     * @return Mail
     */
    public static Mail create(MailAccount mailAccount) {
        return new Mail(mailAccount);
    }
    /**
     * 创建邮件客户端,使用全局邮件帐户
     *
     * @return Mail
     */
    public static Mail create() {
        return new Mail();
    }
    /**
     * 创建邮件客户端,使用全局邮件帐户
     *
     * @return Mail
     */
    public static Mail create() {
        return new Mail();
    }
    // --------------------------------------------------------------- Constructor start
    // --------------------------------------------------------------- Constructor start
    /**
     * 构造,使用全局邮件帐户
     */
    public Mail() {
        this(GlobalMailAccount.INSTANCE.getAccount());
    }
    /**
     * 构造,使用全局邮件帐户
     */
    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
    /**
     * 构造
     *
     * @param mailAccount 邮件帐户,如果为null使用默认配置文件的全局邮件配置
     */
    public Mail(MailAccount mailAccount) {
        mailAccount = (null != mailAccount) ? mailAccount : GlobalMailAccount.INSTANCE.getAccount();
        this.mailAccount = mailAccount.defaultIfEmpty();
    }
    // --------------------------------------------------------------- Constructor end
    // --------------------------------------------------------------- Getters and Setters start
    // --------------------------------------------------------------- Getters and Setters start
    /**
     * 设置收件人
     *
     * @param tos 收件人列表
     * @return this
     * @see #setTos(String...)
     */
    public Mail to(String... tos) {
        return setTos(tos);
    }
    /**
     * 设置收件人
     *
     * @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;
    }
    /**
     * 设置多个收件人
     *
     * @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;
    }
    /**
     * 设置多个抄送人(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;
    }
    /**
     * 设置多个密送人(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;
    }
    /**
     * 设置多个回复地址(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;
    }
    /**
     * 设置标题
     *
     * @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;
    }
    /**
     * 设置正文<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;
    }
    /**
     * 设置是否是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);
    }
    /**
     * 设置正文
     *
     * @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;
        }
    /**
     * 设置文件类型附件,文件可以是图片文件,此时自动设置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);
    }
        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;
    }
    /**
     * 增加附件或图片,附件使用{@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);
    }
    /**
     * 增加图片,图片的键对应到邮件模板中的占位字符串,图片类型默认为"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 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 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;
    }
    /**
     * 设置字符集编码
     *
     * @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;
    }
    /**
     * 设置是否使用全局会话,默认为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
    /**
     * 设置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);
        }
    }
    @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);
        }
    }
    /**
     * 发送
     *
     * @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
    // --------------------------------------------------------------- Private method start
    /**
     * 执行发送
     *
     * @return message-id
     * @throws MessagingException 发送异常
     */
    private String doSend() throws MessagingException {
        final MimeMessage mimeMessage = buildMsg();
        Transport.send(mimeMessage);
        return mimeMessage.getMessageID();
    }
    /**
     * 执行发送
     *
     * @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 {@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;
    }
        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);
    /**
     * 构建邮件信息主体
     *
     * @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;
    }
        return this.multipart;
    }
    /**
     * 获取默认邮件会话<br>
     * 如果为全局单例的会话,则全局只允许一个邮件帐号,否则每次发送邮件会新建一个新的会话
     *
     * @return 邮件会话 {@link Session}
     */
    private Session getSession() {
        final Session session = MailUtils.getSession(this.mailAccount, this.useGlobalSession);
    /**
     * 获取默认邮件会话<br>
     * 如果为全局单例的会话,则全局只允许一个邮件帐号,否则每次发送邮件会新建一个新的会话
     *
     * @return 邮件会话 {@link Session}
     */
    private Session getSession() {
        final Session session = MailUtils.getSession(this.mailAccount, this.useGlobalSession);
        if (null != this.debugOutput) {
            session.setDebugOut(debugOutput);
        }
        if (null != this.debugOutput) {
            session.setDebugOut(debugOutput);
        }
        return session;
    }
    // --------------------------------------------------------------- Private method end
        return session;
    }
    // --------------------------------------------------------------- Private method end
}
ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/MailAccount.java
@@ -18,642 +18,642 @@
 * @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";
    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";
    // 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";
    // 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";
    // 其他
    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"};
    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;
    /**
     * 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;
    /**
     * 是否打开调试模式,调试模式会显示与邮件服务器通信过程,默认不开启
     */
    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;
    /**
     * 使用 STARTTLS安全连接,STARTTLS是对纯文本通信协议的扩展。它将纯文本连接升级为加密连接(TLS或SSL), 而不是使用一个单独的加密通信端口。
     */
    private boolean starttlsEnable = false;
    /**
     * 使用 SSL安全连接
     */
    private Boolean sslEnable;
    /**
     * SSL协议,多个协议用空格分隔
     */
    private String sslProtocols;
    /**
     * 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;
    /**
     * 指定实现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;
    /**
     * SMTP超时时长,单位毫秒,缺省值不超时
     */
    private long timeout;
    /**
     * Socket连接超时值,单位毫秒,缺省值不超时
     */
    private long connectionTimeout;
    /**
     * Socket写出超时值,单位毫秒,缺省值不超时
     */
    private long writeTimeout;
    /**
     * 自定义的其他属性,此自定义属性会覆盖默认属性
     */
    private final Map<String, Object> customProperty = new HashMap<>();
    /**
     * 自定义的其他属性,此自定义属性会覆盖默认属性
     */
    private final Map<String, Object> customProperty = new HashMap<>();
    // -------------------------------------------------------------- Constructor start
    // -------------------------------------------------------------- Constructor start
    /**
     * 构造,所有参数需自行定义或保持默认值
     */
    public MailAccount() {
    }
    /**
     * 构造,所有参数需自行定义或保持默认值
     */
    public MailAccount() {
    }
    /**
     * 构造
     *
     * @param settingPath 配置文件路径
     */
    public MailAccount(String settingPath) {
        this(new Setting(settingPath));
    }
    /**
     * 构造
     *
     * @param settingPath 配置文件路径
     */
    public MailAccount(String settingPath) {
        this(new Setting(settingPath));
    }
    /**
     * 构造
     *
     * @param setting 配置文件
     */
    public MailAccount(Setting setting) {
        setting.toBean(this);
    }
    /**
     * 构造
     *
     * @param setting 配置文件
     */
    public MailAccount(Setting setting) {
        setting.toBean(this);
    }
    // -------------------------------------------------------------- Constructor end
    // -------------------------------------------------------------- Constructor end
    /**
     * 获得SMTP服务器域名
     *
     * @return SMTP服务器域名
     */
    public String getHost() {
        return host;
    }
    /**
     * 获得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服务器域名
     *
     * @param host SMTP服务器域名
     * @return this
     */
    public MailAccount setHost(String host) {
        this.host = host;
        return this;
    }
    /**
     * 获得SMTP服务端口
     *
     * @return SMTP服务端口
     */
    public Integer getPort() {
        return port;
    }
    /**
     * 获得SMTP服务端口
     *
     * @return SMTP服务端口
     */
    public Integer getPort() {
        return port;
    }
    /**
     * 设置SMTP服务端口
     *
     * @param port SMTP服务端口
     * @return this
     */
    public MailAccount setPort(Integer port) {
        this.port = port;
        return this;
    }
    /**
     * 设置SMTP服务端口
     *
     * @param port SMTP服务端口
     * @return this
     */
    public MailAccount setPort(Integer port) {
        this.port = port;
        return this;
    }
    /**
     * 是否需要用户名密码验证
     *
     * @return 是否需要用户名密码验证
     */
    public Boolean isAuth() {
        return auth;
    }
    /**
     * 是否需要用户名密码验证
     *
     * @return 是否需要用户名密码验证
     */
    public Boolean isAuth() {
        return auth;
    }
    /**
     * 设置是否需要用户名密码验证
     *
     * @param isAuth 是否需要用户名密码验证
     * @return this
     */
    public MailAccount setAuth(boolean isAuth) {
        this.auth = isAuth;
        return this;
    }
    /**
     * 设置是否需要用户名密码验证
     *
     * @param isAuth 是否需要用户名密码验证
     * @return this
     */
    public MailAccount setAuth(boolean isAuth) {
        this.auth = isAuth;
        return this;
    }
    /**
     * 获取用户名
     *
     * @return 用户名
     */
    public String getUser() {
        return user;
    }
    /**
     * 获取用户名
     *
     * @return 用户名
     */
    public String getUser() {
        return user;
    }
    /**
     * 设置用户名
     *
     * @param user 用户名
     * @return this
     */
    public MailAccount setUser(String user) {
        this.user = user;
        return this;
    }
    /**
     * 设置用户名
     *
     * @param user 用户名
     * @return this
     */
    public MailAccount setUser(String user) {
        this.user = user;
        return this;
    }
    /**
     * 获取密码
     *
     * @return 密码
     */
    public String getPass() {
        return pass;
    }
    /**
     * 获取密码
     *
     * @return 密码
     */
    public String getPass() {
        return pass;
    }
    /**
     * 设置密码
     *
     * @param pass 密码
     * @return this
     */
    public MailAccount setPass(String pass) {
        this.pass = pass;
        return this;
    }
    /**
     * 设置密码
     *
     * @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标准
     *
     * @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;
    }
    /**
     * 设置发送方,遵循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;
    }
    /**
     * 是否打开调试模式,调试模式会显示与邮件服务器通信过程,默认不开启
     *
     * @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;
    }
    /**
     * 设置是否打开调试模式,调试模式会显示与邮件服务器通信过程,默认不开启
     *
     * @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;
    }
    /**
     * 获取字符集编码
     *
     * @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;
    }
    /**
     * 设置字符集编码,此选项不会修改全局配置,若修改全局配置,请设置此项为{@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(国内邮箱附件不支持切分的附件名)
     *
     * @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;
    }
    /**
     * 设置对于超长参数是否切分为多份,默认为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() {
    /**
     * 对于文件名是否使用{@link #charset}编码,默认为 {@code true}
     *
     * @return 对于文件名是否使用{@link #charset}编码,默认为 {@code true}
     * @since 5.7.16
     */
    public boolean isEncodefilename() {
        return encodefilename;
    }
        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;
    }
    /**
     * 设置对于文件名是否使用{@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), 而不是使用一个单独的加密通信端口。
     *
     * @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;
    }
    /**
     * 设置是否使用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安全连接
     *
     * @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安全连接
     *
     * @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协议,多个协议用空格分隔
     *
     * @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;
    }
    /**
     * 设置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的套接字
     *
     * @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;
    }
    /**
     * 设置指定实现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
     *
     * @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;
    }
    /**
     * 如果设置为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;
    }
    /**
     * 获取指定的端口连接到在使用指定的套接字工厂。如果没有设置,将使用默认端口
     *
     * @return 指定的端口连接到在使用指定的套接字工厂。如果没有设置,将使用默认端口
     */
    public int getSocketFactoryPort() {
        return socketFactoryPort;
    }
    /**
     * 指定的端口连接到在使用指定的套接字工厂。如果没有设置,将使用默认端口
     *
     * @param socketFactoryPort 指定的端口连接到在使用指定的套接字工厂。如果没有设置,将使用默认端口
     * @return this
     */
    public MailAccount setSocketFactoryPort(int socketFactoryPort) {
        this.socketFactoryPort = socketFactoryPort;
        return this;
    }
    /**
     * 指定的端口连接到在使用指定的套接字工厂。如果没有设置,将使用默认端口
     *
     * @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;
    }
    /**
     * 设置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 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;
    }
    /**
     * 设置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;
    }
    /**
     * 获取自定义属性列表
     *
     * @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;
    }
    /**
     * 设置自定义属性,如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));
    /**
     * 获得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));
        }
        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));
        p.put(MAIL_DEBUG, String.valueOf(this.debug));
        if (this.starttlsEnable) {
            //STARTTLS是对纯文本通信协议的扩展。它将纯文本连接升级为加密连接(TLS或SSL), 而不是使用一个单独的加密通信端口。
            p.put(STARTTLS_ENABLE, "true");
        if (this.starttlsEnable) {
            //STARTTLS是对纯文本通信协议的扩展。它将纯文本连接升级为加密连接(TLS或SSL), 而不是使用一个单独的加密通信端口。
            p.put(STARTTLS_ENABLE, "true");
            if (null == this.sslEnable) {
                //为了兼容旧版本,当用户没有此项配置时,按照starttlsEnable开启状态时对待
                this.sslEnable = 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);
            }
        }
        // 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);
        // 补充自定义属性,允许自定属性覆盖已经设置的值
        p.putAll(this.customProperty);
        return p;
    }
        return p;
    }
    /**
     * 如果某些值为null,使用默认值
     *
     * @return this
     */
    public MailAccount defaultIfEmpty() {
        // 去掉发件人的姓名部分
        final String fromAddress = InternalMailUtil.parseFirstAddress(this.from, this.charset).getAddress();
    /**
     * 如果某些值为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;
        }
        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;
    }
        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 + "]";
    }
    @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 + "]";
    }
}
ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/MailException.java
@@ -7,33 +7,34 @@
/**
 * 邮件异常
 *
 * @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);
    }
}
ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/MailUtils.java
@@ -5,13 +5,12 @@
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;
ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/UserPassAuthenticator.java
@@ -11,23 +11,23 @@
 */
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);
    }
}