FTP遍历目录查找指定目录下的文件导入HDFS

 最近有个需求,ftp主机上有很多目录,目录下层级不一,需要把父目录下所有满足的文件夹下的指定文件下载到HDFS,并且保留目录结构,因为数据在ftp主机已经落地,如果用公司内部的etl工具并不好实现这种层级不一的文件导入,故改用开发代码去实现。

思路:1.遍历ftp指定目录,找到指定文件夹的路径
2.拿到所有文件夹的路径,下载到hdfs

实现(这里只放本地测试代码):
1.FTP连接工具类


import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPReply;
import org.apache.log4j.Logger;

import java.io.IOException;

/**
 * @ClassName FTPUtils
 * @Decription FTP Connect Util Class
 * @Author KGC
 * @Date 2019/03/21
 **/
public class FTPUtils {

    private static Logger logger = Logger.getLogger(FTPUtils.class);

    /**
     * FTP连接
     */
    public static FTPClient loginFTP(String host, int port, String userName, String passWord) {
       // ftpClient.enterLocalPassiveMode();

        FTPClient ftpClient = null;

        try {
            ftpClient = new FTPClient();
            //连接ftp
            ftpClient.connect(host, port);
            //登录ftp
            ftpClient.login(userName, passWord);

            ftpClient.enterLocalPassiveMode();
            //字符格式
            ftpClient.setControlEncoding("UTF-8");


          //  ftpClient.setFileType(ftpClient.BINARY_FILE_TYPE);
            if (!FTPReply.isPositiveCompletion(ftpClient.getReplyCode())) {
                logger.error("ftp连接失败,请检查用户名或密码是否错误");
                ftpClient.disconnect();
            } else {
                logger.info("连接成功!");

            }

        }catch (Exception e){
            logger.error("连接失败,检查用户名和密码");
        }
            return ftpClient;
    }
}

很简单,直接使用org.apache.commons.net.ftp.FTPClient中的api

2.遍历ftp文件类


import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;

import java.util.List;


/**
 * @ClassName TraverseFtpDir, return  dir List
 * @Decription
 * @Author KGC
 * @Date DATE
 **/
public class TraverseFtpDir {

    static List<String> list = new ArrayList<String>();

    public static List func(String file, String fileName,FTPClient ftpClient) throws IOException {



        FTPFile[] fs =ftpClient.listFiles(file);

        //遍历目录,如果是子目录则递归继续遍历
        for (FTPFile f : fs) {
         //   System.out.println(f);

            if (f.isDirectory()) {

                func(file + f.getName()+ "/", fileName, ftpClient);


                if ((f.toString().substring((f.toString().length()) - 8, (f.toString().length()))).equals(fileName)) {
                    list.add(file + f.getName());

                }

            }

        }

        return list;


    }


}

简单说下逻辑,ftpClients.listFiles拿到该目录下的所有文件,包括文件夹和文件,然后循环遍历,判断是否为目录(文件夹),如果是则递归该方法,再继续判断是否为指定的文件夹,如果是的话,加入到list。这里需要注意的就是,遍历函数的参数传入的改变,递归调用的时候,参数传入的路径并不是原来第一次传入的路径参数了,而是遍历一次后的子目录的路径: func(file + f.getName()+ “/”, fileName, ftpClient);不然拿到的始终是第一层目录的指定文件路径。

3.下载数据到HDFS



import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IOUtils;
import org.apache.log4j.BasicConfigurator;


import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;

/**
 * @ClassName LoadDataToHdfs
 * @Decription get dir and download data to hdfs from it
 * @Author KGC
 * @Date 2019/3/24
 **/
public class LoadDataToHdfs {

    public static void loadDatatoHdfs(Configuration conf) throws IOException {

        //ftp客户端对象,参数需修改
        FTPClient ftp = FTPUtils.loginFTP("10.211.55.6", 21, "wangjuncheng", "wjc5524568");

        InputStream inputStream = null;
        FSDataOutputStream outputStream = null;

        List li = TraverseFtpDir.func("test/", "20190319", ftp);
        FileSystem fileSystem = FileSystem.get(conf);

        for (int i = 0; i < li.size(); i++) {

            if (!li.get(i).equals(null)) {
                //拿到该目录下的所有文件
                FTPFile[] files = ftp.listFiles(li.get(i).toString());
                ftp.changeWorkingDirectory(li.get(i).toString());
                for (FTPFile f : files) {
                    inputStream = ftp.retrieveFileStream(f.getName());
                    //这里修改/interface下的路径
                    outputStream = fileSystem.create(new Path(li.get(i).toString()));
                    IOUtils.copyBytes(inputStream, outputStream, conf, false);
                    if (inputStream != null) {
                        inputStream.close();
                        ftp.completePendingCommand();
                    }
                }


            }
        }

        ftp.disconnect();

    }


    public static void main(String[] args) throws IOException {
        BasicConfigurator.configure();
        Configuration conf = new Configuration();
      //  conf.set("fileSystem.defaultFS", "hdfs://10.211.55.6:9000");
      // System.setProperty("HADOOP_USER_NAME", "root");

        Path path  = new Path("hdfs://10.211.55.6:9000/0001");
        FileSystem  fs = FileSystem.get(conf);
        fs.mkdirs(path);

       LoadDataToHdfs.loadDatatoHdfs(conf);


    }


}

这里也遇到几个坑,inputstream循环取出来的时候一直报nullException,查资料大部分说是inputstream每次循环后需要手动关闭,即加入 inputStream.close(); ftp.completePendingCommand();这两句,但我是已经加入的,并且并不是第一次循环有值,而是每次循环都为空,后来找找别的原因,其实是在调用方法之前,需要吧ftp目录定位到当前目录,即需加入ftp.changeWorkingDirectory(li.get(i).toString());
这样才能取得到每次的文件路径。

发布了14 篇原创文章 · 获赞 1 · 访问量 684

猜你喜欢

转载自blog.csdn.net/qq_33891419/article/details/88844267