FTP是常用的文件存储方式,本文简单描述了在sping boot项目中使用连接池管理FTP客户端,并实现上传及下载时的断点续传的功能。
1. maven依赖:
<dependency>
<groupId>commons-net</groupId>
<artifactId>commons-net</artifactId>
<version>3.9.0</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.11.1</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
2. 自定义FTP客户端对象工厂类,继承org.apache.commons.pool2.BasePooledObjectFactory,重写create,wrap,destoryObject和validateObject方法。
2.1 创建FTPClient
public FTPSClient create() throws IOException {
FTPSClient client = new FTPSClient("TLS", true);
client.setConnectTimeout(this.ftpProperties.getClientTimeOut());
client.connect(this.ftpProperties.getHost(), this.ftpProperties.getPort());
boolean result = client.login(this.ftpProperties.getUsername(), this.ftpProperties.getPassword());
if (!result) {
log.error("FTPServer refused connection,username is {}", this.ftpProperties.getUsername());
return null;
} else {
int replyCode = client.getReplyCode();
if (!FTPReply.isPositiveCompletion(replyCode)) {
client.disconnect();
log.error("FTPServer refused connection");
return null;
} else {
client.setFileTransferMode(this.ftpProperties.getTransferFileType());
client.setControlEncoding(this.ftpProperties.getEncoding());
// 被动模式
if (this.ftpProperties.isPassiveMode()) {
client.enterLocalPassiveMode();
} else {
client.enterLocalActiveMode();
}
client.execPBSZ(0L);
client.execPROT("P");
// 对于压缩包、可执行文件等若不采用二进制,可能会损坏
client.setFileType(FTP.BINARY_FILT_TYPE);
// 缓存大小
client.setBufferSize(this.ftpProperties.getBufferSize() * 1024 * 1024);
client.setControlEncoding(StandardCharsets.UTF_8.name());
// ... 其他配置,如setDataTimeout(),setReceiveBufferSize()等等
FTPClientConfig conf = new FTPClientConfig("UNIX");
client.configure(conf);
return client;
}
}
}
2.2 其中验证可用性可以使用isAvailable和sendNoOp方法:
@Override
public boolean validateObject(PooledObject<FTPSClient> pooledObject) {
FTPSClient client = (FTPSClient)pooledObject.getObject();
if (client == null) {
return false;
}
boolean available = client.isAvailable();
if (!available) {
return false;
}
try {
return client.sendNoOp();
} catch (IOException var5) {
// 可打debug日志输出异常信息
return false;
}
}
3. 自定义连接池管理类,可使用org.apache.commons.pool2.impl.GenericObjectPool
@Component
public class FTPClientPoolManager{
private GenericObjectPool<FTPSClient> sftpPool;
@PostConstruct
public void initPool{
GenericObjectPoolConfig<FTPSClient> poolConfig = new GenericObjectPoolConfig();
poolConfig.setTestOnBorrow(true);
// ... 其他连接池配置
this.sftpPool = new GenericObjectPool(this.factory, poolConfig);
}
public FTPSClient borrowObject() throws Exception {
return (FTPSClient)this.sftpPool.borrowObject();
}
public void returnObject(SSLSessionReuseFTPSClient client) {
if (client != null) {
try {
// 恢复client当前工作目录到根目录
client.changeWorkingDirectory("/");
this.sftpPool.returnObject(client);
} catch (Exception e) {
// 日志信息
}
}
}
4. 定义FTP服务类,实现上传下载等功能。
4.1 中文路径乱码的处理。FTP服务端默认字符集ISO_8859_1,对路径统一转码即可。如果切换工作目录时仍出现乱码,可以尝试一层一层循环切换进入。
public static String convert2FtpCharset(String str) {
return str == null ? null : new String(str.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1);
}
// 切换工作目录。目录不存在则创建
public static void changeWorkingDirectory(FTPSClient client, String ftpFilePath) {
try {
String ftpFileName = ftpFilePath.replaceAll("\\\\", "/");
String directory = ftpFileName.substring(0, ftpFileName.lastIndexOf("/"));
String d = convert2FtpCharset(directory);
String[] arr = d.split("/");
String[] var6 = arr;
int var7 = arr.length;
for(int var8 = 0; var8 < var7; ++var8) {
String s = var6[var8];
if (!StringUtils.isBlank(s)) {
boolean aBoolean = client.changeWorkingDirectory(s);
if (!"".equals(s) && !aBoolean) {
boolean bBoolean = client.makeDirectory(s);
if (bBoolean) {
client.changeWorkingDirectory(s);
}
}
}
}
} catch (Exception var12) {
// 异常日志
}
}
4.2 文件上传到FTP服务器。读取本地文件流,从连接池获取client,创建FTP目录并切换,保存文件,执行后返还回连接池。
public boolean upload(String localFilePath, String ftpFilePath){
FTPClient client;
try{
File localFile = new File(localFilePath);
if ( !localFile.exists()){
return false;
}
try (InputStream ins = Files.newInputStream(localFile.toPath()){
// 创建FTP目录并切换为当前工作目录
changeWorkingDirectory(client, ftpFilePath);
// 文件名
String fileName = ftpFilePath.substring(ftpFileName.lastIndexOf("/") + 1);
// 上传
boolean success = client.storeFile(convert2FtpCharset(fileName), ins);
}
} finally {
returnClient(pool, client);
}
4.3 下载。使用client.retrieveFile 或者 retrieveFileStream进行下载。如果本地文件已经且小于FTP上的文件client.size(),设置client.restartOffset(localFileSize)进行续传,本地文件输出流可追加写入 new FileOutputStream(file, true)。
3351

被折叠的 条评论
为什么被折叠?



