根据后端数据实现下载或者预览的几种方式

最近遇到后端要直接传送文件流,让我把二进制文件流流转成图片的需求。

一开始以为是请求后得到一个图片的 src,但是当我打开响应数据时,发现这样的乱码 image 很明显,这是一个二进制文件流,那么我需要把他转成图片展示在页面上。应该如何做呢?

主要思路:还是用 Blob 对象的 createObjectURL这个 API,让返回的数据能够生成本地对象 URL,然后我们将生成的 URl 放到 img 标签上就行了。

responseType

这里重点要介绍一下 responseType 这个 XMLHttpRequest 对象的属性,它允许我们手动的设置返回数据的类型。如果我们将它设置为一个空字符串,它将使用默认的"text"类型。

也就是说我们可以手动将返回的 response 数据转化成 blob 对象,以下是我用 axios 设置responseType发送请求的代码

javascript
1axios({
2        url: `${apiPrefix}/workflow/api/getCirculatePictures`,
3        params: { processInstanceId },
4        responseType: 'blob',
5      })

使用 axios 可以很方便地手动设置这个属性。如果用原生XMLHttpRequest的话就是这样设置的

javascript
1let request=new XMLHttpRequest()
2request.open('GET',url)
3xhr.responseType = 'blob'  //这里写
4request.onreadystatechange=()=>{
5  if(httpRequest.readystate===4 && httpRequest.status===200){
6    console.log('success')
7  }else{
8    console.log('fail')
9  }
10}
11request.send()

设置完之后我们打印出来看看 image

会发现默认 type 是"text/xml",那么我们需要使用 new Blob 构造函数重新生成一个图片的 blob 对象。

blob 转化

语法 var aBlob = new Blob( array, options );

options 中有个 type 选项,可以设置 blob 的 MIME类型,我们需要转化成image/png。

得到我们要的 blob 对象类型后,我们就可以使用 createObjectURL生成对象 URL。

实现代码

javascript
1axios({
2        url: `${apiPrefix}/workflow/api/getCirculatePictures`,
3        params: { processInstanceId },
4        responseType: 'blob',
5      }).then((res) => {
6        console.log(res);
7        let blob = new Blob([res.data], { type: 'image/png' });
8        let url = window.URL.createObjectURL(blob); //这个 url 就可以用啦
9        setImgSrc(url);//这个函数用来设置 img 的 src 属性
10      });

不过为了性能着想,这里最好再设置一下revokeObjectURL。当你结束使用某个 URL 对象之后,应该通过调用这个方法来让浏览器知道不用在内存中继续保留对这个文件的引用了。

javascript
1        imgElement.onload = function () {
2          window.URL.revokeObjectURL(this.src);
3        };

将 blob 转回 JSON

虽然上面的代码已经能够处理后端成功返回的文件流了,但是实际业务中客户不一定能够成功导出文件,比如有可能客户想导出 100 万条数据但是后端不允许。 所以我们还需要告诉客户为什么不能导出,这个错误原因自然也需要后端传递给前端。错误信息此时很有可能被 responseType 设置成 blob 对象,在这种情况下需要转回 JSON。我们可以用 fileReader 去读取 blob。 具体代码如下:

javascript
1var reader = new FileReader()
2reader.readAsText(blob, 'utf-8') //将blob读成text
3reader.onload = function () {
4  // 读完之后的结果给JSON转化一下
5  let data = JSON.parse(reader.result)
6  // 于是就可以愉快地打印后端的报错信息啦
7  message.error(data.apiMessage)
8}

根据图片、pdf、excel 的下载地址实现纯下载

如果后端传递过来的是可以直接用来下载的链接,有时候我们会将其绑在 a 标签上,然后赋予 download 属性,这样就可以下载了。 但是浏览器有时候会自作聪明地帮我们实现常见的图片、pdf 等预览~这就非常蛋疼了。

这是因为浏览器会根据 Content-type 属性来判断是否支持,如果支持时会尝试去在页面上展示该资源。我们也不能要求后端将 Content-type 改成'application/octet-stream'之类的绕过浏览器的机制,还是自己动手实现吧。

思路也很简单:

  • 将拿到的 response 中的数据改成 blob 对象
  • 用 createObjectUrl 将其传给一个创建出来的 a 标签
  • a 标签直接用代码触发点击

下面直接贴代码

javascript
1function download(href, filename = '') {
2  const a = document.createElement('a')
3  a.download = filename
4  a.href = href
5  document.body.appendChild(a)
6  a.click()
7  a.remove()
8}
9
10function downloadFile(url, filename = '') {
11  fetch(url, {
12    headers: new Headers({
13      Origin: window.location.origin,
14    }),
15    mode: 'cors',
16  })
17    .then((res) => res.blob())
18    .then((blob) => {
19      const blobUrl = window.URL.createObjectURL(blob)
20      download(blobUrl, filename)
21      window.URL.revokeObjectURL(blobUrl)
22    })
23}