react 上传文件分片及断点续传
前言
一直没有更新,是因为工作太忙了,没有时间去更新博客,在加上这几年从vue转到了react技术栈了,以后都将以react +arco.design+ ts来记录下可能平时不太常用到的功能,比如一些第三方插件的相关功能
html
<Upload
multiple
showUploadList={false}
beforeUpload={(file)=>{beforeUpload(file,'md5')}}
onChange={(files, file) => {
}}
customRequest={option => {
handleFileChange(option);
}}
>
<Space size="large">
<Button type="primary"> <IconUpload></IconUpload>点击上传(断点续传)</Button>
</Space>
</Upload>
ts,代码只上了逻辑代码,至于里面setUploadId这样开头的变量,都需要在data里面定义就行 const [uploadId, setUploadId] = useState('');类似这样就行,我这里是是以弹窗的形式展示的文件是否上传成功
import SparkMD5 from 'spark-md5'
const beforeUpload=(file,type)=> {
let fileMaxSize=5;
const maxSize = fileMaxSize * 1024 * 1024;
const maxNameLen = 64;
if(type == 'upload'){
if (file.size > maxSize) {
Message.warning(`${langT('文件大小不能超过')}${fileMaxSize}Mb,超过将用断点续传上传!`);
setIsSurportUpload(true);
return false;
}
setIsSurportUpload(false);
return true;
}else{
if (file.size < maxSize) {
setIsSurportUpload(false);
return true;
}
}
};
// 计算文件的 MD5 值
const calculateMD5 = (file: any) => {
return new Promise((resolve) => {
const spark = new SparkMD5.ArrayBuffer();
const fileReader = new FileReader();
const chunkSize = 5 * 1024 * 1024;
let currentChunk = 0;
fileReader.onload = function (e: any) {
spark.append(e.target.result);
currentChunk++;
if (currentChunk < chunks) {
loadNext();
} else {
const result = spark.end();
resolve(result);
}
};
// 加载下一个分片
function loadNext() {
const start = currentChunk * chunkSize;
const end = Math.min(file.size, start + chunkSize);
const buffer = file.slice ? file.slice(start, end) : file.webkitSlice(start, end); // 使用 slice 方法
fileReader.readAsArrayBuffer(buffer);
}
const chunks = Math.ceil(file.size / chunkSize); // 文件划分成的分片数量
loadNext(); // 开始加载第一个分片
});
}
// 将文件划分成多个分片
const chunkFile = (file: any, chunkSize: any) => {
const chunks = Math.ceil(file.size / chunkSize); // 文件划分成的分片数量
const chunksList = [];
let currentChunk = 0;
while (currentChunk < chunks) {
const start = currentChunk * chunkSize;
const end = Math.min(file.size, start + chunkSize);
const chunk = file.slice ? file.slice(start, end) : file.webkitSlice(start, end); // 使用 slice 方法
chunksList.push(chunk); // 将分片添加到列表中
currentChunk++;
}
return chunksList; // 返回分片列表
}
const handleFileChange = async (option) => {
const { onProgress, onError, onSuccess, file } = option;
//第一个接口
const initResponse = await axios.post('_api/upload', { filename: file.name,filePath: filePath[filePath.length - 1]?.path });
setUploadFialFile([]);//清空上传失败的文件
console.log('option/file',file);
let arr = file.type.split('/');
let obj = {
fileExtend: arr[1],
fileName: file.name,
fileSize: file.size,
id: guid(),
progress:0,
state:0,//上传开始
}
setUploadListModalVisible(true);
upDatasRef.current.push(obj);
// uploadListData.push(obj);
setUploadListData([...upDatasRef.current]);
setUploading(true); // 开始文件上传
const md5 = await calculateMD5(file); // 计算文件的 MD5 值
console.log('md5',md5)
md5Ref.current[obj.id] = md5; // 保存 MD5 值到引用
// 将文件划分成多个分片并保存到引用对象中 1MB来分割1 * 1024 * 1024
const chunksList: any = chunkFile(file,1 * 1024 * 1024);
console.log('分隔后的文件', chunksList)
chunkRefs.current[obj.id] = chunksList.map((chunk: any, index: any) => {
const formData = new FormData();
formData.append('path', '/dir1');
formData.append("file", chunk);
formData.append("fileName", file.name);
formData.append("uploadId", uploadId);
formData.append("partNumber", index+1);//.toString()
// formData.append("md5", md5Ref.current[obj.id]); // 添加 MD5 参数
console.log('formData',formData)
return formData;
});
// 定义递归函数用于逐个上传分片
const uploadChunk = async (index: any) => {
// debugger
const chunkStatuses = new Array(chunkRefs.current[obj.id]).fill(false);
let uploadedChunks = [];
if (chunkRefs.current[obj.id].length > 0){
let status = chunkStatuses.every(status => status);
if (index >= chunkRefs.current[obj.id].length ) {//每个分片都上传了
if(chunkStatuses.every(status => status)){//每个分片都上传了成功了
// 所有分片上传完成
// setUploadListData([{ ...obj, state:1 }]);//上传完成
obj.state = 1;
setUploadListData([...upDatasRef.current]);
Message.success(`${obj.fileName}上传成功!`);
setUploading(false); // 文件上传完成,修改上传状态
return;
}else{
return;
}
}
try {
const chunkResponse = await partFile(chunkRefs.current[obj.id][index]);//我这是接口地址,可以换成自己的接口
console.log('chunkResponse',chunkResponse)
if(chunkResponse.data.success){
// 更新进度条的值
const newProgress = Math.ceil(((index + 1) / chunkRefs.current[obj.id].length) * 100);
chunkStatuses[index] = true;
obj.progress = newProgress;
setUploadListData([...upDatasRef.current]);
console.log('newProgress', upDatasRef.current,newProgress)
}else{//上传失败-发起重新上传
obj.progress = 0;
Message.error(`${chunkResponse.data.devMsg}`);
let fialFile={
file:chunkRefs.current[obj.id][index],
index:index
};
setUploadFialFile([...uploadFialFile,fialFile]);
}
// 递归调用上传下一个分片
await uploadChunk(index + 1);
return;
} catch (error) {
console.error(`分片 ${index + 1} 上传失败`, error);
Message.error( `${obj.fileName}上传失败!`);
setChunks([]);
setFailedChunks([]);
setUploading(false); // 文件上传失败,修改上传状态
//重新上传按钮
return;
}
}
};
// 开始递归上传第一个分片
await uploadChunk(0);
};
如果有不明白的可以私信我,有时间看到会回复,完善功能