# 大文件上传

# 1、前端实现

后端需要提供三个接口:

// 检查已上传的切片
export const fileCheck = (data: any) => {
  return axios.request({
    method: 'post',
    url: '/client_api/user/files/check',
    data,
  });
};

// 上传文件切片
export const fileUploads = (data: any) => {
  return axios.request({
    method: 'post',
    url: '/upload_api/user/files/uploads',
    data,
  });
};

// 合并文件切片
export const fileMerge = (data: any) => {
  return axios.request({
    method: 'post',
    url: '/client_api/user/files/merge',
    data,
  });
};
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

封装分片公共方法:

npm i spark-md5
npm i hash-wasm
1
2
import SparkMD5 from 'spark-md5';
import { createMD5 } from 'hash-wasm';

/**
 * 大文件分片
 * @param file 文件
 * @param chunkSize 分片大小
 * @returns Blob[] 分片列表
 */
export function createFileChunks(file: File | Blob, chunkSize: number) {
  const chunks: Blob[] = [];
  let currentChunk = 0;
  while (currentChunk < file.size) {
    const chunk = file.slice(currentChunk, currentChunk + chunkSize);
    chunks.push(chunk);
    currentChunk += chunkSize;
  }
  return chunks;
}

/**
 * 分片计算文件哈希值
 * @param fileChunks 分片列表
 * @returns
 */
export async function calculateHash(fileChunks: Array<Blob>) {
  if (
    typeof WebAssembly === 'object' &&
    typeof WebAssembly.instantiate === 'function'
  ) {
    const maker = await createMD5();
    for (let i = 0; i < fileChunks.length; i++) {
      const chunk = await fileChunks[i].arrayBuffer();
      maker.update(new Uint8Array(chunk));
    }
    return maker.digest();
  } else {
    const spark = new SparkMD5.ArrayBuffer();
    for (let i = 0; i < fileChunks.length; i++) {
      const chunk = await fileChunks[i].arrayBuffer();
      spark.append(chunk);
    }
    return spark.end();
  }
}
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

上传示例:

// 选择文件上传
const onFileUpload = async (evt: any) => {
  console.log(new Date().toLocaleString() + ':开始上传');
  const file: File = evt.target.files[0];
  const chunkSize = 10 * 1024 * 1024; // 10MB
  const fileChunks = createFileChunks(file, chunkSize); // 文件切片
  console.log('计算md5中...');
  const fileHash = await calculateHash(fileChunks); // 获取文件哈希(当文件超过GB读取会非常慢)
  console.log(new Date().toLocaleString() + ':' + fileHash);

  let uploadedChunks: number[] = [];
  const { data } = await fileCheck({ fileHash }); // 检查已上传的分片
  if (data) {
    uploadedChunks = data;
  }

  const uploadPromises = fileChunks.map(async (chunk, index) => {
    if (uploadedChunks.includes(index)) {
      return Promise.resolve(); // 跳过已上传的切片
    }
    const formData = new FormData();
    formData.append('file', chunk);
    formData.append('index', index.toString());
    formData.append('fileHash', fileHash);
    await fileUploads(formData); // 开始上传
  });

  await Promise.all(uploadPromises);
  const fileName = file.name;
  const chunkCount = fileChunks.length;
  await fileMerge({ fileName, chunkCount, fileHash }); // 合并文件分片
  console.log(new Date().toLocaleString() + ':上传完成');
};
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
上次更新: 7/26/2024, 6:59:50 PM