前端文件分片上传

120 阅读2分钟

import SparkMD5 from "spark-md5"; import { mergeFilePart } from "@/api/common.js"; import store from "@/store"; import UI from "@/utils/ui"; let ui = new UI(); //自定义文件分片上传 export const fileSlice = async (options) => { return new Promise((resolve) => { ui.showLoading(); console.log(options, "options"); const action = options.action; // 文件上传上传路径 应该用不上 const optionFile = options.file; // 需要分片的文件 const spark = new SparkMD5.ArrayBuffer(); // md5的ArrayBuffer加密类 const fileReader = new FileReader(); // 文件读取类 const chunkSize = 1024 * 200; // 单个分片大小 设置为100kb let md5 = ""; // 文件的唯一标识 let fileChunkedList = []; // 文件分片完成之后的数组

//文件开始分片,push到fileChunkedList数组中, 并用第一个分片去计算文件的md5
for (let i = 0; i < optionFile.size; i = i + chunkSize) {
  const tmp = optionFile.slice(i, Math.min(i + chunkSize, optionFile.size));
  if (i === 0) {
    fileReader.readAsArrayBuffer(tmp); // 读取第一个切片方便生成唯一标识
  }
  fileChunkedList.push(tmp);
}
// fileReader.readAsDataURL(optionFile);
// 在文件读取完毕之后,开始计算文件md5,作为文件唯一标识
fileReader.onload = async (e) => {
  // console.log(e.target.result); //base64数据
  // var imgBase64Data = e.target.result;
  // var head = imgBase64Data.indexOf('4') + 2;
  // var base64Data = imgBase64Data.substring(head, imgBase64Data.length - head);
  // console.log(base64Data.length);
  // for (let i = 0; i < base64Data.length; i = i + chunkSize) {
  //   const tmp = base64Data.slice(i, Math.min(i + chunkSize, base64Data.length));
  //   fileChunkedList.push(tmp);
  // }
  spark.append(e.target.result); //base64数据 转位 标识
  md5 = spark.end() + new Date().getTime();
  console.log("文件md5为--------", md5);
  //整合 合并文件参数
  let mergeFileData = {
    identifier: md5,
    filename: options.file.name,
    totalSize: optionFile.size,
    totalChunks: fileChunkedList.length,
  };
  // 将fileChunkedList转成FormData对象,并加入上传时需要的数据
  fileChunkedList = fileChunkedList.map((item, index) => {
    const formData = new FormData();
    // if (options.data) {
    //   // 额外加入外面传入的data数据
    //   Object.keys(options.data).forEach(key => {
    //     formData.append(key, option.data[key]);
    //   });
    // }
    // 这些字段看后端需要哪些,就传哪些,也可以自己追加额外参数
    // formData.append('chunkSize', chunkSize); // 单个分块大小
    formData.append("chunk", item); // 文件
    formData.append("chunkNumber", index + 1); // 当前文件块索引
    formData.append("currentChunkSize", item.size); // 当前分块大小

    formData.append("identifier", md5); // 文件标识
    formData.append("filename", options.file.name); // 文件名
    formData.append("totalSize", optionFile.size); // 文件总大小
    formData.append("totalChunks", fileChunkedList.length); // 总块数
    return formData;
  });

  // 创建队列上传任务
  // const sendRequest = chunks => {
  //   return Promise.all(
  //     //同时发送请求
  //     chunks.map(async v => {
  //       return uploadFileRedis(v);
  //     })
  //   );
  // };
  // 创建队列上传任务,limit是上传并发数
  function sendRequest(chunks, limit = 5) {
    return new Promise((resolve, reject) => {
      let resList = chunks.length;
      let isStop = false;
      const start = async () => {
        if (isStop) return;
        const params = chunks.shift();
        if (params) {
          const xhr = new XMLHttpRequest();
          if (options.withCredentials && "withCredentials" in xhr) {
            xhr.withCredentials = true;
          }
          // const headers = option.headers || {};
          // for (const item in headers) {
          //   if (headers.hasOwnProperty(item) && headers[item] !== null) {
          //     xhr.setRequestHeader(item, headers[item]);
          //   }
          // }
          // 文件开始上传
          // 分片上传成功回调
          xhr.onload = function onload(e) {
            if (xhr.status < 200 || xhr.status >= 300) {
              isStop = true;
              reject("上传图片失败");
            }
            let res = JSON.parse(e.target.response);
            if (res.code !== 200) {
              console.log(chunks.length, "res.code");
              isStop = true;
              reject(res.message);
            }
            resList--;
            //控制所有的请求完成响应后 resolve
            resList <= 0 ? resolve() : start();
          };
          // 分片上传失败回调
          xhr.onerror = function error(e) {
            isStop = true;
            reject(e);
          };
          xhr.open("post", action, true);
          xhr.send(params);
          //这里是把所有分片上传的xhr存到全局中,如果用户手动取消上传,或者上传出现错误,则要调用xhr.abort()把store中所有xhr的停止,不然文件还会继续上传
          store.dispatch("setChunkedUploadXhr", xhr);
        }
      };
      while (limit > 0) {
        setTimeout(() => {
          start();
        }, Math.random() * 100);
        limit -= 1;
      }
    });
  }
  try {
    // 调用上传队列方法 等待所有文件上传完成
    await sendRequest(fileChunkedList).then(async () => {
      setTimeout(async () => {
        // 给后端发送文件合并请求
        let fileSliceInfo = await mergeFilePart(mergeFileData);
        if (fileSliceInfo.code == 200)
          resolve({
            response: fileSliceInfo?.data,
            file: optionFile,
            data: options.data,
          });
      }, 0);
    });
  } catch (error) {
    $ui.hideLoading();
    store.state.chunkedUploadXhr.forEach((item) => {
      item.abort();
    });
    store.dispatch("delChunkedUploadXhr", []);
    options.onError(error);
  }
};

}); };