实现大文件上传等相关功能(汇总)

最近在做大文件上传的需求,所以就有了这一系列前端文件上传功能的记录。

流程图

一个大概的流程图

design_img.jpg

相关代码

获取文件md5值

const handleMD5 = (file) => {
  return new Promise((resolve, reject) => {
    const fileReader = new FileReader();
    fileReader.readAsBinaryString(file);
    fileReader.onload = (event) => {
      if (event.target) {
        const md5 = SparkMD5.hashBinary(event.target.result);
        return resolve(md5);
      }
      return reject();
    };
  });
};

大文件切片处理

const createFileChunk = (file: any, uploadId, size?: number) => {
  const SIZE = (size || configSize) * 1024 * 1024;
  const fileChunkList = [] as any[];
  for (let cur = 0, index = 0; cur < file.size; cur += SIZE) {
    fileChunkList.push({ file: file.slice(cur, cur + SIZE), position: cur, index });
    index += 1;
  }
  return fileChunkList;
};

文件上传请求

const loopArray = (index: number, list: any[], uid: string) => {
  setPageProps((pre) => {
    const listId = _.findIndex(pre.upload.list, (items) => items.id === uid);
    pre.upload.list[listId].status = 'doing';
  });
  bucketAPI
    .putNameFsUpload(...list[index].props)
    .then(async (res) => {
    if (res.code === 200 && res.data.done) {
      setPageProps((pre) => {
        if (index < list.length - 1) {
          // 存入已上传片段
          const listId = _.findIndex(pre.upload.list, (items) => items.id === uid);
          const arr = pre.upload.list[listId];
          arr.doneIndex = index;
          // 存入已上传片段
          const requestIndex = fineFileIndex(requestQueue, uid);
          if (requestIndex > -1) {
            requestQueue[requestIndex] = { ...requestQueue[requestIndex], doneIndex: index };
          }

          if (arr.status !== 'stop') {
            loopArray(index + 1, list, uid);
          }
        }
        if (index === list.length - 1) {
          const listId = fineFileIndex(pre.upload.list, uid);
          const arr = pre.upload.list[listId];
          if (arr.md5 && arr.file && arr.uploadId) {
            bucketAPI.postNameFsUpload(
              detailId,
              `${pre.path}${arr.file.name}`,
              {
                md5: arr.md5,
                size: arr.file.size,
                uploadId: arr.uploadId,
              },
              {
                successCallback: () => {
                  uploadSuccess(uid);
                  setPageProps((pree) => {
                    const data = _.cloneDeep(pree);
                    lineUp({ ...data, requestQueue, uid });
                  });
                },
              }
            );
          }
        }
      });
    } else {
      uploadFailed(uid);
      setPageProps((pree) => {
        const data = _.cloneDeep(pree);
        lineUp({ ...data, requestQueue, uid });
      });
    }
  })
  .catch((err) => {
    changeFileStatus('failed', uid);
  });
};

构建发送请求列表

const createRequestList = (fileList: any[], name: string, uid: string, uploadId: string) => {
  const requestList = fileList.map((item, index) => {
    return {
      request: bucketAPI.putNameFsUpload,
      props: [
        detailId,
        `${pagePros.path}${name}`,
        formatFormData({
          blockSeq: item.index,
          file: item.file,
          position: item.position,
          uploadId,
        }),
        {
          // 取消请求设置
          cancelToken: new CancelToken((c) => {
            setPageProps((pre) => {
              const length = _.findIndex(pre.upload.list, (items) => items.id === uid);
              pre.upload.list[length].cancelReq = pre.upload.list[length].cancelReq.concat([c]);
            });
          }),
          // 计算上传进度
          onUploadProgress: (progressEvent) => {
            setPageProps((pre) => {
              const length = fineFileIndex(pre.upload.list, uid);
              const progress = {
                loaded: progressEvent.loaded || 0,
                total: progressEvent.total || 0,
              } as any;
              pre.upload.list[length].progress[index] = progress;
            });
          },
        },
      ],
    };
  });
  return requestList;
};

处理上传文件

const handleFile = (e, files: FileList | File[]) => {
  if (Array.isArray(files)) {
    files.forEach((file) => {
      const uid = getUid(64);
      // 组装下载列表参数
      const params: UploadList = {
        id: uid,
        name: `${pagePros.path}${file.name}`,
        size: file.size,
        status: 'waiting',
        progress: [],
        file,
        cancelReq: [],
        requestList: [],
        doneIndex: 0,
      };
      // 更新下载列表
      setPageProps((pre) => {
        pre.upload.showText = false;
        pre.upload.list = pre.upload.list.concat([params]);
      });
      // 入队
      requestQueue.push(params);

      if (requestQueue.length <= fileLimit) {
        handle(file, uid);
      }
    });
  }
};

其他上传相关

前端实现显示文件上传进度的功能

前端实现大文件上传切片和文件秒传的功能

前端实现文件断点续传功能

前端实现同时上传文件个数的限制

参考资料

使用FileReader进行文件读取

js-spark-md5

字节跳动面试官:请你实现一个大文件上传和断点续传

前端大文件上传方法(深度好文)