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

Java FTP 客戶端工具類:上傳、下載、刪除、斷點續傳完整實現

     編輯:Java 相關知識

依賴坐標

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

核心工具類

package com.yinhai.team.action;

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 FavFTPUtil {

    /** 獲取連接並登錄 */
    private static FTPClient connect(String hostname, int port, String username, String password) throws IOException {
        FTPClient ftp = new FTPClient();
        ftp.setControlEncoding("UTF-8"); // 解決中文目錄亂碼
        ftp.connect(hostname, port);
        ftp.login(username, password);
        int reply = ftp.getReplyCode();
        if (!FTPReply.isPositiveCompletion(reply)) {
            ftp.disconnect();
            throw new IOException("FTP 登錄失敗,響應碼:" + reply);
        }
        ftp.enterLocalPassiveMode(); // 被動模式穿透防火牆
        ftp.setFileType(FTPClient.BINARY_FILE_TYPE); // 二進制傳輸
        return ftp;
    }

    /** 上傳文件 (InputStream) */
    public static boolean uploadFile(String hostname, int port, String username, String password,
                                     String remoteDir, String remoteFileName, InputStream input) {
        FTPClient ftp = null;
        try {
            ftp = connect(hostname, port, username, password);
            ftp.makeDirectory(remoteDir); // 不存在則創建,存在忽略
            ftp.changeWorkingDirectory(remoteDir);
            boolean success = ftp.storeFile(remoteFileName, input);
            return success;
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        } finally {
            closeQuietly(ftp);
            closeQuietly(input);
        }
    }

    /** 上傳本地文件 (支持重命名) */
    public static boolean uploadLocalFile(String hostname, int port, String username, String password,
                                          String remoteDir, String remoteFileName, String localFilePath) {
        try (FileInputStream fis = new FileInputStream(localFilePath)) {
            return uploadFile(hostname, port, username, password, remoteDir, remoteFileName, fis);
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
    }

    /** 下載文件 */
    public static boolean downloadFile(String hostname, int port, String username, String password,
                                       String remoteDir, String remoteFileName, String localDir) {
        FTPClient ftp = null;
        try {
            ftp = connect(hostname, port, username, password);
            ftp.changeWorkingDirectory(remoteDir);
            FTPFile[] files = ftp.listFiles();
            for (FTPFile file : files) {
                String name = new String(file.getName().getBytes("ISO-8859-1"), "UTF-8"); // 編碼轉換
                if (remoteFileName.equalsIgnoreCase(name)) {
                    File localFile = new File(localDir, name);
                    try (OutputStream os = new FileOutputStream(localFile)) {
                        return ftp.retrieveFile(file.getName(), os);
                    }
                }
            }
            return false; // 文件不存在
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        } finally {
            closeQuietly(ftp);
        }
    }

    /** 刪除文件 */
    public static boolean deleteFile(String hostname, int port, String username, String password,
                                     String remoteDir, String remoteFileName) {
        FTPClient ftp = null;
        try {
            ftp = connect(hostname, port, username, password);
            ftp.changeWorkingDirectory(remoteDir);
            return ftp.deleteFile(remoteFileName);
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        } finally {
            closeQuietly(ftp);
        }
    }

    /** 靜默關閉資源 */
    private static void closeQuietly(FTPClient ftp) {
        if (ftp != null && ftp.isConnected()) {
            try { ftp.logout(); } catch (IOException ignored) {}
            try { ftp.disconnect(); } catch (IOException ignored) {}
        }
    }
    private static void closeQuietly(Closeable c) {
        if (c != null) try { c.close(); } catch (IOException ignored) {}
    }
}

進階:斷點續傳 (隨機訪問)

/** 斷點續傳下載 */
public static boolean downloadResume(String hostname, int port, String username, String password,
                                     String remoteDir, String remoteFileName, String localFilePath) {
    FTPClient ftp = null;
    RandomAccessFile raf = null;
    try {
        ftp = connect(hostname, port, username, password);
        ftp.changeWorkingDirectory(remoteDir);
        long remoteSize = ftp.getFileSize(remoteFileName); // 需服務器支持 SIZE 指令
        
        File localFile = new File(localFilePath);
        long localSize = localFile.exists() ? localFile.length() : 0;
        if (localSize >= remoteSize) return true; // 已完整
        
        // 設置斷點
        ftp.setRestartOffset(localSize);
        raf = new RandomAccessFile(localFile, "rw");
        raf.seek(localSize);
        
        return ftp.retrieveFile(remoteFileName, new FileOutputStream(raf.getFD()));
    } catch (IOException e) {
        e.printStackTrace();
        return false;
    } finally {
        closeQuietly(ftp);
        if (raf != null) try { raf.close(); } catch (IOException ignored) {}
    }
}

/** 斷點續傳上傳 (APPE 指令) */
public static boolean uploadResume(String hostname, int port, String username, String password,
                                   String remoteDir, String remoteFileName, String localFilePath) {
    FTPClient ftp = null;
    FileInputStream fis = null;
    try {
        ftp = connect(hostname, port, username, password);
        ftp.changeWorkingDirectory(remoteDir);
        long remoteSize = ftp.getFileSize(remoteFileName);
        
        File localFile = new File(localFilePath);
        long localSize = localFile.length();
        if (remoteSize >= localSize) return true;
        
        fis = new FileInputStream(localFile);
        fis.skip(remoteSize); // 跳過已傳部分
        ftp.setRestartOffset(remoteSize);
        return ftp.appendFile(remoteFileName, fis); // APPE 指令
    } catch (IOException e) {
        e.printStackTrace();
        return false;
    } finally {
        closeQuietly(ftp);
        closeQuietly(fis);
    }
}

編碼問題終極解決

  • 控制連接:ftp.setControlEncoding("UTF-8") (Commons Net 3.3+)。
  • 文件名/目錄名:服務器返回通常為 ISO-8859-1,需手動轉碼:
    String utf8Name = new String(isoName.getBytes("ISO-8859-1"), "UTF-8");
  • 被動模式:ftp.enterLocalPassiveMode() 解決防火牆/NAT 導致數據連接失敗。

最佳實踐清單

  1. 所有流/連接在 finally 中靜默關閉。
  2. 上傳前檢查目標目錄存在性,makeDirectory 可多級創建。
  3. 大文件必須使用 BINARY_FILE_TYPE,避免換行符轉換損壞數據。
  4. 生產環境建議封裝為 Spring Bean,注入連接池 (PoolingFTPClientFactory) 復用連接。
  5. 異常處理:區分「文件不存在」、「權限不足」、「磁盤滿」、「網絡中斷」返回不同錯誤碼。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved