最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 【文件上传那些事儿】- 05 断点续传

    正文概述 掘金(初心Yearth)   2021-02-13   556

    前文链接

    文件上传那些事儿】- 01 简单的拖拽上传和进度条

    【文件上传那些事儿】- 02 二进制级别的格式验证

    【文件上传那些事儿】- 03 两种计算 hash 的方式

    【文件上传那些事儿】- 04 切片上传和网格进度条

    V1.5:断点续传

    在前面四章循序渐进的迭代开发中,我们的上传 demo 已经初具规模,实现了简单的拖拽上传,二进制级别的格式验证,能够对大文件进行切片上传,接下来就是对切片上传的进一步优化,实现文件秒传和断点续传功能。

    其实这两个功能原理都非常简单,下面将分别介绍具体实现。

    秒传

    前文有提到,我们是通过 hash 来确定文件是否存在于服务器上的,那么在前端计算完 hash 之后,只要在上传之前将 hash 和 ext 传到后端进行查询即可知道文件是否已经存在于服务器中,如果存在,则直接前端提示文件秒传成功,否则正常上传即可。

    这里我们先定义后端接口,而这个接口,需要做什么呢?

    • 当文件存在的时候,返回 uploadedtrue 即可

    根据上述思路,很容易得出接口代码如下:

    router.post("/api/v1/checkchunks", async ctx => {
      const { hash, ext } = ctx.request.body;
      const filepath = path.resolve(uploadPath, `${hash}.${ext}`);
    
      let uploaded = false;
    
      if (fse.existsSync(filepath)) {
        uploaded = true;
      }
    
      ctx.body = {
        uploaded
      };
    });
    

    这样,就可以在前端通过判断 uploaded 的真假来确认文件是否秒传成功了:

    const handleFileUpload = async () => {
      /*...*/
      const res = await axios.post("/dev-api/checkchunks", {
        hash: fileHash,
        ext: getFileExtension((fileRef.value as File).name)
      });
    
      const uploaded = res.data.uploaded;
    
      if (uploaded) return alert("秒传成功");
    	/*...*/
    };
    

    断点续传

    相比于秒传功能,断点续传稍微要复杂一些,不过慢慢分析之后也能将逻辑梳理清晰,一步一步来实现这个功能。

    能够实现断点续传最关键的一点是:我们要知道后端有哪些残存的切片,从而在前端上传的时候,过滤掉这些切片。

    为了实现这一功能,可以稍微扩展一下 checkchunks 的功能,让其不止返回 uploaded,同时去读取 chunks 文件夹,获取其中的 chunkname

    const getUploadList = async chunkspath => {
      return fse.existsSync(chunkspath)
    		// 过滤掉隐藏文件
        ? (await fse.readdir(chunkspath)).filter(filename => filename !== ".")
        : [];
    };
    
    router.post("/api/v1/checkchunks", async ctx => {
      /*...*/
      let uploadedList = [];
    
      if (fse.existsSync(filepath)) {
        uploaded = true;
      } else {
        uploadedList = await getUploadList(chunkpath);
      }
    
      ctx.body = {
        uploaded,
        uploadedList
      };
    });
    

    这样,我们就能在前端获取到服务器上的残存切片了,在上传之前有几点需要注意:

    • 首先要过滤已经存在的切片
    • 将已经存在的切片对应的网格进度条设置为 100%

    上述二者皆可以通过 filter 来实现,唯一需要注意的是,一个是将存在的设置成 100%,而一个是将存在的过滤掉,条件是相反的:

    // 设置进度条
    chunks.value = fileChunks.map((c, i) => {
      const name = `${fileHash}-${i}`;
      return {
        name,
        index: +i,
        hash: fileHash,
        chunk: c.fileChunk,
        progress: uploadedList.includes(name) ? 100 : 0
      };
    });
    
    // 过滤服务器上残存的切片
    const requests = chunks.value
    									      .filter(({ name }) => !uploadedList.includes(name))
    									      .map/*...*/
    

    为了模拟网络不稳定的环境,我们先将切片上传到服务器上,随后随机删除一部分切片,然后再次上传文件(当然也可以写个 random 在上传过程中直接随机让一部分切片失败,这样可以省去手动去删掉切片的测试过程),效果如下:

    【文件上传那些事儿】- 05 断点续传

    可以看到,一开始残存切片的进度条展示是正确的,然而在进度条走了一部分之后,就停止了。再来到服务端查看确认:

    【文件上传那些事儿】- 05 断点续传

    文件是成功上传并且合并了的,那么问题就出在进度条上了,于是定位到网格进度条的位置:

    const requests =
     chunks.value
    .filter(({ name }) => !uploadedList.includes(name))
    .map(({ name, index, hash, chunk }: ChunkRequestType) => {
      console.log("[chunks]:", { name, index, hash, chunk });
      const formdata = new FormData();
      formdata.append("name", name);
      formdata.append("index", index);
      formdata.append("hash", hash);
      formdata.append("chunk", chunk);
      return form;
    })
    .map((form, idx) => {
      return axios.post("/dev-api/upload", form, {
        onUploadProgress: progress => {
          const { loaded, total } = progress;
          chunks.value[idx].progress = Number(
            ((loaded / total) * 100).toFixed(2)
          );
        }
      });
    });
    

    可以看到,在计算进度条的时候,使用的下标是数组的 index,而这个数字永远是从 0 开始往后数的,所以这里我们应该使用 chunkindex,稍微修正一下:

    const requests = 
    chunks.value
    .filter(({ name }) => !uploadedList.includes(name))
    .map(({ name, index, hash, chunk }: ChunkRequestType) => {
      console.log("[chunks]:", { name, index, hash, chunk });
      const formdata = new FormData();
      formdata.append("name", name);
      formdata.append("index", index);
      formdata.append("hash", hash);
      formdata.append("chunk", chunk);
    M return { formdata, index };
    })
    M.map(({ formdata, index }) => {
      return axios.post("/dev-api/upload", formdata, {
        onUploadProgress: progress => {
          const { loaded, total } = progress;
    M     chunks.value[index].progress = Number(
            ((loaded / total) * 100).toFixed(2)
          );
        }
      });
    });
    

    最终效果如下:

    【文件上传那些事儿】- 05 断点续传

    结束语

    今天的文章到这里就告一段落了,接下来还有什么值得注意的呢?

    • 并发控制:前面的切片上传是一股脑直接创建了所有的请求,虽然浏览器有请求限制,但过多的请求同时发出,也会对浏览器造成一定的压力,导致卡顿
    • 错误处理:如果切片在上传过程中失败,能够自动尝试重发,而不是导致整体的失败

    这些将在未来的文章中继续探讨迭代,那么新年第一天,新年新气象,祝好运!


    起源地下载网 » 【文件上传那些事儿】- 05 断点续传

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元