需求
- 将文件切片上传 / 如何切片 / 如何确定切片的数量 / 如何确定切片的大小
- 使用
Blob.protype.slice(),因为file的原型是Blob - 以切片数量优先,先规定
maxCount,maxSize;如果file.size / maxSize大于maxCount,则使用file.size / maxCount来重新计算maxCount
- 使用
- 何时通知后端将切片合并
- 在所有切片都上传之后,使用一个接口通知后端合并 (需要按照顺序传输)
- 在上传切片的接口中传送当前切片是第几个,总共有多少的切片 (需要按照顺序传输)
- 在前端切片都上传之后,使用一个接口获取后端获取状态,后端返回已经获取的切片,前端过滤出还没有上传的切片,重新上传一次 (可以并发传输),需要限制重新上传的次数,否则会导致死循环
- 显示上传进度条
- 此次上传的切片次序除以切片总数 (
index/count)
- 此次上传的切片次序除以切片总数 (
- 上传中止之后继续上传 (断点续传)
步骤
封装成一个实现类
constructor() {
this.id = ''
this.md5 = ''
this.chunkList = []
// 已经成功上传的切片列表,用于验证上传时
this.uploadedChunkList = []
// Number of current failures
this.count = 0
this.progress = 0
}
const fileChunk = new FileChunk()
export default fileChunk
-
获取文件
- 使用上传组件
- 自己定义上传文件的组件
-
将文件进行切片,存入
chunkListcreateFileChunk(file, maxSize = 1024 * 1024) { // 判断文件大小,超过多少才进行分片上传 if (file.size > maxSize) { // 限制上传的切片数量,限制上传的大小 let max = maxSize, count = Math.ceil(file.size / maxSize), index = 0, chunkList = [] if (count > 18) { max = Math.ceil(file.size / 18) count = 18 } while (index < count) { (index + 1) * max < file.size ? chunkList.push( { name: file.name, start: index * max, chunks: count, chunk: index, file: file.slice(index * max, (index + 1) * max) } ) : chunkList.push( { name: file.name, start: index * max, chunks: count, chunk: index, file: file.slice(index * max, file.size) } ) } return chunkList } else { // 一次性上传操作 } } -
并发发送
// 上传切片 uploadChunkList(chunks) { // 获取已经上传的切片的index const uploadedChunkIndex = this.uploadedChunkList.map((item) => item.chunkIndex) // 过滤出没有上传成功的切片 const chunkList = this.chunkList.filter((item) => !item.find(uploadedChunkIndex)) // 并发上传,限制并发数量6(因为浏览器每个域最多允许6个链接<分浏览器,有些是,有些不是,但是一般都是>) new Promise((resolve) => { const length = chunkList.length let index = 0 let counter = 0 let max = 6 const start = async () => { while (index < length && max > 0) { // 同步代码 max-- const formData = new FormData() formData.append('name', chunkList[index].name) formData.append('start', chunkList[index].start) formData.append('chunks', chunkList[index].chunks) formData.append('chunk', chunkList[index].chunk) formData.append('file', chunkList[index].file) index++ // 异步代码 await uploadChunkPromise(formData).then(res => { this.id = res.data counter++ max++ // Keep the decimal place to one place this.progress = Math.floor((index + 1) / length * 1000) / 10 if (counter === length) { resolve() } else { start() } }) } } start() }) } -
获取上传结果,上传失败,上传成功,上传部分成功,分别进行处理
async getUploadResult() { // 只能失败三次 const id = this.id const res = await getUploadStatusPromise(id) if (res.status === 0 && this.count < 3) { // 不成功,有切片未上传成功 this.count++ // 执行上传 this.uploadedChunkList = res.uploadedChunkList this.uploadChunkList() // 判断状态 await this.getUploadResult(id) } else if (res.status === 1) { // 上传成功 this.progress = 'success' } else { this.progress = 'fail' } } -
清除数据
clearData() { this.id = '' this.md5 = '' this.chunkList = [] this.uploadedChunkList = [] this.count = 0 this.progress = 0 } -
创建md5
使用
object-hash库生成md5,一共有40位calculateFileMd5(file) { return hash(file) + (new Date()).valueOf() } -
入口方法
async doUpload(file, size) { this.clearData() this.chunkList = this.createFileChunk(file, size) await this.uploadChunkList() await this.getUploadResult() return this.progress }