10.文件切片
leezozz 2/13/2023 笔记
# 实现文件切片上传,断点续传的功能
# 思路:
使用文件File (opens new window)继承自Blob的方法,核心是Blob.prototype.slice方法,和数组的slice方法相似,文件的slice方法可以放回原文件的某个切片
预先定义好单个切片大小,将文件切分为一个个切片,然后借助http的可并发性,同时上传多个切片。这样从原本传一个大文件,变成了并发传多个小的文件切片,可以大大减少上传时间
另外由于是并发,传输到服务端的顺序可能会发上变化,因此我们还需要给每个切片记录顺序
# 实现文件切片
- 使用Blob对象的slice方法,将文件进行切片。
- 对每一个切片生成md5标识。
# 使用md5进行标识:
- 在切片时对每一个切片进行md5标识。
- 在上传时使用每个切片的md5标识来确保顺序。
# 文件上传中断:
- 判断文件切片是否已经上传,通过md5标识。
- 如果已经上传,跳过这个文件切片。
# 合并文件:
- 按照文件切片的md5标识顺序进行合并。
- 使用Blob对象的concat方法合并文件切片。
# 实现:
# 分割切片
// 上传切片
createFileChunk(file, size = SIZE) {
const fileChunkList = [];
let cur = 0;
while (cur < file.size) {
fileChunkList.push({ file: file.slice(cur, cur + size) });
cur += size;
}
return fileChunkList;
},
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# 断点续传
断点续传的原理在于前端/服务端需要记住已上传的切片,这样下次上传就可以跳过之前已上传的部分,有两种方案实现记忆的功能
- 前端使用 localStorage 记录已上传的切片 hash
- 服务端保存已上传的切片 hash,前端每次上传前向服务端获取已上传的切片
第一种是前端的解决方案,第二种是服务端,而前端方案有一个缺陷,如果换了个浏览器就失去了记忆的效果,所以这里选后者
简单介绍一下文件秒传
所谓的文件秒传,即在服务端已经存在了上传的资源,所以当用户再次上传时会直接提示上传成功
文件秒传需要依赖上一步生成的 hash,即在上传前,先计算出文件 hash,并把 hash 发送给服务端进行验证,由于 hash 的唯一性,所以一旦服务端能找到 hash 相同的文件,则直接返回上传成功的信息即可
# md5
关于md5,md5是一种常用的哈希算法,可以将任意长度的数据映射为固定长度的唯一值,即数字指纹。在文件上传的场景中,可以使用md5算法对文件进行校验,以保证文件上传完整。
// (以下代码并未测试,仅参考!!!)
// 实现文件切片
let file = event.target.files[0];
let chunkSize = 2 * 1024 * 1024; // 2MB
let chunks = Math.ceil(file.size / chunkSize);
let currentChunk = 0;
function upload() {
let start = currentChunk * chunkSize;
let end = start + chunkSize >= file.size ? file.size : start + chunkSize;
let blob = file.slice(start, end);
// 使用md5进行标识
let md5 = getMd5(blob);
// 判断是否已经上传过
if (checkMd5(md5)) {
currentChunk++;
if (currentChunk < chunks) {
upload();
}
return;
}
// 上传文件切片
let formData = new FormData();
formData.append("file", blob);
formData.append("md5", md5);
axios.post("/upload", formData).then((res) => {
if (res.data.success) {
currentChunk++;
if (currentChunk < chunks) {
upload();
}
}
});
}
upload();
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
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
// 上传中断时的代码
let file = event.target.files[0];
let chunkSize = 2 * 1024 * 1024; // 2MB
let chunks = Math.ceil(file.size / chunkSize);
let currentChunk = 0;
function upload() {
let start = currentChunk * chunkSize;
let end = start + chunkSize >= file.size ? file.size : start + chunkSize;
let blob = file.slice(start, end);
// 使用md5进行标识
let md5 = getMd5(blob);
// 判断是否已经上传过
if (checkMd5(md5)) {
currentChunk++;
if (currentChunk < chunks) {
upload();
}
return;
}
// 上传文件切片
let formData = new FormData();
formData.append("file", blob);
formData.append("md5", md5);
axios.post("/upload", formData).then((res) => {
if (res.data.success) {
currentChunk++;
if (currentChunk < chunks) {
upload();
}
}
}).catch((error) => {
// 上传失败,进行重试
setTimeout(upload, 1000);
});
}
upload();
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
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