开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 21 天,点击查看活动详情
切片上传
思路,由于大文件上传需要时间较长,而由于网络波动导致文件上传失败,需要重新上传,从而使用户精神崩溃,所以人们想到了分片上传。所谓分片上传就是把大文件切成小文件然后将小文件传到服务器,小文件传输有专门接口,小文件的命名都是 xxxxx_0.后缀。xxxxx_1.后缀,文件名都是相同的,让后端知道这些小文件是一个大文件的片段,但是为了做到这一点,首先要保证文件名唯一且固定,且同一个文件上传多次分片名也一样,就是由文件内容生成的hash,因为这样能保证同一个文件每次都会当成同一个文件对待,才能从根本上做到继续。就是干了一件事干了一半了,然后继续干这件事,这件事必须是唯一的。
生成内容 hash
首先有这么一个方法,利用FileReader类读取文件类型为buffer格式,然后利用sparkMd5.ArrayBuffer,使用文件内容生成hash,最后得到了文件hash和后缀
export const readFileBuffer = (file) => {
return new Promise((resove, reject) => {
const fileReader = new FileReader()
fileReader.readAsArrayBuffer(file)
fileReader.onload = (e) => {
const buffer = e.target.result
const spark = new sparkMd5.ArrayBuffer()
spark.append(buffer)
const HASH = spark.end()
const suffix = /.([a-zA-Z0-9]+)$/.exec(file.name)[1]
resove({ buffer, HASH, suffix, filename: `${HASH}.${suffix}` })
}
fileReader.onerror = (e) => {
reject('错误')
}
})
}
获取已经上传的切片 和秒传
// 请求接口,通过文件hash,获取当前服务器上是否有该文件的切片或者有这个文件,如果有这个文件了,就直接返回成功,直接把文件地址返回,就是所谓的秒传 当然这里没做秒传成功的处理。
let already = []
try {
const res = await uploadAlready({ params: { HASH } })
if (res.code === 0) {
already = res.fileList
} else {
throw new Error()
}
} catch {
message.error('当前切片上传失败')
}
文件切片的规格
首先默认切片大小为1Mb,然后根据这个规格算一下切片个数,如果超过了100个切片,那么就重新算一个单个切片的大小,不能传过多的切片。可能单个切片体积大一点,比较小的文件切片大小就是1M,大的就是100分之1
// 文件切片
let max = 1024 * 1024
let count = Math.ceil(file.size / max)
if (count > 100) {
count = 100
max = Math.ceil(file.size / count)
}
切片
利用文件类型原型对象上的slice方法,将文件切分成上面的规格大小
let chunks = []
for (let i = 0; i < count; i++) {
chunks.push({
file: file.slice(i * max, (i + 1) * max),
filename: `${HASH}_${i}.${suffix}`,
})
}
上传成功一个切片时要做的事
首先定义一个变量保存完成的切片的个数,完成一个就算一下完成的个数占总数的多少,算出进度,如果完成的个数和总数相同,那么,这个文件就传完了。
let complateCount = 0
const compalte = async () => {
complateCount++
const p = parseInt((complateCount / count) * 100) + '%'
setProgress(p)
if (complateCount === count) {
setProgress('100%')
try {
const res = await uploadMerge({ HASH, count })
if (res.code === 0) {
message.success('上传成功')
await delay()
clearFile()
} else {
throw new Error('xxx')
}
} catch (error) {
message.error('合并错误')
await delay()
clearFile()
}
}
}
上传
有多少切片发送多少次上传请求,所以不能一下都发送了,不用说服务器受不了,浏览器也受不了。每次截取10个,就可以,当然高端操作是先测试一下,然后根据情况再决定数量,上传操作和普通上传一样
while (chunks.length > 0) {
const list = chunks.splice(0, 10)
const promiseList = list.map(async (chunk, index) => {
if (already.includes(chunk.filename)) {
compalte()
return
}
let formData = new FormData()
formData.append('file', chunk.file)
formData.append('filename', chunk.filename)
try {
const res = await uploadChunk(formData)
if (res.code === 0) {
compalte()
} else {
throw Error(res.codeText)
}
} catch (error) {
message.error('上传失败,稍后重试')
clearFile()
}
})
const res= await promiseList()
console.log(res);
}