郵件模組
Package:
io.leandev.appfuse.mail.*
AppFuse Server 提供完整的郵件發送工具集,支援 SMTP 和 OAuth2 認證(Gmail、Office365),並提供防火牆功能用於開發和測試環境。
核心特色
1. 認證方式
| 認證方式 | 類別 | 適用場景 |
|---|---|---|
| Basic SMTP | BasicAuthenticator | 傳統帳號密碼認證 |
| Gmail OAuth2 | OAuth2MailAuthenticatorBuilder | Google Workspace |
| Office365 OAuth2 | OAuth2MailAuthenticatorBuilder | Microsoft 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):
- 建立 OAuth 2.0 用戶端 ID
- 啟用 Gmail API
- 設定 scope:
https://www.googleapis.com/auth/gmail.send
Office365 (Azure Portal):
- 註冊應用程式
- 新增 API 權限:
https://outlook.office365.com/.default - 授予管理員同意
防火牆功能
啟用防火牆
// 建立時啟用
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();
}
下一步
- OAuth2 模組 - 深入了解 OAuth2 認證機制
- Security 模組 - 安全性相關功能
- HTTP 模組 - HTTP 客戶端工具