疯狂的狮子li
2023-04-14 44a796ed57e87a386cf212d6e750343faf9affdc
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
}