一、前端大文件上传文件的痛点

1、文件过大会导致带宽资源紧张,请求速度下降 ;
2、如果上传过程中服务中断、网络中断 、页面崩溃,可能会导致文件重新开始上传。

二、痛点的分析

前端选择文件后上传,后端在处理文件过程中,首先会将文件加载到运行内存中,之后再调用相应的API进行写入硬盘内存的操作,完成整个文件的上传。

但这样直接上传文件,可能会因为某个环节出了问题导致整个流程的雪崩,所以大文件直接上传是不可取的。

解决问题最好办法是分片断点续传,该方式主要是针对大文件(比如100M以上的文件)

三、断点续传的原理

顾名思义就是断点续传

什么是断点?

在文件上传过程中,将一个要上传的文件分成N块,然后使用多线程并发多块上传,因为某种原因导致上传被中断或暂停,此时中断或暂停的位置就成为断点

前端每上传一片,将会被加载到运行内存中,加载完毕后再写入硬盘,此时运行内存的临时变量会被释放,然后此临时变量会被下一片占用,再进行写入,释放...

什么是续传?

意思是指从中断的位置继续上传剩下的部分文件,而不是从头开始上传。

上传完毕后,在服务端进行合并(合并的操作是在后端进行的,前端只是调用接口,合并的方式是由后端决定的,到底是上传一片就合并一片,或者是上传所有的之后整体进行合并)。

断点续传的实现

1)分片的实现

方式:
html5z之前的方式是flashactiveX
html5提供了文件二进制流进行分割的slice方法。

const chunks = Math.ceil(file.size / eachSize)

文件的分片,一般在2-5M之间。这一步得到了每一片文件的内容、每一块的序号、每一块的大小、总块数等数据。

2)续传的实现

  1. 续传首先要确定需要继续上传的是哪一个文件,而确定一个文件的方式是对文件进行加密,只要某个文件内容发生变化,就需要重新上传。可以对文件进行MD5加密作为文件唯一的标识符,MD5加密是不可逆的。
    要注意:对整个大文件进行加密,可能会导致页面崩溃,需要对文件进行分片加密。
    spark-md5插件支持文件分片加密
    基于elementUI的spark-md5的使用

  2. 在第一片文件上传之前,需要用文件名称 + 此文件唯一标识符 +当前片数来查询文件是否上传过。通过服务器返回的已经上传的结果,我们可以通过分片的结果获取剩余部分进行上传。如果页面重新加载或者上传中断,只需要在重新上传之前在哪一片中断便可以继续上传。

  3. 在上传完毕后,请求合并接口(合并接口也可以不请求,后端拿到所有文件后自己进行合并),在服务端将文件进行合并,此时整个文件上传结束。

  4. 在上传过程中,可以根据服务器返回的当前上传成功的片数和总片数对前端进度条进行展示

element-ui中Upload spark-md5的使用

//js部分
import chunkedUpload from './chunkedUpload'
export default {data() {return {uploadData: {//这里面放额外携带的参数},//文件上传的路径uploadUrl: process.env.BASE_API + '/oss/oss/uploadChunkFile', //文件上传的路径chunkedUpload: chunkedUpload // 分片上传自定义方法,在头部引入了}},methods: {onError(err, file, fileList) {this.$store.getters.chunkedUploadXhr.forEach(item => {item.abort()})this.$alert('文件上传失败,请重试', '错误', {confirmButtonText: '确定'})},beforeRemove(file) {// 如果正在分片上传,则取消分片上传if (file.percentage !== 100) {this.$store.getters.chunkedUploadXhr.forEach(item => {item.abort()})}}}
}
//chunkedUpload.js
import SparkMD5 from 'spark-md5'
import axios from 'axios'
import store from '@/store'
// 如果上传错误,获取报错信息
function getError(action, option, xhr) {let msgif (xhr.response) {msg = `${xhr.response.error || xhr.response}`} else if (xhr.responseText) {msg = `${xhr.responseText}`} else {msg = `fail to post ${action} ${xhr.status}`}const err = new Error(msg)err.status = xhr.statuserr.method = 'post'err.url = actionreturn err
}
// 上传成功完成合并之后,获取服务器返回的信息
function getBody(xhr) {const text = xhr.responseText || xhr.responseif (!text) {return text}try {return JSON.parse(text)} catch (e) {return text}
}// 分片上传的自定义请求,以下请求会覆盖element的默认上传行为
export default function upload(option) {if (typeof XMLHttpRequest === 'undefined') {return}const spark = new SparkMD5.ArrayBuffer()// md5的ArrayBuffer加密类const fileReader = new FileReader()// 文件读取类const action = option.action // 文件上传上传路径const chunkSize = 1024 * 1024 * 30 // 单个分片大小let md5 = ''// 文件的唯一标识const optionFile = option.file // 需要分片的文件let fileChunkedList = [] // 文件分片完成之后的数组const percentage = [] // 文件上传进度的数组,单项就是一个分片的进度// 文件开始分片,push到fileChunkedList数组中, 并用第一个分片去计算文件的md5for (let i = 0; i < optionFile.size; i = i + chunkSize) {const tmp = optionFile.slice(i, Math.min((i + chunkSize), optionFile.size))if (i === 0) {fileReader.readAsArrayBuffer(tmp)}fileChunkedList.push(tmp)}// 在文件读取完毕之后,开始计算文件md5,作为文件唯一标识fileReader.onload = async(e) => {spark.append(e.target.result)md5 = spark.end() + new Date().getTime()console.log('文件md5为--------', md5)// 将fileChunkedList转成FormData对象,并加入上传时需要的数据fileChunkedList = fileChunkedList.map((item, index) => {const formData = new FormData()if (option.data) {// 额外加入外面传入的data数据Object.keys(option.data).forEach(key => {formData.append(key, option.data[key])})// 这些字段看后端需要哪些,就传哪些,也可以自己追加额外参数formData.append(option.filename, item, option.file.name)// 文件formData.append('chunkNumber', index + 1)// 当前文件块formData.append('chunkSize', chunkSize)// 单个分块大小formData.append('currentChunkSize', item.size)// 当前分块大小formData.append('totalSize', optionFile.size)// 文件总大小formData.append('identifier', md5)// 文件标识formData.append('filename', option.file.name)// 文件名formData.append('totalChunks', fileChunkedList.length)// 总块数}return { formData: formData, index: index }})// 更新上传进度条百分比的方法const updataPercentage = (e) => {let loaded = 0// 当前已经上传文件的总大小percentage.forEach(item => {loaded += item})e.percent = loaded / optionFile.size * 100option.onProgress(e)}// 创建队列上传任务,limit是上传并发数function sendRequest(chunks, limit = 3) {return new Promise((resolve, reject) => {const len = chunks.lengthlet counter = 0let isStop = falseconst start = async() => {if (isStop) {return}const item = chunks.shift()console.log()if (item) {const xhr = new XMLHttpRequest()const index = item.index// 分片上传失败回调xhr.onerror = function error(e) {isStop = truereject(e)}// 分片上传成功回调xhr.onload = function onload() {if (xhr.status < 200 || xhr.status >= 300) {isStop = truereject(getError(action, option, xhr))}if (counter === len - 1) {// 最后一个上传完成resolve()} else {counter++start()}}// 分片上传中回调if (xhr.upload) {xhr.upload.onprogress = function progress(e) {if (e.total > 0) {e.percent = e.loaded / e.total * 100}percentage[index] = e.loadedconsole.log(index)updataPercentage(e)}}xhr.open('post', action, true)if (option.withCredentials && 'withCredentials' in xhr) {xhr.withCredentials = true}const headers = option.headers || {}for (const item in headers) {if (headers.hasOwnProperty(item) && headers[item] !== null) {xhr.setRequestHeader(item, headers[item])}}// 文件开始上传xhr.send(item.formData)//这里是把所有分片上传的xhr存到全局中,如果用户手动取消上传,或者上传出现错误,则要调用xhr.abort()把store中所有xhr的停止,不然文件还会继续上传store.commit('SET_CHUNKEDUPLOADXHR', xhr)}}while (limit > 0) {setTimeout(() => {start()}, Math.random() * 1000)limit -= 1}})}try {// 调用上传队列方法 等待所有文件上传完成await sendRequest(fileChunkedList, 3)// 这里的参数根据自己实际情况写const data = {identifier: md5,filename: option.file.name,totalSize: optionFile.size}// 给后端发送文件合并请求const fileInfo = await axios({method: 'post',url: '/api/oss/oss/mergeChunkFile',data: data})// 这个8200是我们oss存储成功的code,根据自己实际情况可以变if (fileInfo.data.code === 8200) {const success = getBody(fileInfo.request)option.onSuccess(success)return}} catch (error) {option.onError(error)}}
}

前端通过spark-md5.js计算本地文件md5

这里提供了两个方法;一种是用SparkMD5.hashBinary( ) 直接将整个文件的二进制码传入直接返回文件的md5、这种方法对于小文件会比较有优势——简单并且速度快。
另一种方法是利用js中File对象的slice( )方法(File.prototype.slice( ))将文件分片后逐个传入spark.appendBinary( )方法来计算、最后通过spark.end( )方法输出结果,很明显,这种方法对于大型文件会非常有利——不容易出错,并且能够提供计算的进度信息

第一种方式:

 var running = false;    //running用于判断是否正在计算md5function doNormalTest( input ) {    //这里假设直接将文件选择框的dom引用传入if (running) {    // 如果正在计算、不允许开始下一次计算return;}var fileReader = new FileReader(),    //创建FileReader实例time;fileReader.onload = function (e) {    //FileReader的load事件,当文件读取完毕时触发running = false;// e.target指向上面的fileReader实例if (file.size != e.target.result.length) {    //如果两者不一致说明读取出错alert("ERROR:Browser reported success but could not read the file until the end.");} else {console.log(Finished loading!success!!);return SparkMD5.hashBinary(e.target.result);    //计算md5并返回结果}};fileReader.onerror = function () {    //如果读取文件出错,取消读取状态并弹框报错running = false;alert("ERROR:FileReader onerror was triggered, maybe the browser aborted due to high memory usage.");};running = true;fileReader.readAsBinaryString( input.files[0] );    //通过fileReader读取文件二进制码};

第二种方式

function doIncrementalTest( input ) {    //这里假设直接将文件选择框的dom引用传入if (running) {return;}//这里需要用到File的slice( )方法,以下是兼容写法var blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice,file = input.files[0],chunkSize = 2097152,                           // 以每片2MB大小来逐次读取chunks = Math.ceil(file.size / chunkSize),currentChunk = 0,spark = new SparkMD5(),    //创建SparkMD5的实例time,fileReader = new FileReader();fileReader.onload = function (e) {console("Read chunk number (currentChunk + 1) of  chunks ");spark.appendBinary(e.target.result);                 // append array buffercurrentChunk += 1;if (currentChunk < chunks) {loadNext();} else {running = false;console.log("Finished loading!");return spark.end();     // 完成计算,返回结果}};fileReader.onerror = function () {running = false;console.log("something went wrong");};function loadNext() {var start = currentChunk * chunkSize,end = start + chunkSize >= file.size ? file.size : start + chunkSize;fileReader.readAsBinaryString(blobSlice.call(file, start, end));}running = true;loadNext();}

基于elementui的大文件断点续传相关推荐

  1. 【vue】 前端 基于 vue-simple-uploader 实现大文件断点续传和分片上传

    文章目录 一.前言 二.后端部分 新建Maven 项目 后端 pom.xml 配置文件 application.yml HttpStatus.java AjaxResult.java CommonCo ...

  2. 精品分享:基于 SpringBoot + Vue 开发的云盘系统(含大文件断点续传剖析)

    引言 作为开发人员,我们经常需要存储和下载文件,为了使用方便,通常都会将文件存储在云端,市面上使用率最高的云端存储莫过于百度网盘了,但使用别人的东西难免会受到各种各样的限制,必须花钱才会享受到更好的服 ...

  3. iOS开发之网络编程--使用NSURLConnection实现大文件断点续传下载

    前言:iOS开发之网络编程--使用NSURLConnection实现大文件断点续传下载是在前篇iOS开发之网络编程--使用NSURLConnection实现大文件下载的基础上进行    断点续传的设置 ...

  4. 多线程大文件断点续传和流媒体的处理方法

    2019独角兽企业重金招聘Python工程师标准>>> 在使用Squid做反向代理的CDN节点时.多线程大文件断点续传和流媒体的处理是怎么样啦.前些日子花了点时间研究了一下. 在Sq ...

  5. 文件上传控件-如何上传文件-大文件断点续传

    需求: 项目要支持大文件上传功能,经过讨论,初步将文件上传大小控制在20G内,因此自己需要在项目中进行文件上传部分的调整和配置,自己将大小都以20G来进行限制. PC端全平台支持,要求支持Window ...

  6. html5解决大文件断点续传6,解决html5大文件断点续传

    一.概述 所谓断点续传,其实只是指下载,也就是要从文件已经下载的地方开始继续下载.在以前版本的HTTP协议是不支持断点的,HTTP/1.1开始就支持了.一般断点下载时才用到Range和Content- ...

  7. 基于js管理大文件上传以及断点续传

    大厂技术  高级前端  Node进阶 点击上方 程序员成长指北,关注公众号 回复1,加入高级Node交流群 前言 前端小伙伴们平常在开发过程中文件上传是经常遇到的一个问题,也许你能够实现相关的功能,但 ...

  8. php - 基于 webuploader 视频大文件分片分段上传,支持断点续传(刷新、关闭页面、重新上传、网络中断等情况)带进度条,前端后端都有示例源码详细教程

    效果图 文件上传前先检测该文件是否已上传,如果已上传提示 "文件已存在",如果未上传则直接上传. 基于 php+webuploader的大文件分片上传,带进度条,支持断点续传(刷新 ...

  9. nodejs文件服务器断点续传,基于Nodejs的大文件上传之断点续传

    接着<扒一扒Nodejs formidable的onPart>和<也说文件上传之兼容IE789的进度条---丢掉flash>:前面已完成兼容IE789的大文件上传:无flash ...

最新文章

  1. Android之LocalBroadcastManager源码解析
  2. python为text添加滚动条_Python GUI编程(Tkinter)7、带滚动条的Text
  3. vue 路由跳转并打开新页面
  4. angular学习的一些小笔记(中)之表单验证
  5. 如何在Debian 8上安装Percona XtraDB Cluster for MySQL
  6. 中外合作有开计算机课吗,探究中外合作办学计算机应用课程建设.doc
  7. Gstreamer1.18.4编译(二十六)
  8. C++ 中 ifstream读取txt文件内容
  9. Delphi--“Range check error“ 错误解决方案之一
  10. java 表单验证必填的_avalon2 非必填项的表单验证规则
  11. MTK修改sysemUI下拉的宽度为全屏
  12. 计算机语言的魅力,四年级语文下册《语言的魅力》说课稿
  13. 单张、批量识别图片中文字(写入txt文件、窗口视图创建、打包.exe文件)(百度文字识别SDK+Python的GUI之tklinker+打包pyinstaller)
  14. STM32CubeMX SDRAM的使用(二)
  15. 2018 Arab Collegiate Programming Contest (ACPC 2018) L.Looking for Taste(按位或)
  16. 微软都有哪些开源项目?
  17. 畅捷通T+ v17任意文件上传漏洞复现
  18. SpringMVC返回数据到页面的方法
  19. 读书笔记-大颠狂(非同寻常的大众幻想与群众性癫狂)
  20. 清者自清!国际泳联为孙杨“药检风波”盖棺定论

热门文章

  1. PG的timestamp
  2. mysql中的declare_sql中declare是什么意思
  3. 【身体这些部位不舒服的时候,你知道意味着什么吗?】收藏起来吧,震惊!实在是太...
  4. Apache Spark RDD 论文(中文翻译)
  5. 基于脱敏数据,使用huggingface的Transformers预训练模型
  6. ubuntu20.02安装显卡驱动常见问题总结
  7. Oracle 11g 安装与彻底卸载
  8. MATLAB----符号微积分
  9. osgEarth测高程方法
  10. Win11,cmd闪退的一种解决思路