文章目录
- 👉 前言及含义
- 切片上传
- 断点续传
- 👉 一、实现思路
- 👉 二、使用场景
- 👉 参考文献
- 👉 伸手党福利: 即拿即用(前/后端思路均有)
- 往期内容 💨
👉 前言及含义
在开发过程中,不管怎样简单的需求,在量级
达到一定层次时,都会变得异常复杂。
就拿今天要说的文件上传来说,文件上传简单,但是当文件大小变得太大超出控制时,就会变得复杂了!
当上传大文件时,会存在以下几个变量会影响我们的用户体验:
- 服务器处理数据的能力
- 上传时间会变长,高频次文件上传失败,失败后又需要重新上传等等
- 当遇到网络波动时,大文件上传容错率下降
- 上传文件请求超时
为了解决上述问题,我们需要对大文件上传单独处理
这里涉及到切片上传
及断点续传
两个概念
切片上传
顾名思义,切片上传,就是将所要上传的大文件,按照一定的大小,将整个文件切割成多个数据块(Part
)来进行分片上传。上传完之后再由服务端对所有上传的文件进行汇总整合成原始的文件。
大致流程如下:
- 将需要上传的文件按照一定的分割规则,分割成相同大小的数据块;
- 初始化一个分片上传任务,返回本次分片上传唯一标识;
- 按照一定的策略(串行或并行)发送各个分片数据块;
- 发送完成后,服务端根据判断数据上传是否完整,如果完整,则进行数据块合成得到原始文件
断点续传
断点续传,指的是在下载或上传时,将下载或上传任务人为的划分为几个部分
每一个部分采用一个线程进行上传或下载,如果碰到网络故障,可以从已经上传或下载的部分开始继续上传下载未完成的部分,而没有必要从头开始上传下载。用户可以节省时间,提高速度。现在常用的网盘下载工具和云存储平台大多采用了这种方式,可由用户手动暂停或继续文件上传、下载等操作。
一般实现方式有两种:
- 服务器端返回,告知从哪开始
- 浏览器端自行处理
上传过程中将文件在服务器写为临时文件,等全部写完了(文件上传完),将此临时文件重命名为正式文件即可
如果中途上传中断过,下次上传的时候根据当前临时文件大小,作为在客户端读取文件的偏移量,从此位置继续读取文件数据块,上传到服务器从此偏移量继续写入文件即可。不过反复中断,小概率导致文件合并后,数据乱码的情况出现。
👉 一、实现思路
- 拿到文件,对文件进行fingerprint = md5(file),得到文件指纹。
- 将唯一标识指纹保存服务器。
- 切割文件,分段上传,每次上传一段。
- 服务器根据指纹进行索引判断文件上传进度,直到文件的全部片段上传完毕。
下面的内容都是伪代码(仅提供思路参考)
1. 读取文件内容:
// <input type="file" name="XXX" id="XXX" />
const input = document.querySelector('input')
input.addEventListener('change', function() {var file = this.files[0]
});
2. 可以使用md5, 实现文件的唯一性加密
const md5Code = md5(file)
3. 将文件进行切割,分成若干“块”小文件
// 创建一个reader对象, 允许操作file或blob
var reader = new FileReader()
// 用于启动读取指定的 Blob 或 File 内容
reader.readAsArrayBuffer(file)
// 当文件成功读取时,执行load 事件
reader.addEventListener("load", (e) => {//每10M切割一段,这里只做一个切割演示,实际切割需要根据实际文件大小,指定循环切割的片数和每片的大小...var slice = e.target.result.slice(0, 10*1024*1024)
});
FileReader相关理论知识 (点击跳转)
4.将切片逐片上传
const formData = new FormData();
// 与后端协商,看看如何界定上传片数。
// 如上面流程图所述,可以在第一片(尽量切小片一点,降低出错率)上传的时候,携带这个大文件对应的信息,如:整体裁切片数,文件总大小,文件名称及文件类型等等
//这里是有一个坑的,部分设备无法获取文件名称和文件类型,可以通过读取文件二进制流,解析文件类型,具体可以通过百度了解,不展开叙述。主要讲思路
formData.append('file-' + index, slice) // index 代表 此片的序号
formData.append('fileName', file.filename)
formData.append('fileType', file.fileType)
formData.append('md5Code', md5Code)// 》XMLHttp
var xhr = new XMLHttpRequest()
xhr.addEventListener('load', function() {//xhr.responseText
})
xhr.open('POST', '')
xhr.send(formData)
// 监听
xhr.addEventListener('progress', updateProgress)
xhr.upload.addEventListener('progress', updateProgress)function updateProgress(event) {if (event.lengthComputable) {//进度条}
}// 》 ajax
$.ajax({type:"post",url:"http://XXXXX",async:true,data:formData,cache:false,processData: false,contentType: false,xhr: function xhr() {//获取原生的xhr对象var xhr = $.ajaxSettings.xhr();if (xhr.upload) {//添加 progress 事件监听xhr.upload.addEventListener('progress', function (e) {//e.loaded 已上传文件字节数//e.total 文件总字节数var percentage = parseInt(e.loaded / e.total * 100)}, false)}return xhr},success:function(res){alert(JSON.stringify(res))}
})// 》 axios
let config = {headers:{'Content-Type':'multipart/form-data',},transformRequest: [function (data) {return data}],onUploadProgress: progressEvent => {//上传进度百分比let persent = (progressEvent.loaded / progressEvent.total * 100 | 0)console.log(persent)},
}
axios.post('http://xxxxxxxx/video/upload',formData,config
).then(response=>{var result = response.dataif(result.status == 0){console.log(result)}else{this.$message({message: '上传失败',type: 'error',duration:'1000'})}
}).catch(err => {console.log(err)
})
说明:e.loaded 为已上传文件字节数,e.total 为文件总字节数,可通过计算得出已上传字节数占比,读者可根据自己项目需要使用进度条或进度环
有了切割上传后,也就有了文件唯一标识信息,断点续传变成了后台的一个小小的逻辑判断
后端主要做的内容为:根据前端传给后台的md5值,到服务器磁盘查找是否有之前未完成的文件合并信息(也就是未完成的半成品文件切片),取到之后根据上传切片的数量,通过接口返回,告诉前端开始从第几节上传,继续进行上次未完成的文件上传
如果想要暂停切片的上传,可以使用XMLHttpRequest 的 abort 方法,其他请求同样有对应的暂停方法,可百度了解。
👉 二、使用场景
- 大文件加速上传:当文件大小超过预期大小时,使用分片上传可实现并行上传多个 Part, 以加快上传速度
- 网络环境较差:建议使用分片上传。当出现上传失败的时候,仅需重传失败的Part。而不会导致因网络问题,不断重新上传整个文件,提高容错率。
- 流式上传:可以在需要上传的文件大小还不确定的情况下开始上传。这种场景在视频监控等行业应用中比较常见
👉 参考文献
js实现文件切片上传,断点续传
百度冲浪
👉 伸手党福利: 即拿即用(前/后端思路均有)
提供自掘金大佬:前端切片上传文件(可暂停),以链接形式返回
往期内容 💨
🔥 < 每日技巧: JavaScript代码优化 >
🔥 < 每日知识点:关于Javascript 精进小妙招 ( Js技巧 ) >
🔥 <Javascript技巧: Javascript 是个难泡的妞,学点技巧征服 “ 她 ” >
🔥 < 在element-ui中: 使用el-tree + el-table组件,联动请求用户数据表格组件 (基础版,后续可能更新) >