上一篇文章
【文件上传系列】No.2 秒传(原生前端 + Node 后端)
断点续传效果展示

准备工作:暂停上传
前端点击暂停按钮,之后把所有上传的请求取消掉,需要使用 xhr.abort() 这个方法,点我跳转到 MDN 。
写个按钮,监听点击事件
html
<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,暂停可以,顺便把页面的按钮显示和隐藏写一下
javascript
(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');
});
}
接下来开始断点续传!
思路:断点续传
- 我们在上传之前都要去验证一下,服务器中
是否存在已经上传的文件块
,然后后端返回已经上传的文件块数组 index,前端处理的时候过滤掉已经上传的块即可。
实现断点续传
前端:过滤已上传的 chunk 以及进度条改进
断点续传需要验证的地方大概有这几个
- 刚开始上传
- 暂停之后点击继续上传
因为验证的函数是通用的,所以把验证的逻辑封装成函数,大概的逻辑如下图:

javascript
/**
* - 功能:处理验证 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('文件已秒传');
}
}
然后在上传的函数里把已上传的过滤掉

后端:验证接口

整理代码如下:
javascript
/**
* 功能:验证服务器中是否存在文件
* - 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);
}
}
这样一个断点续传的功能就做好啦~
