首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 程式設計疑難解答

Java 實現 QQ 郵箱自動下載附件:基於 JavaMail API 的完整方案

     編輯:程式設計疑難解答

技術棧選型

層級技術選擇說明
核心協議IMAP / POP3QQ 郵箱支持 IMAP(推薦,支持雙向同步)和 POP3
Java 庫Jakarta Mail (原 JavaMail)官方標準郵件 API,Maven 坐標:com.sun.mail:jakarta.mail
構建工具Maven / Gradle依賴管理
IDEIntelliJ IDEA / Eclipse開發調試

QQ 郵箱配置前置條件

  1. 登錄 QQ 郵箱網頁版 → 設置 → 賬戶
  2. 開啟 IMAP 服務(或 POP3)
  3. 生成/獲取 授權碼(非 QQ 登錄密碼,專用於第三方客戶端)
  4. 記錄服務器地址:
    • 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 → 連接泄漏,導致郵箱服務器拒絕新連接
  • ❌ 單線程同步大量郵件 → 超時,建議分批、異步處理

安全合規提醒

  1. 授權碼屬敏感信息,嚴禁硬編碼,應使用環境變量、配置中心、密鑰管理系統
  2. 遵守《網絡安全法》《個人信息保護法》,僅下載業務必需附件
  3. 實施最小權限原則:郵箱賬戶僅授權 IMAP 讀取,不開啟 SMTP 發信權限
  4. 定期輪換授權碼,審計下載日誌
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved