技術棧選型
| 層級 | 技術選擇 | 說明 |
| 核心協議 | IMAP / POP3 | QQ 郵箱支持 IMAP(推薦,支持雙向同步)和 POP3
|
| Java 庫 | Jakarta Mail (原 JavaMail) | 官方標準郵件 API,Maven 坐標:com.sun.mail:jakarta.mail
|
| 構建工具 | Maven / Gradle | 依賴管理
|
| IDE | IntelliJ IDEA / Eclipse | 開發調試
|
QQ 郵箱配置前置條件
- 登錄 QQ 郵箱網頁版 → 設置 → 賬戶
- 開啟 IMAP 服務(或 POP3)
- 生成/獲取 授權碼(非 QQ 登錄密碼,專用於第三方客戶端)
- 記錄服務器地址:
- IMAP:
imap.qq.com,端口 993 (SSL) / 143 (STARTTLS)
- POP3:
pop.qq.com,端口 995 (SSL) / 110 (STARTTLS)
核心實現代碼
import jakarta.mail.*;
import jakarta.mail.internet.MimeMultipart;
import jakarta.mail.internet.MimeBodyPart;
import java.io.File;
import java.io.FileOutputStream;
import java.util.Properties;
public class QQMailAttachmentDownloader {
// 配置常量
private static final String IMAP_HOST = "imap.qq.com";
private static final String USERNAME = "your_qq@qq.com";
private static final String AUTH_CODE = "your_authorization_code"; // 授權碼
private static final String SAVE_DIR = "D:/Attachments/";
public static void main(String[] args) {
downloadAttachments();
}
public static void downloadAttachments() {
Properties props = new Properties();
props.setProperty("mail.store.protocol", "imaps"); // SSL
props.setProperty("mail.imaps.host", IMAP_HOST);
props.setProperty("mail.imaps.port", "993");
props.setProperty("mail.imaps.ssl.enable", "true");
Session session = Session.getInstance(props);
session.setDebug(true); // 開啟調試輸出
try (Store store = session.getStore("imaps")) {
store.connect(USERNAME, AUTH_CODE);
Folder inbox = store.getFolder("INBOX");
inbox.open(Folder.READ_WRITE); // READ_WRITE 允許設置已讀/刪除
// 搜索條件:未讀、有附件、特定發件人等
FlagTerm unreadFlag = new FlagTerm(new Flags(Flags.Flag.SEEN), false);
Message[] messages = inbox.search(unreadFlag);
System.out.println("找到未讀郵件: " + messages.length + " 封");
for (Message message : messages) {
processMessage(message);
// 可選:標記已讀
message.setFlag(Flags.Flag.SEEN, true);
}
} catch (Exception e) {
e.printStackTrace();
}
}
private static void processMessage(Message message) throws Exception {
String subject = MimeUtility.decodeText(message.getSubject());
Address[] from = message.getFrom();
String sender = from.length > 0 ? from[0].toString() : "Unknown";
System.out.println("處理郵件: " + subject + " From: " + sender);
Object content = message.getContent();
if (content instanceof Multipart) {
Multipart multipart = (Multipart) content;
for (int i = 0; i < multipart.getCount(); i++) {
BodyPart bodyPart = multipart.getBodyPart(i);
if (Part.ATTACHMENT.equalsIgnoreCase(bodyPart.getDisposition())
|| bodyPart.getFileName() != null) {
saveAttachment(bodyPart);
}
}
}
}
private static void saveAttachment(BodyPart bodyPart) throws Exception {
String fileName = MimeUtility.decodeText(bodyPart.getFileName());
if (fileName == null) return;
File saveDir = new File(SAVE_DIR);
if (!saveDir.exists()) saveDir.mkdirs();
File saveFile = new File(saveDir, fileName);
// 處理重名文件
int counter = 1;
String baseName = fileName.substring(0, fileName.lastIndexOf('.'));
String extension = fileName.substring(fileName.lastIndexOf('.'));
while (saveFile.exists()) {
saveFile = new File(saveDir, baseName + "_" + counter++ + extension);
}
try (InputStream is = bodyPart.getInputStream();
FileOutputStream fos = new FileOutputStream(saveFile)) {
byte[] buffer = new byte[8192];
int len;
while ((len = is.read(buffer)) > 0) {
fos.write(buffer, 0, len);
}
}
System.out.println("附件已保存: " + saveFile.getAbsolutePath());
}
}
Maven 依賴配置
<dependencies>
<!-- Jakarta Mail 2.x (Java 8+) -->
<dependency>
<groupId>com.sun.mail</groupId>
<artifactId>jakarta.mail</artifactId>
<version>2.0.1</version>
</dependency>
<!-- 舊版 JavaMail 1.6 (Java 7/8) -->
<dependency>
<groupId>com.sun.mail</groupId>
<artifactId>javax.mail</artifactId>
<version>1.6.2</version>
</dependency>
</dependencies>
進階功能擴展
| 功能 | 實現要點 |
| 定時自動下載 | Spring @Scheduled 或 Quartz 定時任務 |
| 增量同步 | 記錄最後處理郵件 UID,下次從 UID 之後搜索 |
| 附件類型過濾 | 檢查 bodyPart.getContentType() 或文件擴展名 |
| 多郵箱支持 | 配置文件化,工廠模式創建 Session |
| 錯誤重試與告警 | 指數退避重試 + 郵件/釘釘/企微通知 |
| 大附件流式處理 | 使用 SharedFileInputStream 避免內存溢出 |
常見坑點避雷
- ❌ 使用 QQ 登錄密碼而非授權碼 → 認證失敗
- ❌ 忽略 SSL/TLS 配置 → 連接超時或握手失敗
- ❌ 未處理 MimeUtility.decodeText → 中文附件名亂碼
- ❌ 未關閉 Folder/Store → 連接泄漏,導致郵箱服務器拒絕新連接
- ❌ 單線程同步大量郵件 → 超時,建議分批、異步處理
安全合規提醒
- 授權碼屬敏感信息,嚴禁硬編碼,應使用環境變量、配置中心、密鑰管理系統
- 遵守《網絡安全法》《個人信息保護法》,僅下載業務必需附件
- 實施最小權限原則:郵箱賬戶僅授權 IMAP 讀取,不開啟 SMTP 發信權限
- 定期輪換授權碼,審計下載日誌