大致原理就是将大文件分割成好几个部分(根据固定数量/固定大小方式),每个切片都有自己的数据和各自的名字,每一部分都发起一次ajax请求,将切片传递到服务器端。服务器端根据文件创建一个文件夹,用来存放大文件的切片,当客户端将全部切片传递到服务器端的时候,再发起一次请求告知服务器端,前端将数据全部传递完成了,服务器端接收到传递完成的通知的时候,将刚刚文件夹里面的文件全部合并成一个文件,最后将该文件夹删除。简短概括:大文件-->拆成很多小文件-->发起很多ajax请求发送小文件-->服务器端接收小文件-->组装成大文件

  • 1、将大文件拆分成很多小文件来上传

...
// 根据文件内容生成唯一的hash
import SparkMD5 from "spark-md5";
...
// 开始上传
async confirmUpload () {
  let formData = new FormData();
  const currentFile = this.currentFile;
  // 上传前的钩子函数
  const flag = this.beforeUpload(currentFile);
  if (!flag) {
    return false;
  };
  const fileBuffer = await this.fileParse(currentFile, 'buffer');
  let spark = new SparkMD5.ArrayBuffer();
  spark.append(fileBuffer);
  const hash = spark.end();
  const suffix = /\.([0-9a-zA-Z]+)$/i.exec(currentFile.name)[1];
  // 将文件切割为100份来上传
  let partList = [];
  const partSize = currentFile.size / 100;
  let cur = 0;
  for (let i = 0; i < 100; i++) {
    let item = {
      chunk: currentFile.slice(cur, cur + partSize),
      filename: `${hash}_${i}.${suffix}`,
    }
    cur += partSize;
    partList.push(item);
  }
  this.partList = partList;
  this.hash = hash;
  // 发送ajax请求到服务器端
  this.sendRequest();
},

根据文件切片发起ajax请求

async sendRequest () {
  // 根据多少切片来创建多少请求
  let requestList = [];
  // 设置请求头
  const headers = {
    // "Content-Type": "multipart/form-data",
  }
  this.partList.forEach((item, index) => {
    const fn = () => {
      let formData = new FormData();
      formData.append('chunk', item.chunk);
      formData.append('filename', item.filename);
      // 发送ajax请求
      axios.post('/upload3', formData, { headers }).then(res => {
        const data = res.data;
        if (data.code == 0) {
          this.total += 1;
          // 传完的切片我们把它移除掉
          this.partList.splice(index, 1);
        }
      })
    }
    requestList.push(fn);
  });
  let currentIndex = 0;
  const send = async () => {
    // 如果中断上传就不在发送请求
    if (this.abort) return;
    if (currentIndex >= requestList.length) {
      // 调用上传完成的按钮,告诉后端合并文件
      this.complete();
      return;
    }
    await requestList[currentIndex]();
    currentIndex++;
    send();
  }
  send();
},

全部切片上传完成后通知后端上传完成

// 文件上传,需要后端合并文件
complete () {
  axios.get('/merge', {
    params: {
      hash: this.hash,
    }
  }).then(res => {
    console.log(res, '上传完成');
  })
},

模拟暂停与开始

// 暂停和开始
handleBtn () {
  if (this.btn) {
    //断点续传
    this.abort = false;
    this.btn = false;
    this.sendRequest();
    return;
  }
  //暂停上传
  this.btn = true;
  this.abort = true;
}

大文件上传后端部分代码

接收文件切片

// 切片上传
app.post('/upload3', async (req, res) => {
  const {fields,files} = await handleMultiparty(req, res, true);
  const [chunk] = files.chunk;
  const [filename] = fields.filename;
  // 获取上传文件的hash
  const hash = /([0-9a-zA-Z]+)_\d+/.exec(filename)[1];
  const dir = `${uploadDir}/${hash}`;
  if (!fs.existsSync(dir)) {
    fs.mkdirSync(dir);
  }
  const path = `${dir}/${filename}`;
  fs.access(path, async err => {
    // 如果已经存在了就不做任何处理
    if (!err) {
      res.send({
        code: 0,
        path: path.replace(__dirname, `http://127.0.0.1:${PORT}`)
      })
    }
    // 测试上传需要时间,手动延迟
    await new Promise(resolve => {
      setTimeout(() => {
        resolve();
      }, 100);
    });
    // 不存在的时候就创建
    const readStream = fs.createReadStream(chunk.path);
    const writeStream = fs.createWriteStream(path);
    readStream.pipe(writeStream);
    readStream.on('end', function() {
      fs.unlinkSync(chunk.path);
      res.send({
        code: 0,
        path: path.replace(__dirname, `http://127.0.0.1:${PORT}`)
      });
    })
  })
});

合并多个切片文件

// 大文件上传后
app.get('/merge',(req, res) => {
  const { hash } = req.query;
  const path = `${uploadDir}/${hash}`;
  const fileList = fs.readdirSync(path);
  let suffix = null;
  fileList.sort((a, b) => {
    const reg = /_(\d+)/;
    return reg.exec(a)[1] - reg.exec(b)[1];
  }).forEach(item => {
    !suffix ? suffix = /\.([0-9a-zA-Z]+)$/.exec(item)[1] : null;
    // 写入文件
    fs.appendFileSync(`${uploadDir}/${hash}.${suffix}`, fs.readFileSync(`${path}/${item}`));
    // 删除文件
    fs.unlinkSync(`${path}/${item}`);
  });
  fs.rmdirSync(path);
  res.send({
    code: 0,
    path: `http://127.0.0.1:${PORT}/upload/${hash}.${suffix}`
  });
})

 

 

posted on 2021-03-04 22:55  Jack·zhou  阅读(86)  评论(0编辑  收藏  举报