10.文件切片

2/13/2023 笔记

# 实现文件切片上传,断点续传的功能

# 思路:

使用文件File (opens new window)继承自Blob的方法,核心是Blob.prototype.slice方法,和数组的slice方法相似,文件的slice方法可以放回原文件的某个切片
预先定义好单个切片大小,将文件切分为一个个切片,然后借助http的可并发性,同时上传多个切片。这样从原本传一个大文件,变成了并发传多个小的文件切片,可以大大减少上传时间
另外由于是并发,传输到服务端的顺序可能会发上变化,因此我们还需要给每个切片记录顺序

# 实现文件切片

  1. 使用Blob对象的slice方法,将文件进行切片。
  2. 对每一个切片生成md5标识。

# 使用md5进行标识:

  1. 在切片时对每一个切片进行md5标识。
  2. 在上传时使用每个切片的md5标识来确保顺序。

# 文件上传中断:

  1. 判断文件切片是否已经上传,通过md5标识。
  2. 如果已经上传,跳过这个文件切片。

# 合并文件:

  1. 按照文件切片的md5标识顺序进行合并。
  2. 使用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

# 断点续传

断点续传的原理在于前端/服务端需要记住已上传的切片,这样下次上传就可以跳过之前已上传的部分,有两种方案实现记忆的功能

  • 前端使用 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
// 上传中断时的代码
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/14/2023, 2:30:01 AM
강남역 4번 출구
Plastic / Fallin` Dild