跳至主要内容

郵件模組

Package: io.leandev.appfuse.mail.*

AppFuse Server 提供完整的郵件發送工具集,支援 SMTP 和 OAuth2 認證(Gmail、Office365),並提供防火牆功能用於開發和測試環境。

核心特色

1. 認證方式

認證方式類別適用場景
Basic SMTPBasicAuthenticator傳統帳號密碼認證
Gmail OAuth2OAuth2MailAuthenticatorBuilderGoogle Workspace
Office365 OAuth2OAuth2MailAuthenticatorBuilderMicrosoft 365

2. 防火牆功能

  • 開發環境:啟用防火牆,只允許發送到特定網域
  • 測試環境:阻擋所有郵件或限制收件者
  • 生產環境:停用防火牆,正常發送

3. Fluent API

使用 Builder 模式,程式碼簡潔易讀。

基本用法

建立 Mailer

import io.leandev.appfuse.mail.*;
import org.springframework.mail.javamail.JavaMailSender;

// 1. 建立 JavaMailSender(SMTP 認證)
JavaMailSender javaMailSender = JavaMailSenderBuilder
.create("smtp.example.com", 587)
.smtp("smtp.example.com", 587, "user@example.com", "password")
.build();

// 2. 建立 Mailer
Mailer mailer = MailerBuilder.create(javaMailSender)
.enableFirewall() // 啟用防火牆
.allowDomain("example.com") // 只允許發送到 example.com
.allowDomain("test.com") // 也允許 test.com
.enableDebug() // 啟用 JavaMail debug
.build();

發送簡單郵件

import org.springframework.mail.SimpleMailMessage;

SimpleMailMessage message = new SimpleMailMessage();
message.setFrom("sender@example.com");
message.setTo("recipient@example.com");
message.setSubject("測試郵件");
message.setText("這是一封測試郵件");

mailer.send(message);

發送 MIME 郵件(HTML + 附件)

import io.leandev.appfuse.mail.MimeMessageBuilder;

MimeMessageBuilder.create(mailer)
.from("sender@example.com")
.to("recipient@example.com")
.to("another@example.com", "收件者名稱") // 可指定顯示名稱
.cc("cc@example.com")
.subject("含附件的 HTML 郵件")
.html("<h1>標題</h1><p>這是 <strong>HTML</strong> 內容</p>")
.attachment("report.pdf", "/path/to/report.pdf")
.attachment("data.xlsx", excelBytes, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
.send(); // 建立並發送

使用 InputStream 附件

try (InputStream inputStream = new FileInputStream("document.pdf")) {
MimeMessageBuilder.create(mailer)
.from("sender@example.com")
.to("recipient@example.com")
.subject("附件測試")
.text("請查閱附件")
.attachment(inputStream, "document.pdf", "application/pdf")
.send();
}

OAuth2 認證

Gmail OAuth2

// 1. 建立 OAuth2 認證器
OAuth2MailAuthenticator authenticator = OAuth2MailAuthenticatorBuilder.forGmail()
.clientId("your-client-id")
.clientSecret("your-client-secret")
.username("sender@gmail.com")
.build();

// 2. 建立 JavaMailSender
JavaMailSender javaMailSender = JavaMailSenderBuilder.forGmail(authenticator)
.build();

// 3. 建立 Mailer
Mailer mailer = MailerBuilder.create(javaMailSender).build();

Office365 OAuth2

// 1. 建立 OAuth2 認證器
OAuth2MailAuthenticator authenticator = OAuth2MailAuthenticatorBuilder
.forOffice365("your-tenant-id")
.clientId("your-client-id")
.clientSecret("your-client-secret")
.username("sender@yourcompany.onmicrosoft.com")
.build();

// 2. 建立 JavaMailSender
JavaMailSender javaMailSender = JavaMailSenderBuilder.forOffice365(authenticator)
.build();

// 3. 建立 Mailer
Mailer mailer = MailerBuilder.create(javaMailSender).build();

OAuth2 應用程式設定

Gmail (Google Cloud Console):

  1. 建立 OAuth 2.0 用戶端 ID
  2. 啟用 Gmail API
  3. 設定 scope: https://www.googleapis.com/auth/gmail.send

Office365 (Azure Portal):

  1. 註冊應用程式
  2. 新增 API 權限: https://outlook.office365.com/.default
  3. 授予管理員同意

防火牆功能

啟用防火牆

// 建立時啟用
Mailer mailer = MailerBuilder.create(javaMailSender)
.enableFirewall()
.allowDomain("dev.example.com")
.allowDomains("test.com", "staging.example.com")
.build();

// 或執行時動態控制
mailer.enableFirewall();
mailer.addAllowedDomain("new-domain.com");
mailer.removeAllowedDomain("old-domain.com");
mailer.disableFirewall();

防火牆行為

情境行為
防火牆停用所有郵件正常發送
防火牆啟用 + 無白名單阻擋所有郵件
防火牆啟用 + 有白名單只發送給白名單網域的收件者
部分收件者在白名單過濾後發送(只發給白名單收件者)

Spring Profile 整合

@Configuration
public class MailConfig {

@Bean
@Profile("dev")
public Mailer devMailer(JavaMailSender javaMailSender) {
return MailerBuilder.create(javaMailSender)
.enableFirewall()
.allowDomain("dev.example.com")
.enableDebug()
.build();
}

@Bean
@Profile("prod")
public Mailer prodMailer(JavaMailSender javaMailSender) {
return MailerBuilder.create(javaMailSender)
.disableFirewall()
.build();
}
}

測試與除錯

連接測試

// 測試 SMTP 連接
Mailer.MailConnectionTestResult result = mailer.testConnection();

if (result.isSuccess()) {
System.out.println("連接成功: " + result.getHost() + ":" + result.getPort());
} else {
System.out.println("連接失敗: " + result.getMessage());
}

發送測試郵件

// 發送測試郵件(會暫時繞過防火牆)
Mailer.MailSendTestResult result = mailer.sendTestEmail(
"test@example.com",
"郵件發送器測試"
);

if (result.isSuccess()) {
System.out.println("測試郵件發送成功");
} else {
System.out.println("測試郵件發送失敗: " + result.getMessage());
}

取得發送器資訊

Mailer.MailSenderInfo info = mailer.getMailSenderInfo();

System.out.println("Host: " + info.getHost());
System.out.println("Port: " + info.getPort());
System.out.println("Protocol: " + info.getProtocol());
System.out.println("防火牆啟用: " + info.isFirewallEnabled());
System.out.println("白名單數量: " + info.getAllowedDomainsCount());

Debug 模式

// 啟用 JavaMail debug(輸出詳細 SMTP 交互日誌)
mailer.enableDebug();

// 發送郵件...

// 停用 debug
mailer.disableDebug();

// 檢查狀態
boolean isDebug = mailer.isDebugEnabled();

動態更新

更新 JavaMailSender

// 執行時更新底層 JavaMailSender(例如 OAuth2 token 更新)
JavaMailSender newSender = JavaMailSenderBuilder.forGmail(newAuthenticator).build();
mailer.updateDelegate(newSender);

動態控制防火牆

// 執行時控制防火牆
mailer.enableFirewall();
mailer.setAllowedDomains(Set.of("domain1.com", "domain2.com"));
mailer.addAllowedDomain("domain3.com");
mailer.clearAllowedDomains();
mailer.disableFirewall();

Spring Boot 配置範例

application.yml

# 開發環境
spring:
profiles: dev
mail:
host: smtp.gmail.com
port: 587

app:
mail:
firewall:
enabled: true
allowed-domains:
- dev.example.com
- test.example.com
debug: true

---
# 生產環境
spring:
profiles: prod
mail:
host: smtp.gmail.com
port: 587

app:
mail:
firewall:
enabled: false
debug: false

配置類別

@Configuration
@ConfigurationProperties(prefix = "app.mail")
public class MailProperties {
private Firewall firewall = new Firewall();
private boolean debug = false;

@Data
public static class Firewall {
private boolean enabled = false;
private Set<String> allowedDomains = new HashSet<>();
}

// getters and setters
}

@Configuration
public class MailConfig {

@Bean
public Mailer mailer(JavaMailSender javaMailSender, MailProperties properties) {
MailerBuilder builder = MailerBuilder.create(javaMailSender);

if (properties.getFirewall().isEnabled()) {
builder.enableFirewall();
properties.getFirewall().getAllowedDomains()
.forEach(builder::allowDomain);
}

if (properties.isDebug()) {
builder.enableDebug();
}

return builder.build();
}
}

最佳實踐

1. 環境分離

// 開發環境:啟用防火牆 + debug
// 測試環境:啟用防火牆,白名單測試信箱
// 生產環境:停用防火牆

2. 錯誤處理

try {
MimeMessageBuilder.create(mailer)
.from("sender@example.com")
.to("recipient@example.com")
.subject("重要通知")
.html(htmlContent)
.send();
} catch (MailException e) {
log.error("郵件發送失敗", e);
// 記錄到資料庫或通知系統
}

3. 附件大小控制

// 建議限制附件大小
long maxSize = 10 * 1024 * 1024; // 10MB
if (file.length() > maxSize) {
throw new IllegalArgumentException("附件過大");
}

4. 使用 try-with-resources

// InputStream 附件使用 try-with-resources
try (InputStream is = new FileInputStream(file)) {
MimeMessageBuilder.create(mailer)
.attachment(is, file.getName())
.send();
}

下一步