js自己实现一个大文件切片上传+断点续传的示例代码

目录
  • 首先我们来分析一下需求
  • 一、 格式校验
  • 二、 文件切片
  • 三、 断点续传 + 秒传 + 上传进度

PM:喂,那个切图仔,我这里有个100G的视频要上传,你帮我做一个上传后台,下班前给我哦,辛苦了。
我:。。。
相信每个切图工程师,都接触过文件上传的需求,一般的小文件,我们直接使用 input file,然后构造一个 new FormData()对象,扔给后端就可以了。如果使用了 Ant design 或者 element ui 之类的ui库,那更简单,直接调用一下api即可。当然了,复杂一些的,市面上也有不少优秀的第三方插件,比如WebUploader。但是作为一个有追求的工程师,怎么能仅仅满足于使用插件呢,今天我们就来自己实现一个。

首先我们来分析一下需求

一个上传组件,需要具备的功能:

  • 需要校验文件格式
  • 可以上传任何文件,包括超大的视频文件(切片)
  • 上传期间断网后,再次联网可以继续上传(断点续传)
  • 要有进度条提示
  • 已经上传过同一个文件后,直接上传完成(秒传)

前后端分工:

前端:

  • 文件格式校验
  • 文件切片、md5计算
  • 发起检查请求,把当前文件的hash发送给服务端,检查是否有相同hash的文件
  • 上传进度计算
  • 上传完成后通知后端合并切片

后端:

  • 检查接收到的hash是否有相同的文件,并通知前端当前hash是否有未完成的上传
  • 接收切片
  • 合并所有切片

架构图如下

接下来开始具体实现

一、 格式校验

对于上传的文件,一般来说,我们要校验其格式,仅需要获取文件的后缀(扩展名),即可判断其是否符合我们的上传限制:

  //文件路径
  var filePath = "file://upload/test.png";
  //获取最后一个.的位置
  var index= filePath.lastIndexOf(".");
  //获取后缀
  var ext = filePath.substr(index+1);
  //输出结果
  console.log(ext);
  // 输出: png

但是,这种方式有个弊端,那就是我们可以随便篡改文件的后缀名,比如:test.mp4 ,我们可以通过修改其后缀名:test.mp4 -> test.png ,这样即可绕过限制进行上传。那有没有更严格的限制方式呢?当然是有的。

那就是通过查看文件的二进制数据来识别其真实的文件类型,因为计算机识别文件类型时,并不是真的通过文件的后缀名来识别的,而是通过 “魔数”(Magic Number)来区分,对于某一些类型的文件,起始的几个字节内容都是固定的,根据这几个字节的内容就可以判断文件的类型。借助十六进制编辑器,可以查看一下图片的二进制数据,我们还是以test.png为例:

由上图可知,PNG 类型的图片前 8 个字节是 0x89 50 4E 47 0D 0A 1A 0A。基于这个结果,我们可以据此来做文件的格式校验,以vue项目为例:

  <template>
  <div>
    <input
      type="file"
      id="inputFile"
      @change="handleChange"
    />
  </div>
</template>

<script>
export default {
  name: "HelloWorld",
  methods: {
    check(headers) {
      return (buffers, options = { offset: 0 }) =>

      headers.every(

      (header, index) => header === buffers[options.offset + index]

      );
    },
    async handleChange(event) {
      const file = event.target.files[0];

      // 以PNG为例,只需要获取前8个字节,即可识别其类型
      const buffers = await this.readBuffer(file, 0, 8);

      const uint8Array = new Uint8Array(buffers);

      const isPNG = this.check([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);

      // 上传test.png后,打印结果为true
      console.log(isPNG(uint8Array))

    },
    readBuffer(file, start = 0, end = 2) {
      // 获取文件的二进制数据,因为我们只需要校验前几个字节即可,所以并不需要获取整个文件的数据
        return new Promise((resolve, reject) => {
          const reader = new FileReader();

          reader.onload = () => {
            resolve(reader.result);
          };

          reader.onerror = reject;

          reader.readAsArrayBuffer(file.slice(start, end));
        });
    }
  }
};
</script>

以上为校验文件类型的方法,对于其他类型的文件,比如mp4,xsl等,大家感兴趣的话,也可以通过工具查看其二进制数据,以此来做格式校验。

以下为汇总的一些文件的二进制标识:

1.JPEG/JPG - 文件头标识 (2 bytes): ff, d8 文件结束标识 (2 bytes): ff, d9
  2.TGA - 未压缩的前 5 字节 00 00 02 00 00 - RLE 压缩的前 5 字节 00 00 10 00 00
  3.PNG - 文件头标识 (8 bytes) 89 50 4E 47 0D 0A 1A 0A
  4.GIF - 文件头标识 (6 bytes) 47 49 46 38 39(37) 61
  5.BMP - 文件头标识 (2 bytes) 42 4D B M
  6.PCX - 文件头标识 (1 bytes) 0A
  7.TIFF - 文件头标识 (2 bytes) 4D 4D 或 49 49
  8.ICO - 文件头标识 (8 bytes) 00 00 01 00 01 00 20 20
  9.CUR - 文件头标识 (8 bytes) 00 00 02 00 01 00 20 20
  10.IFF - 文件头标识 (4 bytes) 46 4F 52 4D
  11.ANI - 文件头标识 (4 bytes) 52 49 46 46

二、 文件切片

假设我们要把一个1G的视频,分割为每块1MB的切片,可定义 DefualtChunkSize = 1 * 1024 * 1024,通过 spark-md5来计算文件内容的hash值。那如何分割文件呢,使用文件对象File的方法File.prototype.slice即可。

需要注意的是,切割一个较大的文件,比如10G,那分割为1Mb大小的话,将会生成一万个切片,众所周知,js是单线程模型,如果这个计算过程在主线程中的话,那我们的页面必然会直接崩溃,这时,就该我们的 Web Worker 来上场了。

Web Worker 的作用,就是为 JavaScript 创造多线程环境,允许主线程创建 Worker 线程,将一些任务分配给后者运行。在主线程运行的同时,Worker 线程在后台运行,两者互不干扰。具体的作用,不了解的同学可以自行去学些一下。这里就不展开讲了。

以下为部分关键代码:

  // upload.js

  // 创建一个worker对象
  const worker = new worker('worker.js')
  // 向子线程发送消息,并传入文件对象和切片大小,开始计算分割切片
  worker.postMessage(file, DefualtChunkSize)

  // 子线程计算完成后,会将切片返回主线程
  worker.onmessage = (chunks) => {
    ...
  }

子线程代码:

  // worker.js

  // 接收文件对象及切片大小
  onmessage (file, DefualtChunkSize) => {
    let blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice,
      chunks = Math.ceil(file.size / DefualtChunkSize),
      currentChunk = 0,
      spark = new SparkMD5.ArrayBuffer(),
      fileReader = new FileReader();

    fileReader.onload = function (e) {
      console.log('read chunk nr', currentChunk + 1, 'of');

      const chunk = e.target.result;
      spark.append(chunk);
      currentChunk++;

      if (currentChunk < chunks) {
        loadNext();
      } else {
        let fileHash = spark.end();
        console.info('finished computed hash', fileHash);
        // 此处为重点,计算完成后,仍然通过postMessage通知主线程
        postMessage({ fileHash, fileReader })
      }
    };

    fileReader.onerror = function () {
      console.warn('oops, something went wrong.');
    };

    function loadNext() {
      let start = currentChunk * DefualtChunkSize,
        end = ((start + DefualtChunkSize) >= file.size) ? file.size : start + DefualtChunkSize;
      let chunk = blobSlice.call(file, start, end);
      fileReader.readAsArrayBuffer(chunk);
    }

    loadNext();
  }

以上利用worker线程,我们即可得到计算后的切片,以及md5值。

三、 断点续传 + 秒传 + 上传进度

在拿到切片和md5后,我们首先去服务器查询一下,是否已经存在当前文件。

  • 如果已存在,并且已经是上传成功的文件,则直接返回前端上传成功,即可实现"秒传"。
  • 如果已存在,并且有一部分切片上传失败,则返回给前端已经上传成功的切片name,前端拿到后,根据返回的切片,计算出未上传成功的剩余切片,然后把剩余的切片继续上传,即可实现"断点续传"。
  • 如果不存在,则开始上传,这里需要注意的是,在并发上传切片时,需要控制并发量,避免一次性上传过多切片,导致崩溃。
// 检查是否已存在相同文件
   async function checkAndUploadChunk(chunkList, fileMd5Value) {
    const requestList = []
    // 如果不存在,则上传
    for (let i = 0; i < chunkList; i++) {
      requestList.push(upload({ chunkList[i], fileMd5Value, i }))
    }

    // 并发上传
    if (requestList?.length) {
      await Promise.all(requestList)
    }
  }

 // 上传chunk
  function upload({ chunkList, chunk, fileMd5Value, i }) {
    current = 0
    let form = new FormData()
    form.append("data", chunk) //切片流
    form.append("total", chunkList.length) //总片数
    form.append("index", i) //当前是第几片     
    form.append("fileMd5Value", fileMd5Value)
    return axios({
      method: 'post',
      url: BaseUrl + "/upload",
      data: form
    }).then(({ data }) => {
      if (data.stat) {
        current = current + 1
        // 获取到上传的进度
        const uploadPercent = Math.ceil((current / chunkList.length) * 100)
      }
    })
  }

所有切片上传完成后,再向后端发送一个上传完成的请求,即通知后端把所有切片进行合并,最终完成整个上传流程。
大功告成!由于篇幅有限,本文主要讲了前端的实现思路,最终落地成完整的项目,还是需要大家根据真实的项目需求来实现。

到此这篇关于js自己实现一个大文件切片上传+断点续传的示例代码的文章就介绍到这了,更多相关js大文件切片上传+断点续传内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 基于JavaScript实现大文件上传后端代码实例

    这里只写后端的代码,基本的思想就是,前端将文件分片,然后每次访问上传接口的时候,向后端传入参数:当前为第几块文件,和分片总数 下面直接贴代码吧,一些难懂的我大部分都加上注释了: 上传文件实体类: 看得出来,实体类中已经有很多我们需要的功能了,还有实用的属性.如MD5秒传的信息. 首先是文件数据接收逻辑,负责接收控件上传的文件块数据,然后写到服务器的文件中.控件已经提供了块的索引,大小,MD5和长度信息,我们可以根据需要来灵活进行处理,也可以将文件块的数据保存到分布式存储系统中. 文件初始化部分

  • Vue+NodeJS实现大文件上传的示例代码

    目录 整体思路 项目演示 前端界面 文件切片 hash计算 查询切片状态 切片上传(断点续传) 文件总体上传进度 合并文件 优化 请求并发数控制 hash值计算优化 常见的文件上传方式可能就是new一个FormData,把文件append进去以后post给后端就可以了.但如果采用这种方式来上传大文件就很容易产生上传超时的问题,而且一旦失败还得从新开始,在漫长的等待过程中用户还不能刷新浏览器,不然前功尽弃.因此这类问题一般都是通过切片上传. 整体思路 将文件切成多个小文件 hash计算,需要计算一

  • 原生JS上传大文件显示进度条 php上传文件代码

    JS原生上传大文件显示进度条,php上传文件,供大家参考,具体内容如下 在php.ini修改需要的大小: upload_max_filesize = 8M    post_max_size = 10M    memory_limit = 20M <!DOCTYPE html> <html> <head> <title>原生JS大文件显示进度条</title> <meta charset="UTF-8"> <s

  • js实现分割上传大文件

    本文实例介绍了js上传文件操作,分享给大家供大家参考,具体内容如下 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" lang="zh-CN"> &l

  • 基于Node.js的大文件分片上传示例

    我们在做文件上传的时候,如果文件过大,可能会导致请求超时的情况.所以,在遇到需要对大文件进行上传的时候,就需要对文件进行分片上传的操作.同时如果文件过大,在网络不佳的情况下,如何做到断点续传?也是需要记录当前上传文件,然后在下一次进行上传请求的时候去做判断. 先上代码:代码仓库地址 前端 1. index.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8

  • JavaScript实现大文件分片上传处理

    很多时候我们在处理文件上传时,如视频文件,小则几十M,大则 1G+,以一般的HTTP请求发送数据的方式的话,会遇到的问题: 1.文件过大,超出服务端的请求大小限制: 2.请求时间过长,请求超时: 3.传输中断,必须重新上传导致前功尽弃 这些问题很影响用户的体验感,所以下面介绍一种基于原生JavaScript进行文件分片处理上传的方案,具体实现过程如下: 1.通过dom获取文件对象,并且对文件进行MD5加密(文件内容+文件标题形式),采用SparkMD5进行文件加密: 2.进行分片设置,文件Fil

  • 如何基于js管理大文件上传及断点续传详析

    目录 前言 前端结构 后端结构(node + express) 基于FormData实现文件上传 基于BASE64实现文件上传 BASE64具体方法 前端生成文件名传给后端 上传进度管控 大文件上传 服务端代码(大文件上传+断点续传) 总结 前言 前端小伙伴们平常在开发过程中文件上传是经常遇到的一个问题,也许你能够实现相关的功能,但是做完后回想代码实现上是不是有点"力不从心"呢?你真的了解文件上传吗?如何做到大文件上传以及断电续传呢,前后端通讯常用的格式,文件上传进度管控,服务端是如何

  • js自己实现一个大文件切片上传+断点续传的示例代码

    目录 首先我们来分析一下需求 一. 格式校验 二. 文件切片 三. 断点续传 + 秒传 + 上传进度 PM:喂,那个切图仔,我这里有个100G的视频要上传,你帮我做一个上传后台,下班前给我哦,辛苦了.我:...相信每个切图工程师,都接触过文件上传的需求,一般的小文件,我们直接使用 input file,然后构造一个 new FormData()对象,扔给后端就可以了.如果使用了 Ant design 或者 element ui 之类的ui库,那更简单,直接调用一下api即可.当然了,复杂一些的,

  • vue 大文件分片上传(断点续传、并发上传、秒传)

    对于大文件的处理,无论是用户端还是服务端,如果一次性进行读取发送.接收都是不可取,很容易导致内存问题.所以对于大文件上传,采用切块分段上传,从上传的效率来看,利用多线程并发上传能够达到最大效率. 本文是基于 springboot + vue 实现的文件上传,本文主要介绍vue实现文件上传的步骤及代码实现,服务端(springboot)的实现步骤及实现请移步本人的另一篇文章: springboot 大文件上传.分片上传.断点续传.秒传 上传分步: 本人分析上传总共分为: MD5读取文件,获取文件的

  • Java实现文件分片上传接口的示例代码

    目录 java后端分片上传接口 前端分片 java后端分片上传接口 文件上传工具--FileUtil package com.youmejava.chun.util; import lombok.Data; import org.apache.tomcat.util.http.fileupload.FileUtils; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; imp

  • vue实现大文件分片上传与断点续传到七牛云

    问题: 前段时间做视频上传业务,通过网页上传视频到服务器. 视频大小 小则几十M,大则 1G+,以一般的HTTP请求发送数据的方式的话,会遇到的问题: 1.文件过大,超出服务端的请求大小限制: 2.请求时间过长,请求超时: 3.传输中断,必须重新上传导致前功尽弃: 探索过程: 1.原先咨询过组里的大佬给我推荐了百度的webupload,但后来引入之后发现它是基于jquery封装的.由于本身项目是基于vue开发的所以与jquery相关的开源框架就尽量不考虑了. 2.后来查阅了资料后自己手动实现了文

  • vue 文件切片上传的项目实现

    目录 流程简说 获取文件的 MD5 唯一标识码 文件切片 获取文件名 name 分片文件大小 chunkSize 文件切片 chunkList 列表 切片总数 chunks 切片大小 size 合并 在实际开发项目过程中有时候需要上传比较大的文件,然后呢,上传的时候相对来说就会慢一些,so,后台可能会要求前端进行文件切片上传,很简单哈,就是把比如说1个G的文件流切割成若干个小的文件流,然后分别请求接口传递这个小的文件流. 流程简说 实现文件切片导入,首先我们使用 elementUI 也好,原生的

  • webuploader在springMVC+jquery+Java开发环境下的大文件分片上传的实例代码

    注意: 1,webuploader上传组件会和jQuery自带的上传组件冲突,所以不要使用<form>标签中添加上传文件的属性; enctype="multipart/form-data" 2.并且屏蔽ApplicationContext-mvc.xml里面的拦截配置! <!-- 上传拦截,如最大上传值及最小上传值 --> <!--新增加的webuploader上传组件,必须要屏蔽这里的拦截机制 <bean id="multipartRes

  • Java实现浏览器端大文件分片上传

    目录 背景介绍 项目介绍 需要知识点 启动项目 项目示范 核心讲解 核心原理 功能分析 分块上传 秒传功能 断点续传 总结 参考文献 背景介绍   Breakpoint-http,是不是觉得这个名字有点low,break point断点.这是一个大文件上传的一种实现.因为本来很久没写过前端了,本来想自己好好写一番js,可惜因为种种原因而作罢了.该项目是基于一款百度开源的前端上传控件:WebUploader(百度开源的东西文档一如既往的差,哈哈.或者是我理解能力差).   Breakpoint-h

  • java实现文件切片上传百度云+断点续传的方法

    前言: 本文代码通过dubbo进行远程调用的接口,如果不使用dubbo,直接将service放到你的service,并稍作修改,redis替换成自己封装的工具即可.下方代码有点多,但基本上都加注释了. 切片上传百度云和上传到本地有所不同,上传到本地是需要前端给个唯一标识,通过自定义唯一标识保存到本地,最后在合并文件.而上传百度云则需要直接把切片传到百度云,唯一标识uploadId是百度云提供的,上传及合并都是直接调用百度云提供的接口.下方为百度云的使用文档. 对象存储BOS - 文件管理 | 百

随机推荐