天天看點

記一次FTPClient的使用。(複制、删除、移動)

由于項目有個需求是要去ftp伺服器上取檔案,檔案為xml格式,解析後定時抽取入庫。

一開始分了三步,第一步取檔案,第二步解析,第三步抽取入庫。後來做完後又要根據将檔案抽取入庫失敗的移動到error目錄上。是以最後大緻上可以分成四步。由于定時器可以利用spring+quartz實作,用到的是spring配置檔案,是以就不記錄了。

在剛開始動手時,由于沒搭建好ftp伺服器測試,是以就先做了第二步xml的解析。開始時将解析方法聲明為parseXML(File file),将檔案放到了本地磁盤上測試,通過。然後開始有點坑的就來了,當搭建好ftp伺服器後,就嘗試第一步,ftp上取檔案。就去調用 commons.net上的FTPClient。

第一步,ftp伺服器上取檔案。

第一小步,肯定就是連接配接上ftp伺服器,度娘,連接配接ftp如下:

public void connectServer(String ip, int port, String userName, String userPwd) {
        ftpClient = new FTPClient();
        try {
            int reply; 
            // 連接配接
            ftpClient.connect(ip, port);
            // 登入
            ftpClient.login(userName, userPwd);
            reply = ftpClient.getReplyCode(); 

        } catch (Exception e) {
            log.error("ftp連結失敗", e);
        }
    }
           

第二小步就是擷取ftp上的檔案:

FTPFile[] ftpFIles = ftpClient.listFiles();
           

第二步:解析xml檔案

檔案取到了,當然就是去解析xml了,然後坑爹的就來了,之前定好的方法parseXML(File file),接受的是File檔案,然而ftpClient擷取下來的檔案是FtpFile格式。一開始想着怎麼去轉,花費了一些時間,可能有轉過去的方法,但是我沒找到。然後度娘上又給了一個是将FtpFile格式的xml解析的,沒辦法,就換成了 parseXML(FTPFile fPath)。由于SAXReader裡的read方法可以接受inputstream流,是以隻要将FtpFile轉換成流就ok了,ins = ftpClient.retrieveFileStream(fPath.getName());

這裡有個坑,就是每次調用ftpClient.retrieveFileStream()後,需要調用ftpClient.getReply()這個方法,把接下來的226消費掉,如果沒有寫,下一次ftpClient.retrieveFileStream()傳回的InputStream對象會一直為null!!!代碼如下:

public List<Map<String, Object>> parseXML(FTPFile fPath) {
        List<Map<String, Object>> parseList = new ArrayList<Map<String, Object>>();

        String rtn = "";
        Document document = null;
        InputStream ins = null;
        try {
            SAXReader saxreader = new SAXReader();
            ins = this.ftpClient.retrieveFileStream(fPath.getName());
            document = saxreader.read(ins);
            Element rootEle = document.getRootElement(); //<dataroot>
            //得到所有的一級子元素
            List firstElements = rootEle.elements(); 
            Iterator it = firstElements.iterator();
            while(it.hasNext()){
                 //依次得到每一個一級子元素
                Element firstElement  = (Element) it.next(); //<YW_LZXX>
                //得到一級子元素下面的所有元素,及其附帶值
                List second_Elements=firstElement.elements();
                Iterator second_Element=second_Elements.iterator();
                Map<String, Object> map = new HashMap<String, Object>();
                while(second_Element.hasNext()){
                    Element sec_Element=(Element)second_Element.next();
                    map.put(sec_Element.getName(), sec_Element.getText());
                }
                parseList.add(map);
            }
            ftpClient.getReply();
        } catch (Exception e) {
            log.error("xml解析失敗", e);
        } finally {
            if(ins != null){
                try {
                    ins.close();
                } catch (IOException e) {
                    log.error("IO關閉失敗", e);
                }
            }
        }
        return parseList;
    }
           

第三步就是将xml解析後的資料入庫了,這個略。

第四步就是要将入庫失敗的檔案進行移動

這一步是在xml解析入庫時做的,循環周遊時,先确定一個xml檔案裡面總入庫的資料有多少,然後成功入庫的累加,最後判斷成功入庫數和總入庫數是否相等,當成功數小于入庫數時,就将該檔案進行複制到errors目錄,再将原檔案删除。

一開始的思想是先将檔案轉換成流,判斷有沒有errors目錄,有的話切換目錄,然後将資料流轉化成檔案,再把檔案删除。

是以一開始的做法如下:

InputStream ins = ftpClient.retrieveFileStream(ftpFile.getName()); //轉換成輸入流
ftpClient.changeWorkingDirectory("\\errors");//切換目錄
ftpClient.storeFile(ftpFile.getName(), ins);//複制檔案
ins.close();
ftpClient.deleteFile("/"+ftpFile.getName());//删除檔案
           

坑一:ftpClient.changeWorkingDirectory(String pathname);//切換目錄

由于沒發現FTPClient有直接判斷是否存在某個目錄,是以在問度娘的時候,得到了一個巧方法,就是ftpClient.changeWorkingDirectory(“\errors”);根據傳回值true或false來判斷有沒有該目錄。為什麼說他是坑呢,因為在用的時候,看不到效果,用ftpClient.printWorkingDirectory()看目前的路徑時,很多時候發現切換後的路徑為null。

坑二:ftpClient.storeFile(String remote, InputStream local);//複制檔案

在進行檔案複制時,調用該方法後,檔案并未複制到指定的目錄。沒辦法,又是度娘,

發現很多都是說未成功是需要設定ftpClient.enterLocalPassiveMode();将其設定為消極模式。具體百度 FTPClient主動模式和被動模式 了解一下差別,但是我發現設定後,還是沒用啊,沒辦法。

坑三:InputStream ins = ftpClient.retrieveFileStream(String remote); //轉換成輸入流

當進行了該轉化後,不管後面的檔案有沒有複制成功,下次循環時,去解析xml時,得到的inputstream為null,可能就是因為沒設定ftpClient.getReply(); 的原因。

因為當時趕着下班要送出,是以第四步的複制移動删除就沒做了。沒錯,别人下班,你還是要加班。作為一個新手,好不容易有點事做了,怎麼可以做一半就不做了呢,是以那天晚上加班。

加班嘛,中間隔了一段時間去吃飯,腦子也放空一下,跳出了複制後删除的思維。因為要求是将錯誤檔案移動到指定目錄,是以就百度了FTPClient的移動功能,沒想到還真有,

ftpClient.rename(String from, String to);//移動檔案

至于目錄是否存在,直接用ftpClient.makeDirectory(String pathname);//建立目錄

存在傳回false,不存在就建立。

至此,功能就實作了,但是覺得以後萬一遇到複制功能 的呢,是以就又去找度娘一下。并實驗了一下,可以用,代碼如下:

ftpClient.setBufferSize(); 
    ByteArrayOutputStream fos=new ByteArrayOutputStream();
    ftpClient.retrieveFile("\\"+ftpFile.getName(), fos);
    ByteArrayInputStream in=new ByteArrayInputStream(fos.toByteArray());
    ftpClient.storeFile("\\"+errorDir+"\\"+ftpFile.getName(), in);
    fos.close();
    in.close();
           

這是将流在記憶體裡轉換,實作了複制功能。

小結:

在實作該功能後,個人感覺有幾個常用的FTPClient的方法:

1、ftpClient.retrieveFileStream(String remote);

使用該方法後,調用ftpClient.getReply()方法,否則下次調用該方法會傳回null;
           

2、ftpClient.storeFile(String remote, InputStream local);//複制檔案

當使用該方法傳回true,但是目錄沒有成功複制檔案時,可以設定一下被動模式ftpClient.enterLocalPassiveMode();
           

3、ftpClient.changeWorkingDirectory(String pathname);//切換目錄

此方法可以切換目錄。但是個人在用過程中,對其傳回值true和false感覺有點怪。
           

4、ftpClient.rename(String from, String to); //移動檔案到新目錄

當隻是需要移動檔案時,可以選擇此方法,不必像樓主剛開始時那樣,又是判斷有沒有目錄、複制、删除檔案。
           

5、ftpClient.deleteFile(String pathname); //删除檔案

6、ftpClient.makeDirectory(String pathname);//建立目錄

7、ftpClient.retrieveFile(String remote, OutputStream local)//移動檔案

對于複制檔案,如果小檔案,直接調用ftpClient.storeFile(String remote, InputStream local);可能可以成功,但是如果檔案偏大,可能複制就會出問題。是以找了一個利用将檔案讀到記憶體的方法http://bbs.csdn.net/topics/390373219,據說16Mb以上的都能複制成功,雖然沒去實驗,但是基本的複制确實沒問題。

ftpClient.setBufferSize(); 
ByteArrayOutputStream fos=new ByteArrayOutputStream();
ftpClient.retrieveFile("\\"+ftpFile.getName(), fos);
ByteArrayInputStream in=new ByteArrayInputStream(fos.toByteArray());
ftpClient.storeFile("\\"+errorDir+"\\"+ftpFile.getName(), in);
fos.close();
in.close();
           

完成後,在本地伺服器測試沒發現問題。但是别的伺服器會出現程式卡死現象,是以在程式開始時設定ftpClient.enterLocalPassiveMode();,這個方法的意思就是每次資料連接配接之前,ftp client告訴ftp server開通一個端口來傳輸資料。為什麼要這樣做呢,因為ftp server可能每次開啟不同的端口來傳輸資料,但是在linux上,由于安全限制,可能某些端口沒有開啟,是以就出現阻塞。

https://www.cnblogs.com/CopyPaster/p/3494579.html