项目中有一个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才可以下载。可以通过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()主动释放。

a标签添加文件名

正常情况下,通过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下载文件常用的方式

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐