功能特性
- 文件上傳(字節數組/流)
- 文件下載
- 遠程目錄創建(按日期動態生成)
- 遠程文件列表獲取與過濾
- 舊文件清理(保留 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);
}
}
生產環境強化建議
- 連接池:頻繁操作時引入
FTPClientPool(Commons Pool)複用連接,減少握手開銷。
- 重試機制:網絡抖動常見,上傳/下載關鍵操作加入指數退避重試(如 Resilience4j Retry)。
- 校驗和:上傳後下載驗證 MD5/SHA256,確保數據完整性。
- 監控指標:上傳成功率、平均耗時、失敗原因分布,接入 Prometheus/Grafana。
- 安全:生產環境強制使用 FTPS (FTP over SSL/TLS) 或 SFTP (SSH),Commons Net 支持
FTPSClient,JSch 支持 SFTP。