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

Java FTP 文件上傳完整實現:目錄管理、二進制傳輸、異常重試最佳實踐

     編輯:Java 相關知識

功能特性

  • 文件上傳(字節數組/流)
  • 文件下載
  • 遠程目錄創建(按日期動態生成)
  • 遠程文件列表獲取與過濾
  • 舊文件清理(保留 N 天)
  • 完整的連接生命周期管理與異常處理

核心工具類

import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.io.IOUtils;
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.Date;

public class FTPFileTransmit {
    private final String host;
    private final String user;
    private final String password;
    private final String basePath; // 基礎遠程路徑,如 "data/incoming/"

    public FTPFileTransmit(String host, String user, String password, String basePath) {
        this.host = host;
        this.user = user;
        this.password = password;
        this.basePath = basePath.endsWith("/") ? basePath : basePath + "/";
    }

    /** 獲取配置好的 FTPClient 實例 */
    private FTPClient createClient() throws IOException {
        FTPClient client = new FTPClient();
        client.setDefaultPort(21);
        client.setConnectTimeout(60_000);
        client.setDataTimeout(120_000);
        client.setControlEncoding("UTF-8");
        client.connect(host);
        if (!client.login(user, password)) {
            throw new IOException("登錄失敗: " + client.getReplyString());
        }
        client.enterLocalPassiveMode();
        client.setFileType(FTPClient.BINARY_FILE_TYPE);
        client.setBufferSize(1024);
        return client;
    }

    /** 上傳字節數組到指定子目錄 */
    public boolean upload(String subDir, String fileName, byte[] data) {
        String remotePath = basePath + subDir;
        try (FTPClient client = createClient();
             ByteArrayInputStream in = new ByteArrayInputStream(data)) {

            if (!ensureDir(client, remotePath)) return false;
            return client.storeFile(fileName, in);
        } catch (IOException e) {
            logError("上傳失敗: " + fileName, e);
            return false;
        }
    }

    /** 下載文件到本地 */
    public boolean download(String remotePath, String localFilePath) {
        try (FTPClient client = createClient();
             FileOutputStream out = new FileOutputStream(localFilePath)) {
            return client.retrieveFile(remotePath, out);
        } catch (IOException e) {
            logError("下載失敗: " + remotePath, e);
            return false;
        }
    }

    /** 確保遠程目錄存在,不存在則遞歸創建 */
    private boolean ensureDir(FTPClient client, String remoteDir) throws IOException {
        String[] dirs = remoteDir.split("/");
        String current = "";
        for (String dir : dirs) {
            if (dir.isEmpty()) continue;
            current += dir + "/";
            if (!client.changeWorkingDirectory(current)) {
                if (!client.makeDirectory(current)) {
                    logError("創建目錄失敗: " + current);
                    return false;
                }
                client.changeWorkingDirectory(current);
            }
        }
        return true;
    }

    /** 創建按日期命名的子目錄(如 20240115_ReTransmit) */
    public String createDailyDir() {
        String dirName = new SimpleDateFormat("yyyyMMdd").format(new Date()) + "_ReTransmit";
        try (FTPClient client = createClient()) {
            if (ensureDir(client, basePath + dirName)) {
                return dirName;
            }
        } catch (IOException e) {
            logError("創建每日目錄失敗", e);
        }
        return null;
    }

    /** 獲取基礎路徑下所有子目錄名 */
    public String[] listSubDirs() {
        try (FTPClient client = createClient()) {
            if (client.changeWorkingDirectory(basePath)) {
                return client.listNames(); // 僅名稱
            }
        } catch (IOException e) {
            logError("列出目錄失敗", e);
        }
        return new String[0];
    }

    /** 刪除早於指定天數的子目錄(含文件) */
    public void cleanOldDirs(int retainDays) {
        String cutoff = new SimpleDateFormat("yyyyMMdd")
                .format(new Date(System.currentTimeMillis() - retainDays * 86400000L));

        try (FTPClient client = createClient()) {
            if (!client.changeWorkingDirectory(basePath)) return;

            for (String dir : client.listNames()) {
                if (dir.length() >= 8 && dir.substring(0, 8).compareTo(cutoff) <= 0) {
                    deleteDirRecursive(client, dir);
                }
            }
        } catch (IOException e) {
            logError("清理舊目錄失敗", e);
        }
    }

    private void deleteDirRecursive(FTPClient client, String dir) throws IOException {
        client.changeWorkingDirectory(dir);
        for (String file : client.listNames()) {
            client.deleteFile(file);
        }
        client.changeToParentDirectory();
        client.removeDirectory(dir);
    }

    private void logError(String msg, Exception e) {
        System.err.println(msg + ": " + e.getMessage());
        // 接入 SLF4J/Log4j2 等日誌框架
    }
}

使用示例

public class FtpDemo {
    public static void main(String[] args) {
        FTPFileTransmit ftp = new FTPFileTransmit("192.168.1.100", "user", "pass", "data/incoming/");

        // 1. 創建每日目錄並上傳
        String dailyDir = ftp.createDailyDir(); // e.g. "20240115_ReTransmit"
        if (dailyDir != null) {
            byte[] data = "測試數據".getBytes(StandardCharsets.UTF_8);
            boolean ok = ftp.upload(dailyDir, "test_" + System.currentTimeMillis() + ".txt", data);
            System.out.println("上傳 " + (ok ? "成功" : "失敗"));
        }

        // 2. 定時清理 3 天前的目錄
        // ftp.cleanOldDirs(3);
    }
}

生產環境強化建議

  1. 連接池:頻繁操作時引入 FTPClientPool(Commons Pool)複用連接,減少握手開銷。
  2. 重試機制:網絡抖動常見,上傳/下載關鍵操作加入指數退避重試(如 Resilience4j Retry)。
  3. 校驗和:上傳後下載驗證 MD5/SHA256,確保數據完整性。
  4. 監控指標:上傳成功率、平均耗時、失敗原因分布,接入 Prometheus/Grafana。
  5. 安全:生產環境強制使用 FTPS (FTP over SSL/TLS) 或 SFTP (SSH),Commons Net 支持 FTPSClient,JSch 支持 SFTP。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved