大文件-分片上传 vue3+java

0.需求背景

  1. 遇到大文件上传时,会存在文件过大,后端无法一次性接受
  2. 上传过程中,异常失败后,需要重新上传,耗时
  3. 单次请求时间过长,请求受限
    分片上传,相比于普通的单线程上传,速度更快,更灵活。

1.大文件上传的解决思路

  1. 前端文件切片:把一个大文件转换成二进制内容,然后按照一个固定的大小对二进制内容进行切割,得到多个小文件,然后循环上传所有的小文件。在js中,文件File对象是Blob对象的子类,可以使用slice()方法完成对文件的切割;
  2. 后端文件合并:当所有小文件上传完成,调用接口通知后端把所有的文件按编号进行合并,组成大文件;
  3. 并发控制:结合Promise.race和异步函数实现,限制多个请求同时并发的数量,防止浏览器内存溢出;
  4. 断点续传:把所有上传失败的小文件加入一个数组里面,在所有小文件都上传结束(成功和失败都算结束)之后再上传一次上传失败了的小文件,反复执行这一步,直到所有小文件都上传成功,可以通过递归实现。

思考的点

  1. 上传多次失败的分片,如何处理?
    1. 如上思路,会自动重新上传,但是不可以无限制的一直重复上传。大概率是接口并发问题,二次上传或者调整并发量,就可以上传上去了。
    2. 提供重新上传机会,但是原则上,必须所有的分片都上传成功后,才能合并出最终的大文件。
  2. 断网重连如何处理?待实现
    1. 出现请求失败的请求,则循环停止,暂停后面的请求了,直到网络连接后再继续上传!定时器,判断网络是否可行。
    2. 暴力的方案:界面检测到没有网络了,直接提示让客户重新上传,原来上传的作废。
  3. 上传一半,浏览器关闭,已上传的切片垃圾数据,如何处理?
    1. 方案1,界面销毁,触发销毁事件,告知后台,删除掉剩下的文件
    2. 方案2,后台定时检查分片数据。如果文件夹的创建日期是隔天的,则删除。
    3. 客户要二次上传,就重新进界面了
  4. 进度显示,切片的上传返回结果

3.核心代码参考

前端大文件切片

// 文件分片  
let hash = 0 // 切片序号  
let size = 1024 * 50 // 切片大小  
let fileArr = []  
  
total.value = Math.ceil(file.size / size)  
console.log('total: ', total)  
  
for (let i = 0; i < file.size; i = i + size) {  
  fileArr.push({  
    hash: hash++,  
    chunk: file.slice(i, i + size),  
    uploadTimes: 0  
  })  
  progress.value = Math.round(hash / total.value * 10000) / 100  
  console.log('拆分', hash)  
  console.log('拆分进度', progress.value + '%')  
}

前端切片上传

// 遍历文件列表,上传文件  
for (let i = 0; i < list.length; i++) {  
  let item = list[i]  
  if (item.uploadTimes > 5) {  
    errorList.push(item)  
    console.log('item.uploadTimes: ', item.uploadTimes)  
    continue  
  }  
  item.uploadTimes = item.uploadTimes + 1//记录  
  
  let formData = new FormData()  
  formData.append('filename', file.name)  
  formData.append('hash', item.hash)  
  formData.append('dir', uuid)  
  formData.append('chunk', item.chunk)  
  console.log('上传', item.hash)  
  
  // 上传  
  let res = axios({  
    method: 'post',  
    url: 'bigfile/upload',  
    data: formData  
  })  
  // 把上传文件的异步操作放入并发池里  
  pool.push(res)  
  if (pool.length === max) {  
    // 每当并发池跑完一个任务,就再塞入一个任务  
    await Promise.race(pool)  
  }  
  
  res.then((response) => {  
    let status = response.data.status  
    if (status == '201') {  
      // 请求成功,从并发池里移除  
      const index = pool.findIndex(it => it === res)  
      pool.splice(index, 1)  
      finalFine.value++  
      progress.value = Math.round(finalFine.value / total.value * 10000) / 100  
      console.log('response: ', response)  
      console.log('上传进度 ----- ', finalFine.value, total.value, progress.value + '%')  
    } else {  
      //上传有问题  
      const index = pool.findIndex(it => it === res)  
      pool.splice(index, 1)  
      failList.push(item)  
    }  
  }).catch((response) => {  
    console.log('response-error: ', response)  
    // 请求失败,从并发池里移除,添加到失败的文件列表  
    const index = pool.findIndex(it => it === res)  
    pool.splice(index, 1)  
    failList.push(item)  
  }).finally(() => {  
    finish++  
    // 如果请求都完成了,递归调用自己,把上传失败的文件列表再上传一次  
    if (finish === list.length) {  
      uploadFileChunks(failList, uuid)  
    }  
  })  
}

后端切片合并

// 创建目标文件  
RandomAccessFile writeFile = new RandomAccessFile(targetFile, "rw");  
  
// 位置起始点  
long position = 0L;  
for (String tempFilename : fileNames) {  
    // System.out.println(tempFilename);  
    File sourceFile = new File(parentDir, tempFilename);// 切片文件  
    RandomAccessFile readFile = new RandomAccessFile(sourceFile, "rw");  
    int chunksize = 1024 * 3;  
    byte[] buf = new byte[chunksize];  
    writeFile.seek(position);  
    int byteCount = 0;  
    while ((byteCount = readFile.read(buf)) != -1) {  
       if (byteCount != chunksize) {  
          byte[] tempBytes = new byte[byteCount];  
          System.arraycopy(buf, 0, tempBytes, 0, byteCount);  
          buf = tempBytes;  
       }  
       writeFile.write(buf);// 写入  
       position = position + byteCount;  
    }  
    readFile.close();  
}  
writeFile.close();

4.案例源码

百度网盘 链接:https://pan.baidu.com/s/1wMJoE0ETiSPHniJLV5IPZA

码微信小程序。获取 提取码

大文件 分片上传.png|600

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DO_IT_JACK

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值