第一步 利用node开发服务端接口

// 获取文件大小
app.get('/length', (req, res, next) => {
  res.setHeader('Access-Control-Allow-Origin', '*');
  res.end('' + fs.statSync(`${__dirname}/lifivcyc.gif`).size);
});

// 下载
app.get('/', (req, res, next) => {
  res.setHeader('Access-Control-Allow-Origin', '*');
  res.download('lifivcyc.gif', {
    acceptRanges: true
  });
});

服务端 配置请求头'Access-Control-Allow-Headers',

下载 接口 配置 'Range' acceptRanges: true

第二步:前端请求头配置


  headers: {
            Range: `bytes=${rangeStart}-${rangeEnd}`,
          },
          responseType: 'arraybuffer'

Range 是一个请求首部,告知服务器返回文件的哪一部分。在一个 Range 首部中,可以一次性请求多个部分,服务器会以 multipart 文件的形式将其返回。如果服务器返回的是范围响应,需要使用 206 Partial Content 状态码。假如所请求的范围不合法,那么服务器会返回 416 Range Not Satisfiable 状态码,表示客户端错误。服务器允许忽略 Range 首部,从而返回整个文件,状态码用 200 。

通过 Range 的 header 告诉服务端下载哪一部分内容

第三步 使用promise.all 发请求,合并请求结果

1 根据文件大小,每次请求大小,遍历生成多个请求

const downloadTask = [];
for (let i = 1; i <= chunkNum; i++) {
   const rangeStart = chunkSize * (i - 1);
   const rangeEnd = chunkSize * i - 1;

    downloadTask.push(axios.get(path, {
      headers: {
        Range: `bytes=${rangeStart}-${rangeEnd}`,
      },
      responseType: 'arraybuffer'
      }))
    }

2 并发请求

      // map 返回新数组
      const arrayBuffers = await Promise.all(downloadTask.map(task => {
        return task.then(res => res.data)
      }))
      return mergeArrayBuffer(arrayBuffers);

3 合并返回结果

function mergeArrayBuffer(arrays) {
      let totalLen = 0;
      for (let arr of arrays) {
        totalLen += arr.byteLength;
      }
      let offset = 0
      for (let arr of arrays) {
        let uint8Arr = new Uint8Array(arr)
        console.log(uint8Arr)
        //在类型化数组中储存多个值,从特定数组中读取输入。
        //移量参数 offset 指定从什么地方开始使用源数组 array 的值进行写入操作。
      // ES2017 最新语法  创建初始化为 0 的,包含 length 个元素的无符号整型数组
        let res = new Uint8Array(totalLen)
        res.set(uint8Arr, offset)
        offset += arr.byteLength
      }
      return res.buffer
    }

4 转为blob数据

const blob = new Blob([res]);
// 拿到最终数据后,创建链接
const url = URL.createObjectURL(blob);

四 全部代码

1.服务端

const express = require('express');
const fs = require('fs');
const app = express();
// 获取文件大小
app.get('/length', (req, res, next) => {
  res.setHeader('Access-Control-Allow-Origin', '*');
  res.end('' + fs.statSync(`${__dirname}/lifivcyc.gif`).size);
});
// 预检请求 options
app.options('/', (req, res, next) => {
  res.setHeader('Access-Control-Allow-Origin', '*');
  res.setHeader('Access-Control-Allow-Headers', 'Range');
  res.end('');
});
// 下载
app.get('/', (req, res, next) => {
  res.setHeader('Access-Control-Allow-Origin', '*');
  res.download('lifivcyc.gif', {
    acceptRanges: true
  });
});

app.listen(3006, () => {
  console.log(`server is running at port 3006`);
});

2.web端

<!DOCTYPE html>
<html lang="en">

<head>
  <script src="https://www.unpkg.com/axios@1.3.5/dist/axios.min.js"></script>
</head>

<body>
  <img id="img" />
  <script>
    async function concurrencyDownload(path, size, chunkSize) {
      let chunkNum = Math.ceil(size / chunkSize);

      const downloadTask = [];
      for (let i = 1; i <= chunkNum; i++) {
        const rangeStart = chunkSize * (i - 1);
        const rangeEnd = chunkSize * i - 1;

        downloadTask.push(axios.get(path, {
          headers: {
            Range: `bytes=${rangeStart}-${rangeEnd}`,
          },
          responseType: 'arraybuffer'
        }))
      }
      // map 返回新数组
      const arrayBuffers = await Promise.all(downloadTask.map(task => {
        return task.then(res => res.data)
      }))
      return mergeArrayBuffer(arrayBuffers);
    }

    function mergeArrayBuffer(arrays) {
      let totalLen = 0;
      for (let arr of arrays) {
        totalLen += arr.byteLength;
      }
      let offset = 0
      for (let arr of arrays) {
        let uint8Arr = new Uint8Array(arr)
        console.log(uint8Arr)
        //在类型化数组中储存多个值,从特定数组中读取输入。
        //移量参数 offset 指定从什么地方开始使用源数组 array 的值进行写入操作。
      // ES2017 最新语法  创建初始化为 0 的,包含 length 个元素的无符号整型数组
        let res = new Uint8Array(totalLen)
        res.set(uint8Arr, offset)
        offset += arr.byteLength
      }
      return res.buffer
    }

    (async function () {
      // 请求文件大小
      const { data: len } = await axios.get('http://localhost:3006/length');
      // 下载文件
      const res = await concurrencyDownload('http://localhost:3006', len, 300000);
      
      // 转为blob数据
      const blob = new Blob([res]);
      // 拿到最终数据后,创建链接
      const url = URL.createObjectURL(blob);
      img.src = url;
    })();

  </script>
</body>

</html>

Logo

技术共进,成长同行——讯飞AI开发者社区

更多推荐