实现大文件分片上传纪实

关于大文件上传的实现细节,网上已经有很多讲的细致清楚的教程以及代码可供参考。正所谓实践出真知,因此,本文的目的仅为了记录笔者开发工作中功能的逐渐实现和解决其中遇到的一些问题。如有错误,请大佬们不吝赐教!

文件分片、合并、删除及断点续传

对文件分片即是调用File对象的slice方法。在下图1上进行切片,笔者切片时文件对象如下图2,因此是对File的raw属性进行切片并得到Blob数据,然后调用后端接口进行上传。上传之后将这些切片进行合并删除切片(调用合并切口若返回数组说明有切片未上传成功,需要重新上传)。
图1文件对象形式
我的文件对象
话不多说,直接上代码:

const state = reactive({
  hash: '',
})
const chunkSize = 1024 * 1024 * 5; // 每块切片5MB
const fileChunks = [];
for (let i = 0; i < files.size; i += chunkSize) {
  const chunk = files.raw.slice(i, i + chunkSize); // files.raw是我们要切片的原始数据
  fileChunks.push(chunk);
}
const calculateFileHash = (fileChunks) => { // 使用第三方库spsrk-md5计算文件的hash值
  return new Promise((resolve) => {
    // 之前使用new SparkMD5()时有小bug:同一个文件内容变化后计算得到的hash值不变
    const spark = new SparkMD5.ArrayBuffer(); // 解决办法
    function _read(i) {
      if (i >= fileChunks.length) return;
      const reader = new FileReader();
      reader.readAsArrayBuffer(fileChunks[i]);
      reader.onload = (e) => {
        spark.append(e.target.result);
        i < fileChunks.length - 1 ? _read(i + 1) : resolve(spark.end());
      };
    }
    _read(0);
  });
};
let resSet = []; // 初始化需要重传的切片索引
const mapper = async (chunk, index) => { // mapper函数支持两个参数
  if(!state.hash) {
	  // 由于是并发请求,因此第一批次的若干个请求都会进行hash的计算(即下述p-map控制并发数为3,这里就会进行3次文件hash的计算),此后请求不会计算
    state.hash = await calculateFileHash(fileChunks);
  }
  const chunkHash = resSet.length ? `${state.hash}_${resSet.splice(0, 1)[0]}` : `${state.hash}_${index+1}`;
  const fd = new FormData();
  fd.append('file', chunk);
  fd.append('chunkHash', chunkHash);
  fd.append('mark', 1); // 后端接口参数标识,用来进行分片上传的不同场景
  fd.append('markVal', xxx); // 即向id为xxx的数据集中传入文件
  const result = await uploadReqeust(fd);
  return result;
};
const merge = async (fileHash, fileName, filesPath, chunkNums) => {  // 合并/删除切片
  const res = await mergeFiles(fileHash, fileName, filesPath, chunkNums); // 调用合并接口
  if(res.filePath) {
    state.uploading = false;
    const dir = res.filePath.slice(res.filePath.indexOf(arr[0]),res.filePath.length)
    ctx.emit('uploadSuccess', dir);
    deleteSliceFile(fileHash, filesPath)// 删除切片文件
    .then((res) => state.hash = '')
    .catch((err) => console.log(err))
    return dir;
  }
  return res.unUploadFiles;
}
// 使用p-map(或p-limit等相关第三方库)控制上传并发数
// stopOnError: 默认为true,取false时不会因为一个请求失败而影响其它请求的进行
return pMap(fileChunks, mapper, { concurrency: 3, stopOnError: false })
.then(async (res) => {
	const result = await merge(state.hash, files.name, res.filePath, chunkNums)
	if(Array.isArray(result)) {  // 断点续传
      resSet = result;
      const reUploadList = [];
      for (const item of resSet) {
        reUploadList.push(fileChunks[item-1]);
      }
      pMap(reUploadList, mapper, { concurrency: 3, stopOnError: false })
      .then(async (res) => { 
         const result2 = await merge(state.hash, files.name, res.filePath, chunkNums)
      })
    }
})
.catch(errFn);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值