前言
本文主要介绍Java实现FtpClient对象池、FtpClient对象实现以及FTP方法类封装。
配置文件
将所需FTP涉及参数配置在配置文件中
#ftp服务器配置
ftp:
host: 10.20.73.186
port: 21
username: test
password: 123456
#超时时间(0表示一直连接)
clientTimeout: 0
connectTimeout: 0
#编码格式
encoding: UTF-8
#缓冲器大小
bufferSize: 1024
#每次数据连接之前,ftp client告诉ftp server开通一个端口来传输数据
passiveMode: true
#连接池数量
defaultpoolsize: 10
#FTP连接池配置
#最大数
ftpPool:
maxTotal: 50
#最小空闲
minIdle: 0
#最大空闲
maxIdle: 50
#最大等待时间
maxWait: -1
#池对象耗尽之后是否阻塞,maxWait<0时一直等待
blockWhenExhausted: true
#取对象是验证
testOnBorrow: true
#回收验证
testOnReturn: true
#创建时验证
testOnCreate: true
#空闲验证
testWhileIdle: false
#后进先出
lifo: false
#本地目录
localPath: /test/
#ftp服务器上传目录
remotePath: /
FTP连接池配置
@Component
@Slf4j
public class FtpClientPool {
@Value("${ftpPool.maxTotal}")
private int maxTotal;
@Value("${ftpPool.minIdle}")
private int minIdle;
@Value("${ftpPool.maxIdle}")
private int maxIdle;
@Value("${ftpPool.maxWait}")
private long maxWait;
@Value("${ftpPool.blockWhenExhausted}")
private boolean blockWhenExhausted;
@Value("${ftpPool.testOnBorrow}")
private boolean testOnBorrow;
@Value("${ftpPool.testOnReturn}")
private boolean testOnReturn;
@Value("${ftpPool.testOnCreate}")
private boolean testOnCreate;
@Value("${ftpPool.testWhileIdle}")
private boolean testWhileIdle;
@Value("${ftpPool.lifo}")
private boolean lifo;
//连接池
private GenericObjectPool<FTPClient> ftpClientPool;
@Autowired
private FtpClientFactory ftpClientFactory;
private BlockingDeque<FtpClient> blockingDeque;
/**
* 初始化连接池
*/
@PostConstruct //加上该注解表明该方法会在bean初始化后调用
public void init() {
// 初始化对象池配置
GenericObjectPoolConfig<FTPClient> poolConfig = new GenericObjectPoolConfig<FTPClient>();
poolConfig.setBlockWhenExhausted(blockWhenExhausted);
poolConfig.setMaxWaitMillis(maxWait);
poolConfig.setMinIdle(minIdle);
poolConfig.setMaxIdle(maxIdle);
poolConfig.setMaxTotal(maxTotal);
poolConfig.setTestOnBorrow(testOnBorrow);
poolConfig.setTestOnReturn(testOnReturn);
poolConfig.setTestOnCreate(testOnCreate);
poolConfig.setTestWhileIdle(testWhileIdle);
poolConfig.setLifo(lifo);
// 初始化对象池
ftpClientPool = new GenericObjectPool<FTPClient>(ftpClientFactory, poolConfig);
}
public FTPClient borrowObject() throws Exception {
return ftpClientPool.borrowObject();
}
public void returnObject(FTPClient ftpClient) {
ftpClientPool.returnObject(ftpClient);
}
}
FtpClient对象配置
@Component
@Slf4j
public class FtpClientFactory extends BasePooledObjectFactory<FTPClient> {
@Value("${ftp.host}")
private String host;
@Value("${ftp.port}")
private int port;
@Value("${ftp.username}")
private String username;
@Value("${ftp.password}")
private String password;
@Value("${ftp.clientTimeout}")
private int clientTimeout;
@Value("${ftp.connectTimeout}")
private int connectTimeout;
@Value("${ftp.encoding}")
private String encoding;
@Value("${ftp.bufferSize}")
private int bufferSize;
@Value("${ftp.passiveMode}")
private boolean passiveMode;
/**
* 创建FtpClient对象
*/
@Override
public FTPClient create() {
FTPClient ftpClient = new FTPClient();
ftpClient.setConnectTimeout(connectTimeout);
try {
ftpClient.setAutodetectUTF8(true);
ftpClient.setControlEncoding(encoding);
ftpClient.connect(host, port);
int replyCode = ftpClient.getReplyCode();
if (!FTPReply.isPositiveCompletion(replyCode)) {
ftpClient.disconnect();
log.warn("FTPServer 连接失败,replyCode: " + replyCode);
return null;
}
if (!ftpClient.login(username, password)) {
log.warn("ftpClient 登录失败: " + username + password);
return null;
}
ftpClient.setFileType(FTP.BINARY_FILE_TYPE);//文件类型
ftpClient.setBufferSize(bufferSize);
if (passiveMode) {
//这个方法的意思就是每次数据连接之前,ftp client告诉ftp server开通一个端口来传输数据
ftpClient.enterLocalPassiveMode();
}
ftpClient.setSoTimeout(clientTimeout);
} catch (IOException e) {
log.error("FtpClient 创建错误: " + e.toString());
}
return ftpClient;
}
/**
* 用PooledObject封装对象放入池中
*/
@Override
public PooledObject<FTPClient> wrap(FTPClient ftpClient) {
return new DefaultPooledObject<>(ftpClient);
}
/**
* 销毁FtpClient对象
*/
@Override
public void destroyObject(PooledObject<FTPClient> ftpPooled) {
if (ftpPooled == null) {
return;
}
FTPClient ftpClient = ftpPooled.getObject();
try {
if (ftpClient.isConnected()) {
ftpClient.logout();
}
} catch (Exception io) {
log.error("销毁FtpClient错误..." + io.toString());
} finally {
try {
ftpClient.disconnect();
} catch (IOException io) {
log.error("销毁FtpClient错误..." + io.toString());
}
}
}
/**
* 验证FtpClient对象
*/
@Override
public boolean validateObject(PooledObject<FTPClient> ftpPooled) {
try {
FTPClient ftpClient = ftpPooled.getObject();
return ftpClient.sendNoOp();
} catch (IOException e) {
log.error("验证FtpClient对象错误: " + e.toString());
}
return false;
}
}
FTP方法类封装
我在FTP上传、下载方法中,有设计对其文件传输进度的监控,可根据实际场景选择是否需要使用。
@Component
@Slf4j
public class FtpClientTemplate {
@Autowired
private FtpClientPool ftpClientPool;
/***
* 上传Ftp文件
*
* @param filePath 本地文件路径
* @param remotePath 上传服务器路径 - (/abc/1.txt)
* @return true or false
*/
public boolean uploadFile(String filePath, String remotePath, String localPath) {
File localFile = new File(filePath);
FTPClient ftpClient = null;
BufferedInputStream inStream = null;
try {
//从池中获取对象
ftpClient = ftpClientPool.borrowObject();
log.info(">>>>>>>>>>>>>>>>>>>>"+ftpClient);
// 验证FTP服务器是否登录成功
int replyCode = ftpClient.getReplyCode();
if (!FTPReply.isPositiveCompletion(replyCode)) {
log.warn("FTP服务器校验失败, 上传replyCode:{}" + replyCode+" "+localFile);
return false;
}
//切换到上传目录
ftpClient.changeWorkingDirectory(remotePath);
String[] dir = filePath.replace(localPath,"").split("/");
if(dir.length > 1) {
String[] childDir = Arrays.copyOf(dir,dir.length-1);
String childFilePath = String.join("/", childDir) + "/";
if (!ftpClient.changeWorkingDirectory(childFilePath)) {
if (!ftpClient.makeDirectory(childFilePath)) {
return false;
} else {
ftpClient.changeWorkingDirectory(childFilePath);
}
}
}
inStream = new BufferedInputStream(new FileInputStream(localFile));
//设置上传文件的类型为二进制类型
ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
//尝试上传三次
for (int j = 0; j < 3; j++) {
/*//避免进度回调过于频繁
final int[] temp = {0};
//上传进度监控
ftpClient.setCopyStreamListener(new CopyStreamAdapter() {
@Override
public void bytesTransferred(long totalBytesTransferred, int bytesTransferred, long streamSize) {
int percent = (int) (totalBytesTransferred * 100 / localFile.length());
if (temp[0] < percent) {
temp[0] = percent;
}
}
});*/
boolean success = ftpClient.storeFile(localFile.getName(), inStream);
if (success) {
log.info("↑↑ 上传进度 100% -----" + localFile.getAbsolutePath());
log.info("文件上传成功! " + localFile.getName());
return true;
}
log.info("文件上传失败" + localFile.getName() + " 重试 " + j);
}
log.info("文件上传多次仍失败" + localFile.getName());
} catch (Exception e) {
log.error("文件上传错误! " + localFile.getName(), e);
} finally {
IOUtils.closeQuietly(inStream);
//将对象放回池中
ftpClientPool.returnObject(ftpClient);
}
return false;
}
/**
* 下载文件
*
* @param remotePath FTP服务器文件目录
* @param fileName 需要下载的文件名称
* @param localPath 下载后的文件路径
* @return true or false
*/
public boolean downloadFile(String remotePath, String fileName, String localPath) {
FTPClient ftpClient = null;
OutputStream outputStream = null;
try {
ftpClient = ftpClientPool.borrowObject();
// 验证FTP服务器是否登录成功
int replyCode = ftpClient.getReplyCode();
if (!FTPReply.isPositiveCompletion(replyCode)) {
log.warn("FTP服务器校验失败, 下载replyCode:{}" + replyCode + " " + localPath + "/" + fileName);
return false;
}
// 切换FTP目录
ftpClient.changeWorkingDirectory(remotePath);
FTPFile[] ftpFiles = ftpClient.listFiles();
for (FTPFile file : ftpFiles) {
if (fileName.equalsIgnoreCase(file.getName())) {
//保存至本地路径
File localFile = new File(localPath + "/" + file.getName());
//创建父级目录
if (!localFile.getParentFile().exists()) {
localFile.getParentFile().mkdirs();
}
//尝试下载三次
for (int i = 0; i < 3; i++) {
//避免进度回调过于频繁
final int[] temp = {0};
//下载进度监控
ftpClient.setCopyStreamListener(new CopyStreamAdapter() {
@Override
public void bytesTransferred(long totalBytesTransferred, int bytesTransferred, long streamSize) {
int percent = (int) (totalBytesTransferred * 100 / file.getSize());
if (temp[0] < percent) {
temp[0] = percent;
log.info(" ↓↓ 下载进度 " + percent + " " + localFile.getAbsolutePath());
}
}
});
outputStream = new FileOutputStream(localFile);
boolean success = ftpClient.retrieveFile(file.getName(), outputStream);
outputStream.flush();
if (success) {
log.info("文件下载成功! " + localFile.getName());
return true;
}
log.info("文件下载失败" + localFile.getName() + " 重试 " + i);
}
log.info("文件下载多次仍失败" + localFile.getName());
}
}
} catch (Exception e) {
log.error("文件下载错误! " + remotePath + "/" + fileName, e);
} finally {
IOUtils.closeQuietly(outputStream);
ftpClientPool.returnObject(ftpClient);
}
return false;
}
}