Vue: 文件下载深度总结

830 阅读4分钟

简介

巩固基础,非止于CV实现文件下载。这句话我想表达的意思是,我们不仅仅只会从csdn或者其他网站上复制别人封装或者写好的插件方法来用,却不知道其具体的实现原理,以及所用到的属性方法的含义,这就是止步于copy and paste。 (本文主要总结文件下载思路和文件不同格式的预览方式)

当同一种实现原理可以完成不同需求任务的时候,我们却只知道通过需求的关键词去网上查找解决方案,却不知我们之前的实现另一种需求的方式方法也能同时应用到当前需求上来,这很大程度上跟自个的基础能力相关;基础不牢固,有病乱投医,是不是这个理?

我们在上初高中的时候,听数学老师说得最多的一句话,什么万变不离其宗,举一反三,那也是建立在你完全把原理理解透彻的基础上的。

所以接下来:

  1. 先解读下文件(包含图片)所涉及到的属性方法的含义和用途,以及相关概念等知识,
  2. 然后比对实际需求
  3. 最后代码实现及总结

文件下载思路

一、使用axios请求将文件转化为blob对象的二进制数据流下载

axios({
    method: 'get',  
    params: {
      file_url: re.data_url,
      file_name: re.data_name
    },
    url: `https:/xxx.com${url}`, 
    // for example: http://dev-head-api.xht-kyy.com/downSkuFile?id=2196
    // 必须显式指明响应类型是一个Blob对象,这样生成二进制的数据,
    //才能通过window.URL.createObjectURL进行创建成功
    responseType: 'blob',
}).then((res) => {
    if (!res) {
        return
    }
    // 将blob对象转换为域名结合式的url
    let blobUrl = window.URL.createObjectURL(res.data)
    let link = document.createElement('a')
    document.body.appendChild(link)
    link.style.display = 'none'
    link.href = blobUrl
    // 设置a标签的下载属性,设置文件名及格式,后缀名最好让后端在数据格式中返回
    link.download = 'xx.csv'
    // 自触发click事件
    link.click()
    document.body.removeChild(link)
    window.URL.revokeObjectURL(blobUrl);
})

通过在body中增加个a标签,并立即执行点击事件,进行跳转访问url,get到的内容为blob文件下载。

代码中提到设置a标签的下载属性,设置文件名及格式,后缀名最好让后端在数据格式中返回,那么后端返回的后缀和文件名该如何提取? 可以在请求返回的.then(res)中提取到,其中res.data是blob对象的二进制数据流,res.header包含了文件信息,如下图:

image.png 可以通过 res.headers['content-disposition'] 获取到filename中的文件名

改进后的blob下载方法:

outImport (re) {
  this.$axios({
    method: 'get',  
    params: {
      file_url: re.data_url,
      file_name: re.data_name
    },
    url: `https:/xxx.com${url}`, 
    responseType: 'blob'
  }).then((res) => {
    console.log(res)
    let str = res.headers['content-disposition']
    str = str.substr(str.indexOf('=') + 1, str.length)
    this.downloadFile(res.data, decodeURI(str))
  })
},
downloadFile (blob, fileName) {
  if (window.navigator.msSaveOrOpenBlob) {
    navigator.msSaveBlob(blob, fileName)
  } else {
    const link = document.createElement('a')
    const evt = document.createEvent('HTMLEvents')
    evt.initEvent('click', false, false)
    
    document.body.appendChild(link)
    link.href = URL.createObjectURL(blob)
    link.download = fileName
    link.style.display = 'none'
 
    link.click()
    document.body.removeChild(link)
    window.URL.revokeObjectURL(link.href)
  }
},

这里涉及到几个属性:

msSaveBlob()

Internet Explorer 10 的 msSaveBlob 和 msSaveOrOpenBlob 方法允许用户在客户端上保存文件,方法如同从 Internet 下载文件,这是此类文件保存到“下载”文件夹的原因。

URL.createObjectURL()和URL.revokeObjectURL()

URL.createObjectURL可以得到当前文件的一个内存URL, 即返回一段带hash的url,按我的理解就是该链接直接指向存储内存中的数据,并且一直存储在内存中,所以当不使用后,需要清除下,防止内存泄露,那么就有了下面这些清除方法

URL.createObjectURL(file)存在于当前doucment内,清除方式只有unload()事件或revokeObjectURL()手动清除 。

二、使用fetch请求进行下载

使用fetch替代a标签,进行下载MP3文件,在浏览器中说使用a标签会直接跳转页面进行播放,达不到下载的目地

下载服务器的MP3文件

export const downloadMp3 = (filePath) => {
  fetch(filePath).then(res => res.blob()).then(blob => {
    const a = document.createElement('a');
    document.body.appendChild(a)
    a.style.display = 'none'
    // 使用获取到的blob对象创建的url
    const url = window.URL.createObjectURL(blob);
    a.href = url;
    // 指定下载的文件名
    a.download = 'xx.mp3';
    a.click();
    document.body.removeChild(a)
    // 移除blob对象的url
    window.URL.revokeObjectURL(url);
  });
}

这里涉及到fetch, 简要总结下fetch的使用(结合上述代码所使用的情况),也和方法一种axios的方法做一个简要的比较:

fetch 是浏览器内置的 API,用于发送网络请求。它提供了一种现代化、基于 Promise 的方式来进行网络通信。与 axios 类似,fetch 也提供了一种较低级别的封装,但相比于 axios,它的功能和语法更为简单。fetch 通过链式调用的方式设置请求参数从上述代码中就可以看出来,返回的是一个 Promise 对象

另外:fetch也提供了第二个参数,用于设置相关的请求头、方式、类型等相关配置入参 如下参考:

{
  method: "GET",//请求方式
  headers: {//定制http请求的标头
    "Content-Type": "text/plain;charset=UTF-8"
  },
  body: undefined,//post请求的数据体,因为此时为get请求,故为undefined
  referrer: "about:client",
  referrerPolicy: "no-referrer-when-downgrade",//用于设定fetch请求的referer标头
  mode: "cors", //指定请求模式,此时为cors表示支持跨域请求
  credentials: "same-origin",//发送cookie
  cache: "default",//指定如何处理缓存
  redirect: "follow",
  integrity: "",
  keepalive: false,
  signal: undefined 
}

而其链式调用方式:

fetch('网址')
	// fetch()接收到的response是一个 Stream 对象
	// response.json()是一个异步操作,取出所有内容,并将其转为 JSON 对象
  .then(response => response.json()) 
  .then(json => console.log(json))//获取到的json数据
  .catch(err => console.log('Request Failed', err)); 

后续写一篇针对该方法的总结

文件不同格式的预览方式

封装一个预览与下载相结合的方法,实现不同文件格式的预览方式

思路:将文件名解剖,取文件类型后缀,通过枚举方式调用不同类型的预览方法

    thumbnailView (item) {
      item.name = item.original_name
      const suffix = item.name.substring(item.name.lastIndexOf('.') + 1)
      if (['jpg', 'jpeg', 'png', 'gif', 'bmp', 'svg', 'tiff', 'webp'].includes(suffix)) {
        this.preView(item)
      } else if (['xlsx', 'xls', 'doc', 'docx', 'ppt', 'pptx'].includes(suffix)) {
        this.viewNew(item, suffix)
      } else if (['pdf', 'PDF'].includes(suffix)) {
        this.viewNewPDF(item)
      } else {
        this.showConfirm(item)
      }
    },
    preView (val) {
      this.$refs.imageView.open(val.file_name)
    },
    preView2 (val) {
      this.$refs.imageView.open(val)
    },
    viewNewPDF (item) {
      window.open(item.file_name, '_blank')
    },
    showConfirm (item) {
      this.$confirm({
        title: '提示',
        content: h => <div>该文件不支持预览、请下载后查阅</div>,
        okText: '下載文件',
        onOk: () => {
          saveAs(item.file_name, item.name)
        }
      })
    },
    viewNew (val, suffix) {
      window.open(`https://view.officeapps.live.com/op/view.aspx?src=${val.file_name}`, '_blank')
    },

saveAs

上述代码中涉及到一个插件:saveAs,这里简要介绍下:

import { saveAs } from 'file-saver';

saveAs(Blob/File/Url, optional DOMString filename, optional Object { autoBom }

第一个参数支持三种类型,这也时下载文件的一个插件

后端返回PK类型数据流

当使用普通的文件下载方式时,后端返回的文件流的开头通常是以\x50\x4b\x03\x04 (PK\003\004) 开头。这是Zip文件格式的标识符之一,用于表示文件是一个Zip压缩文件。

具体来说,PK开头是Zip文件的本地文件头或文件目录的标识符,用于标记文件的起始位置。这个标识符是由4个字节的十六进制值组成,分别是0x50("P")、0x4b("K")、0x03和0x04。

在使用JavaScript进行文件下载时,通常会将这个开头与其他字节一起放入一个Blob对象中,然后通过创建URL.createObjectURL方法将Blob对象转换为可下载的链接,最后通过a标签的download属性实现文件下载。

responseType: 'blob' 转成blob类型

如下:

// 以下是一个使用JavaScript实现文件下载的示例代码:
<script>
function Download() {
  axios({
    url: "www.baidu.pdf",
    method: 'GET',
    responseType: 'blob', // 这里就是转化为blob文件流
    headers: {
      token: 'sss'     // 可以携带token
    }
  }).then(res => {
    const href = URL.createObjectURL(res.data)
    const box = document.createElement('a')
    box.download = '附件.pdf'
    box.href = href
    box.click()
  })
}
</script>

在上面的示例代码中,假设后端返回的文件流数据存储在变量 fileStream 中。首先,我们创建一个Blob对象,将文件流数据传递给它。然后,使用URL.createObjectURL方法创建一个下载链接,将Blob对象转换为可下载的链接。

接下来,我们创建一个a标签,并设置其href属性为下载链接,再设置其download属性为要下载文件的名称。然后,将a标签添加到页面中。

最后,我们模拟点击a标签来触发文件下载,下载完成后,使用URL.revokeObjectURL方法清除下载链接,并删除a标签。这样就实现了文件的下载功能。请注意,上述代码中的'filename.ext'应替换为要下载文件的实际名称和扩展名。

总结

掌握:axios和fetch的用法

学习 msSaveBlob和URL.createObjectURL() 和 revokeObjectURL()

响应blob数据类型转换

插件

file-saver

www.npmjs.com/package/fil…

StreamSaver.js:保存大于 blob 大小限制的非常大的文件或没有足够的 RAM它可以利用新流的强大功能将数据异步直接保存到硬盘驱动器 应用程序接口。

FileSaver.js 是在客户端保存文件的解决方案,非常适合在客户端生成文件的 Web 应用程序,但是如果文件是 来自服务器,先尝试使用 Content-Disposition 附件响应标头,因为它具有更多的跨浏览器兼容性。

canvas-toBlob.js 寻找用于保存画布的 canvas.toBlob()? 以了解跨浏览器的实现。