安装依赖
pnpm install spark-md5
pnpm install @types/spark-md5 -D
分片上传、断点续传函数定义
CHUNK_SIZE = 10 * 1024 * 1024;
getFileMD5(file: File) {
const fileReader = new FileReader();
const blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;
const start = 0;
const end = this.CHUNK_SIZE >= file.size ? file.size : this.CHUNK_SIZE;
fileReader.readAsBinaryString(blobSlice.call(file, start, end));
return new Promise((resolve, reject) => {
fileReader.onload = (e: any) => {
resolve(SparkMD5.hashBinary(e.target.result, false));
};
fileReader.onerror = function () {
message.error('文件读取出错,请检查该文件!');
reject(new Error('文件读取出错,请检查该文件!'));
};
});
}
checkUploadByMD5(md5: string) {
return this.get(`xxxxxxxxxx/check-upload?identifier=${md5}`);
}
createBigFile(file: File, onProcess: any = null): any {
return new Promise(async (resolve, reject) => {
if (!file) {
reject(new Error('文件为空,请检查!'));
}
const md5 = await this.getFileMD5(file);
let uploadedChunks = [];
try {
const checkResult: any = await this.checkUploadByMD5(`${md5}`);
if (checkResult.uploaded && Object.keys(checkResult.commonFile || {}).length) {
resolve(checkResult.commonFile);
return;
}
uploadedChunks = checkResult.uploadedChunks || [];
} catch (err) {
console.error(err);
}
const fileChunks = [];
let index = 0;
for (let cur = 0; cur < file.size; cur += this.CHUNK_SIZE) {
if (!uploadedChunks.includes(++index)) {
fileChunks.push({
chunkNumber: index,
chunk: file.slice(cur, cur + this.CHUNK_SIZE),
});
}
}
const totalChunks = index;
let success = uploadedChunks.length;
let percent = (success / totalChunks ?? 0) * 100;
onProcess && onProcess({ percent: percent.toFixed(2) });
const processDetail: number[] = [];
let consecutiveFailure = 0;
const uploadFileChunks = async (list: { [key: string]: any }[]) => {
const pool: any[] = [];
const max = 3;
const failureMax = 3;
let finish = 0;
const failList: { [key: string]: any }[] = [];
for (let i = 0; i < list.length; i++) {
if (consecutiveFailure >= failureMax) {
message.error('文件上传失败,请稍后再试!');
reject(new Error('文件上传失败,请稍后再试!'));
return;
}
const item = list[i];
const chunkData = {
chunkNumber: item.chunkNumber,
totalChunks,
chunkSize: item.chunk.size,
totalSize: file.size,
identifier: md5,
filename: file.name,
file: item.chunk,
};
const task = this.postFormLarge('xxxxxxx/upload', chunkData, {
onUploadProgress: (info) => {
if (onProcess) {
const poolIndex = pool.findIndex((item) => item === task);
const progress = info?.progress ?? 0;
processDetail[poolIndex] = progress === 1 ? 0.99 : progress;
const percentNew = (processDetail.reduce((pre, cur) => pre + cur, success) / totalChunks) * 100;
if (percentNew > percent) {
percent = percentNew;
onProcess({ percent: percentNew.toFixed(2) });
}
}
},
});
task.then((taskResult: any) => {
if (taskResult && taskResult.uploadFlag) {
const poolIndex = pool.findIndex((item) => item === task);
pool.splice(poolIndex, 1);
success++;
consecutiveFailure = 0;
if (onProcess) {
try {
processDetail.splice(poolIndex, 1);
const percentNew = (processDetail.reduce((pre, cur) => pre + cur, success) / totalChunks) * 100;
if (percentNew > percent) {
percent = percentNew;
onProcess({ percent: percentNew.toFixed(2) });
}
} catch (err) {
console.error(err);
}
}
} else {
consecutiveFailure++;
failList.push(item);
}
if (Object.keys(taskResult.commonFile || {}).length) {
resolve(taskResult.commonFile);
}
})
.catch(() => {
consecutiveFailure++;
failList.push(item);
})
.finally(() => {
finish++;
if (finish === list.length && failList.length) {
uploadFileChunks(failList);
}
});
pool.push(task);
processDetail.push(0);
if (pool.length === max) {
await Promise.race(pool);
}
}
};
uploadFileChunks(fileChunks);
});
}
调用
const commonFile = await createBigFile(file, (e: any) => {
uploadingObjs.value.percent = e?.percent || 0;
});