# 大文件上传
# 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
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
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
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
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
← 使用animate动画 常见问题 →