前端中关于下载文件问题

大部分文件下载都是在服务器放置好需要下载的资源,然后由服务器配置可以下载的文件类型,由系统去判断权限。如果文件比较小,不想通过系统去判断权限给出下载地址,如何在前端完成下载功能呢?



只需要将数据准备好,然后调用以下js函数即可:





调用方式:downLoadFile('test.txt', "Hello world!");





以上文档 转自:pakinguo(http://blog.csdn.net/dragon_q/article/details/46970699)



由于我的工作重心转向网盘的开发和维护,最近整了一个html版的文件浏览器demo,核心内容为上传和下载,积累了一点经验,这里把其中下载的内容拿出来谈一谈。(本文涉及的前端使用vue.js,后台使用node.js+express) 

一个文件的下载

-下载链接

开始之前,回想起我们平时上网下载文件接触到的链接,都是譬如 
http://xxxxxx/name=cccc.rar 
http://xxxxxx/download/abcabc.exe 
等这种类型的链接。

  观察后不难发现这些都是get形式的链接。事实上如果用post,后台会在xhr的responseText中返回文件内容,无法保存到本地,不过可以直接append到页面上,然后让用户自己复制。综合以上考虑,因此我们需要用get建立传输的链接。

-从打开开始

  首先,我们根据需要的参数,拼出需要的get链接。要下载一个文件,需要传入它的文件地址和文件名,get链接就可以这么设计:

http://localhost:2333/downloadSingle?dir=f%3A%5Cdemo&name=desktop.ini
 
 
  • 1
  • 1

其中有两个参数:dir,name

  后台中根据这两个参数拿到对应的文件,用node.js里fs流的方式传出来:

var currFilePath = path.join(dir,name);
var fReadStream = fs.createReadStream(currFilePath);
fReadStream.on("data",(chunk) => res.write(chunk,"binary"));
 
 
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

  这样下载下来的文件,文件名是混乱的,应该在报文里传入Content-Disposition告诉浏览器文件名(Content-type可以不设置):

res.set({
    "Content-type":"application/octet-stream",
    "Content-Disposition":"attachment;filename="+encodeURI(fileName)
});
 
 
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

  当然下载前应该判断下是否存在这个文件,最后后台的代码如下:

router.get('/downloadSingle',function(req, res, next){
    var currDir = path.normalize(req.query.dir),
        fileName = req.query.name,
        currFile = path.join(currDir,fileName),
        fReadStream;

    fs.exists(currFile,function(exist) {
        if(exist){
            res.set({
                "Content-type":"application/octet-stream",
                "Content-Disposition":"attachment;filename="+encodeURI(fileName)
            });
            fReadStream = fs.createReadStream(currFile);
            fReadStream.on("data",(chunk) => res.write(chunk,"binary"));
            fReadStream.on("end",function () {
                res.end();
            });
        }else{
            res.set("Content-type","text/html");
            res.send("file not exist!");
            res.end();
        }
    });
});
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

建立好后台之后,前端直接拿链接去测试。浏览器上打开(具体链接自己配置) 
http://localhost:2333/downloadSingle?dir=f%3A%5Cdemo&name=desktop.ini 
小文件在浏览上冒出来了,下载成功。

-无“闪现”下载

  你问怎么用代码下载下来?最简单的方法就是把这个链接放进window.open(url)里面,然后浏览器会新开个页面,建立链接后会关闭访问页,开始下载。然而,这种方法会闪一下,体验起来比较挫。接下来介绍一种没有”闪现”的方法。 
   
  我在《ie8下用iframe解决表单submit以及二级域名跨域的方法》中介绍过如何使用iframe来避免submit提交后页面的自动跳转(使用iframe屏蔽submit提交表单后的自动跳转),基本原理就是隐藏一个iframe,随浏览器怎么折腾它的死活,反正我们看不到就行了。 
  下面贴代码:

function downloadByIframe(url){
    var iframe = document.getElementById("myIframe");
    if(iframe){
        iframe.src = url;
    }else{
        iframe = document.createElement("iframe");
        iframe.style.display = "none";
        iframe.src = url;
        iframe.id = "myIframe";
        document.body.appendChild(iframe);
    }
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

  需要下载文件时,调用downloadByIframe(url)即可。 
  不过这样的方法有个缺点,当后台找不到文件的时候,无法返回错误信息,不知道的还以为挂掉了。如果要即时相应的话,同样可以参考我这篇文章介绍的方法:使用onload获取后台返回在iframe中的数据

多个文件的下载

  以上介绍了下载一个文件时的处理方法。当然很可能会遇到多个文件要下载的情况。

  遇到多个文件要下载时,如果直接使用单个文件下载的方法,可以使用for循环,然后往里面扔downloadByIframe(url),简单暴力。 
  不过文件越多越容易引起强迫症,而且对于文件夹而言,无法使用这种方式下载。

  现在很多网盘对待多文件下载的情况时,会先把它们全部压缩起来,打包成一个文件再下载。通过这种思路,我们可以先在服务器端压缩文件,存到本地,然后通过这个压缩文件的路径和名称,拼出get的链接,再使用上文介绍的下载单个文件方法,即可顺利下载。使用这种思路,文件夹同样也可以打包进来。

-archiver压缩模块

  在开始之前,首先要介绍下在本文node.js中使用的压缩模块archiver,npm可以下载到。我是通过《 nodejs 中压缩/解压方案》(倪舒扬)这篇文章了解到archiver的,该篇文章比较详细的讨论了node.js其他几个压缩模块的利弊,有兴趣可以阅读一下。

  先贴代码:

var output = fs.createWriteStream(path.join("zip",zipName));
var archive = archiver.create('zip', {});
archive.pipe(output);   //和输出流相接
archive.append(fs.createReadStream(fileDir),{"name":fileName}); //塞文件进去
archive.finalize();
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

其中,archive.append只能放入单个文件,如果要放入文件夹或多个文件,需要使用archive.bulk()这个方法:

archive.bulk([ 
    {
        cwd:currDir,    //设置相对路径
        src: [folderName1, fileName],//文件夹格式:xxx/**
        expand: currDir
    }
]);
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

官方文档对这个方法有更多的介绍archive.bulk传送门,有兴趣的可以啃一下。

archiver还提供了几个监听事件,用于处理阶段性的事件:

archive.on('error', function(err){
    //报错
});
archive.on('end', function(a){
    //压缩完毕生成文件
});
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

-代码设计

前端代码比较简单,直接贴出来(为省事我用的vue.js,大家可以用jQuery或者XMLHttpRequest自己写一个):

download:function(fileArray){
    var rootDir = this.$data.dir;
    this.$http.post("/download",{
        dir:rootDir,
        fileArray: fileArray
    }).then(function(result){
        //success
        var data = result.data;
        if(data.code == "s_ok"){
            downloadByIframe(data.url);
        }else{
            alert(data.summary);
        }
    });
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

后台综合上面的archiver,代码如下:

router.post('/download',function(req, res){
    var currDir = path.normalize(req.body.dir),
        fileArray = req.body.fileArray,
        fileNameArray = [];

    //将文件和文件夹分开命名
    fileArray.forEach(function(file) {
        if(file.type == 1){
            fileNameArray.push(file.name);
        }else{
            fileNameArray.push(path.join(file.name,"**"));  //文件夹格式:folderName/**
        }
    });

    if(fileArray.length == 0){
        res.send({"code":"fail", "summary":"no files"});
        return;
    }

    var output = fs.createWriteStream(path.join("zip",zipName));
    var archive = archiver.create('zip', {});
    archive.pipe(output);   //和输出流相接
    //打包文件
    archive.bulk([ 
        {
            cwd:currDir,    //设置相对路径
            src: fileNameArray,
            expand: currDir
        }
    ]);

    archive.on('error', function(err){
        res.send({"code":"failed", "summary":err});
        throw err;
    });
    archive.on('end', function(a){
        //输出下载链接
        var downloadUrl = "/downloadSingle?dir="+encodeURIComponent(zipDir)+"&name="+encodeURIComponent(zipName)+"&comefrom=archive";
        res.send({"code":"s_ok", "url":downloadUrl});
    });
    archive.finalize();
});
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

其中需要强调的一点是,archiver的bulk中src传入的文件夹和文件写法不同,可以参考如下格式:

//[文件夹, 文件夹, 文件, 文件]
["calendar\**, nginx-1.8.1\**, .DS_Store, desktop.ini"]
 
 
  • 1
  • 2
  • 1
  • 2

锦上添花

-统一下载接口

  后台download这个接口经过改造,可以一起适配单个文件和多个文件下载的情况。 
  通过判断fileNameArray中是否为单个文件,可以建立一个分支:

if(filesCount == 1 && fileNameArray.length == 1){
    //只有一个文件的时候直接走get
    var downloadUrl = "/downloadSingle?dir="+encodeURIComponent(currDir)+"&name="+encodeURIComponent(fileNameArray[0]);
    res.send({"code":"s_ok", "url":downloadUrl});
}else{
    //多个文件就压缩后再走get
    //用archiver压缩
    ... 
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

  这样前端不管用户选择了几个文件,只需要post download这个接口,拿到返回的链接塞进downloadByIframe()这个方法即可。

  至此,本文文件下载的方法全部介绍完毕。


以上node.js的文档转自:Lienviws(http://blog.csdn.net/liyijun4114/article/details/51743068#t1)






  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在Vue实现前端点击后的文件下载,可以按照以下步骤进行操作: 1. 首先,在Vue组件定义一个方法,用于处理下载文件的逻辑。 ```javascript methods: { async downloadFile() { // 处理文件下载的逻辑 } } ``` 2. 在该方法,发送请求到后端获取文件的数据。 ```javascript async downloadFile() { const response = await axios.get('/api/download'); // 假设后端接口为/api/download const fileData = response.data; // 获取返回的文件数据 } ``` 3. 将获取到的文件数据转换为Blob对象,并创建一个临时的下载链接。 ```javascript async downloadFile() { const response = await axios.get('/api/download'); const fileData = response.data; const blob = new Blob([fileData], { type: 'application/octet-stream' }); const downloadUrl = URL.createObjectURL(blob); } ``` 4. 创建一个a标签,并设置其href属性为临时下载链接,以及download属性为文件名。 ```javascript async downloadFile() { const response = await axios.get('/api/download'); const fileData = response.data; const blob = new Blob([fileData], { type: 'application/octet-stream' }); const downloadUrl = URL.createObjectURL(blob); const downloadLink = document.createElement('a'); downloadLink.href = downloadUrl; downloadLink.download = 'filename.txt'; // 设置下载文件名 } ``` 5. 将a标签添加到页面,并模拟用户点击该链接进行文件下载。 ```javascript async downloadFile() { const response = await axios.get('/api/download'); const fileData = response.data; const blob = new Blob([fileData], { type: 'application/octet-stream' }); const downloadUrl = URL.createObjectURL(blob); const downloadLink = document.createElement('a'); downloadLink.href = downloadUrl; downloadLink.download = 'filename.txt'; document.body.appendChild(downloadLink); // 将a标签添加到页面 downloadLink.click(); // 模拟用户点击下载链接 document.body.removeChild(downloadLink); // 下载完成后移除a标签 } ``` 通过以上步骤,就可以在Vue实现前端点击后的文件下载。你可以根据自己的需求修改文件名、后端接口等相关参数。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值