iT邦幫忙

0

應用系統建置前準備工具 - MailSenderUtils 郵件發送工具類別

  • 分享至 

  • xImage
  •  

MailSenderUtils 郵件發送工具類別

概述

MailSenderUtils 是一個用於發送電子郵件的工具類別。此類別設計為 Spring 元件,提供多種郵件發送方式,支援 HTML 內容、多附件、多收件人等功能,特別適合在企業應用程式中使用。

專案相關程式

  • GlobalConstants
  • NetUtils

第三方元件(Dependency)

  • jakarta.mail
  • spring-context
  • lombok
  • commons-lang3

主要功能

1. 基本郵件發送

純文字郵件

// 發送簡單郵件
List<String> recipients = Arrays.asList("user@example.com");
String subject = "測試郵件";
String content = "這是一封測試郵件";
String result = mailSenderUtils.sendMailContent(recipients, subject, content);

HTML 郵件

// 發送 HTML 格式郵件
String htmlContent = "<h1>歡迎</h1><p>這是一封 HTML 格式的郵件</p>";
mailSenderUtils.sendMailContent(recipients, "HTML 測試", htmlContent);

2. 附件處理

單一附件

// 發送帶附件的郵件
File attachment = new File("report.pdf");
String result = mailSenderUtils.sendMailWithFiles(
    recipients, 
    "附件測試", 
    "請查收附件",
    attachment
);

多個附件

// 發送多個附件
File file1 = new File("doc1.pdf");
File file2 = new File("doc2.xlsx");
mailSenderUtils.sendMailWithFiles(
    recipients,
    "多附件測試",
    "請查收附件",
    file1, file2
);

3. 收件人設定

完整郵件設定

// 設定完整郵件資訊
MailMessageModel message = new MailMessageModel();
message.setListRecipients(Arrays.asList("user1@example.com"));
message.setListCCAddress(Arrays.asList("cc@example.com"));
message.setListBccAddress(Arrays.asList("bcc@example.com"));
message.setSubject("完整測試");
message.setContentBody("這是一封完整的測試郵件");
mailSenderUtils.sendMailMessage(message);

4. 客製化設定

寄件者資訊

// 自訂寄件者
MailMessageModel message = new MailMessageModel();
message.setSenderName("系統管理員");
message.setSenderAddress("admin@example.com");
mailSenderUtils.sendMailMessage(message);

5. SMTP 伺服器設定

直接設定

// 使用指定的 SMTP 伺服器
Session session = mailSenderUtils.getJavaMailSession("smtp.example.com", 25);

// 使用 JNDI 資源
Session session = mailSenderUtils.getJbossMailResourceSession("java:jboss/mail/Default");

基本發信範例

List<String> recipients = new ArrayList<>();
recipients.add("user@example.com");
String subject = "測試郵件";
String content = "<h1>這是一封測試郵件</h1><p>使用 HTML 格式</p>";

MailSenderUtils mailUtils = new MailSenderUtils();
String result = mailUtils.sendMailContent(recipients, subject, content);

發送含附件的郵件

List<String> recipients = new ArrayList<>();
recipients.add("user@example.com");
String subject = "含附件的測試郵件";
String content = "請查看附件";
File attachment = new File("document.pdf");

MailSenderUtils mailUtils = new MailSenderUtils();
String result = mailUtils.sendMailWithFiles(recipients, subject, content, attachment);

使用完整選項發送郵件

MailMessageModel mailMessage = new MailMessageModel();
mailMessage.setSenderName("系統管理員");
mailMessage.setSenderAddress("admin@example.com");
mailMessage.setListRecipients(Arrays.asList("user1@example.com"));
mailMessage.setListCCAddress(Arrays.asList("user2@example.com"));
mailMessage.setSubject("重要通知");
mailMessage.setContentBody("<h1>系統更新通知</h1>");

MailSenderUtils mailUtils = new MailSenderUtils();
String result = mailUtils.sendMailMessage(mailMessage);

環境變數

需要在系統中設定以下環境變數:

  • SMTP_SERVER: SMTP 伺服器位址
  • SMTP_PORT: SMTP 伺服器埠號
  • MAIL_RESOURCE_JNDI: JNDI 資源名稱(選用)
  • MAIL_SENDER_ADDRESS: 預設寄件者地址
  • MAIL_SENDER_NAME: 預設寄件者名稱

效能最佳化

1. 記憶體管理

  • 大型附件使用 InputStream 串流處理
  • 適當設定 JVM 記憶體參數
  • 定期清理暫存檔案

2. 連線管理

  • 使用連線池處理大量郵件發送
  • 設定適當的連線逾時時間
  • 啟用 TLS/SSL 加密提升安全性

3. 最佳實踐建議

  • 批次處理多筆郵件
  • 實作重試機制
  • 非同步處理大量發送

注意事項

1. 一般使用

  • 優先使用 JNDI 資源設定,提供更好的資源管理
  • 所有字串內容預設使用 UTF-8 編碼
  • 附件檔案使用 InputStream 處理,確保資源釋放
  • 支援 IPv4 方式寄信 (java.net.preferIPv4Stack=true)
  • 大型附件建議先壓縮,避免影響效能

2. 安全性考量

  • 避免在程式碼中硬編碼敏感資訊
  • 使用 TLS/SSL 加密保護郵件傳輸
  • 實作適當的存取控制
  • 定期更新相依套件版本

3. 錯誤處理

工具類會在以下情況回傳錯誤訊息:

  • "Mail Sent Failed": 寄送過程發生錯誤
  • "get Mail Session error": 無法取得郵件 Session
  • "Internet Address AddressException": 電子郵件地址格式錯誤

4. 監控與維護

  • 實作郵件發送日誌
  • 監控 SMTP 伺服器狀態
  • 定期檢查郵件佇列
  • 建立效能監控機制

單元測試範例

以下測試範例示範 MailSenderUtils 的常見測試思路:

1. 建構 MailMessageModel

@Test
void testBuildMailMessageModel() {
    MailMessageModel m = new MailMessageModel();
    m.setSenderAddress("admin@example.com");
    m.setListRecipients(Arrays.asList("user@example.com"));
    m.setSubject("測試");
    m.setContentBody("內容");

    assertEquals("admin@example.com", m.getSenderAddress());
    assertFalse(m.getListRecipients().isEmpty());
}

2. 發信方法(示範 mock 或整合測試)

@Test
void testSendMailContentWithMock() throws Exception {
    // 建議在單元測試中使用 mock(例如 Mockito)來模擬實際傳送
    MailSenderUtils mail = Mockito.spy(new MailSenderUtils());
    List<String> recipients = Arrays.asList("user@example.com");

    // 假設 sendMailContent 會回傳 "OK" 或錯誤訊息
    Mockito.doReturn("OK").when(mail).sendMailContent(recipients, "sub", "body");

    String result = mail.sendMailContent(recipients, "sub", "body");
    assertEquals("OK", result);
}

3. SMTP 連線取得(錯誤處理)

@Test
void testGetJavaMailSessionError() {
    // 測試取得 session 時發生錯誤的處理(視實作回傳方式)
    assertThrows(Exception.class, () -> {
        MailSenderUtils.getJavaMailSession("invalid.host", -1);
    });
}

測試說明

  • 建議使用 Mock/Spy 避免真實寄信
  • 提供整合測試可在隔離環境(測試 SMTP)下驗證傳送
  • 驗證郵件內容組裝與例外處理

程式碼 MailSenderUtils.java

package tw.lewishome.webapp.base.utility.common;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

import javax.naming.InitialContext;
import javax.naming.NamingException;

import org.apache.commons.lang3.StringUtils;
import jakarta.activation.CommandMap;
import jakarta.activation.DataHandler;
import jakarta.activation.MailcapCommandMap;
import jakarta.activation.MimetypesFileTypeMap;
import jakarta.mail.Address;
import jakarta.mail.Multipart;
import jakarta.mail.Part;
import jakarta.mail.Session;
import jakarta.mail.Transport;
import jakarta.mail.internet.AddressException;
import jakarta.mail.internet.InternetAddress;
import jakarta.mail.internet.MimeBodyPart;
import jakarta.mail.internet.MimeMessage;
import jakarta.mail.internet.MimeMultipart;
import jakarta.mail.internet.MimeUtility;
import jakarta.mail.util.ByteArrayDataSource;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import tw.lewishome.webapp.GlobalConstants;

/**
 *
 * MailSenderUtils 是一個用於發送電子郵件的工具類,支援多種寄信方式,包括透過 JNDI 資源或直接指定 SMTP 主機與埠號。
 * 此工具類可處理郵件主旨、內容、收件人、CC、BCC 及附件,並支援自訂寄件人名稱與編碼格式。
 *
 *
 *
 * 主要功能:
 * <ul>
 * <li>發送含附件的電子郵件</li>
 * <li>支援 HTML 格式郵件內容</li>
 * <li>可設定收件人、CC、BCC、寄件人名稱與地址</li>
 * <li>自動取得系統參數設定的 SMTP 主機、埠號與資源</li>
 * <li>支援 JNDI 方式取得郵件 Session,或直接建立 JavaMail Session</li>
 * <li>自動判斷附件檔案型態</li>
 * </ul>
 *
 *
 *
 * 使用方式:
 * <ul>
 * <li>可透過 sendMailMessage(MailMessageModel) 傳送自訂郵件物件</li>
 * <li>可使用 sendMailWithFiles(...) 傳送含附件的郵件</li>
 * <li>可使用 sendMailContent(...) 傳送純文字或 HTML 內容郵件</li>
 * </ul>
 *
 *
 *
 * 注意事項:
 * <ul>
 * <li>若 SMTP 主機或資源未設定,將使用預設值</li>
 * <li>附件需以 InputStream 方式傳入,並自動判斷檔案型態</li>
 * <li>支援多收件人、CC、BCC</li>
 * <li>郵件內容預設編碼為 UTF-8</li>
 * <li>寄信失敗時會回傳錯誤訊息,並記錄 log</li>
 * </ul>
 *
 *
 *
 * 主要成員說明:
 * <ul>
 * <li>sendMailMessage:以 MailMessageModel 物件發送郵件</li>
 * <li>sendMailWithFiles:以收件人、主旨、內容及檔案清單發送郵件</li>
 * <li>sendMailContent:以收件人、主旨、內容發送郵件(無附件)</li>
 * <li>getSession:自動取得郵件 Session,優先使用 JNDI 資源</li>
 * <li>getJavaMailSession:直接建立 JavaMail Session</li>
 * <li>getJbossMailResourceSession:透過 JNDI 取得郵件 Session</li>
 * <li>MailMessageModel:郵件內容物件,包含收件人、主旨、內容、附件等</li>
 * </ul>
 *
 *
 *
 * 適用場景:
 * <ul>
 * <li>Spring Boot 或 JBoss 等 Java Web 應用程式發送郵件通知</li>
 * <li>需支援多附件、多收件人、CC、BCC 的郵件發送需求</li>
 * <li>需自訂郵件主旨、內容格式(HTML)、寄件人資訊</li>
 * </ul>
 *
 *
 * @author Lewis
 * @version 1.0
 * @since 2024-06
 */

@Slf4j
public class MailSenderUtils {

    /**
     * Fix for javadoc warning :
     * use of default constructor, which does not provide a comment
     * Constructs a new MailSenderUtils instance.
     * This is the default constructor, implicitly provided by the compiler
     * and can be used to create a new instance of the class.
     * 
     * This constructor does not require any parameters and can be called without
     * any arguments.
     */
    public MailSenderUtils() {
        // Constructor body (can be empty)
    }

    /**
     * send Email Message with mailMessage Object
     *
     * @param mailMessageModel MailMessage 物件
     * @return String SendMail執行訊息
     */
    public String sendMailMessage(MailMessageModel mailMessageModel) {
        try {
            Session session = getSession();
            if (session == null) {
                return "get Mail Session error.";
            }
            // create a message
            MimeMessage mimeMessage = new MimeMessage(session);
            String mailSenderEmail = mailMessageModel.getSenderAddress();
            if (StringUtils.isBlank(mailSenderEmail)) {
                mailSenderEmail = GlobalConstants.ENV_VAR.get("MAIL_SENDER_ADDRESS");
            }
            String mailSenderName = mailMessageModel.getSenderName();
            if (StringUtils.isBlank(mailSenderName)) {
                mailSenderName = GlobalConstants.ENV_VAR.get("MAIL_SENDER_NAME");
            }
            Address from = new InternetAddress(mailSenderEmail, mailSenderName);
            mimeMessage.setFrom(from);
            String contentTestString = ""; 
            List<String>  mergedListRecipients = new ArrayList<>();
            if (mailMessageModel.getListRecipients()!= null) {
                mergedListRecipients.addAll(mailMessageModel.getListRecipients());
            }
            if (mailMessageModel.getListCCAddress()!= null) {
                mergedListRecipients.addAll(mailMessageModel.getListCCAddress());
            }
            if (mailMessageModel.getListBccAddress()!= null) {
                mergedListRecipients.addAll(mailMessageModel.getListBccAddress());
            }
            if (isAllEmailInvaild(mergedListRecipients)) {
                return "All recipients are invalid email address : sendMailMessage Failed.";        
            }
            // Recipients
            Address[] arrayAddressTO = getInternetAddress(mailMessageModel.getListRecipients());
            mimeMessage.setRecipients(jakarta.mail.Message.RecipientType.TO, arrayAddressTO);

            if (mailMessageModel.getListCCAddress() != null) {
                Address[] arrayAddressCC = getInternetAddress(mailMessageModel.getListCCAddress());
                mimeMessage.setRecipients(jakarta.mail.Message.RecipientType.CC, arrayAddressCC);
            }
            if (mailMessageModel.getListBccAddress() != null) {
                Address[] arrayAddressBCC = getInternetAddress(mailMessageModel.getListBccAddress());
                mimeMessage.setRecipients(jakarta.mail.Message.RecipientType.BCC, arrayAddressBCC);
            }
            // subject
            // mimeMessage.setSubject(MailMessageModel.getSubject());
            mimeMessage.setSubject(
                    MimeUtility.encodeText(mailMessageModel.getSubject(), GlobalConstants.DEFAULT_CHAR_SET, "B"));
            // create and fill the first message part
            MimeBodyPart mimeBodyPart = new MimeBodyPart();
            String contentBody = mailMessageModel.getContentBody();
            String contentMimeType = "text/html; charset=" + GlobalConstants.DEFAULT_CHAR_SET;
            mimeBodyPart.setContent(contentTestString + contentBody, contentMimeType);
            // create the Multipart and add its parts to it
            Multipart multipart = new MimeMultipart();
            multipart.addBodyPart(mimeBodyPart);
            // add the Multipart to the message
            mimeMessage.setContent(multipart);
            MailcapCommandMap mailcapCommandMap = (MailcapCommandMap) CommandMap.getDefaultCommandMap();
            mailcapCommandMap.addMailcap("text/html; x-java-content-handler=com.sun.mail.handlers.text_html");
            mailcapCommandMap.addMailcap("text/xml; x-java-content-handler=com.sun.mail.handlers.text_xml");
            mailcapCommandMap.addMailcap("text/plain; x-java-content-handler=com.sun.mail.handlers.text_plain");
            mailcapCommandMap.addMailcap("multipart/*; x-java-content-handler=com.sun.mail.handlers.multipart_mixed");
            mailcapCommandMap.addMailcap("message/rfc822; x-java-content-handler=com.sun.mail.handlers.message_rfc822");
            CommandMap.setDefaultCommandMap(mailcapCommandMap);
            List<MimeBodyPart> listMimeBodyPart = getAttached(mailMessageModel.getListAttachment());
            for (int i = 0; i < listMimeBodyPart.size(); i++) {
                multipart.addBodyPart(listMimeBodyPart.get(i));
            }
            Transport.send(mimeMessage);
        } catch (Exception ex) {
            ex.printStackTrace();
            return "Mail Sent Failed.";
        }
        return "Mail Sent Successfully.";
    }

    /**
     * send Email message with parm without attached
     *
     * @param recipients  ArrayList for recipients
     * @param subject     Subject String
     * @param contentBody Content String
     * @return String
     */
    public String sendMailContent(List<String> recipients, String subject, String contentBody) {
        
        log.info("sendMailContent:" + recipients.toString());
        MailMessageModel mailMessage = new MailMessageModel();
        mailMessage.setListRecipients(recipients);
        mailMessage.setSubject(subject);
        mailMessage.setContentBody(contentBody);
        List<MailMessageModel.Attachment> listAttachment = new ArrayList<>();
        mailMessage.setListAttachment(listAttachment);
        return sendMailMessage(mailMessage);
    }

    /**
     * send Email message with parm
     *
     * @param recipients  收件者 Email List
     * @param subject     Subject String (主旨)
     * @param contentBody Context String (內文)
     * @param listFiles   Attached File... ( 附件檔案)
     * @return String SendMail 執行訊息
     */
    public String sendMailWithFiles(List<String> recipients, String subject, String contentBody, File... listFiles) {
        MailMessageModel mailMessage = new MailMessageModel();
        mailMessage.setListRecipients(recipients);
        mailMessage.setSubject(subject);
        mailMessage.setContentBody(contentBody);
        List<MailMessageModel.Attachment> listAttachment = new ArrayList<>();
        for (File oneFile : listFiles) {
            try (InputStream fileInputStream = new FileInputStream(oneFile)) {
                MailMessageModel.Attachment oneAttachment = new MailMessageModel.Attachment();
                oneAttachment.setFileName(oneFile.getName());
                oneAttachment.setInputStream(fileInputStream);
                listAttachment.add(oneAttachment);
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
        mailMessage.setListAttachment(listAttachment);
        return sendMailMessage(mailMessage);
    }

    /**
     * get Mail Session
     *
     * @return Session 取得 Mail Session
     */

    public Session getSession() {
        Session rtnSession = null;

        String smtpServer = GlobalConstants.ENV_VAR.get("SMTP_SERVER");
        String smtpPort = GlobalConstants.ENV_VAR.get("SMTP_PORT");
        String mailResourceJndi = GlobalConstants.ENV_VAR.get("MAIL_RESOURCE_JNDI");
        rtnSession = getJbossMailResourceSession(mailResourceJndi);
        if (rtnSession == null) {
            int intSmtpPort = Integer.parseInt(smtpPort);
            rtnSession = getJavaMailSession(smtpServer, intSmtpPort);
        }
        return rtnSession;
    }

    private List<MimeBodyPart> getAttached(List<MailMessageModel.Attachment> listAttachment) throws IOException {
        List<MimeBodyPart> listAttachmentPart = new ArrayList<>();
        String oneFileName = "";
        MimetypesFileTypeMap fileTypeMap = new MimetypesFileTypeMap();

        for (MailMessageModel.Attachment oneAttachment : listAttachment) {
            MimeBodyPart attachmentPart = new MimeBodyPart();
            oneFileName = oneAttachment.getFileName();
            String oneFileType = oneAttachment.getFileType();
            if ("".equalsIgnoreCase(oneFileType)) {
                oneFileType = fileTypeMap.getContentType(oneFileName);
            }
            try (InputStream oneInputStream = oneAttachment.getInputStream()) {
                // InputStream oneInputStream = oneAttachment.getInputStream();
                ByteArrayDataSource byteArrayDataSource = new ByteArrayDataSource(oneInputStream, oneFileType);
                attachmentPart.setFileName(MimeUtility.encodeText(oneFileName, GlobalConstants.DEFAULT_CHAR_SET, "B"));
                attachmentPart.setDataHandler(new DataHandler(byteArrayDataSource));
                attachmentPart.setDisposition(Part.ATTACHMENT);
                listAttachmentPart.add(attachmentPart);
            } catch (Exception ex) {
                throw new IOException("attachmentPart IOException " + oneFileName);
            }
        }
        return listAttachmentPart;
    }

    private Address[] getInternetAddress(List<String> listAddress) throws AddressException {
        Address[] arrayAddress = new InternetAddress[listAddress.size()];
        try {
            for (int i = 0; i < listAddress.size(); i++) {
                arrayAddress[i] = new InternetAddress(listAddress.get(i));
            }
        } catch (Exception ex) {
            throw new AddressException("Internet Address AddressException");
        }
        return arrayAddress;
    }

    private Boolean isAllEmailInvaild(List<String> listAddress) {
        boolean isAllInvalidRecipients = true;
       
        for (String oneRecipient : listAddress) {
            if (StringUtils.isNotBlank(oneRecipient)) {
                if (CommUtils.isValidEmailAddress(oneRecipient)) {
                    isAllInvalidRecipients = false;
                    break;
                }
            }
        }
        return isAllInvalidRecipients;
    }

    /**
     * get Java Mail Session with host port
     *
     * @param smtpHost String SMTP Host name or IP
     * @param smtpPort int SMTP port Number
     * @return Session 取得 Mail Session
     */
    public Session getJavaMailSession(String smtpHost, int smtpPort) {
        System.setProperty("mail.mime.splitLongParameters", "false");
        System.setProperty("java.net.preferIPv4Stack", "true"); // 以IPv4方式寄信
        try {
            Boolean pingMailServer = NetUtils.checkSocket(smtpHost, smtpPort, 100);
            if (Boolean.FALSE.equals(pingMailServer)) {
                log.info("Ping SMTP Server Error." + smtpHost + ":" + smtpPort);
                return null;
            }
        } catch (Exception ex) {
            ex.printStackTrace();
            log.error("Ping SMTP Server Exception." + smtpHost + ":" + smtpPort);
            return null;
        }

        // for Spring Boot mail session
        Properties props = System.getProperties();
        props.put("mail.smtp.host", smtpHost);
        props.put("mail.smtp.port", smtpPort);
        Session mailSession = Session.getInstance(props);
        if (mailSession == null) {
            log.info("Get Jboss Mail Session Failed. " + smtpHost + ":" + smtpPort);
            return null;
        }
        return mailSession;
    }

    /**
     * get Java Mail Session with JNDI
     *
     * @param mailResource String(JNDI )
     * @return Session 取得 Mail Session
     */
    public Session getJbossMailResourceSession(String mailResource) {
        System.setProperty("mail.mime.splitlongparameters", "false");
        System.setProperty("java.net.preferIPv4Stack", "true"); // 以IPv4方式寄信
        try {
            // get mail session from container (Jboss or Liberty)
            // InitialContext initialContext = new InitialContext();
            // Session mailSession = (Session) initialContext.lookup(mailResource);
            if (StringUtils.isNotBlank(mailResource)) {
                Session mailSession = (Session) new InitialContext().lookup(mailResource);
                log.info("Get Mail Resource " + mailResource);
                return mailSession;
            }
        } catch (NamingException ex) {
            ex.printStackTrace();
            log.info("Get Jboss Mail Resource Failed. " + mailResource);
        }
        return null;
    }

    /**
     * SMTP MAIL Message 物件
     *
     * @author Lewis
     **/
    @Data
    public static class MailMessageModel implements Serializable {

        /**
         * Fix for javadoc warning :
         * use of default constructor, which does not provide a comment
         * 
         * Constructs a new MailMessageModel instance.
         * This is the default constructor, implicitly provided by the compiler
         * if no other constructors are defined.
         */
        public MailMessageModel() {
            // Constructor body (can be empty)
        }

        /** serialVersionUID */
        private static final long serialVersionUID = 1L;

        /** 寄件者Email */
        private String senderAddress = ""; // sender email address
        /** 寄件者名稱 */
        private String senderName = ""; // sender name
        /** 收件者Email List */
        private List<String> listRecipients; // list of recipient
        /** CC收件者Email List */
        private List<String> listCCAddress; // list of cc
        /** BCC收件者Email List */
        private List<String> listBccAddress; // list of bcc
        /** Email主旨 */
        private String subject = ""; // mail subject
        /** Email內容 */
        private String contentBody = ""; // main content body
        /** Email附件檔案 */
        private List<Attachment> listAttachment; //

        /**
         * SMTP MAIL Message 附件明細資料
         *
         * @author Lewis
         **/
        @Data
        public static class Attachment implements Serializable {

            /**
             * Fix for javadoc warning :
             * use of default constructor, which does not provide a comment
             * 
             * Constructs a new Attachment instance.
             * This is the default constructor, implicitly provided by the compiler
             * if no other constructors are defined.
             */
            public Attachment() {
                // Constructor body (can be empty)
            }

            /** serialVersionUID */
            private static final long serialVersionUID = 1L;

            /** Email檔案名稱 */
            private String fileName = "";
            /** Email檔案類型 */
            private String fileType = "";
            /** Email檔案 (InputStream) */
            private InputStream inputStream;
        }
    }

}

單元測試程式碼 MailSenderUtilsTest.java

package tw.lewishome.webapp.base.utility.common;

import static org.junit.jupiter.api.Assertions.*;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

import com.icegreen.greenmail.util.GreenMail;
import com.icegreen.greenmail.util.GreenMailUtil;
import com.icegreen.greenmail.util.ServerSetup;
import jakarta.mail.MessagingException;
import jakarta.mail.Session;
import jakarta.mail.internet.MimeMessage;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;

import tw.lewishome.webapp.GlobalConstants;
import tw.lewishome.webapp.base.utility.common.MailSenderUtils.MailMessageModel;

/**
 * MailSenderUtils 的單元測試類別
 */
class MailSenderUtilsTest {

    private MailSenderUtils mailSender;
    private GreenMail greenMail;
    private static final String TEST_SMTP_HOST = "127.0.0.1";
    private static final int TEST_SMTP_PORT = 3025; // GreenMail 預設 SMTP 埠號
    private static final String TEST_SENDER = "sender@test.com";
    private static final String TEST_RECIPIENT = "recipient@test.com";

    @TempDir
    Path tempDir;

    @BeforeEach
    void setUp() {
        // 啟動 GreenMail
        ServerSetup serverSetup = new ServerSetup(TEST_SMTP_PORT, TEST_SMTP_HOST, ServerSetup.PROTOCOL_SMTP);
        greenMail = new GreenMail(serverSetup);
        greenMail.start();
        System.out.println("GreenMail started on " + serverSetup.getBindAddress() + ":" + serverSetup.getPort());

        mailSender = new MailSenderUtils();
        // 設定測試用的環境變數
        GlobalConstants.ENV_VAR.put("SMTP_SERVER", TEST_SMTP_HOST);
        GlobalConstants.ENV_VAR.put("SMTP_PORT", String.valueOf(TEST_SMTP_PORT));
        GlobalConstants.ENV_VAR.put("MAIL_SENDER_ADDRESS", TEST_SENDER);
        GlobalConstants.ENV_VAR.put("MAIL_SENDER_NAME", "Test Sender");
    }

    @AfterEach
    void tearDown() {
        // 停止 GreenMail 伺服器
        if (greenMail != null) {
            greenMail.stop();
        }
        // 清理環境變數
        GlobalConstants.ENV_VAR.remove("SMTP_SERVER");
        GlobalConstants.ENV_VAR.remove("SMTP_PORT");
        GlobalConstants.ENV_VAR.remove("MAIL_SENDER_ADDRESS");
        GlobalConstants.ENV_VAR.remove("MAIL_SENDER_NAME");
    }

    @Test
    void testSendMailContent() throws MessagingException {
        // 準備測試數據
        List<String> recipients = Arrays.asList(TEST_RECIPIENT);
        String subject = "Test Subject";
        String content = "<html><body>Test Content</body></html>";

        // 執行測試
        String result = mailSender.sendMailContent(recipients, subject, content);

        // 驗證結果
        assertEquals("Mail Sent Successfully.", result);

        // 驗證郵件內容
        MimeMessage[] receivedMessages = greenMail.getReceivedMessages();
        assertEquals(1, receivedMessages.length);
        MimeMessage receivedMessage = receivedMessages[0];

        assertEquals(subject, receivedMessage.getSubject());
        assertTrue(GreenMailUtil.getBody(receivedMessage).contains("Test Content"));
        assertEquals(TEST_RECIPIENT, receivedMessage.getAllRecipients()[0].toString());
    }

    @Test
    void testSendMailWithFiles() throws IOException {
        // 準備測試檔案
        File testFile = tempDir.resolve("test.txt").toFile();
        FileUtils.stringListToFile(Arrays.asList("Test content"), testFile.getAbsolutePath());

        // 準備測試數據
        List<String> recipients = Arrays.asList(TEST_RECIPIENT);
        String subject = "Test Subject with Attachment";
        String content = "Test Content with Attachment";

        // 執行測試
        String result = mailSender.sendMailWithFiles(recipients, subject, content, testFile);

        // 驗證結果
        assertNotNull(result);

        // 清理測試檔案
        FileUtils.deleteFile(testFile.getAbsolutePath());
    }

    @Test
    void testMailMessageModel() throws MessagingException, IOException {
        // 建立完整的郵件物件

        mailSender = new MailSenderUtils();
        MailMessageModel mailMessage = new MailSenderUtils.MailMessageModel();

        mailMessage.setSenderAddress(TEST_SENDER);
        mailMessage.setSenderName("Test Sender");
        mailMessage.setListRecipients(Arrays.asList(TEST_RECIPIENT));
        mailMessage.setListCCAddress(Arrays.asList("cc@test.com"));
        mailMessage.setListBccAddress(Arrays.asList("bcc@test.com"));
        mailMessage.setSubject("Test Complete Message");
        mailMessage.setContentBody("Test Complete Content");

        // 建立並添加附件
        File testFile = tempDir.resolve("test.txt").toFile();
        FileUtils.stringListToFile(Arrays.asList("Test attachment content"), testFile.getAbsolutePath());

        List<MailMessageModel.Attachment> attachments = new ArrayList<>();
        MailMessageModel.Attachment attachment = new MailMessageModel.Attachment();
        attachment.setFileName("test.txt");
        attachment.setFileType("text/plain");
        // 設定附件的 InputStream
        attachment.setInputStream(java.nio.file.Files.newInputStream(testFile.toPath()));
        attachments.add(attachment);
        mailMessage.setListAttachment(attachments);

        // 執行測試
        String result = mailSender.sendMailMessage(mailMessage);

        // 驗證結果
        assertEquals("Mail Sent Successfully.", result);

        // 驗證郵件內容
        MimeMessage[] receivedMessages = greenMail.getReceivedMessages();
        for (int i = 0; i < receivedMessages.length; i++) {
            System.out.println("Received message " + i + " subject: " + receivedMessages[i].getSubject());
        }

        // 三個receiver
        assertEquals(3, receivedMessages.length);
        MimeMessage receivedMessage = receivedMessages[0];

        assertEquals("Test Complete Message", receivedMessage.getSubject());
        assertTrue(GreenMailUtil.getBody(receivedMessage).contains("Test Complete Content"));

        // 驗證收件人
        assertEquals(TEST_RECIPIENT, receivedMessage.getAllRecipients()[0].toString());

        // 清理測試檔案
        FileUtils.deleteFile(testFile.getAbsolutePath());
    }

    @Test
    void testGetJavaMailSession() {
        // 測試取得 JavaMail Session
        Session session = mailSender.getJavaMailSession(TEST_SMTP_HOST, TEST_SMTP_PORT);

        // 因為測試環境可能無法連接 SMTP,所以這裡可能為 null
        if (session != null) {
            assertNotNull(session.getProperties());
            assertEquals(TEST_SMTP_HOST, session.getProperties().getProperty("mail.smtp.host"));
        }
        @SuppressWarnings("null")
        String sessionSmtpPort = session.getProperties().getProperty("mail.smtp.port");
        // 因為測試環境可能無法連接 SMTP,所以這裡可能為 null
        if (sessionSmtpPort != null) {
            assertEquals(String.valueOf(TEST_SMTP_PORT), sessionSmtpPort);
        }
        
    }

    @Test
    void testGetJbossMailResourceSession() {
        // 測試 JNDI 資源
        String testJndi = "java:jboss/mail/Default";
        Session session = mailSender.getJbossMailResourceSession(testJndi);

        // 在測試環境中,因為沒有實際的 JNDI 資源,預期會返回 null
        assertNull(session);
    }

    @Test
    void testGetSession() {
        // 測試預設的 Session 取得方法
        Session session = mailSender.getSession();

        // 在測試環境中可能返回 null(因為無法連接 SMTP 伺服器)
        if (session != null) {
            Properties props = session.getProperties();
            assertNotNull(props);
            assertEquals(TEST_SMTP_HOST, props.getProperty("mail.smtp.host"));
        }
    }

    @Test
    void testSendMailWithInvalidRecipient() {
        // 測試無效的收件人地址
        List<String> recipients = Arrays.asList("invalid-email");
        String subject = "Test Subject";
        String content = "Test Content";

        String result = mailSender.sendMailContent(recipients, subject, content);

        // 預期會回傳錯誤訊息
        assertTrue(result.contains("Failed") || result.contains("error"));
    }

    @Test
    void testSendMailWithEmptyContent() {
        // 測試空內容
        List<String> recipients = Arrays.asList(TEST_RECIPIENT);
        String subject = "Test Subject";
        String content = "";

        String result = mailSender.sendMailContent(recipients, subject, content);

        assertNotNull(result);
    }

    @Test
    void testSendMailWithLargeAttachment() throws IOException {
        // 準備大型測試檔案
        File largeFile = tempDir.resolve("large.txt").toFile();
        List<String> largeContent = new ArrayList<>();
        for (int i = 0; i < 1000; i++) {
            largeContent.add("Test content line " + i);
        }
        FileUtils.stringListToFile(largeContent, largeFile.getAbsolutePath());

        // 執行測試
        List<String> recipients = Arrays.asList(TEST_RECIPIENT);
        String subject = "Test Large Attachment";
        String content = "Test Content with Large Attachment";

        String result = mailSender.sendMailWithFiles(recipients, subject, content, largeFile);

        // 驗證結果
        assertNotNull(result);

        // 清理測試檔案
        FileUtils.deleteFile(largeFile.getAbsolutePath());
    }

    @Test
    void testSendMailWithMultipleRecipients() {
        // 測試多個收件人
        List<String> recipients = Arrays.asList(
                "recipient1@test.com",
                "recipient2@test.com",
                "recipient3@test.com");
        String subject = "Test Multiple Recipients";
        String content = "Test Content";

        String result = mailSender.sendMailContent(recipients, subject, content);

        assertNotNull(result);
    }
}

圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言