上传文件
SimpleFtpClient.upload(本地路径, 远端路径) 使用 FTP STOR 命令上传一个本地文件。它会切换到二进制模式,将文件流式写入数据连接,关闭数据连接,并在报告成功前检查最终 FTP 响应。
基本上传
import com.ajaxjs.net.ftp.SimpleFtpClient;
try (SimpleFtpClient ftp = new SimpleFtpClient("ftp.example.com", 21)) {
ftp.login("deploy", System.getenv("FTP_PASSWORD"));
ftp.upload("C:/build/app.zip", "/releases/app-1.5.zip");
}
第一个参数必须是可读取的本地文件;第二个参数是 FTP 服务端理解的远端文件名和路径。请确保目标目录已存在,且账户具有写权限;upload 不会创建远端目录。
路径、文件名与编码
除非目标服务端另有约定,远端路径应使用 /。服务端支持 UTF-8 时,请在登录后、发送非 ASCII 路径前调用 enableUtf8():
ftp.login("deploy", password);
ftp.enableUtf8();
ftp.upload("C:/exports/季度报告.zip", "/incoming/季度报告.zip");
如果服务端拒绝 OPTS UTF8 ON,enableUtf8() 会抛出 IOException。未确认服务端配置时,不应擅自假定其他编码可用。
完成确认与失败处理
FTP 传输同时包含数据连接和控制连接的最终响应。upload 会关闭数据流,并在内部调用 completePendingCommand()。因此,upload 正常返回代表客户端已经收到成功的最终响应,而不只是本地文件已经读完。
IOException 可能表示本地文件不可读、数据连接失败、权限不足、连接中断或服务端返回失败。应将其视为不完整上传。服务端可能留下部分远端文件;若下游消费者不能看到半成品,可先上传到临时名称,成功后再重命名:
String finalName = "/releases/app.zip";
String temporaryName = finalName + ".part";
try (SimpleFtpClient ftp = new SimpleFtpClient(host, 21)) {
ftp.login(user, password);
ftp.upload(localFile, temporaryName);
ftp.rename(temporaryName, finalName);
}
rename 需要服务端允许,且并非每种 FTP 服务端都保证原子操作。失败的临时文件应按服务端保留策略清理。
进度和超时
内置 ProgressListener 会在复制数据时通过 SLF4J 记录字节数,它不是业务回调 API。需要保存或显示传输进度时,请配置相应的 SLF4J 日志后端。
长时间传输前应设置合适的超时:setConnectTimeout 控制新建 Socket 连接,setReadTimeout 控制 Socket 读取。超时应覆盖预期文件大小和网络速度:
ftp.setConnectTimeout(10_000);
ftp.setReadTimeout(120_000);
底层上传流
仅在需要自定义流写入时使用 put(远端路径)。调用方负责关闭其返回的流,并调用 completePendingCommand();否则,即使字节已写入,也可能误以为上传已经完成。
ftp.binary();
try (OutputStream remote = ftp.put("/incoming/data.bin")) {
// 将业务数据写入 remote。
}
ftp.completePendingCommand();