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

Java FTPClient 文件上傳下載核心 API 詳解與生產級封裝最佳實踐

     編輯:Java 相關知識

Apache Commons Net FTPClient 核心流程

org.apache.commons.net.ftp.FTPClient 是 Java 操作 FTP 的標準客戶端。核心生命週期:

  1. 連線:connect(host, port)
  2. 登入:login(user, pass)
  3. 配置:編碼、二進制模式、被動模式、工作目錄
  4. 操作:上傳/下載/列表/刪除/建立目錄
  5. 斷開:logout() + disconnect()

關鍵配置詳解

配置方法說明生產建議值
控制編碼setControlEncoding()命令/回應文本編碼,影響列表解析UTF-8
傳輸模式setFileType()ASCII(文本) vs BINARY(二進制)BINARY_FILE_TYPE
被動模式enterLocalPassiveMode()客戶端主動發起數據連線,穿透防火牆true (必須)
連線超時setConnectTimeout()TCP 建立連線最長等待10-15 秒
數據超時setDataTimeout()數據傳輸閒置最長等待60-300 秒
緩衝區大小setBufferSize()內部 IO 緩衝區,影響大文件吞吐8192 / 65536

上傳下載核心 API


// 上傳:storeFile / storeFileStream / appendFile / appendFileStream
boolean storeFile(String remote, InputStream local)
OutputStream storeFileStream(String remote) // 需手動 completePendingCommand()

// 下載:retrieveFile / retrieveFileStream
boolean retrieveFile(String remote, OutputStream local)
InputStream retrieveFileStream(String remote)

// 斷點續傳關鍵
void setRestartOffset(long offset) // 設置下次傳輸起始位置
long getRestartOffset()

生產級封裝類


public class FtpOperator implements AutoCloseable {
    private final FTPClient ftp = new FTPClient();
    private final String encoding;
    
    public FtpOperator(String host, int port, String user, String pass, 
                       String workDir, String encoding, boolean passive) throws IOException {
        this.encoding = encoding;
        ftp.setControlEncoding(encoding);
        ftp.setDefaultTimeout(30000);
        ftp.setConnectTimeout(10000);
        ftp.setDataTimeout(120000);
        ftp.setBufferSize(65536);
        ftp.connect(host, port);
        checkReply("connect");
        ftp.login(user, pass);
        checkReply("login");
        ftp.setFileType(FTP.BINARY_FILE_TYPE);
        if (passive) ftp.enterLocalPassiveMode();
        if (workDir != null) ftp.changeWorkingDirectory(workDir);
    }
    
    private void checkReply(String op) throws IOException {
        if (!FTPReply.isPositiveCompletion(ftp.getReplyCode())) {
            throw new IOException(op + " 失敗,回應碼:" + ftp.getReplyCode() + " " + ftp.getReplyString());
        }
    }
    
    // 編碼安全的文件名轉換
    private String encodeName(String name) throws UnsupportedEncodingException {
        return new String(name.getBytes(encoding), "ISO-8859-1");
    }
    
    public boolean upload(String remoteName, InputStream in) throws IOException {
        return ftp.storeFile(encodeName(remoteName), in);
    }
    
    public boolean download(String remoteName, OutputStream out) throws IOException {
        return ftp.retrieveFile(encodeName(remoteName), out);
    }
    
    // 斷點續傳上傳
    public boolean uploadResume(String remote, File local) throws IOException {
        long remoteSize = getFileSize(remote);
        if (remoteSize >= local.length()) return true;
        
        try (RandomAccessFile raf = new RandomAccessFile(local, "r")) {
            raf.seek(remoteSize);
            ftp.setRestartOffset(remoteSize);
            return ftp.storeFile(encodeName(remote), 
                new BufferedInputStream(Channels.newInputStream(raf.getChannel())));
        }
    }
    
    private long getFileSize(String remote) throws IOException {
        FTPFile[] files = ftp.listFiles(encodeName(remote));
        return (files.length == 1) ? files[0].getSize() : 0;
    }
    
    public FTPFile[] listFiles(String path) throws IOException {
        return ftp.listFiles(encodeName(path));
    }
    
    public boolean deleteFile(String remote) throws IOException {
        return ftp.deleteFile(encodeName(remote));
    }
    
    public boolean makeDirectory(String dir) throws IOException {
        return ftp.makeDirectory(encodeName(dir));
    }
    
    @Override
    public void close() {
        try { if (ftp.isConnected()) { ftp.logout(); ftp.disconnect(); } } 
        catch (IOException ignored) {}
    }
}

使用範例


// Try-with-resources 自動關閉
try (FtpOperator ftp = new FtpOperator("10.0.0.1", 21, "user", "pass", 
        "/data", "UTF-8", true)) {
    
    // 上傳
    try (FileInputStream in = new FileInputStream("/local/report.pdf")) {
        ftp.upload("report.pdf", in);
    }
    
    // 大文件斷點續傳
    ftp.uploadResume("backup.tar.gz", new File("/data/backup.tar.gz"));
    
    // 下載
    try (FileOutputStream out = new FileOutputStream("/local/report.pdf")) {
        ftp.download("report.pdf", out);
    }
    
    // 列表
    for (FTPFile f : ftp.listFiles("/data")) {
        System.out.printf("%s %12d %s%n", 
            f.isDirectory() ? "DIR" : "FILE", f.getSize(), f.getName());
    }
}

常見異常與排查

異常/現象可能原因排查步驟
Connection refusedIP/端口錯誤、防火牆阻擋、FTP 服務未啟動telnet host port、檢查安全組/iptables
530 Login incorrect用戶名/密碼錯誤、用戶被鎖定、權限不足驗證憑證、檢查 vsftpd/proftpd 配置
425 Can't open data connection主動模式下服務器無法連回客戶端強制 enterLocalPassiveMode()
550 Failed to change directory目錄不存在、權限不足、路徑編碼錯誤檢查編碼轉換、絕對路徑 vs 相對路徑
中文文件名亂碼/無法訪問編碼轉換不一致統一 UTF-8 → ISO-8859-1 雙向轉換
上傳大文件失敗/卡死超時設置過短、緩衝區過小、網絡抖動調大 dataTimeout、bufferSize、啟用斷點續傳

連線池化:高併發必備


public class FtpClientPool {
    private final GenericObjectPool pool;
    
    public FtpClientPool(FtpConfig config, int maxTotal, int maxIdle) {
        PooledObjectFactory factory = new PooledObjectFactory() {
            @Override public PooledObject makeObject() throws Exception {
                return new DefaultPooledObject<>(new FtpOperator(
                    config.getHost(), config.getPort(), config.getUser(), config.getPass(),
                    config.getWorkDir(), config.getEncoding(), config.isPassive()));
            }
            @Override public void destroyObject(PooledObject p) {
                try { p.getObject().close(); } catch (Exception ignored) {}
            }
            @Override public boolean validateObject(PooledObject p) {
                try { return p.getObject().ftp.sendNoOp(); } catch (Exception e) { return false; }
            }
            @Override public void activateObject(PooledObject p) throws Exception {
                p.getObject().ftp.sendNoOp(); // 保活
            }
            @Override public void passivateObject(PooledObject p) throws Exception {}
        };
        pool = new GenericObjectPool<>(factory);
        pool.setMaxTotal(maxTotal);
        pool.setMaxIdle(maxIdle);
        pool.setMinIdle(2);
        pool.setTestOnBorrow(true);
        pool.setTestWhileIdle(true);
    }
    
    public FtpOperator borrow() throws Exception { return pool.borrowObject(); }
    public void returnObj(FtpOperator ftp) { pool.returnObject(ftp); }
}

總結

掌握 FTPClient 核心在於:編碼雙向轉換、被動模式強制啟用、二進制模式固定、超時與緩衝區調優、斷點續傳 API 正確使用、連線池複用。將上述最佳實踐封裝為 FtpOperator + FtpClientPool,即可獲得生產級穩健的 FTP 操作能力。

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