项目中有一个download
接口,这个接口直接返回二进制文件流。之前如果下载文件,一般后端传一个加密串过来,然后前端进行解密,拼接成URL再去下载。现在等于直接把文件发给前端,这种情况下怎么下载文件?问了下同事,他们也没遇到过这种情况,好在网上有不少资料可以参考。下面先给出解决问题的思路,最后再给链接。
首先需要接收后端传过来的二进制流。默认情况下axios不会处理二进制数据,即请求可以正常被浏览器接收,但是axios不会去处理。需要在请求的时候设置responseType: 'blob'
才可以,代码如下:
axios.get({
url: 'xxxxxx',
method: 'get',
data:{
},
responseType: 'blob', // 声明返回blob格式
}).then(res => {
downLoadBlobFile(res.data, fileName, mimeType);
});
什么是Blob呢,MDN官方解释:Blob 对象表示一个不可变、原始数据的类文件对象。Blob
表示的不一定是JavaScript原生格式的数据(https://developer.mozilla.org/zh-CN/docs/Web/API/Blob)
拿到文件流之后,需要生成一个URL才可以下载。可以通过URL.createObjectURL()
方法生成一个链接,代码如下:
let URL = window.URL || window.webkitURL;
let objectUrl = URL.createObjectURL(blob);
关于这个方法,可以参考MDN官方的解释:https://developer.mozilla.org/zh-CN/docs/Web/API/URL/createObjectURL
后端传过来的文件流存储在内存中,然后生成的URL就是文件流在内存中的地址,当然这个地址是临时的,浏览器在 document 卸载的时候,会自动释放它们。MDN官方建议,当不再需要这些URL对象的时候,应该调用URL.revokeObjectURL()
主动释放。
正常情况下,通过window.location = url
就可以下载文件。浏览器判断这个链接是一个资源而不是页面的时候,就会自动下载文件。
但是通过文件流生成的url对应的资源是没有文件名的,需要添加文件名。这个时候就可以用到a标签,a标签我们经常会用到href
属性,但是其实还有一个download
属性,这个属性就指定了下载的文件名,代码如下:
let a = document.createElement('a');
a.href = objectUrl; // 文件流生成的url
a.download = fileName; // 文件名
document.body.appendChild(a);
a.click();
a.remove();
从代码上可以看出,下载的流程就是创建一个a标签,点击一下再删掉。那文件名需要从哪里搞呢?很简单,加到请求头里。
调用API的时候,在axios里面加一个请求头,代码如下:
svnDocument.download = function (path, name) {
return request({
url: `${
baseUrl}/download?path=${
path}`,
method: 'get',
responseType: 'blob',
headers: {
'x-real-name': encodeURI(name),
}
})
}
这里有一个注意点,请求头中不能有中文。文件名很有可能会带中文,此时如果直接添加请求头就会报错,连请求都不会发送。
Failed to execute ‘setRequestHeader’ on ‘XMLHttpRequest’: String contains non ISO-8859-1 code point.
所以可以在添加请求头的时候先进行encodeURI
,然后接收的时候再进行decodeURI
。
一般项目都会对axios封装一层叫requests的东西,可以在requests里面做请求拦截和响应拦截。这里需要用到响应拦截,在axios接收到响应的时候,获取一下请求头中的文件名,代码如下:
service.interceptors.response.use(
(response) => {
const {
headers, data, config } = response;
if (
config &&
config.responseType &&
config.responseType == 'blob'
) {
const fileName = config.headers['x-real-name'];
downloadFile(data, decodeURI(fileName));
return data;
}
},
(error) => {
// 响应异常的处理
}
)
请求的时候添加的字段都在config
里面,在发请求的时候我们添加了responseType: 'blob'
,可以通过config.responseType == 'blob'
判断是否是二级制文件流。然后文件名也用同样的方法获取,config.headers['x-real-name']
别忘了还要再decode一下。
拿到二进制流和文件名之后,就可以调用downloadFile
方法下载文件了。
这里再说一下请求拦截和相应拦截的用法,请求拦截一般是给请求携带token,代码如下:
service.interceptors.request.use(
(config) => {
const token = util.cookies.get('token');
if (token && token !== 'undefined') {
// 让每个请求携带token
config.headers['Authorization'] = token;
}
},
(error) => {
// 发送失败
}
)
响应拦截一般用于返回请求成功的数据。一般项目中用的都是RESTful接口,返回的格式是这样的:
{
code: 200,
data: [], // 请求成功的时候回传数据
msg: "", // 请求失败的时候返回信息
state: "ok",
success: true
}
项目中调接口的时候,直接就能获取到data中的数据,是因为响应拦截的时候做了封装:
service.interceptors.response.use(
(response) => {
// dataAxios 是 axios 返回数据中的 data
const dataAxios = response.data;
// 这个状态码是和后端约定的
const {
success, result } = dataAxios;
// 根据 code 进行判断
if (success || result === 'success') {
// 请求成功返回数据
return dataAxios.data;
}
}
)
参考链接:
设置了responseType:Blob之后,如果返回json错误信息,如果获取?
URL.createObjectURL() - MDN官方
如何下载后台接口返回给我们的二进制数据文件(vue +axios)
接口返回二进制文件流,前端通过blob对象实现下载
后端返回文件流,前端处理进行文件下载
处理下载接口返回的文件流数据
后台返回文件流,前端下载成pdf文件,下载成功,但是,展示的是空白,没有任何文件,请指点?
JS下载文件常用的方式