最近在做大文件上传的需求,所以就有了这一系列前端文件上传功能的记录。
流程图
一个大概的流程图
相关代码
获取文件md5值
const handleMD5 = (file) => {
return new Promise((resolve, reject) => {
const fileReader = new FileReader();
fileReader.readAsBinaryString(file);
fileReader.onload = (event) => {
if (event.target) {
const md5 = SparkMD5.hashBinary(event.target.result);
return resolve(md5);
}
return reject();
};
});
};
大文件切片处理
const createFileChunk = (file: any, uploadId, size?: number) => {
const SIZE = (size || configSize) * 1024 * 1024;
const fileChunkList = [] as any[];
for (let cur = 0, index = 0; cur < file.size; cur += SIZE) {
fileChunkList.push({ file: file.slice(cur, cur + SIZE), position: cur, index });
index += 1;
}
return fileChunkList;
};
文件上传请求
const loopArray = (index: number, list: any[], uid: string) => {
setPageProps((pre) => {
const listId = _.findIndex(pre.upload.list, (items) => items.id === uid);
pre.upload.list[listId].status = 'doing';
});
bucketAPI
.putNameFsUpload(...list[index].props)
.then(async (res) => {
if (res.code === 200 && res.data.done) {
setPageProps((pre) => {
if (index < list.length - 1) {
// 存入已上传片段
const listId = _.findIndex(pre.upload.list, (items) => items.id === uid);
const arr = pre.upload.list[listId];
arr.doneIndex = index;
// 存入已上传片段
const requestIndex = fineFileIndex(requestQueue, uid);
if (requestIndex > -1) {
requestQueue[requestIndex] = { ...requestQueue[requestIndex], doneIndex: index };
}
if (arr.status !== 'stop') {
loopArray(index + 1, list, uid);
}
}
if (index === list.length - 1) {
const listId = fineFileIndex(pre.upload.list, uid);
const arr = pre.upload.list[listId];
if (arr.md5 && arr.file && arr.uploadId) {
bucketAPI.postNameFsUpload(
detailId,
`${pre.path}${arr.file.name}`,
{
md5: arr.md5,
size: arr.file.size,
uploadId: arr.uploadId,
},
{
successCallback: () => {
uploadSuccess(uid);
setPageProps((pree) => {
const data = _.cloneDeep(pree);
lineUp({ ...data, requestQueue, uid });
});
},
}
);
}
}
});
} else {
uploadFailed(uid);
setPageProps((pree) => {
const data = _.cloneDeep(pree);
lineUp({ ...data, requestQueue, uid });
});
}
})
.catch((err) => {
changeFileStatus('failed', uid);
});
};
构建发送请求列表
const createRequestList = (fileList: any[], name: string, uid: string, uploadId: string) => {
const requestList = fileList.map((item, index) => {
return {
request: bucketAPI.putNameFsUpload,
props: [
detailId,
`${pagePros.path}${name}`,
formatFormData({
blockSeq: item.index,
file: item.file,
position: item.position,
uploadId,
}),
{
// 取消请求设置
cancelToken: new CancelToken((c) => {
setPageProps((pre) => {
const length = _.findIndex(pre.upload.list, (items) => items.id === uid);
pre.upload.list[length].cancelReq = pre.upload.list[length].cancelReq.concat([c]);
});
}),
// 计算上传进度
onUploadProgress: (progressEvent) => {
setPageProps((pre) => {
const length = fineFileIndex(pre.upload.list, uid);
const progress = {
loaded: progressEvent.loaded || 0,
total: progressEvent.total || 0,
} as any;
pre.upload.list[length].progress[index] = progress;
});
},
},
],
};
});
return requestList;
};
处理上传文件
const handleFile = (e, files: FileList | File[]) => {
if (Array.isArray(files)) {
files.forEach((file) => {
const uid = getUid(64);
// 组装下载列表参数
const params: UploadList = {
id: uid,
name: `${pagePros.path}${file.name}`,
size: file.size,
status: 'waiting',
progress: [],
file,
cancelReq: [],
requestList: [],
doneIndex: 0,
};
// 更新下载列表
setPageProps((pre) => {
pre.upload.showText = false;
pre.upload.list = pre.upload.list.concat([params]);
});
// 入队
requestQueue.push(params);
if (requestQueue.length <= fileLimit) {
handle(file, uid);
}
});
}
};