需求与问题
- 经常发现一些站点通过按钮下载文档浏览器静默(不刷新)地就下载了,而且还可以看到下载的状态等。
- 日常开发里的逻辑是后端返回文件流并带好header,前端直接通过a标签访问这个地址就实现了下载,下载名称在后端header里定义好。这样实现前端不需要任何操作,按照正常的页面访问链接就可以。
- 直到有一天,我通过axios的post方式向后台发送数据,后台经过处理返回文件给我,我却只拿到乱码字符,无法下载,所以就一直想是不是有啥办法可以实现axios下载文件。
总结一下: 下载文件的传统方式是:完全后端处理,前端只需访问该地址(get)就可以下载。 注意此处的访问是get方式,如a标签,window.open和location.href都是get方式。 这种方式的缺点:
- http的get方式携带参数少
- 无法静默下载(浏览器总会刷新)
- 因为浏览器要刷新,因此无法实现一些前端交互细节
- 后端一般存储的都不是中文名称,而返回下载的文件也很少会自定义,文件名难看。
解决方案
后来经过探索,发现xhr的请求方式获取到的文件流数据,通过blob对象处理之后再通过a标签访问的形式可以实现前端下载,而最新的html5为a标签提供了download属性,这个属性可以强制下载文件并提供属性值作为文件名。这样通过axios请求、blob对象处理文件流数据,a标签加download属性方式访问blob对象,就实现了axios post方式下载文件,不用看浏览器转圈,也可通过post传递大量数据(我想传html字符串)给后台处理并返回,并且还可前端定义文件名。
代码
download(config,filename){
config['responseType'] = 'arraybuffer';//关键点1
// config['blobType'] = 'application/vnd.ms-excel';
const blobType = 'application/force-download';//关键点2
const theConfig = this._configSerialize(config);//这里可忽略,我封装的对config进行序列化设置后才能返回给axios用的方法
return axios(theConfig).then((res) => {
const blobo = new Blob([res.data],{type:blobType})
const archor = document.createElement('a');
const href = window.URL.createObjectURL(blobo)//关键点3
archor.setAttribute('href',href)
// /* 关键之处:使用download属性必须要html5的页面才行 ,而且它不会刷新,文件名及扩展名均由这里控制*/
archor.setAttribute('download',filename)//关键点4
archor.click();
return true;
})
}
//use
//download(axiosConfig,'abc.json')
- 关键点1:在给axios传递请求配置时,必须指定
responseType
,值笔者尝试了blob和arraybuffer,都是可行的- 关键点2:在new blob对象的时候,需要传递配置,type需指定为文件的mime类型,笔者尝试发现使用
application/force-download
可能是通用的,并且连后端都无需设置header(我尝试发送jpg、pdf、xls都行)- 关键点3:用
URL.createObjectURL
将blob实例创建成一个链接(理解成blob对象转变为a标签可访问的href链接地址)- 关键点4:最后一点,也就是为创建的a标签设置download属性了。注意文件名应该带扩展名,否则下载的文件无法识别(下载了再手动添加个扩展名也行?不嫌麻烦就好)
使用这个函数很简单,传入axios请求的配置和一个文件名就行。笔者尝试后端无论返回啥(纯文本字符串也行)都可以下载。且下载的文件类型由download属性的扩展名规定,因此使用时应该明确知道后端返回了什么格式的文件,否则文件会打不开(下载了再改回来也行,用户表示不懂)。
这样实现下载文件感觉很牛啊,后端只管返回字符串或者文件流。前端也无需复杂设置,只需要带对了扩展名就可以下载。而且解决了get访问参数限制的问题。
注意:download属性必须要求html页面类型为html5:<!Doctype html> download属性还可以强制下载文件,平常通过a标签访问图片资源、pdf文档时如果后台没有设置header,一般都是直接在浏览器中显示,不是下载。
缺点
使用这种方式下载文件有一个明显的缺点,当后台出错,返回了布尔值或者json对象时,就无法识别并处理。因为设置了responseType
,axios拿到请求资源后会将其包装为一个arraybuffer对象,也就是res.data永远都是个arraybuffer object
。参考网上一些解决办法是通过前后协定在header中设置一些错误识别代码,axios拿到之后可以先拿header进行判断,然后再决定是否下载文件。