0%

antd+react实现断点续传

应用场景

文件上传之后,获得文件的唯一标识(MD5值),将文件切割成多个分块,根据文件标识查询文件未上传的分块,所有分块上传完之后,将分块进行合并。

实现原理

1、上传文件
1
2
3
4
5
const uploadProps = {
name: "file",
accept: ".zip",
maxCount: 1,
}
1
2
3
4
5
6
7
8
9
<Dragger {...uploadProps}>
<p className="ant-upload-drag-icon">
<InboxOutlined />
</p>
<p className="ant-upload-text">点击或将文件拖拽到这里上传</p>
<p className="ant-upload-hint">
支持文件扩展名:.zip
</p>
</Dragger>
2、文件预处理

在文件上传之前,beforeUpload里对文件进行切割预处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
// 兼容性的处理
const blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;
const chunkSize = 1024 * 1024 * 5; // 切片每次5M
const chunks = Math.ceil(file.size / chunkSize);
let currentChunk = 0; // 当前上传的chunk
const spark = new SparkMD5.ArrayBuffer();
// 对arrayBuffer数据进行md5加密,产生一个md5字符串
const chunkFileReader = new FileReader(); // 用于计算出每个chunkMd5
const params = { chunks: [], file: {}}; // 用于上传所有分片的md5信息
const arrayBufferDataT = []; // 用于存储每个chunk的arrayBuffer对象,用于分片上传使用
/* 文件合并所需参数 */
params.file.fileName = file.name; // 文件名
params.file.totalSize = file.size; // 文件总大小
params.file.fileType = file.type; // 文件类型
params.file.relativePath = file.name; // 相对路径
params.file.refProjectId = "dbxt-sf-gd-sz"; // 文件唯一标识

function loadNext()
{
const start = currentChunk * chunkSize;
const end = ((start + chunkSize) >= file.size) ? file.size : start + chunkSize;
chunkFileReader.readAsArrayBuffer(blobSlice.call(file, start, end));
}
loadNext();
let identifier;
chunkFileReader.onload = function(e)
{
// 对每一片分片进行md5加密
if (currentChunk === 0)
{
spark.append(e.target.result);
identifier = spark.end();
}
// 每一个分片需要包含的信息
const obj = {
totalSize: file.size,
relativePath: file.name,
chunkNumber: currentChunk + 1, // 块编号
fileName: file.name,
identifier, // 块编号
totalChunks: chunks, // 总分片数
chunkSize, // 块大小
currentChunkSize: ((currentChunk * chunkSize + chunkSize) >= file.size) ? file.size - currentChunk * chunkSize : chunkSize // 当前块大小
};
// 每一次分片onload,currentChunk都需要增加,以便来计算分片的次数
currentChunk += 1;
params.chunks.push(obj);

// 将每一块分片的arrayBuffer存储起来,用来partUpload
const tmp = {
chunk: obj.chunkNumber,
currentBuffer: e.target.result
};
arrayBufferDataT.push(tmp);
if (currentChunk < chunks)
{
// 当前切片总数没有达到总数时
loadNext();
// 计算预处理进度
setpreUploading(true);
setpreUploadPercent(Number((currentChunk / chunks * 100).toFixed(2)));
}
else
{
setchunksSize(chunks);
setpreUploadPercent(100);
setFileList([file]);
// 记录所有chunks的长度
params.file.identifier = identifier;
params.file.fileChunks = params.chunks.length;
// 表示预处理结束,将上传的参数,arrayBuffer的数据存储起来
setarrayBufferData(arrayBufferDataT);
setpreUploading(false);
setuploadParams(params);
}
};

chunkFileReader.onerror = function()
{
console.warn("oops, something went wrong.");
};
3、上传分块
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

try
{
// 判断是否上传
const data = await examChunk(chunks[0]);
let { uploadedChunks } = data || []; // 已上传文件
uploadedChunks = Array.from(new Set(uploadedChunks));
if (uploadedChunks.length === uploadParams.file.fileChunks || data.skipUpload) // 如果全部文件快已上传 则直接完成 实现秒传
{
setuploadPercent(100); // 文件上传进度
mergeFile();// 合并文件
}
else
{
setuploadPercent(Number((uploadedChunks.length / uploadParams.file.fileChunks * 100).toFixed(2)));
const uploadList = chunks.filter(item => { return uploadedChunks.indexOf(item.chunkNumber) < 0; });// 过滤出未上传分片
handlePartUpload(uploadList, uploadList.length); // 分片上传
}
}
catch (error)
{
console.log(error);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// 分片上传
const handlePartUpload = (uploadList, currentChunks) =>
{
let current = currentChunks;
uploadList.forEach(async(element) =>
{
const blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;
const { identifier, chunkNumber } = element;
const formData = new FormData();
// 新建一个Blob对象,将对应分片的arrayBuffer加入Blob中
const blob = new Blob([arrayBufferData[chunkNumber - 1].currentBuffer], { type: "application/octet-stream" });
// 将生成blob塞入到formdata中传入服务端
formData.append("upfile", blob, identifier);
Object.keys(element).forEach(el =>
{
formData.append(el, element[el]);
});
try
{
const uploadData = await upload(formData);
if (uploadData === 200)
{
current -= 1;
setuploadPercent(Number(((uploadParams.file.fileChunks - current) / uploadParams.file.fileChunks * 100).toFixed(2)));
if (current === 0)
{
mergeFile();
}
}
else
{
setUploadFailed(true);
setspinning(false);
}
}
catch (error)
{
setUploadFailed(true);
setspinning(false);
console.log(error);
}
});
};
4、合并文件块
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const mergeFile = async() =>
{
try
{
const mergeData = await merge(uploadParams.file);
if (mergeData.id)
{
// setUpfileId(mergeData.id);
// message.success('上传成功');
createData(mergeData.id);
setuploading(false);
setuploaded(true);
}
else
{
setspinning(false);
setUploadFailed(true);
}
}
catch (error)
{
setspinning(false);
setUploadFailed(true);
}
};
-------------The End-------------
坚持原创技术分享,您的支持将鼓励我继续创作!