实现一个大文件切片上传+断点续传功能

云计算笔记
• 阅读 501

相信每个切图工程师,都接触过文件上传的需求,一般的小文件,我们直接使用 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项目为例:

以上为校验文件类型的方法,对于其他类型的文件,比如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) } }) }

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

点赞
收藏
评论区
推荐文章
Easter79 Easter79
3年前
vue+element UI + axios封装文件上传及进度条组件
1.前言之前在做项目的时候,需要实现一个文件上传组件并且需要有文件上传进度条,现将之前的实现过程简单记录一下,希望可以帮助到有需要的人。项目用的是Vue框架,UI库使用的是elementUI,前后端交互请求使用的是Vue官方推荐的axios。其中,UI方面主要使用了elementUI库中的Upload文件上传组件、Progress
徐小夕 徐小夕
3年前
基于create-react-app打包编译自己的第三方UI组件库并发布到npm
前言这篇文章主要是总结一下我们在工作中如何为公司开发内部的第三方UI组件,并通过npminstall的方式安装的一些步骤和思路。在学习完这套发布方法后大家也可以快速的发布自己的UI库到npm,供他人使用,就比如elementUI或者AntDesign。如果想学习如何发布一个js库或者框架,那么使用rollup更为适合,可以参考如下文章:前端组
Wesley13 Wesley13
3年前
10分钟教你用eclipse上传代码到GitHub
好久没有更新了,这两天小编在整理以前的代码,上传到GitHub做备份。加上现在GitHub的私有仓库不是免费了嘛,所以今天顺便给大家讲讲怎么用eclipse上传代码到GitHub吧。现在最新版的eclipse都集成了git插件了,所以直接使用即可。假如我们有一个HelloWorld的项目要上传到GitHub管理:
Stella981 Stella981
3年前
ASP.Net MVC
学习一下WebUploader实现多图片上传,在网上看了一些博客,但大部分都是一样,几乎都是从一篇博客复制的,代码不完整,所以花了一些时间看了一些相关视频,学习一下!记录一下!首先:引入需要的文件:一个jQuery,一个webuploader的css样式和js文件<linkhref"~/webuploader0.1.5/webuploa
Stella981 Stella981
3年前
Shiro INI 配置
之前章节我们已经接触过一些INI配置规则了,如果大家使用过如Spring之类的IoC/DI容器的话,Shiro提供的INI配置也是非常类似的,即可以理解为是一个IoC/DI容器,但是区别在于它从一个根对象securityManager开始。根对象SecurityManager从之前的
Easter79 Easter79
3年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Stella981 Stella981
3年前
Spring Boot 与 Kotlin 上传文件
如果我们做一个小型的web站,而且刚好选择的kotlin和SpringBoot技术栈,那么上传文件的必不可少了,当然,如果你做一个中大型的web站,那建议你使用云存储,能省不少事情。这篇文章就介绍怎么使用kotlin和SpringBoot上传文件构建工程如果对于构建工程还不是很熟悉的可以参考《我的第一个Kotlin应用》
Wesley13 Wesley13
3年前
IOS上线过程详解
众所周知,苹果的APP都是人工审核的,只一点比安卓的好,安卓的一些软件上传到第三方之后估计压根就没人测试,下载了之后直接闪退或者美观性很差劲,更不要说用户体验了,那么,苹果的APP怎么一步步的上传到应用商店呢?下边我就我上一个项目的流程说一下首先你要有开发好APP,然后你需要花费99$去买一个账号,以前手机开发和mac开发分开的,现在可以通用了,然后
Stella981 Stella981
3年前
Spring Boot 2.x基础教程:实现文件上传
文件上传的功能实现是我们做Web应用时候最为常见的应用场景,比如:实现头像的上传,Excel文件数据的导入等功能,都需要我们先实现文件的上传,然后再做图片的裁剪,excel数据的解析入库等后续操作。今天通过这篇文章,我们就来一起学习一下如何在SpringBoot中实现文件的上传。动手试试第一步:创建一个基础的SpringBo
京东云开发者 京东云开发者
8个月前
大文件上传实践分享
一、方案背景:在此前的项目中有个需求是用户需要通过前端页面上传大约1.5G的压缩包,存储到OSS,后提供给其他用户下载。于是我开始了大文件上传方案的探索。本文主要探究的是前端技术实现,后端给予相应的支持。二、原理探索之路2.1大文件上传想要实现的目标在此项
云计算笔记
云计算笔记
Lv1
https://www.compucen.com/ 云计算笔记啊
文章
1
粉丝
1
获赞
1
热门文章

暂无数据