vue3 + Element + nodejs 大文件上传、断点续传

前言

        大文件上传,一般时间都比较长,这么长的时间内,可能会出现各种各样的问题,比如断网,一旦出错,我们的文件就需要重新上传,这样造成资源浪费,如果我们使用了断点续传继续就不会造成资源浪费了,因为当出现错误的时候,我们再重新上传文件,就会从我们出现错误的地方开始上传了,对于出错前上传的内容就不用再上传了,对于已经上传过的文件,就可以实现秒传的效果了。

完整仓库地址

流程

1、上传方法

const onUpload = async ({ file }) => {
  if (!file) return;
  // 文件切片
  const chunks = createChunks(file);

  // 计算哈希值
  const hash = await calculateHash(chunks);

  // 校验是否曾经上传过
  const { data } = await verifyFile(hash, file.name);

  if (!data.shouldUpload) {
    // 这里就相当于是秒传了,只需要将提示语改一下即可
    ElMessage.warning("文件已存在");
    return;
  }

  // 上传文件切片
  await uploadChunks(chunks, file.name, hash, data.existsChunks);

  // 发送合并请求
  await mergeRequest(hash, file.name);
};

2、将文件切片

const createChunks = (file) => {
  const chunks = [];
  let cur = 0;

  while (cur < file.size) {
    chunks.push(
      file.slice(
        cur,
        cur + CHUNK_SIZE > file.size ? file.size : cur + CHUNK_SIZE
      )
    );
    cur += CHUNK_SIZE;
  }

  return chunks;
};

3、计算文件哈希值

const calculateHash = (chunks) => {
  // 为什么要用promise? 因为reader.onload是异步的
  return new Promise((resolve) => {
    // 文件如果太大了,每个切片都计算一遍哈希值,时间会很长
    // 所以可以这样来计算,第一及最后一个切片计算完整哈希值,其他切片只计算前2个字节、中间两个、后面2个字节
    const newChunks = [];
    const MID = CHUNK_SIZE / 2;
    chunks.forEach((chunk, index) => {
      if (index === 0 || index === chunks.length - 1) {
        newChunks.push(chunk);
      } else {
        newChunks.push(chunk.slice(0, 2));
        newChunks.push(chunk.slice(MID, MID + 2));
        newChunks.push(chunk.slice(CHUNK_SIZE - 2, CHUNK_SIZE));
      }
    });

    const spark = new SparkMD5.ArrayBuffer();
    const reader = new FileReader();
    // 由于onload是异步的所以需要用Promise,读取文件切片读取完毕就把哈希值返回出去
    reader.onload = (e) => {
      spark.append(e.target.result);
      resolve(spark.end());
    };
    // 读取文件切片 返回一个ArrayBuffer数据对象,为了就是传给 spark
    // 因为spark new SparkMD5.ArrayBuffer()创建的 只接收ArrayBuffer数据对象
    reader.readAsArrayBuffer(new Blob(newChunks));
  });
};

4、校验文件是否上传过

这块主要是调用后端接口,具体数据可以与后端商议(实际开发过程中一般是java后端与我们这里的nodejs不一样)

// 校验文件是否已经上传过
const verifyFile = async (fileHash, fileName) => {
  const res = await axios.post(
    "http://localhost:3000/verify",
    {
      fileHash,
      fileName,
      size: CHUNK_SIZE,
    },
    {
      headers: {
        "Content-Type": "application/json",
      },
    }
  );

  return res;
};

5、上传切片

const uploadChunks = async (chunks, fileName, hash, existsChunks) => {
  // 将切片转成formDatas数据,过滤已经上传的切片
  let formDatas = chunks.map((chunk, index) => {
    const formData = new FormData();

    formData.append("fileHash", hash);
    formData.append("chunkHash", hash + "-" + index);
    formData.append("chunk", chunk);

    return formData;
  });

  // 过滤掉已经上传的切片
  formDatas = formDatas.filter(
    (item) => !existsChunks.includes(item.get("chunkHash"))
  );

  const requestPool = [];
  let index = 0;
  while (index < formDatas.length) {
    const request = axios.post(
      "http://localhost:3000/upload",
      formDatas[index]
    );
    // 将当前请求添加到请求池中
    requestPool.push(request);
    // 请求数量加1
    index++;
  }
  await Promise.all(requestPool);
};

6、发送合并请求

// 合并请求
const mergeRequest = async (fileHash, fileName) => {
  const res = await axios.post(
    "http://localhost:3000/merge",
    {
      fileHash,
      fileName,
      size: CHUNK_SIZE,
    },
    {
      headers: {
        "Content-Type": "application/json",
      },
    }
  );

  if (res.data.ok) {
    ElMessage.success("上传成功");
  }
};

以上便是大文件上传全部内容了,这是一个简略的demo, 如果有大佬指教一下就更好了

你可能感兴趣的:(vue.js,前端,javascript)