首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 程式設計入門

Java 實作 FTP 檔案下載完整範例與最佳實踐

     編輯:Java 程式設計入門

前言

在企業級應用開發中,透過 FTP 協定進行檔案傳輸是常見需求。本文提供一個基於 Apache Commons Net 庫的完整 Java FTP 下載實作範例,涵蓋連線設定、檔案列表獲取、二進位傳輸模式、資源釋放等關鍵環節,並說明生產環境部署時的注意事項。

核心實作程式碼

以下範例展示了從 FTP 伺服器批次下載指定目錄下所有檔案的完整流程:

import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPClientConfig;
import org.apache.commons.net.ftp.FTPFile;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

public class FtpDownloader {

    private static final int DEFAULT_PORT = 21;
    private static final int CONNECT_TIMEOUT = 120_000; // 120 秒
    private static final int BUFFER_SIZE = 1024;

    public void downloadFiles() {
        FTPClient ftpClient = null;
        InputStream inputStream = null;
        List processedFiles = new ArrayList<>();

        try {
            // 1. 初始化 FTP 客戶端與連線參數
            ftpClient = new FTPClient();
            ftpClient.setDefaultPort(DEFAULT_PORT);
            ftpClient.setConnectTimeout(CONNECT_TIMEOUT);
            ftpClient.setDefaultTimeout(CONNECT_TIMEOUT);
            ftpClient.setDataTimeout(CONNECT_TIMEOUT);
            ftpClient.setControlEncoding("UTF-8");

            // 2. 設定伺服器類型為 UNIX,避免檔案列表解析錯誤
            FTPClientConfig config = new FTPClientConfig(FTPClientConfig.SYST_UNIX);
            config.setServerLanguageCode("en");
            config.setDefaultDateFormatStr("MMM dd HH:mm");
            ftpClient.configure(config);

            // 3. 連線並登入 (配置資訊建議從外部配置檔讀取)
            String host = SystemConfig.getKey("scpFtpIp");
            String username = SystemConfig.getKey("scpFtpUserName").trim();
            String password = SystemConfig.getKey("scpFtpPassword").trim();
            String remoteDir = SystemConfig.getKey("ftpHangupDir").trim();

            ftpClient.connect(host);
            boolean loginSuccess = ftpClient.login(username, password);

            if (!loginSuccess) {
                log.error("FTP 登入失敗,請檢查帳號密碼或伺服器狀態");
                return;
            }

            // 4. 切換工作目錄
            if (!ftpClient.changeWorkingDirectory(remoteDir)) {
                log.error("無法切換至目標目錄: " + remoteDir);
                return;
            }

            // 5. 獲取檔案列表並逐個下載
            FTPFile[] files = ftpClient.listFiles();
            if (files == null || files.length == 0) {
                log.info("目標目錄無檔案,等待 5 秒後重試...");
                Thread.sleep(5_000);
                return;
            }

            log.info("偵測到 {} 個待下載檔案", files.length);

            for (FTPFile file : files) {
                String fileName = file.getName();
                
                // 設定緩衝區與傳輸模式 (二進位模式適用於所有類型檔案)
                ftpClient.setBufferSize(BUFFER_SIZE);
                ftpClient.setFileType(FTP.BINARY_FILE_TYPE);

                // 取得輸入流並處理
                inputStream = ftpClient.retrieveFileStream(fileName);
                if (inputStream != null) {
                    processFileInput(ftpClient, inputStream, processedFiles, fileName);
                } else {
                    log.warn("無法獲取檔案輸入流: {}", fileName);
                }
            }

        } catch (Exception e) {
            log.error("FTP 下載過程發生異常", e);
        } finally {
            // 6. 關鍵:確保資源正確釋放
            closeQuietly(inputStream);
            
            if (ftpClient != null) {
                try {
                    // 下載成功後可選擇刪除遠端檔案
                    deleteRemoteFiles(ftpClient, processedFiles);
                    
                    ftpClient.logout();
                } catch (Exception e) {
                    log.error("FTP 登出或清理時發生錯誤", e);
                } finally {
                    disconnectQuietly(ftpClient);
                }
            }
        }
    }

    /**
     * 處理檔案輸入流的業務邏輯 (如解析內容、存入資料庫等)
     */
    private void processFileInput(FTPClient ftpClient, InputStream input, 
                                  List processedList, String fileName) {
        try {
            // 在此處實作具體業務邏輯,例如 callingService(input);
            callingService(input);
            
            // 確認命令完成並記錄已處理檔案
            input.close();
            ftpClient.completePendingCommand();
            processedList.add(fileName);
            
        } catch (Exception e) {
            log.error("處理檔案 {} 時發生錯誤", fileName, e);
        }
    }
    
    // 輔助方法:安全關閉流、刪除遠端檔案、斷開連線等...
}

關鍵技術點解析

1. 連線參數調優

  • 超時設定:分別設定連線、資料、控制通道超時,防止網路波動導致執行緒無限期阻塞。
  • 編碼設定:`setControlEncoding("UTF-8")` 解決中文檔名亂碼問題。
  • 伺服器類型配置:明確指定 `SYST_UNIX` 並設定日期格式,確保 `listFiles()` 能正確解析不同 FTP 伺服器的回傳格式。

2. 二進位傳輸模式

務必呼叫 `ftpClient.setFileType(FTP.BINARY_FILE_TYPE)`。ASCII 模式會嘗試轉換換行符,導致圖片、壓縮檔、執行檔損壞。二進位模式適用於所有檔案類型,是安全的預設選擇。

3. 資源管理與 finally 區塊

FTP 操作涉及網路連線與 I/O 流,必須在 finally 區塊中釋放資源。範例中包含:關閉輸入流、執行 `completePendingCommand()` 確認傳輸完成、登出、斷開連線。忽略此步驟會導致伺服器連線數耗盡或本地檔案句柄洩漏。

4. 檔案下載後的處理策略

範例中將成功下載的檔案名加入 `processedFiles` 列表,最後統一刪除。這比在迴圈中即時刪除更安全:若下游業務處理失敗,檔案保留在伺服器上,便於重試或人工介入。

生產環境部署建議

  1. 配置外部化:IP、帳號、密碼、目錄路徑、連接埠、超時時間均應放入配置中心或環境變數,避免硬編碼。
  2. 被動模式:若客戶端在防火牆/NAT 後方,需呼叫 `ftpClient.enterLocalPassiveMode()`。
  3. 斷點續傳:大檔案建議使用 `retrieveFileStream` 搭配 `setRestartOffset` 實作斷點續傳。
  4. 連線池:高頻存取場景下,建議使用 `org.apache.commons.pool2` 實作 FTP 連線池,避免頻繁建立/銷毀連線的開銷。
  5. 監控與告警:記錄下載耗時、檔案大小、成功/失敗計數,接入監控系統 (如 Prometheus + Grafana)。

常見問題排查

現象可能原因解決方向
連線逾時防火牆攔截、IP 錯誤、埠號錯誤telnet 測試連通性、檢查安全群組規則
登入失敗帳號密碼錯誤、編碼問題、伺服器限制 IP驗證憑證、確認 `setControlEncoding`、檢查伺服器日誌
列表為空 / 解析錯誤伺服器類型不匹配 (Windows vs UNIX)調整 `FTPClientConfig`,嘗試 `SYST_NT` 或自訂 `FTPFileEntryParser`
下載檔案損壞 (大小為 0 或無法開啟)未設定二進位模式、流未正確關閉強制 `BINARY_FILE_TYPE`、確認 `completePendingCommand()` 呼叫

總結

Java FTP 檔案下載看似簡單,但生產環境需考量網路異常、資源洩漏、編碼相容、併發控制等細節。掌握上述核心 API 與最佳實踐,能構建健壯、可維護的檔案整合模組。建議在專案中將此邏輯封裝為通用工具類或 Spring Bean,搭配重試機制 (如 `@Retryable`) 與熔斷器 (如 Resilience4j) 使用。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved