问题预设
网络断开之后,之前上传的部分没了?
传着传着,网络波动了,结果没有了。
关机后可不可以接着传,怎么做到?
步骤
- 前端切片 chunk 5MB
- 将切片传递给后端,切片要去名:hash、index
- 后端组合切片
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
| <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>大文件上传</title>
</head>
<body>
<input type="file" id="file" multiple>
<button id="upload">上传</button>
</body>
<script>
const CHUNK_SIZE = 5 * 1024 * 1024; // 5MB
function uploadFile() {
const file = document.getElementById('file').files[0];
console.log('上传文件', file.name)
if (!file) {
return
}
const totalSize = file.size
const totalChunks = Math.ceil(totalSize / CHUNK_SIZE)
let currentChunk = 0
const chunks = []
function uploadChunk() {
console.log('上传第', currentChunk, '块')
if (currentChunk >= totalChunks) {
console.log('上传完成')
return
}
const start = currentChunk * CHUNK_SIZE
const end = Math.min(totalSize, start + CHUNK_SIZE)
const chunk = file.slice(start, end)
const formData = new FormData()
formData.append('file', chunk)
formData.append('index', currentChunk)
formData.append('totalChunks', totalChunks)
formData.append('filename', file.name)
// fetch('/api/upload', {
// method: 'POST',
// body: formData
// }).then(res => {
// if (res.ok) {
// currentChunk++;
// uploadChunk() // 递归上传下一块
// } else {
// throw new Error('上传失败')
// }
// }).catch(err => {
// console.error(err)
// });
currentChunk++;
uploadChunk() // 递归上传下一块
};
// 开始上传
uploadChunk();
};
document.getElementById('upload').addEventListener('click', uploadFile);
</script>
</html>
|
上传前对文件进行压缩,减少传输的数据量。
canvas
或第三方库压缩图片、pako
压缩其他文件类型。
使用Promise.all
并发上传多个分片。
注意控制并发数,避免请求过多。
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
| function uploadFile(file) {
const chunkSize = 5 * 1024 * 1024; // 5MB
const chunks = Math.ceil(file.size / chunkSize);
const concurrency = 3; // 并发数
let currentChunk = 0;
function uploadChunk(index) {
const offset = index * chunkSize;
const chunk = file.slice(offset, offset + chunkSize);
const formData = new FormData();
formData.append('file', chunk);
formData.append('offset', offset);
formData.append('totalSize', file.size);
return fetch('/upload', {
method: 'POST',
body: formData
});
}
function uploadNextChunk() {
if (currentChunk >= chunks) {
console.log('Upload complete');
return;
}
const promises = [];
for (let i = 0; i < concurrency && currentChunk < chunks; i++) {
promises.push(uploadChunk(currentChunk));
currentChunk++;
}
Promise.all(promises).then(() => {
uploadNextChunk();
});
}
uploadNextChunk();
}
|
websocket
实时通知上传情况,以及请求序列的控制。
通过XMLHttpRequest
或fetch
的 API 显示上传进度,提升用户体验。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| function uploadFile(file) {
const xhr = new XMLHttpRequest();
xhr.open('POST', '/upload', true);
xhr.upload.onprogress = (event) => {
if (event.lengthComputable) {
const percentComplete = (event.loaded / event.total) * 100;
console.log(`Upload progress: ${percentComplete}%`);
}
};
xhr.onload = () => {
if (xhr.status === 200) {
console.log('Upload complete');
}
};
const formData = new FormData();
formData.append('file', file);
xhr.send(formData);
}
|
尝试使用web-worker
,实现多线程切片,处理完之后交给主进程发送
将 Blob
存储到IndexedDB
,下次打开浏览器后嗅探一下是否存在未完成的切片,如果有就尝试继续上传。
记录已上传的分片信息。
上传前检查服务器上已上传的分片,跳过已上传部分。
将大文件分成多个小块下载,减少单次请求的压力,并支持断点续传。
步骤
- 服务器将文件分片,前端通过多个请求下载分片。
- 使用
Range
请求头指定下载范围。 - 前端将分片合并为完整文件。
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
| async function downloadFile(url, fileName, chunkSize = 5 * 1024 * 1024) {
let offset = 0;
const chunks = [];
while (true) {
const end = offset + chunkSize - 1;
const headers = { Range: `bytes=${offset}-${end}` };
const response = await fetch(url, { headers });
if (response.status === 206) { // 206 Partial Content
const blob = await response.blob();
chunks.push(blob);
offset += chunkSize;
} else if (response.status === 200) {
// 如果服务器不支持分片下载,直接下载整个文件
const blob = await response.blob();
chunks.push(blob);
break;
} else {
throw new Error('Failed to download file');
}
}
// 合并分片
const fullBlob = new Blob(chunks);
const link = document.createElement('a');
link.href = URL.createObjectURL(fullBlob);
link.download = fileName;
link.click();
URL.revokeObjectURL(link.href);
}
// 使用
downloadFile('https://example.com/large-file.zip', 'large-file.zip');
|
使用流式 API(如 ReadableStream
)逐步下载文件,避免内存占用过高。
实现步骤:
- 使用
fetch
获取响应流。 - 通过
ReadableStream
逐步读取数据并写入文件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| async function streamDownload(url, fileName) {
const response = await fetch(url);
const reader = response.body.getReader();
const chunks = [];
while (true) {
const { done, value } = await reader.read();
if (done) break;
chunks.push(value);
}
// 合并数据
const fullBlob = new Blob(chunks);
const link = document.createElement('a');
link.href = URL.createObjectURL(fullBlob);
link.download = fileName;
link.click();
URL.revokeObjectURL(link.href);
}
// 使用
streamDownload('https://example.com/large-file.zip', 'large-file.zip');
|
在网络中断后,从中断处继续下载,避免重新下载。
实现步骤:
- 记录已下载的字节范围。
- 使用
Range
请求头从断点处继续下载。
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
| async function resumeDownload(url, fileName, chunkSize = 5 * 1024 * 1024) {
let offset = 0;
const chunks = [];
// 检查本地是否有部分下载的文件
const savedBlob = localStorage.getItem(fileName);
if (savedBlob) {
const blob = new Blob([savedBlob]);
offset = blob.size;
chunks.push(blob);
}
while (true) {
const end = offset + chunkSize - 1;
const headers = { Range: `bytes=${offset}-${end}` };
const response = await fetch(url, { headers });
if (response.status === 206) { // 206 Partial Content
const blob = await response.blob();
chunks.push(blob);
offset += chunkSize;
// 保存已下载的部分到本地
const fullBlob = new Blob(chunks);
localStorage.setItem(fileName, await fullBlob.text());
} else if (response.status === 200) {
// 如果服务器不支持分片下载,直接下载整个文件
const blob = await response.blob();
chunks.push(blob);
break;
} else {
throw new Error('Failed to download file');
}
}
// 合并分片
const fullBlob = new Blob(chunks);
const link = document.createElement('a');
link.href = URL.createObjectURL(fullBlob);
link.download = fileName;
link.click();
URL.revokeObjectURL(link.href);
localStorage.removeItem(fileName);
}
// 使用
resumeDownload('https://example.com/large-file.zip', 'large-file.zip');
|
- Service Worker 缓存:通过 Service Worker 缓存大文件,提升后续加载速度。。
- 显示下载进度:提升用户体验。