前端文件下载的方式

前言

最近在做一个文件下载的功能,但是对前端下载方式并不了解,以前做过类似的,都是通过超链接的src属性去访问后端,再配置一个download属性,回来的数据,浏览器会自动保存文件。但是目前我的下载需求有点复杂,要先通过权限校验,再去执行下载操作。多了鉴权这一步,我们在提交下载请求的时候,可能要携带用户的cookie信息,而Download这种方式的话,没办法携带cookie值,也没有办法通过权限校验。于是就探究了一下,前端文件下载的方式,做个总结。

前端下载的方式

1. a标签

特点:最简单、便捷的方式,点击一下到后端,后端返回数据浏览器就能自动保存文件

<a href='后端服务的链接' download="文件名">点击下载a>
2. button按钮绑定一个点击事件,该方法内实现一个跳转功能

特点:可以附加一些其他的功能响应,能做的操作更多。

<button type="button" onclick="downloadFile()">downloadbutton>
const downloadFile = () => {
    window.location.href = '后端服务链接'; // 方式一
    window.open('后端服务链接')// 方式二
}

这种方式,等同于你在浏览器上直接输入【下载地址】,如果后端的响应头content-type与content-disposition设置不对的话,如果你下载的文件是【可执行文件】,比如说html,css,js这种,浏览器会打开这个文件,而不是保存文件。

除此之外,还存在一些问题:

window.location.href 如果下载多个文件,而你点击太快的时候,会导致请求重置,又重新下载一遍。

window.open()的这个效果,相信你肯定见过,浏览器会打开一个空白的页面,地址栏上链接,就是页面空白的,然后浏览器会保存文件。这对用户体验来说,并不友好。当然,你可能会说,那我再window.close不就行了吗?也不是不行,至少比那些留着个空白页面的要好点。

3. form表单提交
<form action="后端下载链接">
    <input type='submit'>下载input>
form>

--------------------------------------------------------- 不太正经分割线 ---------------------------------------------------------

前面的两种方式都是比较基础的,入门级方式。只能适用于一些简单的应用,前端能够直接call到后端的,没什么复杂需求的,也没有使用框架的。那一般使用框架、需要请求头携带cookie来进行权限校验的复杂下载请求,该怎么去做呢?这种时候,一般会通过框架去请求后端,后端返回来的数据并不会直接触发浏览器保存文件,而是将数据返回到response中;然后再操作dom,在界面添加一些元素再去触发浏览器来保存文件。下面举的例子是基于axios来实现的下载请求,ajax或者其它工具也大同小异。

4. 通过iframe元素触发浏览器保存文件

什么?iframe?这不是用来做内联框架的标签而已吗?这能够用来下载?你忘了iframe还有个src属性了吗,这个可以用来控制iframe显示的内容。但是我们的目的并不是想让他显示,只是想让它打开链接来触发浏览器保存文件。

<button type="button" onclick="downloadFile()">download</button>
const downloadFile = async () => {
    // 注意,直接通过iframe的src去访问后端的话,是无法实现携带cookie去做权限校验的,在这里我的axios实现了携带cookie了的,所以我得先发送一次axios请求
    let res = await axiosSend();
    // 其实在上面那步执行返回,数据已经保存到了response中,但是无法触发浏览器保存文件
    // 接下来我们操作dom来添加一个iframe
    let iframe = document.createElement('iframe');
    // 防止页面上弹出一个小框
    iframe.style.display = 'none';
    // axios的响应格式是这样可以获取url,其他的你可以根据具体的响应结果来修改这个src,这个src由于依赖了前面的请求,所以再次发送的时候,也会携带cookie值
    iframe.src = res.request.responseURL;
    // 在它加载的时候我们就remove了,不要了
    iframe.onload = function () {
        document.body.removeChild(iframe);
    }
    // 将这个iframe嵌入到页面元素中
    document.body.appendChild(iframe);
}

通过上面的这种方式,就可以实现鉴权文件下载功能。你如果不需要权限校验,其实也可以直接通过iframe的src去访问后端下载链接,也是很不错的一个选择。它的优点也是很明显的,由于每次点击,促发下载事件,都是打开一个新的iframe去执行下载,所以不会出现前面的下载请求重置的问题,适合用于多文件下载。iframe没有展示请求路径,隐秘性较好。

除此之外,iframe还支持跨域,有兴趣的小伙伴可以自行了解一下。

​ 缺点的话,也比较明显,每次下载都要去访问后端两次(如果你要鉴权的话),如果并发量很高的情况下,可能就问题很大了。

5. 利用form表单

这个和前面那个form的原理其实是一样的,只不过也是通过操作dom来实现的,而且通过这种方式实现的也是可以支持下载多文件。

const downlaodFile = () => {
    let form = document.createElement('form');
    document.body.appendChild (form);
    form.method = "GET";
    form.action = '后端下载链接';
    form.submit();
    document.body.removeChild(form);
}

至于想实现鉴权下载,和前面的一样,都需要先通过axios去访问一遍,然后带着cookie值再去访问一遍后端。

6. 利用a标签

可能这个你在看到上一个方法的form的时候已经想到了,那我嵌入一个a标签岂不是更直接吗?确实如此。这种方式也可以实现多文件下载。鉴权下载同上。

const downloadFile = () => {
    let a = document.createElement('a');
    a.style.display = 'none';
    a.href = '后端下载链接';
    a.download = '文件名称';
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
}

-------------------------------------------------- 不太正经分割线 ----------------------------------------------------------------

看到这里,或许你感觉很嫌弃这些方法。想鉴权都必须要访问两次后端,那有没有更好的办法呢?你可能会想,我可不可以拿出来第一次访问后端返回的数据,然后再保存下来,这样不就可以只访问一次后端了嘛。

在这里就要讲一下Blob类,官方文档

Blob 对象表示一个不可变、原始数据的类文件对象。它的数据可以按文本或二进制的格式进行读取,也可以转换成 ReadableStream 来用于数据操作。

我们可以先将第一次axios访问后端返回的数据封装成一个blob对象,然后使用blob创建一个指向类型化数组的URL,再用上面的4、5、6任意一种方式去访问这个URL,也可以将文件保存,而且只是访问了一次后端。

const downloadFile = async () => {
    let res = await axiosSend();	// 假设我的res就是返回的二进制数据
    let blob = new Blob([res]);		// 将二进制数据封装成blob对象
    let iframe = document.createElement('iframe');
    iframe.style.display = 'none';
    iframe.src = URL.createObjectURL(blob); // 创建指向blob数据的url
    iframe.onload = function () {
        document.body.removeChild(iframe);
    }
    document.body.appendChild(iframe);
    URL.revokeObjectURL(iframe.src);		// 释放URL对象
}

同样的,你可以用a标签、form表单的形式去实现同样的功能。

这里再贴上一个a标签的方式,因为这个会比较常用,方便我自己直接复制,哈哈

const downloadFile = async () => {
    let res = await axiosSend();	// 假设我的res就是返回的二进制数据
    let fileName = res.headers["content-disposition"].split(";")[1].split("=")[1].replace(/^"|"$/g, '');	// 获取文件名
    let contentType = res.headers['content-type'];	// 获取content-type
    let blob = new Blob([res.data], { type: contentType });		// 将二进制数据封装成blob对象
    let a = document.createElement('a');
    a.style.display = 'none';
    a.href = URL.createObjectURL(blob);
    a.download = fileName;
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
    URL.revokeObjectURL(a.href);		// 释放URL对象
}

你可能感兴趣的:(JS,前端学习,前端,html,javascript,文件下载)