# 上一篇文章

【文件上传系列】No.2 秒传(原生前端 + Node 后端)


断点续传效果展示

请添加图片描述

准备工作:暂停上传

前端点击暂停按钮,之后把所有上传的请求取消掉,需要使用 xhr.abort() 这个方法,点我跳转到 MDN

写个按钮,监听点击事件

1
2
3
4
5
6
7
8
9
10
11
12
13
<body>
<button id="pauseButton">暂停</button>
</body>

<script>
/**
* 功能:暂停按钮
*/
document.getElementById('pauseButton').addEventListener('click', () => {
console.log(xhrList);
xhrList.forEach((xhr) => xhr.abort());
});
</script>

这里的 xhrList 是我们在上传的时候传递给 request

在这里插入图片描述

来测试一下暂停后是否能拿到有效的 xhrList

在这里插入图片描述
ok,暂停可以,顺便把页面的按钮显示和隐藏写一下

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
(function () {
hiddenButton('resumeButton');
hiddenButton('pauseButton');
})();

function hiddenButton(id) {
document.getElementById(id).setAttribute('style', 'display: none');
}
function displayButotn(id) {
document.getElementById(id).setAttribute('style', 'display: inline-block');
}
/**
* 功能:点击上传之后处理一下按钮的显示隐藏
* - 以及把继续上传所需的变量放到 Options 里,避免把变量放到全局了。
*/

function handleButton(option) {
hiddenButton('uploadButton');
displayButotn('pauseButton');
/**
* 功能:暂停按钮
*/
document.getElementById('pauseButton').addEventListener('click', () => {
xhrList.forEach((xhr) => xhr.abort());
hiddenButton('pauseButton');
displayButotn('resumeButton');
});
/**
* 功能:继续按钮
* - 调用验证端口 verify
*/
document.getElementById('resumeButton').addEventListener('click', async () => {
handleVerify(option);
hiddenButton('resumeButton');
displayButotn('pauseButton');
});
}

接下来开始断点续传!

思路:断点续传

  1. 我们在上传之前都要去验证一下,服务器中是否存在已经上传的文件块,然后后端返回已经上传的文件块数组 index,前端处理的时候过滤掉已经上传的块即可。

实现断点续传

前端:过滤已上传的 chunk 以及进度条改进

断点续传需要验证的地方大概有这几个

  • 刚开始上传
  • 暂停之后点击继续上传

因为验证的函数是通用的,所以把验证的逻辑封装成函数,大概的逻辑如下图:

在这里插入图片描述

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
/**
* - 功能:处理验证 hash
* - 页面刷新 => 上传 => 验证 => 返回服务器已上传的chunkList => 过滤
* - 暂停 => 继续上传 => 验证 => 返回服务器已上传的chunkList => 过滤
* - 后半部分的逻辑是一样的,所以功能合并
*/
async function handleVerify(option) {
const { fileHash, fileName, fileChunkList } = option;
// 根据这个值把进度条修改一下
const { code, message, hadUploadedChunksList } = await handleVerifyHash(fileHash, fileName);
const hadUploadedChunksListMap = {};
hadUploadedChunksList.forEach((item) => (hadUploadedChunksListMap[item.split('_')[1]] = true));
if (code === 0 || code === 1) {
const hanldleData = fileChunkList.map(({ file }, index) => {
return {
chunk: file,
index,
};
});
await uploadChunks(hanldleData, fileName, fileHash, hadUploadedChunksListMap);
}
if (code === 2) {
alert('文件已秒传');
}
}

然后在上传的函数里把已上传的过滤掉

在这里插入图片描述

后端:验证接口

在这里插入图片描述

整理代码如下:

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
/**
* 功能:验证服务器中是否存在文件
* - 1. 主要是拼接的任务
* - 2. ext 的值前面是有 . 的,注意一下。我之前合并好的文件 xxx..mkv 有两个点...
* - 导致 fse.existsSync 怎么都找不到,哭
* - 3. 返回已经上传的 chunkList
* - 这里定义一下状态码吧:
* - 0 为从未上传过
* - 1 为服务器上传过部分
* - 2 为完全上传过,直接妙传
* @param {*} req
* @param {*} res
* @param {*} MERGE_DIR
*/
async handleVerify(req, res, MERGE_DIR, UPLOAD_DIR) {
try {
const postData = await handlePostData(req);
const { fileHash, fileName } = postData;
const ext = path.extname(fileName);
const willCheckMergedName = `${fileHash}${ext}`;
const willCheckPath = path.resolve(MERGE_DIR, willCheckMergedName);

// 上传文件的 chunk
const chunkPathOfFile = path.resolve(UPLOAD_DIR, fileHash);
// 这里如果直接检测文件路径的话,可能因为没有这个路径而报错,所以先检测路径是否存在
const hadUploadedChunksList = (await fse.existsSync(chunkPathOfFile))
? await fse.readdir(chunkPathOfFile)
: [];
console.log('hadUploadedChunksList:>>', hadUploadedChunksList);
if (fse.existsSync(willCheckPath)) {
res.end(
JSON.stringify({
code: 2,
message: 'existed',
hadUploadedChunksList,
})
);
} else if (fse.existsSync(chunkPathOfFile)) {
res.end(
JSON.stringify({
code: 1,
message: 'not all existed',
hadUploadedChunksList,
})
);
} else {
res.end(
JSON.stringify({
code: 0,
message: 'no exist',
hadUploadedChunksList,
})
);
}
} catch (err) {
console.log(err);
}
}

这样一个断点续传的功能就做好啦~

请添加图片描述

参考文章

  1. 字节跳动面试官:请你实现一个大文件上传和断点续传