#yyds干货盘点#web端断点续传的思路

奋斗吧
奋斗吧
擅长邻域:未填写

标签: #yyds干货盘点#web端断点续传的思路 JavaScript博客 51CTO博客

2023-04-22 18:24:02 249浏览

#yyds干货盘点#web端断点续传的思路,讲断点续传前,咱们先讲讲大文件上传。大文件上传,可能会出现,上传时间过长,接口限制了文件大小。所以,大文件直接上传,也很不友好,一般采用分片上传的方式去上传。而blob提供了slice方法, file继承了blob自然也能使用slice去进行分片处理。处理流程:前端对大文件进行分片,分片名采用文件hash后续会讲加下标为了防止完全占用tcp资源,我这里是采用4个4个上传在最后,再发送一个

讲断点续传前,咱们先讲讲大文件上传。大文件上传,可能会出现,上传时间过长,接口限制了文件大小。所以,大文件直接上传,也很不友好,一般采用分片上传的方式去上传。而blob提供了slice方法, file继承了blob自然也能使用slice去进行分片处理。

处理流程:

  • 前端对大文件进行分片,分片名采用文件hash后续会讲下标
  • 为了防止完全占用tcp资源,我这里是采用4个4个上传
  • 在最后,再发送一个合并请求
  • 服务端根据请求,对前面的分片内容进行合并成一个整体文件,然后删除分片
const handleUpload = () => {
    chunks = [];

    const file: File = files.current[dataIndex];
    // 获取对应文件file
    let start = 0, end = BIG_FILE_SIZE;
    
    // const BIG_FILE_SIZE = 1024 * 1024 * 2;
    while(true) {
        const part: Blob = file.slice(start, end);
        if (part.size) {
            chunks.push(part);
        } else {
            break;
        }
        start += BIG_FILE_SIZE;
        end += BIG_FILE_SIZE;
    };
    
    // worker!.postMessage({ chunkList: chunks });
    // 利用webworker获取hash,后面会讲
    return;
};
const partUpload = async (start: number) => {
    const uploadArr = [];
    let restReq = MAX_FILE_TCP;
    // MAX_FILE_TCP = 4;同时发起4个连接
    let index = start;
    while (restReq) {
        if (index >= chunkCount) {
        // chunkCount是chunk的length,即多少个片段
            break;
        };
        
        // const blobPart = `${hash}-${index}`;
        // if (hashData[hash] && hashData[hash][blobPart]) 
        // {
        //     index++;
        //     continue;
        // };
        // 注释部分是,断点续传部分代码,可跳过
        
        const formData = new FormData();
        formData.append('file', chunks[index]);
        formData.append('xx', xx);
        
        const uploadFunc = () => {
            return new Promise(async (res) => {
                const { code } = await uploadPart(formData);
                res(code);
            });
        };
        
        uploadArr.push(uploadFunc);
        index++;
        restReq--;
    };
    const result = await Promise.all(uploadArr.map(v => v()));
    
    result.map((v) => {
      if (v === 0) {
        console.log('上传片段成功');
      } else {
        throw new Error('上传失败');
      }
      return null;
    });
    
    if (index < chunkCount) {
      partUpload(index);
    } else {
        const params = {
            // sth.
        };
        const {code} = await partMerge(params);
        // 发送合并请求
        // todo code sth.
    }
};

服务端的话,我这边是把文件根据对应的hash和下标进行命名,即static/hash/hash-i。利用fs.rename去修改文件&路径, 通过pipe合并文件。

router.post('/upload_part', (req, res) => {
    try {
        const { hash, index } = req.body;
        const { file } = req;
        const sourcePath = path.join(__dirname, file.path);
        const destPath = path.join(__dirname, `xxxx/${hash}/${hash}-${index}`);
        fs.renameSync(sourcePath, destPath);
        return res.json({code: 0});
    } catch(e) {
        return res.json({code: 1, msg: e});
    }
});

router.post('/merge_part', (req, res) => {
    try {
        const destPath = 'xxx/yyy/zzz/a.png';
        const writePath = fs.createWriteStream(destPath);
        // 最终合并结果存储在哪
        const fileMerge = (i: number) => {
            const blobPath = `xxx/part/${hash}/${hash}-${i}`;
            const blobFile = fs.createReadStream(blobPath);
            blobFile.on("end", async (err) => {
                if (err) {
                    return res.json({code: 1, msg: err});
                };
                fs.unlink(blobPath);
                // 删除片段
                if (i + 1 < chunkCount) {
                    fileMerge(i + 1);
                } else {
                    fs.rmdirSync(`xxx/part/${hash}`);
                    // 删除文件夹
                    // 数据库操作 todo
                    return res.json({ code: 0 });
                }
            });
            blobFile.pipe(writeStream, { end: false });
        };
        fileMerge(0);
    } catch(e) {
        return res.json({code: 1, msg: e});
    }
});

断点续传

大文件分片上传,如果客户端发生异常中断了上传过程,那么下次重新上传的时候,比较友好的做法应该是跳过那些已经上传的片段。 那么问题也就是,怎么跳过那些文件?刚才前面的代码,也显示了,其实就是通过${hash}-${i}设置文件名,然后保存已经上传成功的片段数据,在文件分片上传的时候,跳过已经上传过的片段,就是断点续传辽。 对于这类数据存储,一般都是两个方法:

  • 前端存储
  • 服务端存储

前端存储的话,一般就是用localStorage,不太推荐使用。因为用户如果清了缓存,或者换了设备登陆,就无法生效。

服务端的话,就返回对应的已成功上传的片段名。因为对应userId下的文件hash-i,是唯一的。node这里就采用readdirSync/readdir去读取文件名辽。

前端这里就是前面提到过的部分。

const blobPart = `${hash}-${index}`;
if (hashData[hash] && hashData[hash][blobPart]) 
{
    // hashData是服务端传回来的数据,判断片段是否存在,存在就跳过
    // 具体可以看看前面
    index++;
    continue;
};

好博客就要一起分享哦!分享海报

此处可发布评论

评论(0展开评论

暂无评论,快来写一下吧

展开评论

您可能感兴趣的博客

客服QQ 1913284695