由于項目有個需求是要去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