手摸手,带你完成大文件分片下载

·  阅读 2671
手摸手,带你完成大文件分片下载

前言

之前看到,一篇大文件上传的文章,感觉上传里面的细节考虑还是很多的。

字节跳动面试官:请你实现一个大文件上传和断点续传 - 掘金 (juejin.cn)

然后最近就在钻研一下文件下载的功能

描述的不好还请各位大佬见谅,萌新第一次写文章。😁

项目地址:

react 及乐1997/react-demo - 码云 - 开源中国 (gitee.com)

node big-files: nodejs的大文件分片上传下载以及秒传 (gitee.com)

整体思路

  1. 获取要下载文件的大小,然后除以分片的大小,明确要发送几次网络请求
  2. 后端获取请求头里面的range字段
  3. 前端把请求回来的切片做一个合并 然后创建一个URL.createObjectURL来下载

后端 express

我是用了两个接口,一个是获取文件大小的,一个是下载的

router.get('/size/:name',(req,res)=>{
  //获取要下载文件的路径
  let filePath = path.resolve(__dirname,distPath,req.params.name)
  //console.log(filePath)
  //获取文件的大小
  let size = fs.statSync(filePath).size || null
  console.log('下载文件大小' + size)
  res.send({
    msg:'ok',
    data:size.toString()
  })

})
复制代码

下载分为三种情况,一种是不需要分片 直接去下载,第二种是 请求分片的开始位置和结束位置不对,拒绝请求, 第三种是 位置正确 返回分片给前端

router.get("/down/:name", (req, res) => {
  let filename = req.params.name;
  //获取文件的位置 和文件的大小
  let filePath = path.resolve(__dirname, distPath, req.params.name);
  let size = fs.statSync(filePath).size;
  //获取请求头的range字段
  let range = req.headers["range"];
  let file = path.resolve(__dirname, distPath, filename);
  //不使用分片下载  直接传输文件
  if (!range) {
    //res.set({'Accept-Ranges':'bytes'})
    res.set({
      "Content-Type": "application/octet-stream",
      "Content-Disposition": `attachment; filename=${filename}`,
    });
    fs.createReadStream(file).pipe(res);
    return;
  }
  //获取分片的开始和结束位置
  let bytesV = range.split("=");
  bytesV.shift()
  let [start, end] = bytesV.join('').split("-");
  start = Number(start)
  end = Number(end)
  //分片开始 结束位置不对 拒绝下载
  if (start > size || end > size) {
    res.set({ "Content-Range": `bytes */${size}`});
    res.status(416).send(null);
    return;
  }
  //开始分片下载
  res.status(206);
  res.set({
    "Accept-Ranges": "bytes",
    "Content-Range": `bytes ${start}-${end ? end : size}/${size}`,
  });

  console.log(start + '---' + end)
  fs.createReadStream(file, { start, end }).pipe(res);
});
复制代码

前端 react

//视图
<div>
      <Input
        placeholder="请输入文件名"
        onChange={(e) => setname(e.target.value)}
      />
      <Button type="primary" onClick={downfile}>
        下载
      </Button>
</div>
复制代码

downfile

//下载文件
  SIZE为分片大小
  const SIZE = 200 * 1024 * 1024; //设置切片的大小
  const downfile = async () => {
    let contentLength = await filesize(name);
    let chunks = Math.ceil(contentLength / SIZE);
    let chunksl = [...new Array(chunks).keys()];

    //用流的方式操作不行   但是如果后端把文件变成压缩包 每个分片都是一个压缩包 应该就可以   此种方法 下载下来是多个压缩包 然后压缩包数量正确才能解压  有兴趣的可以看一下streamSaver这个库 可以实现边读边下
    /* for (let i of chunksl) {
      let start = i * SIZE;
      let end = i + 1 === chunks ? contentLength : (i + 1) * SIZE - 1;
      let res = await getBinaryContent(start, end, i);
      let fileStream = streamSaver.createWriteStream(name,{flags:'a',start});
      if (res.data.stream().pipeTo) {
      //这个用axios请求的 时候responseType设置为"blob" blob有stream这个方法
        await res.data.stream().pipeTo(fileStream);
      }
    } */
    
    //大文件下载 要控制并发数量 此处偷懒 使用了asyncPool这个库
    let results = await asyncPool(3,chunksl,(i)=>{
      let start = i * SIZE;
      let end = i + 1 === chunks ? contentLength : (i + 1) * SIZE - 1;
      return getBinaryContent(start, end, i);
    }) 
    results.sort((a,b)=>a.index - b.index)
    let arr = results.map(r=>r.data)
    //多个blob排序完合并为一个blob
    let buffers = new Blob(arr)
    saveAs(name,buffers)
  };

复制代码

filesize

//获取要下载文件的大小
const filesize = async (name) => {
    let res = await http.get(`/size/${name}`);
    return res.data;
};
复制代码

getBinaryContent

//根据传入的参数发起范围请求
  const getBinaryContent = async (start, end, i) => {
    let result = await http.get(`down/${name}`, {
      headers: { Range: `bytes=${start}-${end}` },
      responseType: "blob",
    });
    return { index: i, data: result };
  };
复制代码

saveAs

//保存文件
  const saveAs = ( name, buffers, mime = "application/octet-stream" ) => {
    const blob = new Blob([buffers], { type: mime });
    const blobUrl = URL.createObjectURL(blob);
    const a = document.createElement("a");
    a.download = name
    a.href = blobUrl;
    a.click();
    URL.revokeObjectURL(blob);
 };
复制代码

参考资料

JavaScript 中如何实现大文件并行下载? - 掘金 (juejin.cn)

前端大文件下载方案_azurecho-CSDN博客_前端大文件下载

分类:
前端
标签:
收藏成功!
已添加到「」, 点击更改