react 上传文件分片及断点续传

前言

一直没有更新,是因为工作太忙了,没有时间去更新博客,在加上这几年从vue转到了react技术栈了,以后都将以react +arco.design+ ts来记录下可能平时不太常用到的功能,比如一些第三方插件的相关功能

html

 <Upload
                            multiple
                            showUploadList={false}
                            beforeUpload={(file)=>{beforeUpload(file,'md5')}}
                            onChange={(files, file) => {
                       
                            }}
                            customRequest={option => {
                                handleFileChange(option);
                            }}
                           
                        >
                            <Space size="large">
                                <Button type="primary"> <IconUpload></IconUpload>点击上传(断点续传)</Button>
                            </Space>
                        </Upload>

ts,代码只上了逻辑代码,至于里面setUploadId这样开头的变量,都需要在data里面定义就行 const [uploadId, setUploadId] = useState('');类似这样就行,我这里是是以弹窗的形式展示的文件是否上传成功

import SparkMD5 from 'spark-md5'
 const beforeUpload=(file,type)=> {
    let fileMaxSize=5;
    const maxSize = fileMaxSize * 1024 * 1024;
    const maxNameLen = 64;
    if(type == 'upload'){
        if (file.size > maxSize) {
            Message.warning(`${langT('文件大小不能超过')}${fileMaxSize}Mb,超过将用断点续传上传!`);
            setIsSurportUpload(true);
            return false;
        }
        setIsSurportUpload(false);
        return true;
    }else{
        if (file.size < maxSize) {
            setIsSurportUpload(false);
        
            return true;
        }
 
    }
  };
  // 计算文件的 MD5 值
  const calculateMD5 = (file: any) => {
      return new Promise((resolve) => {
          const spark = new SparkMD5.ArrayBuffer();
          const fileReader = new FileReader();
          const chunkSize = 5 * 1024 * 1024;
          let currentChunk = 0;

          fileReader.onload = function (e: any) {
              spark.append(e.target.result);
              currentChunk++;
              if (currentChunk < chunks) {
                  loadNext();
              } else {
                  const result = spark.end();
                  resolve(result);
              }
          };

          // 加载下一个分片
          function loadNext() {
              const start = currentChunk * chunkSize;
              const end = Math.min(file.size, start + chunkSize);
              const buffer = file.slice ? file.slice(start, end) : file.webkitSlice(start, end); // 使用 slice 方法
              fileReader.readAsArrayBuffer(buffer);
          }

          const chunks = Math.ceil(file.size / chunkSize); // 文件划分成的分片数量
          loadNext(); // 开始加载第一个分片
      });
  }
  // 将文件划分成多个分片
  const chunkFile = (file: any, chunkSize: any) => {
      const chunks = Math.ceil(file.size / chunkSize); // 文件划分成的分片数量
      const chunksList = [];
      let currentChunk = 0;

      while (currentChunk < chunks) {
          const start = currentChunk * chunkSize;
          const end = Math.min(file.size, start + chunkSize);
          const chunk = file.slice ? file.slice(start, end) : file.webkitSlice(start, end); // 使用 slice 方法
          chunksList.push(chunk); // 将分片添加到列表中
          currentChunk++;
      }

      return chunksList; // 返回分片列表
  }
 const handleFileChange = async (option) => {

      const { onProgress, onError, onSuccess, file } = option;
      //第一个接口
      const initResponse = await axios.post('_api/upload', { filename: file.name,filePath: filePath[filePath.length - 1]?.path });
     setUploadFialFile([]);//清空上传失败的文件
      console.log('option/file',file);
      let arr = file.type.split('/');
      let obj = {
          fileExtend: arr[1],
          fileName: file.name,
          fileSize: file.size,
          id: guid(),
          progress:0,
          state:0,//上传开始
      }
      setUploadListModalVisible(true);
   
      
      upDatasRef.current.push(obj);
      // uploadListData.push(obj);
      setUploadListData([...upDatasRef.current]);

      setUploading(true); // 开始文件上传
      const md5 = await calculateMD5(file); // 计算文件的 MD5 值
      console.log('md5',md5)
      md5Ref.current[obj.id] = md5; // 保存 MD5 值到引用

      // 将文件划分成多个分片并保存到引用对象中  1MB来分割1 * 1024 * 1024
      const chunksList: any = chunkFile(file,1 * 1024 * 1024);
      console.log('分隔后的文件', chunksList)
      chunkRefs.current[obj.id] = chunksList.map((chunk: any, index: any) => {
          const formData = new FormData();
  
          formData.append('path', '/dir1');
          formData.append("file", chunk);
          formData.append("fileName", file.name);
          formData.append("uploadId", uploadId);
          formData.append("partNumber", index+1);//.toString()
        //   formData.append("md5", md5Ref.current[obj.id]); // 添加 MD5 参数
          console.log('formData',formData)
          return formData;
      });
      // 定义递归函数用于逐个上传分片
      const uploadChunk = async (index: any) => {
        // debugger
        const chunkStatuses = new Array(chunkRefs.current[obj.id]).fill(false);
        let uploadedChunks = [];
        if (chunkRefs.current[obj.id].length > 0){
          let status = chunkStatuses.every(status => status);
            if (index >= chunkRefs.current[obj.id].length ) {//每个分片都上传了
                if(chunkStatuses.every(status => status)){//每个分片都上传了成功了
                  // 所有分片上传完成
                // setUploadListData([{ ...obj, state:1 }]);//上传完成
                  obj.state = 1;
                  setUploadListData([...upDatasRef.current]);
                  Message.success(`${obj.fileName}上传成功!`);
                  setUploading(false); // 文件上传完成,修改上传状态
                    return;
                }else{
                  return;
                }
            }

            try {
                const chunkResponse = await partFile(chunkRefs.current[obj.id][index]);//我这是接口地址,可以换成自己的接口
                console.log('chunkResponse',chunkResponse)
                if(chunkResponse.data.success){
                      // 更新进度条的值
                      const newProgress = Math.ceil(((index + 1) / chunkRefs.current[obj.id].length) * 100);
                      chunkStatuses[index] = true;
                      obj.progress = newProgress;
                      setUploadListData([...upDatasRef.current]);
                      console.log('newProgress', upDatasRef.current,newProgress)
                }else{//上传失败-发起重新上传
                  obj.progress = 0;
                  Message.error(`${chunkResponse.data.devMsg}`);
                  let fialFile={
                      file:chunkRefs.current[obj.id][index],
                      index:index
                  };
                  setUploadFialFile([...uploadFialFile,fialFile]);



                }
             
             
                // 递归调用上传下一个分片
                await uploadChunk(index + 1);
                return;
            } catch (error) {
                console.error(`分片 ${index + 1} 上传失败`, error);
                Message.error( `${obj.fileName}上传失败!`);
                setChunks([]);
                setFailedChunks([]);
                setUploading(false); // 文件上传失败,修改上传状态
                //重新上传按钮

                return;
            }
        }

    };
      // 开始递归上传第一个分片
      await uploadChunk(0);

  };

如果有不明白的可以私信我,有时间看到会回复,完善功能

posted @ 2024-08-26 14:49  Empress&  阅读(191)  评论(0)    收藏  举报