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

Java FTP 文件上傳下載工具類 (基於 Apache Commons Net)

     編輯:Java 相關知識

本文提供基於 Apache Commons Net 的 FTP 客戶端工具類,支援文件上傳(含重命名)、下載、刪除、目錄操作,包含完整異常處理、資源釋放、編碼設置,適合生產環境直接使用。

Maven 依賴


<dependency>
    <groupId>commons-net</groupId>
    <artifactId>commons-net</artifactId>
    <version>3.9.0</version>
</dependency>

完整工具類實現


import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPReply;

import java.io.*;

public class FtpUtil {

    private FTPClient ftpClient;
    private String host;
    private int port;
    private String username;
    private String password;
    private int timeout = 5000;

    public FtpUtil(String host, int port, String username, String password) {
        this.host = host;
        this.port = port;
        this.username = username;
        this.password = password;
        this.ftpClient = new FTPClient();
        this.ftpClient.setControlEncoding("UTF-8");
        this.ftpClient.setDefaultTimeout(timeout);
        this.ftpClient.setConnectTimeout(timeout);
    }

    /**
     * 連接並登錄 FTP 服務器
     */
    public boolean connect() throws IOException {
        ftpClient.connect(host, port);
        int reply = ftpClient.getReplyCode();
        if (!FTPReply.isPositiveCompletion(reply)) {
            disconnect();
            throw new IOException("FTP 服務器拒絕連接: " + reply);
        }
        boolean login = ftpClient.login(username, password);
        if (!login) {
            disconnect();
            throw new IOException("FTP 登錄失敗: 用戶名或密碼錯誤");
        }
        // 設置二進制傳輸模式
        ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
        // 被動模式 (穿透防火牆/NAT)
        ftpClient.enterLocalPassiveMode();
        return true;
    }

    /**
     * 斷開連接
     */
    public void disconnect() {
        if (ftpClient != null && ftpClient.isConnected()) {
            try {
                ftpClient.logout();
            } catch (IOException ignored) {}
            try {
                ftpClient.disconnect();
            } catch (IOException ignored) {}
        }
    }

    /**
     * 上傳文件 (InputStream 方式,支援重命名)
     */
    public boolean upload(String remoteDir, String remoteFileName, InputStream inputStream) {
        boolean success = false;
        try {
            ensureConnected();
            createDirectories(remoteDir);
            ftpClient.changeWorkingDirectory(remoteDir);
            success = ftpClient.storeFile(remoteFileName, inputStream);
        } catch (IOException e) {
            throw new RuntimeException("FTP 上傳失敗", e);
        } finally {
            closeQuietly(inputStream);
        }
        return success;
    }

    /**
     * 上傳本地文件 (支援重命名)
     */
    public boolean uploadFile(String remoteDir, String remoteFileName, String localFilePath) {
        try (FileInputStream fis = new FileInputStream(localFilePath)) {
            return upload(remoteDir, remoteFileName, fis);
        } catch (IOException e) {
            throw new RuntimeException("讀取本地文件失敗", e);
        }
    }

    /**
     * 上傳本地文件 (保持原文件名)
     */
    public boolean uploadFile(String remoteDir, String localFilePath) {
        File file = new File(localFilePath);
        return uploadFile(remoteDir, file.getName(), localFilePath);
    }

    /**
     * 下載文件
     */
    public boolean download(String remoteDir, String remoteFileName, String localDir) {
        boolean success = false;
        try {
            ensureConnected();
            ftpClient.changeWorkingDirectory(remoteDir);
            
            FTPFile[] files = ftpClient.listFiles(remoteFileName);
            if (files.length == 0) {
                throw new FileNotFoundException("FTP 文件不存在: " + remoteFileName);
            }
            
            File localFile = new File(localDir, remoteFileName);
            localFile.getParentFile().mkdirs();
            
            try (FileOutputStream fos = new FileOutputStream(localFile)) {
                success = ftpClient.retrieveFile(remoteFileName, fos);
            }
        } catch (IOException e) {
            throw new RuntimeException("FTP 下載失敗", e);
        }
        return success;
    }

    /**
     * 刪除文件
     */
    public boolean deleteFile(String remoteDir, String remoteFileName) {
        try {
            ensureConnected();
            ftpClient.changeWorkingDirectory(remoteDir);
            return ftpClient.deleteFile(remoteFileName);
        } catch (IOException e) {
            throw new RuntimeException("FTP 刪除失敗", e);
        }
    }

    /**
     * 列出目錄文件
     */
    public FTPFile[] listFiles(String remoteDir) {
        try {
            ensureConnected();
            ftpClient.changeWorkingDirectory(remoteDir);
            return ftpClient.listFiles();
        } catch (IOException e) {
            throw new RuntimeException("FTP 列表失敗", e);
        }
    }

    /**
     * 創建多級目錄 (遞歸)
     */
    private void createDirectories(String remoteDir) throws IOException {
        if (remoteDir == null || remoteDir.isEmpty() || "/".equals(remoteDir)) return;
        
        String[] dirs = remoteDir.split("/");
        StringBuilder path = new StringBuilder();
        for (String dir : dirs) {
            if (dir.isEmpty()) continue;
            path.append("/").append(dir);
            if (!ftpClient.changeWorkingDirectory(path.toString())) {
                boolean created = ftpClient.makeDirectory(path.toString());
                if (!created) {
                    throw new IOException("創建目錄失敗: " + path);
                }
                ftpClient.changeWorkingDirectory(path.toString());
            }
        }
        // 返回根目錄
        ftpClient.changeWorkingDirectory("/");
    }

    private void ensureConnected() throws IOException {
        if (!ftpClient.isConnected()) {
            connect();
        }
    }

    private void closeQuietly(Closeable closeable) {
        if (closeable != null) {
            try { closeable.close(); } catch (IOException ignored) {}
        }
    }

    // 測試主方法
    public static void main(String[] args) {
        String host = "127.0.0.1";
        int port = 21;
        String user = "testuser";
        String pass = "testpass";
        String remoteDir = "/upload/test";
        String localFile = "D:/bigfile.zip";
        
        FtpUtil ftp = new FtpUtil(host, port, user, pass);
        try {
            // 上傳
            boolean up = ftp.uploadFile(remoteDir, "renamed.zip", localFile);
            System.out.println("上傳: " + (up ? "成功" : "失敗"));
            
            // 下載
            boolean down = ftp.download(remoteDir, "renamed.zip", "D:/download");
            System.out.println("下載: " + (down ? "成功" : "失敗"));
            
            // 列表
            for (FTPFile f : ftp.listFiles(remoteDir)) {
                System.out.println(f.getName() + " " + f.getSize() + " " + f.getTimestamp().getTime());
            }
        } finally {
            ftp.disconnect();
        }
    }
}

關鍵配置說明

常見問題排查

設置代碼作用
控制編碼setControlEncoding("UTF-8")解決中文目錄/文件名亂碼
傳輸模式setFileType(BINARY_FILE_TYPE)二進制模式,避免文本模式損壞圖片/壓縮包
被動模式enterLocalPassiveMode()客戶端主動發起數據連接,穿透防火牆/NAT
超時設置setConnectTimeout/setDefaultTimeout避免長時間卡死
問題原因解決
連接超時 / Connection refusedIP/端口錯誤、防火牆攔截、FTP 服務未啟動telnet 測試端口、檢查安全組/防火牆、確認服務狀態
530 Login incorrect用戶名/密碼錯誤、用戶被鎖定、IP 白名單限制核對憑證、檢查 FTP 服務器日誌
上傳/下載卡住主動模式下數據連接被防火牆阻擋必須啟用被動模式 enterLocalPassiveMode()
中文文件名亂碼編碼不一致setControlEncoding("UTF-8"),服務端也需支援 UTF-8
550 Permission denied目錄寫入權限不足、磁盤配額滿檢查 FTP 用戶權限、磁盤空間
大文件傳輸失敗超時、連接中斷、服務器限制單文件大小增加超時、啟用斷點續傳 (REST/APPE 指令)

進階:斷點續傳支援


// 續傳上傳 (APPE 指令)
public boolean appendUpload(String remoteDir, String remoteFileName, 
                           String localFilePath, long resumeOffset) {
    try (RandomAccessFile raf = new RandomAccessFile(localFilePath, "r")) {
        raf.seek(resumeOffset);
        ftpClient.changeWorkingDirectory(remoteDir);
        // Commons Net 3.6+ 支援 appendFile
        return ftpClient.appendFile(remoteFileName, raf);
    } catch (IOException e) {
        throw new RuntimeException("續傳上傳失敗", e);
    }
}

// 續傳下載 (REST 指令)
public boolean resumeDownload(String remoteDir, String remoteFileName,
                             String localFilePath, long resumeOffset) {
    try (RandomAccessFile raf = new RandomAccessFile(localFilePath, "rw")) {
        raf.seek(resumeOffset);
        ftpClient.changeWorkingDirectory(remoteDir);
        ftpClient.setRestartOffset(resumeOffset);
        return ftpClient.retrieveFile(remoteFileName, new FileOutputStream(raf.getFD()));
    } catch (IOException e) {
        throw new RuntimeException("續傳下載失敗", e);
    }
}

生產環境建議

  • 連接池:使用 FTPClientPool (Commons Pool) 管理連接複用
  • FTPS:FTPClient ftps = new FTPSClient("TLS") 加密傳輸
  • SFTP:若需 SSH 加密,改用 JSch 或 Apache MINA SSHD (非 FTP 協議)
  • 監控:記錄傳輸耗時、速度、失敗率,接入 Prometheus
  • 重試:網路抖動時指數退避重試 (配合 Resilience4j)

總結

Apache Commons Net 提供標準、穩定的 FTP 客戶端實現。關鍵最佳實踐:UTF-8 編碼、二進制模式、被動模式、超時設置、資源釋放。工具類封裝連接管理、目錄遞歸創建、異常轉換,可直接投入生產使用。大文件、斷點續傳、加密傳輸等進階需求可在基礎上擴展。

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