HTML5+原生JS實現服務器端目錄樹中多文件下載
做者:雲荒杯傾
做者博客css
需求
需求是這樣的,服務器上有一個目錄,目錄下面可能既有文件又有其餘目錄,其餘目錄下面也同樣,既可能有文件也有目錄;瀏覽器要顯示有這個目錄,並提供這個目錄下全部文件的一鍵下載功能。html
實現原理
實現這個需求,本程序考慮到的知識點有html5 <a>標籤的下載功能、數據結構中樹的廣度優先遍歷算法、一點http知識和一點JS侷限性的知識。前端
HTML5 <a>標籤的下載功能
首先說HTML5 <a>標籤的下載功能,
HTML5的<a>標籤有一個download屬性,若是你設置了這個屬性,那麼點擊這個連接,就再也不是跳轉到這個<a>標籤href引用的地址了,而是直接去下載href所表明的一個文件。html5
即git
<a herf="./bbb.html">這是超連接</a>
這樣,點擊這是超連接幾個字,只能頁面跳轉到bbb.html。
而,github
<a herf="./bbb.html" download="ccc.html">這是超連接</a>
這樣,加了download屬性後,你再點擊這是超連接幾個字,就直接下載bbb.html到你的本地機器(客戶端)了,且文件會更名爲「ccc.html」。若是隻寫download,則是保持原始文件名下載。算法
一點http知識
http是不提供目錄遍歷的,因此暫時不知道其餘實現方法是否碰到了這個點,因此致使程序失敗。某種程度上,本文的程序算是避開了這個點?chrome
一點JS侷限性的知識
JS語言跑在瀏覽器的話,是不能操做本地(客戶端)太多東西的,其中就包括一些文件操做,而文件夾建立操做就包括在這些限制操做以內。這也是沒法實現將服務器上的一個目錄按照其目錄結構原樣拷貝到本地(客戶端)的緣由。後端
看過一篇博文的舉例很好,他說,若是我有一個網站,你是訪客,你訪問,點擊了某個連接,我就把網站的一個目錄樹(假設有一百萬個txt),拷貝到你的本地,你會不會惱火?並且,之因此瀏覽器不提供這些功能,主要是出於客戶端安全方面的考慮。數組
作這個需求的時候,看到IE上有一個activexObject對象,能夠在本地建立文件夾。可是想一想就只有IE支持,仍是算了。
綜上,本程序是將服務器上一個有層次的目錄樹裏面全部的文件,經過瀏覽器下載到本地同一個文件夾下的。
不過由於下載一個目錄樹的功能,最主要的考察點仍是樹狀數據結構的遍歷,若是哪天瀏覽器都實現了本地建立文件夾,本程序的代碼仍有參考價值。
目錄樹的廣度優先遍歷及本程序的實現思路
本例的思路是,先後端定義一個目錄樹的接口。
前端經過xhr拿到數據。
一層一層遍歷這顆目錄樹,直到遍歷完這個目錄樹下全部文件,也就能拿到這全部文件的URL了。假設一共有n個文件,也就是n個URL。
拿到n個URL後,註冊這個目錄對應的下載該目錄下全部文件按鈕的監聽器,監聽器內建立n個html5 <a>標籤,<a>標籤的href分別賦值url路徑,全部<a>標籤設置download屬性。
如此,就實現了加載HTML,HTML上有一個表明着服務器上一個目錄的目錄名,目錄名後面有一個下載該目錄下全部文件的按鈕,點擊該按鈕,就從服務器下載了該目錄下全部文件到本地。
本文定義的目錄樹結構
目錄樹接口定義以下,一個目錄要有name(也就是URL),file是該層目錄下裸露的文件,childDir是該層有的下一層的目錄,如此往復。。。若是到了某一目錄,既沒有文件有沒有目錄,那麼name就定義爲[]數組,childDir也定義爲[]數組。
本例中的目錄樹是:根目錄./chrome61module,其下有三個文件,分別是aa.css,aa.js,module-test.html,還有一個目錄bb;bb目錄下只有兩個文件bb.js和bb.html。
//假設這是從服務器取來的a連接的目錄結構 var rootDir = { "name":"./chrome61module", "file":["/aa.css","/aa.js","/module-test.html"], "childDir":[{ "name":"./chrome61module/bb", "file":["/bb.js","/bb.html"], "childDir":[] },{}] };
效果展現
圖1 HTML頁面
圖2 點擊下載該目錄下全部文件Chrome會提示你是否要下載多個文件
圖3 本例中下載成功的五個文件
代碼展現
代碼比較簡單,就直接放了,你們直接本身粘貼了,就能運行,注意修改目錄樹結構爲你本身的。
<body> <a href="./chrome61module" id="aDir">chrome61module目錄</a> <button id="downloadDir">下載目錄下全部文件</button> <script> //假設這是從服務器取來的a連接的目錄結構 var rootDir = { "name":"./chrome61module", "file":["/aa.css","/aa.js","/module-test.html"], "childDir":[{ "name":"./chrome61module/bb", "file":["/bb.js","/bb.html"], "childDir":[] },{}] }; //DOM var btnDownload = document.getElementById("downloadDir"); var aDir = document.getElementById("aDir"); //按鈕監聽器 btnDownload.addEventListener("click",downloadDir); function downloadDir() { //處理某一層 function oneTreeLayerProcess(dir) { if(JSON.stringify(dir) !== "{}"){ if(dir.file.length !== 0){ for(let i = 0; i< dir.file.length; i++){ var j = document.createElement("a"); j.href = dir.name + dir.file[i]; j.download = dir.file[i].slice(1); j.click(); } } if(dir.childDir.length !== 0){ for(let i =0; i < dir.childDir.length; i++){ //遞歸 oneTreeLayerProcess(dir.childDir[i]); } } } } oneTreeLayerProcess(rootDir); } </script> </body>
總結
一、因爲http和JS能力的限制,目前僅能實現文件下載,不能實現文件夾下載或者文件夾本地建立,所以想原樣拷貝服務器上一個目錄到本地機器,使用JS目前較難作到。
二、因爲本例實現的將一個目錄樹下全部文件都平行下載到本地的同一個文件夾下。形成客戶端即便下載了也沒法理解服務器上原目錄樹的困難。
一點改進是,若是使用download=「」,將「」內的文件名改成包含路徑的文件名,也許下載之後,客戶端能夠獲悉其原來在服務器上的對應的目錄層級,從而能夠本身後續處理。
三、若是可能,仍是按照壓縮包的形式下載一個目錄也許更好。