1. ftpClient实现文件和文件夹上传
1.1ftp实现文件的上传
单个文件的上传是最简单的,直接出代码。
/**
*
* @param ftp 初始化好的ftp连接
* @param remoteBasePath 远程文件夹的路径
* @param localFile 本地文件File对象
* @return
* @throws IOException
*/
public static boolean uploadFile(FTPClient ftp, String remoteBasePath, File localFile) throws IOException {
// 获取本地文件所在目录
boolean success;
// 获取项目中系统文件的绝对路径
String absolutePath = localFile.getParent();
String fixedPath = "/mnt/userdata/ocserver";
// 截取掉固定路径,获取到系统文件的相对路径
String relativePath = absolutePath.substring(fixedPath.length());
// 开启本地文件流
FileInputStream inputStream = new FileInputStream(localFile);
// 获取到ftp服务器上的目录路径
String remotePath = remoteBasePath+relativePath;
// 切换工作目录到ftp服务器路径上
boolean b = ftp.changeWorkingDirectory(remotePath);
if (!b) {
return false;
}
// 将文件流上传到ftp服务器上
success = ftp.storeFile(localFile.getName(), inputStream);
if (!success) {
for (int i = 0; i < 3; i++) {
success = ftp.storeFile(localFile.getName(), inputStream);
if (success) {
break;
}
}
}
inputStream.close();
return success;
}
不要感觉我代码很复杂,就认为ftpClient上传文件很麻烦,其实是因为代码我的代码要和项目的业务场景结合,最重要的就以下3行代码。
// 开启文件流
FileInputStream inputStream = new FileInputStream(localFile);
// 切换工作目录到ftp服务器指定目录上
boolean b = ftp.changeWorkingDirectory(remotePath);
// 将文件流上传到ftp服务器上
success = ftp.storeFile(localFile.getName(), inputStream);
1.2 ftpClient实现文件夹的上传
如果可以顺利完成文件上传,那么文件夹的上传其实也并不难,在做文件夹上传时,因为考虑到文件数量过多,导致服务器内存被撑爆的场景。(其实根本不可能有那么大的文件数量,只是提供给单位内部使用的网盘,怎么可能会出现这么大的文件数量需要去备份 )
因此使用了生产者和消费者模式,一个线程用来扫描文件夹,本地是文件夹的话就在ftp上创建文件夹,是文件的话就将其放到一个有阶的阻塞队列中,一个线程作为消费者,将队列中的文件一个个的读取出来,上传到ftp服务器,1.1中的文件上传,就是作为一个消费者存在。
废话不多说,先看一看本地的文件夹扫描的代码
/***
*
* @param ftpClient ftp对象
* @param file 本地要上传的文件
* @param queue 存放上传文件队列
* @param remotePath ftp服务器创建文件夹路径
* @param remoteBasePath ftp服务器基础路径
* @throws IOException
*/
public static void scanLocalFiles(AtomicLong i, FTPClient ftpClient, File file, BlockingQueue<File> queue, String remotePath, String remoteBasePath) throws IOException, InterruptedException {
// 本地文件夹下全部文件
File[] files = file.listFiles();
for (File file1: files) {
// 是文件夹的话,在ftp服务器上创建文件夹
if (file1.isDirectory()) {
// 切换ftp目录到工作目录上
if (!ftpClient.changeWorkingDirectory(remotePath)) {
ftpClient.makeDirectory(remotePath);
}
ftpClient.changeWorkingDirectory(remotePath);
String directory = remotePath+"/"+file1.getName();
// 创建ftp远程目录
ftpClient.makeDirectory(directory);
String path = file1.getAbsolutePath();
String fixedPath = "/mnt/userdata/ocserver";
// 获取本地相对路径
String relativePath = path.substring(fixedPath.length());
// 远程路径+相对路径 得到远程目录,递归扫描
scanLocalFiles(i, ftpClient, file1, queue, remoteBasePath+relativePath, remoteBasePath);
} else {
i.incrementAndGet();
// 将文件对象放到队列中
queue.put(file1);
// 切换工作路径,如果目录下文件超过10万文件,文件数量过大,或者上传过慢,再次使用命令行,就会报错,连接会被自动关闭
ftpClient.changeWorkingDirectory(remotePath);
}
}
}
以上代码是上传文件夹的一部分,主要是用来扫描本地文件夹,然后在ftp上对应目录下创建文件夹,
文件夹创建好了,直接切换目录上传文件即可。
两个方法之间的关联代码如下
final FTPClient[] ftpClient = new FTPClient[2];
BlockingQueue<File> queue = new ArrayBlockingQueue<>(10000);
String dataDir = userService.getDataDir();
File file = new File(dataDir);
ThreadPoolExecutor excutor = threadPoolService.createExcutor("backFileName", "upload", 4, 4);
excutor.execute(() -> {
log.info("开始备份文件中");
excutor.execute(() -> {
// 递归在ftp服务器上创建文件夹,并且将文件放到队列中
FtpUtil.scanLocalFiles(i, ftpClient[0], file, queue, remotePath, remotePath);
});
excutor.execute(() -> {
File file1;
ftpClient[1] = FtpUtil.loginFtp(ip, port, user, pwd);
log.info("开始上传本地文件");
while ((file1 = queue.take()) != null) {
boolean b = FtpUtil.uploadFile(ftpClient[1], basePath.get(), file1);
}
});
});
以上代码是简化版的,大家理解我的思路就可以了,还需要注意一点的是ftpClient我是初始化了两个对象,一个ftp对象一个线程够用了,但是多线程就很快会报错,我这用最简单的数组来玩,你们想玩也可以用ftp的线程池玩。
2.ftpClient实现文件和文件夹下载
2.1 ftpClient实现文件的下载
单个文件的下载实现也很简单,直接上代码
private void downloadAndImportFile(FTPClient ftpClient, FtpDownloadFile downloadFile) throws Exception {
// 获取ftp远程路径
String remotePath = downloadFile.getRemoteFilePath();
// 获取本地的文件路径
String localPath = downloadFile.getLocalFilePath();
// 切换到ftp的工作目录
ftpClient.changeWorkingDirectory(remotePath);
File localFile = new File(localPath);
OutputStream is;
// 创建本地文件的输出流
is = new FileOutputStream(localFile);
// 将FTP上对应的文件流写入到本地文件流中
boolean b = ftpClient.retrieveFile(localFile.getName(), is);
is.close();
if (!b) {
log.info(remotePath + "/" + localFile.getName() + "下载失败");
throw new Exception("文件下载失败");
}
}
以上代码就是文件下载的一个基础代码了,整个流程很简单,在本地创建一个本地文件的File对象,这个文件对象对应的文件是可以不存在的,只需要根据这个对象创建一个OutputStream流,然后使用ftpClient切换到远程的FTP服务器的工作目录下,调用固定的api将这个文件名对应的文件流写出到本地文件中,完成文件的下载。
2.2 ftpClient实现文件夹的下载
文件夹的下载和文件夹的上传大差不差,使用同一种思想,都是使用生产者和消费者模式,先扫描远程目录,是文件夹就在本地将文件夹创建出来,将文件夹信息加到数据库中,是文件就将文件信息都放在队列中。
可以先看一看我代码中FTP上的文件夹扫描是如何实现的。
/**
*
* @param queue 阻塞队列
* @param ftpClient ftp连接
* @param remotePath 远程路径
* @param localPath 本地路径
* @throws Exception
*/
private void scannerRemoteFiles(BlockingQueue<FtpDownloadFile> queue, FTPClient ftpClient, String remotePath, String localPath) throws Exception {
// 切换远程路径,如果切换失败,说明已经扫描到最下面文件了,返回方法
if (!ftpClient.changeWorkingDirectory(remotePath)) {
return;
}
// 切换目录
ftpClient.changeWorkingDirectory(remotePath);
ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
// 获取远程目录下的文件列表
FTPFile[] ftpFiles = ftpClient.listFiles();
for (int i = 0; i < ftpFiles.length; i++) {
String filePath = localPath + ftpFiles[i].getName();
// 遍历判断文件是文件夹还是文件
if (ftpFiles[i].isDirectory()) {
// 是文件夹的话创建文件夹,数据库中新增文件夹。。。
File file = new File(filePath);
file.mkdir();
// 递归扫描文件夹下的文件
scannerRemoteFiles( queue, ftpClient, remotePath + ftpFiles[i].getName() + File.separator, localPath + ftpFiles[i].getName() + File.separator);
} else {
ftpClient.printWorkingDirectory();
// 将下载文件所需属性封装在对象中。
FtpDownloadFile ftpDownloadFile = new FtpDownloadFile(filePath, remotePath, ftpFiles[i]);
// 将文件属性放在队列中
queue.put(ftpDownloadFile);
}
}
}
以上就是对FTP远程目录的一个递归扫描,实现起来还是比较简单的,我给的是基础的代码,你们可以结合自己项目里的业务,完善和扩充这段代码。
将两个方法关联起来的代码如下所示:
ThreadPoolExecutor excutor = threadPoolService.createExcutor("downLoadFileName", "downLoad", 4, 4);
excutor.execute(() ->{
BlockingQueue<FtpDownloadFile> queue = new ArrayBlockingQueue<>(10000);
// 扫描用户文件夹
excutor.execute(() -> {
scannerRemoteFiles(queue, ftpClient[0], remotePath, localPath);
});
// 下载并导入备份文件
excutor.execute(() -> {
FtpDownloadFile downloadFile;
ftpClient[1] = FtpUtil.loginFtp(ip, port, user, pwd);
log.info("开始给用户导入备份文件");
while ((downloadFile = queue.take())!= null) {
downloadAndImportFile(ftpClient[1], downloadFile);
}
});
});
3. 总结
FTPClient对文件和文件夹的操作无非也就那么几种,以上就是基本的文件和文件夹的上传和下载的功能的实现方式,可以不需要使用生产者和消费者模式,直接一步到位,一边扫描文件夹,一边完成文件的上传或者下载,一个线程完成这个功能,都是可以的,只是可能速度慢了一些,更复杂的操作还是需要结合着业务来完成,比如文件下载用户空间是否充足的判断,数据库中写入文件的信息,等等。
其实一开始我也是想着复制粘贴别人写好的代码,自己随便改改就能用了,但是实际操作起来发现,改起来比自己写还费劲,其实这些东西也并不难,动动脑筋就能想的出来,我的代码也只是提供大家一个更好的想法和思路。