关于文件上传下载那些事

更新日期: 2020-02-28阅读: 2k标签: 文件

前端开发中总免不了关于文件的上传、下载需求。下面来总结一下常用的方法,欢迎讨论和吐槽。


form 表单提交

最传统的文件上传方法是使用form表单上传文件的,只需要把enctype设置为 multipart/form-data。这种方式上传文件不需要 js ,而且没有兼容问题,所有浏览器都支持,就是体验很差,导致页面刷新,页面其他数据丢失。

<form method="post" action="xxxxx" enctype="multipart/form-data">
  选择文件:<input type="file" name="file" />
  <br />
  标题:<input type="text" name="title" />
  <br />
  <button type="submit">提交</button>
</form>

注意:input 必须设置 name 属性,否则数据无法发送


文件接口上传

这种方法由服务端提供接口,设置相应的请求头,前端提交 formData 形式的文件数据。

<input id="uploadFile" type="file" name="file" accept="image/png,image/gif" />
  • accept:表示可以选择的文件 MIME 类型,多个 MIME 类型用英文逗号分开
  • multiple:是否可以选择多个文件
$('#uploadFile').on('change', function (e) {
  var file = this.files[0]

  var formData = new FormData()
  formData.append('file', file)

  $.ajax({
    url: 'xxxx',
    type: 'post',
    data: formData,
    cache: false,
    contentType: false,
    processData: false,
    success: function (res) {
      //
    },
  })
})
  • processData 设置为 false。因为 data 值是 FormData 对象,不需要对数据做处理。
  • cache 设置为 false,上传文件不需要缓存。
  • contentType 设置为 false。


分片上传

有时候我们上传的文件可能很大,比如视频等可能达到 2 个 G,这样会造成上传速度太慢,甚至有时候会出现链接超时的情况。而且有时候服务端会设置文件允许上传的大小,太大的文件就不允许上传了。为解决这个问题,我们可以将文件进行分片上传,每次只上传很小的一部分 比如 1M。

思路

  1. 将文件按一定大小(比如 1M)截取成一小份,并将切片带上 hash 值,用于作为标识。
  2. 将每个切片文件并发提交到服务端,服务端保存每个切片文件的信息。
  3. 切片上传完成后,服务端根据文件标识进行合并,合并完后删除切片文件。

这样因为每个切片是并发上传的,所以可以有效地降低上传时间。下面说一下具体的实现步骤。(PS:这是我司的实现方式,并不是唯一方法,且涉及到具体接口的代码就不贴在这里了)

生成 hash 值

无论上传文件信息还是上传切片文件,都必须要生成文件和切片的 hash。最简单粗暴的 hash 值可以用文件名字+下标来标识,但是这样文件名一旦修改就失去了效果,而事实上只要文件内容不变,hash 就不应该变化,所以正确的做法是根据文件内容生成 hash。我司用的是 spark-md5 库,在这里就不一一细说了。

文件信息上传

在文件分片上传之前需要把整个文件的信息如该文件的总的文件大小、文件名、哈希值等等,主要目的是初始化一个文件分片上传事件,返回文件 id,用于每个分片的提交。

getFileId (file) {
  let vm = this
  let formData = new FormData()
  formData.append('file', file)
  axios({
    timeout: 5 * 60 * 1000,
    headers: {
      'Content-Type': 'application/json-',
      'x-data': JSON.stringify({
        fileName: file.fileName,
        size: file.size,
        hash: 'hashxxx',
      }),
    },
    url: 'xxxxxx',
    method: 'POST',
  })
  .then((res) => {
    if (res.code === '200') {
      return res.data.fileId
    })
  .catch((err) => {
    console.log(err)
  })
}

文件切片分割

当前端获取到本地图片后,利用 Blob.prototype.slice 方法(和数组的 slice 方法相似),将大文件按照没小片 1M 进行切割,返回原文件的某个切片,再并发将各个分片上传到服务端。

getCkunk (file, fileId) {
  let vm = this
  let chunkSize = 1024 * 1024
  let totalSize = file.size
  let count = Math.ceil(totalSize / chunkSize)
  let chunkArr = []
  for (let i = 0; i < count; i++) {
    if (i === count.length - 1) {
      chunkArr.push(file.slice(i * chunkSize, totalSize))
    } else {
      chunkArr.push(file.slice(i * chunkSize, (i + 1) * chunkSize))
    }

  for (let index = 0; index < count; index++) {
    let item = chunkArr[index]
    this.uploadChunk(item, index, fileId)
  }
}

各个分片上传到服务端的方法。此处省略 hash 值得获取方式。

 ploadChunk(item, index, fileId) {
   let formData = new FormData()
   formData.append('file', item)
   request({
     headers: {
       'Content-Type': 'application/octet-stream;',
       'x-data': JSON.stringify({
         fileId: fileId,
         partId: index + 1,
         hash: res,
       })
     },
     url: 'xxxxx',
     method: 'POST',
     data: formData,
   })
   .then((res) => {
     return res.data.path
   })
   .catch((err) => {
     console.log(err)
   })
 }

显示上传进度条

由于文件比较大,即使是采用分片上传的方式也是需要一定的时间的,为了更好的用户体验,前端最好是提示上传的进度。这时候就需要后端在每个分片的放回结果加上上传的 100%字段。前端获取到返回值就改变当前进度。

当最后一个分片上传完成后,服务端返回文件的 url,前端获取 url,同时将进度条状态改变为 100%。


断点续传

上面说到的分片上传,解决了大文件上传超时和服务器的限制。但是对于更大的文件,上传并不是短时间内就上传完成,甚至有时候会面临断网或者手动暂停,难道就要重新将整个文件上传了,我们当然不希望。这时候断点续传就派上用场了。

下面说一下实现思路。
首先断点续传必须是基于分片上传的基础上的

  1. 每个分片上传的时候,服务端记录上传好的文件 hash 值,上传成功后返回 hash 值给前端,前端记录 hash 值
  2. 重新上传时,将每个文件的 hash 值与记录的 hash 值做比对,如果相同的话则跳过,继续下一个分段的上传。
  3. 全部分片上传完成后,服务端根据文件标识进行合并,合并完后删除小文件。


文件下载

文件下载有以下几种方法

form 表单提交

这是最原始的方法,为一个下载按钮添加 click 事件,点击时动态生成一个表单,利用表单提交的功能来实现文件的下载(实际上表单的提交就是发送一个请求)。

function downloadFile(downloadUrl, fileName) {
  // 创建表单
  let form = document.createElement('form')
  form.method = 'get'
  form.action = downloadUrl
  //form.target = '_blank';    // form新开页面
  document.body.appendChild(form)
  form.submit()
  document.body.removeChild(form)
}
  • 优点:兼容性好,不会出现 URL 长度限制问题。
  • 缺点:无法知道下载的进度,无法直接下载浏览器可直接预览的文件类型(如 txt/png 等)

window.open 或 window.location.href

最简单最直接的方式,实际上跟 a 标签访问下载链接一样

window.open('downloadFile.zip')
location.href = 'downloadFile.zip'

缺点

  • 会出现 URL 长度限制问题
  • 需要注意 url 编码问题
  • 浏览器可直接浏览的文件类型是不提供下载的,如 txt、png、jpg、gif 等
  • 不能添加 header,也就不能进行鉴权
  • 无法知道下载的进度

a 标签 download 属性

download 属性是 html5 新增的属性,兼容性可以了解下 can i use download。

<a href="xxxx" download>点击下载</a>
<!-- 重命名下载文件 -->
<a href="xxxx" download="test">点击下载</a>

优点:能解决不能直接下载浏览器可浏览的文件。

缺点

  • 得已知下载文件地址
  • 不能下载跨域下的浏览器可浏览的文件
  • 有兼容性问题,特别是 IE
  • 不能进行鉴权

利用 Blob 对象

此方法除了能利用已知文件地址路径进行下载外,还能通过发送 ajax 请求 api 获取文件流进行下载。利用 Blob 对象可以将文件流转化成 Blob 二进制对象。

进行下载的思路很简单:发请求获取二进制数据,转化为 Blob 对象,利用 URL.createObjectUrl 生成 url 地址,赋值在 a 标签的 href 属性上,结合 download 进行下载。

downdFile (path, name) {
  const xhr = new XMLHttpRequest();
  xhr.open('get', path);
  xhr.responseType = 'blob';
  xhr.send();
  xhr.onload = function () {
    if (this.status === 200 || this.status === 304) {
      // const blob = new Blob([this.response], { type: xhr.getResponseHeader('Content-Type') });
      // const url = URL.createObjectURL(blob);
      const url = URL.createObjectURL(this.response);
      const a = document.createElement('a');
      a.style.display = 'none';
      a.href = url;
      a.download = name;
      document.body.appendChild(a);
      a.click();
      document.body.removeChild(a);
      URL.revokeObjectURL(url);
    }
  }
}
原文:https://segmentfault.com/a/1190000022487528


链接: https://fly63.com/article/detial/8776

h5移动端实现图片文件上传

PC端上传文件多半用插件,引入flash都没关系,但是移动端要是还用各种冗余的插件估计得被喷死,项目里面需要做图片上传的功能,既然H5已经有相关的接口且兼容性良好,当然优先考虑用H5来实现。

前端使用js读取文件操作

首先我们定义一个input标签type=file、然后我们定义一个jsReadFiles的方法将文件作为参数;当上传文件的时候读取这个文件。图片上传成功,只是图片路径变成了base64编码的形式。

HTML5实现文件读取、编辑、保存

HTML5读取文件主要利用的就是FileReader这个API,它的使用需要从一个构造函数开始,保存文件的关键是生成文件对象,可以使用URL.createObjectURL()方法实现,该方法能返回给定对象的URL,用在<a>标签的href属性上就可以创建可下载的文件链接。

血淋淋的事实告诉你:你为什么不应该在JS文件中保存敏感信息

在JavaScript文件中存储敏感数据,不仅是一种错误的实践方式,而且还是一种非常危险的行为,长期以来大家都知道这一点。

在js文件中引入另一个js文件的实现方法总汇

比如我写了一个JS文件,这个文件需要调用另外一个JS文件,该如何实现呢?这篇文章主要介绍:在js文件中引入另一个js文件的实现

使用HTML5来实现本地文件读取和写入

最近有这样一个需求,就是在HTML页面中有个按钮导出,点击它,将构造一个文档并存储到本地文件系统中。另外还有个按钮,点击它,从本地文件系统中读取一个文件并对内容进行分析。

lock文件_我们为什么需要 lock 文件

从 Yarn 横空出世推出 lock 文件以来,已经两年多时间了,npm 也在 5.0 版本加入了类似的功能,lock 文件越来越被开发者们接收和认可。本篇文章想从前端视角探讨一下我们为什么需要 lock 文件,以及它的一些成本与风险,当然其中一些观点对于后端也是适用的

什么是断点续传?前端如何实现文件的断点续传

什么是断点续传?就是下载文件时,不必重头开始下载,而是从指定的位置继续下载,这样的功能就叫做断点续传。前端通过FileList对象获取到相应的文件,按照指定的分割方式将大文件分段,然后一段一段地传给后端,后端再按顺序一段段将文件进行拼接。

form表单文件上传_multipart/form-data文件上传

form表单的enctype属性:规定了form表单数据在发送到服务器时候的编码方式.。application/x-www-form-urlencoded:默认编码方式,multipart/form-data:指定传输数据为二进制数据,例如图片、mp3、文件,text/plain:纯文本的传输。空格转换为“+”,但不支持特殊字符编码。

使用HttpClient发送文件流到服务器端

适用场景: 网络绝对路径的URL文件或图片,不存储到本地,转换成stream,直接使用HTTPClient传送到SpringBoot的服务端,将文件存储下来,并返回一个文件地址。目前分层架构的系统越来越多这种需求,所以记录下来以备不时之需。

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!