大文件分片上传,续传,秒传

201 阅读3分钟

文件上传,vue和react都支持

这个案例虽然我是在vue上完成的,但是用的基本上是原生js方法处理,使用react的小伙伴也可以轻松搬运

一、大文件分片上传

1、分片

/**
 * 这里我们使用Spark-MD5生成hash值
 * 我们不能直接这样使用,我们需要将分片和生成hash操作放到其他线程中处理
 * 直接在主线程执行,会导致页面卡顿,所以建议放到web Worker中;
 * 原因:在实现MD5编码时会耗时很长,它是一个主线程执行的同步任务且是CPU密集型的任务,计算量很大
 * @param {File} file 大文件
 * @param {number} index 第几个块
 * @param {number} chunkSize 每个块的大小
 */
function createChunks(file, index, chunkSize) {
  return new Promise((resolve) => {
    const start = index * chunkSize;
    const end = start + chunkSize;
    const spark = new SparkMD5();
    const fileReader = new FileReader();
    const blob = file.slice(start, end);
    fileReader.onload = (e) => {
      if (e.target && e.target.result) {
        const result = e.target.result;
        if (typeof result === "string") {
          spark.append(result);
        } else {
          const textDecoder = new TextDecoder();
          spark.append(textDecoder.decode(result));
        }
      }
      resolve({
        start, // 分片的开始字节
        end, // 分片的结束字节
        index, // 表示第几片
        hash: spark.end(), // 标识分块文件的唯一值
        blob, // 上传到服务器所需要的File对象
      });
    };
    // 转换成ArrayBuffer目的:SparkMD5库设计用来处理二进制数据
    fileReader.readAsArrayBuffer(blob);
  });
}

2、校验(断点续传,秒传)

/**
 * checkFileExist是一个调用后端接口的操作
 * 参数具体根据后端接口调整;
 * checkFileExist做两件事:
 * 1、上传之前先校验文件是否已上传,若已上传则跳过上传并提示上传成功,实现“秒传”效果;
 * 2、校验文件是否全部上传,若未全部上传,则返回已上传的文件Hash值,前端这边根据返回数据过滤出未上传的文件块,实现“断点续传”效果;
 */
const verifyData = await this.checkFileExist(this.fileHash);
// 如果文件已存在,则后端返回的未上传的文件块的集合值是空数值;具体看对接的后端逻辑调整
if (verifyData.isExist && verifyData.chunksHash.length === 0) 			{
  // 实现秒传
  alert("文件已上传成功!");
  return;
  } else {
  // 过滤出未上传的文件块 实现断点续传
  chunks = chunks.filter((chunk) => {
  	return !verifyData.chunksHash.includes(chunk.hash);
  });
}
// 将分块集合上传给服务器
this.upload(chunks);

3、上传

/**
 * 上传文件到服务器
 * 上传我们还是采用多线程和并发请求来优化性能
 * @param {Array} chunks 切好的文件块
 */
    upload(chunks) {
      if (chunks.length === 0) return;
      // 平均分配给每个Worker的任务量
      const workerChunkCount = Math.ceil(chunks.length / this.THREAD_COUNT);
      // 计数Worker的完成个数
      let finishCount = 0;
      for (let i = 0; i < this.THREAD_COUNT; i++) {
        const worker = new Worker(new URL("../utils/worker.js", import.meta.url), {
          type: "module",
        });
        let startIndex = i * workerChunkCount;
        let endIndex = startIndex + workerChunkCount;
        if (endIndex >= chunks.length) {
          endIndex = chunks.length;
        }
        worker.postMessage({
          chunks,
          startIndex,
          endIndex,
          isUpload: true,
          fileHash: this.fileHash
        });
        worker.onmessage = () => {
          worker.terminate(); // 释放worker
          finishCount++;
          // 全部上传成功后,通知后端合并分片文件
          if (finishCount === this.THREAD_COUNT) {
            const params = {
              fileHash: this.fileHash,
              fileName: this.fileName
            }
            // 所有分片上传成功后通知服务器合并所有分片
            // mergeChunks是后端提供的接口,参数根据后端接口调整
            this.mergeChunks(params);
          }
        };
      }
    },

如果需要完整代码可以在GitHub上下载:大文件分片上传 觉得对您有帮助的话,帮忙在GitHub上点亮星星,谢谢各位啦!如果需要ts版可以给我留言。