谈谈关于文件上传下载那些事

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

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);
    }
  }
}
在利用Java Servlet技术开发具有用户上传文字和图片功能的漂流瓶网站时,确保系统的安全性和性能是至关重要的。根据给出的辅助资料和相关知识点,以下是一些实现策略: 参考资源链接:[Java-Servlet实现的漂流瓶网站支持文字图片上传功能](https://wenku.csdn.net/doc/6162ti11vh) 安全性方面: 1. 输入验证:对于所有用户上传的内容,包括文字和图片,都需要进行严格的输入验证。可以使用Java Servlet API提供的filter机制来实现请求内容的预处理。 2. 文件类型检查:仅允许上传特定类型的文件,并检查文件扩展名。同时,可以通过文件签名来检测文件类型,避免文件名欺骗。 3. 安全文件上传:使用第三方库如Apache Commons FileUpload来处理文件上传,确保上传逻辑的健壮性。对上传的文件进行病毒扫描,确保不上传恶意软件。 4. 数据库安全:使用预编译的SQL语句(PreparedStatement)防止SQL注入攻击,确保与MySQL数据库交互的安全性。 5. 用户认证与授权:实现用户的注册、登录系统,并通过Servlet的过滤器对敏感操作进行权限控制,防止未授权访问。 性能优化方面: 1. 异步处理:在处理文件上传等耗时操作时,使用异步处理机制,以避免阻塞主线程,提升用户体验。 2. 缓存策略:合理使用缓存策略,如页面缓存、数据缓存,减少数据库访问次数和网络请求。 3. 资源压缩:开启Tomcat服务器的压缩功能,对静态资源如CSS、JavaScript和图片进行压缩,加快响应速度。 4. 数据库优化:优化MySQL数据库的查询语句,合理设计数据表结构,定期进行数据库维护和索引优化。 在开发过程中,可以借助IDE和构建工具(如Maven或Gradle)来管理和构建项目,确保开发效率。同时,使用测试工具(如JMeter或Postman)测试应用的性能和安全性。 综上所述,通过综合考虑安全性与性能,并采取相应的策略和工具,可以开发出既安全又高效的漂流瓶网站。此外,详细阅读《Java-Servlet实现的漂流瓶网站支持文字图片上传功能》一书,可以获取更多关于实现细节和技术深度的指导。 参考资源链接:[Java-Servlet实现的漂流瓶网站支持文字图片上传功能](https://wenku.csdn.net/doc/6162ti11vh)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值