场景:
提示:在项目中会遇到大文件上传,使用传统二进制码传输方式,可能因为用户误操作、网络不通畅和其他各种原因导致无法一次性完成,这时用户可能又要重新上传,就浪费了时间,此场景就可以使用文件分片:
涉及知识:
- MD5(使用spark-md5插件进行计算)
- ArrayBuffer操作
代码展示:
这里使用的是
ant-design的upload组件,大家测试也可以直接用input标签,设置type为file就可以了
- 组件部分
<a-upload
name="file"
:show-upload-list="false"
:multiple="multiple"
:headers="tokenHeader"
:accept="accept"
:beforeUpload="beforeUpload"
:customRequest="handleUpload" //重点关注
:data="data"
:action="url.upload"
@change="handleChangeEvent"
>
<a-button>
<m-icon type="iconupload" />
上传附件
</a-button>
</a-upload>
- 用到的数据
首先在选择选择文件后,通过组件的自定义事件的参数获取到对应文件对象
import SparkMD5 from 'spark-md5'
data(){
return {
chunkList:[], //对文件进行分片处理后,储存的数组
successChunk:0,//判断片段是否都上传成功
}
}
- 对文件分片,并计算文件MD5
computeMD5 (file) {
const _this = this
return new Promise((resolve, reject)=>{
try {
let fileReader = new FileReader()
let blobSlice = File.prototype.slice
let currentChunk = 0 // 当前第几片
const chunkSize = 8 * 1024 * 1024 // 每片的大小,这里是5M
let chunks = Math.ceil(file.size / chunkSize) // 总片数
let spark = new SparkMD5.ArrayBuffer()
let loadNext = () => {
let start = currentChunk * chunkSize
let end = ((start + chunkSize) >= file.size) ? file.size : start + chunkSize
fileReader.readAsArrayBuffer(blobSlice.call(file, start, end))
_this.chunkList.push(blobSlice.call(file, start, end)) // 将每次分片的字节流放到数组里
}
loadNext()
fileReader.onload = (e => {
spark.append(e.target.result) //插件计算MD5
currentChunk++
if (currentChunk < chunks) {
loadNext()
} else {
this.md5 = spark.end()
resolve(this.md5)
}
})
fileReader.onerror = function () {
this.$message.error(`文件${file.name}读取出错,请检查该文件`)
}
}catch(e) {
reject(e)
}
})
},
- 分片上传文件
handleUpload (param) {
const file = param.file // 组件提供的文件
const that = this
this.computeMD5(file).then((md5)=>{
// 这里请求接口,发送MD5返回是否已上传该片段
if (this.chunkList.length > 0) {
axios('检查文件接口', { //此处需要后端支持,首先检查文件是否上传完成或者是已经上传过但是未完成,这一步也是断点续传的关键
fileMd5: md5, //通过上面computeMD5最后计算得到
fileName: file.name,
chunkCount: this.chunkList.length
}).then(async res => {
if(res.success) {
if(res.noSendChunkList.length > 0) {
//返回结果,看看有多少分片没有上传
this.chunkList = this.chunkList.filter((item, index) => {
//根据拿到的分片序号res.noSendChunkList对分片数组修改
return res.noSendChunkList.includes(index + 1)
})
for(let i = 0, len = this.chunkList.length;i<len;i++) {
let item = this.chunkList[i]
let formData = new FormData() // 按分片个数发送请求,传输文件使用的是FormData
formData.append('file', item)
formData.append('md5', md5)
formData.append('chunkSize', item.size)
formData.append('chunkIndex', res.noSendChunkList[i]) // 属于第几片
let data = await axios('发送分片接口', formData)
if (data.success) {
this.successChunk++ // 记录当前已上传成功的片数,这里可以看需求可以加个进度条
if(this.chunkList.length == this.successChunk){
this.merge()
}
} else {
this.$message.warning(data.message)
}
}
}else {
this.merge()
}
}
})
}
})
},
- 让后端对文件进行合并
merge(){
//告诉后端分片已全部传输完成,可以进行分片合成,再提供相关信息
}
总结
继续加油!