通过阻塞队列多线程完成ftpClient完成文件和文件夹的上传下载

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对文件和文件夹的操作无非也就那么几种,以上就是基本的文件和文件夹的上传和下载的功能的实现方式,可以不需要使用生产者和消费者模式,直接一步到位,一边扫描文件夹,一边完成文件的上传或者下载,一个线程完成这个功能,都是可以的,只是可能速度慢了一些,更复杂的操作还是需要结合着业务来完成,比如文件下载用户空间是否充足的判断,数据库中写入文件的信息,等等。
其实一开始我也是想着复制粘贴别人写好的代码,自己随便改改就能用了,但是实际操作起来发现,改起来比自己写还费劲,其实这些东西也并不难,动动脑筋就能想的出来,我的代码也只是提供大家一个更好的想法和思路。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值