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=“”,将“”内的文件名改成包含路径的文件名,也许下载之后,客户端能够获悉其原来在服务器上的对应的目录层级,从而能够本身后续处理。
三、若是可能,仍是按照压缩包的形式下载一个目录也许更好。