xiaodongxier / iWebs

开发技术转载
https://github.xiaodongxier.com/iWebs
33 stars 8 forks source link

前端根据后端返回实现下载或者预览的几种方式 #218

Open xiaodongxier opened 11 months ago

xiaodongxier commented 11 months ago

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

一开始以为是请求后得到一个图片的 src,但是当我打开响应数据时,发现这样的乱码
[

](https://user-images.githubusercontent.com/53749772/123616994-638f4000-d839-11eb-9889-c2652dd15087.png)
很明显,这是一个二进制文件流,那么我需要把他转成图片展示在页面上。应该如何做呢?

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

responseType

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

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

axios({
        url: `${apiPrefix}/workflow/api/getCirculatePictures`,
        params: { processInstanceId },
        responseType: 'blob',
      })

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

let request=new XMLHttpRequest()
request.open('GET',url)
xhr.responseType = 'blob'  //这里写
request.onreadystatechange=()=>{
  if(httpRequest.readystate===4 && httpRequest.status===200){
    console.log('success')
  }else{
    console.log('fail')
  }
}
request.send()

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

](https://user-images.githubusercontent.com/53749772/123620167-7fe0ac00-d83c-11eb-959c-af0ea6c2fcdf.png)

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

blob 转化

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

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

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

实现代码

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

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

        imgElement.onload = function () {
          window.URL.revokeObjectURL(this.src);
        };

将blob转回JSON

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

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

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

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

思路也很简单:

下面直接贴代码

function download(href, filename = '') {
  const a = document.createElement('a');
  a.download = filename;
  a.href = href;
  document.body.appendChild(a);
  a.click();
  a.remove();
}

function downloadFile(url, filename = '') {
  fetch(url, {
    headers: new Headers({
      Origin: window.location.origin,
    }),
    mode: 'cors',
  })
    .then((res) => res.blob())
    .then((blob) => {
      const blobUrl = window.URL.createObjectURL(blob);
      download(blobUrl, filename);
      window.URL.revokeObjectURL(blobUrl);
    });
}