大文件分片上传+断点续传(react)同时限制附件大小及视频时长

js部分代码各类框架都适用,稍作改动即可,当前项目为react+Ant Design框架代码

                 <Dragger
                    {...uploadVideoProps}
                    fileList={videoList}
                    maxCount={1}
                    customRequest={customRequest}
                    accept={".mp4,.avi"}>
                    <p className="ant-upload-drag-icon">
                      <CloudUploadOutlined style={{ color: "rgba(0,0,0,.5)" }} />
                    </p>
                    <p className="ant-upload-text">
                      <a>上传视频</a>
                    </p>
                  </Dragger>
 import SparkMD5 from 'spark-md5';
import { Upload } from "antd";
const { Dragger } = Upload;
 // 一 、第一个函数(file前置处理函数)
  const customRequest = async (params) => {
    setcount(0)
    setisOpen(false)
    setVideoList([]);
    const { file } = params // 拿到file
    const { size, type, name } = file // 解构出可能用到的几个属性
    setVideoList([{ name: name }])
    //获取文件md5值(文件的md5值是根据文件内容生成的一个单独的标示,需要安装并引入一个插件:import SparkMD5 from 'spark-md5')
    const result = await fileParse(file, 'buffer') // fileParse转换函数,将file转成buffer格式数据,
    const spark = new SparkMD5.ArrayBuffer()
    spark.append(result)
    const hash = spark.end() // 获取到的文件md5值
    // 文件切片上传
    const partSize = 20 * 1024 * 1024 // 考虑到前后端上传效率,切成10M/片,这个可根据项目实际需要自己决定
    const totalChunks = Math.ceil(file.size / partSize);//分片总数
    //2  32   100
    const share = size <= partSize ? 1 : size / partSize // 判断如果文件<=20M,不切片(或者不调用分片上传),超过按照10M每份切
    let cur = 0
    const partList = [] // 存储文件切片的数组
    for (let i = 0; i < share; i++) {
      const obj = {
        file: file.slice(cur, cur + partSize), // 调用file.slice方法进行file的切片,第一个参数从哪里开始切,第二个参数切到哪里
        id: i // 每一个切片的单独标示,后期续传时用与去除已上传部分的切片
      }
      partList.push(obj) // 把切的每一片统一放到一个数组里
      cur += partSize
    }
    console.log(totalChunks, '文件大小');
    console.log(partList, '分片总数');
    //调用断点续传
    register(partList, hash, name, totalChunks); // 此函数往下看👇
  }

  // 二 、 第二个函数(注册上传任务函数 -- 调用第一个接口,注册上传任务接口)
  const register = (partList, hash, fileName, totalChunks) => {
    // 1.调第一个接口,上传任务注册接口,参数需要将文件的md5传过去,后台专门根据此文件开启一个上传任务(一个文件对应一个上传任务)
    demoUpload({ fileMd5: hash })
      .then((res) => {
        console.log(res);
        const { fileObject, partResultList, taskStatus } = res.data // uploadId是与这个文件对应的上传任务的id标示,status是此文件的状态,如已上传部分切片||从未进行上传,这是第一次||已经上传完了还未进行合并,partResultList是一个数组,里面放着已经上传的切片的id,如果是从未进行上传,就是空数组
        const mergeParame = { hash, fileName }
        sessionStorage.setItem("isRemove", 1)
        if (taskStatus == 'MERGE') { // 1.已全部上传,未合并,直接调合并函数
          setisOpen(true)
          complete(mergeParame)
        } else if (taskStatus == 'PART') { // 2.已上传部分,根据后台返回的partResultList字段(已上传切片id的数组)筛选,去除已上传部分,把剩下的切片组成一个新数组,如果筛选出来的新数组长度是0说明切片都已上传,直接合并,否则调用上传切片函数
          const newPartList = []
          setisOpen(true)
          let newArr = partResultList.map(obj => obj.chunk);
          partList.forEach((item) => {
            if (!newArr.includes(item.id)) {
              newPartList.push(item)
            }
          })
          if (newPartList.length === 0) {
            complete(mergeParame)
          } else {
            console.log(newPartList, 'newPartList,续传数组');
            newRequestArr(newPartList, hash, mergeParame, totalChunks)
          }
        } else if (taskStatus == 'NOT_UPLOADED') { // 3.从未上传过此文件,调用上传切片函数
          setisOpen(true)
          newRequestArr(partList, hash, mergeParame, totalChunks)
        } else if (taskStatus == 'UPLOADED') {  //已合并已上传
          message.success('上传成功')
          setcount(100)
          setisOpen(false)
          setVideoList([{ ...fileObject, name: fileObject.fileName }])
        }
      })
  }

  // 三 、 第三个函数(上传切片函数 -- 调用第二个接口,上传文件切片接口)
  const newRequestArr = (partList, hash, mergeParame, totalChunks) => {
    let i = 0
    const fn = () => {
      const formData = new FormData() // 通过form将file切片传给后台
      formData.append('chunk', partList[i].id);
      formData.append('file', partList[i].file);
      formData.append('chunks', totalChunks);
      formData.append('sourceFileMd5', hash);
      partUpload(formData).then((res) => { //分片上传接口
        // console.log(res,'分片上传接口');
        if (res.code == '-1') {
          message.error('网络异常请,请重试!')
          setisOpen(false)
          sessionStorage.setItem("isRemove", 0)
          setcount(0)
          setVideoList([]);
        }
        if (sessionStorage.getItem("isRemove") == 1) {
          if (res.status == 1) { // 如果不等于-1,则会停止递归调用上传函数,至于在哪里把他置为false,看你在哪里点击触发的暂停或者删除上传函数了,自己决定
            setcount((res.data.chunk / (totalChunks - 1)) * 96)
            i += 1
            if (i < partList.length) {
              fn()
            } else {
              complete(mergeParame) // 如果是最后一个切片,则调用合并函数
            }
          } else {
            setisOpen(false)
            sessionStorage.setItem("isRemove", 0)
            setcount(0)
            setVideoList([]);
            message.error('上传失败,请重试!')
            return
          }
        }


      })
    }
    fn()
  }

  // 四 、 第四个函数(文件合并函数 -- 调用第三个接口,文件合并接口)
  const complete = (mergeParame) => {
    // console.log(mergeParame);
    let params = {
      fileMd5: mergeParame.hash,
      fileName: mergeParame.fileName
    }
    fileMerge(params).then((res) => {//合并分片接口
      const { status, data } = res
      if (res.status == 1) {
        message.success('上传成功')
        setcount(100)
        setVideoList([{ ...data, name: data.fileName }])
        setisOpen(false)
        return
      } else {
        setisOpen(false);
        setcount(0);
        setVideoList([]);
        message.error("上传失败,请重试!");
        return;
      }
    })
  }

  // 转换函数
  const fileParse = (file, type = 'base64') => {
    return new Promise((res) => {
      const fileReader = new FileReader()
      /* fileReader.readAsArrayBuffer() // 转成buffer格式数据
        fileReader.readAsBinaryString() // 转成二进制格式数据
        fileReader.readAsDataURL() // 转成base64格式数据
        解析过程是异步,所以需要调用onload事件的e.target.result获取转换后的结果 
      */
      switch (type) {
        case 'base64':
          fileReader.readAsDataURL(file)
          break
        case 'buffer':
          fileReader.readAsArrayBuffer(file)
          break
        case 'binary':
          fileReader.readAsBinaryString(file)
          break
        default:
          break
      }
      fileReader.onload = (e) => {
        res(e.target?.result)
      }
    })
  }


  // 大视频上传
  const uploadVideoProps = {
    name: "file",
    action: apiHost + "/zjkc/file/uploadFile",
    multiple: true,
    showUploadList: true,
    accept: ".mp4",
    async beforeUpload(file) {
      console.log(file);
      if (file) {
        const isLt2G = file.size / 1024 / 1024 / 1024 < 2;
        // console.log(file,'+++ 视频大小++++');
        if (!['video/mp4'].includes(file.type)) {
          message.warning('请上传.mp4格式视频');
          return Upload.LIST_IGNORE;
        }
        if (!isLt2G) {
          message.warning('视频大小不超过2GB');
          return Upload.LIST_IGNORE;
        }
        const maxDuration = 15 * 60;
        const videoDuration = await checkVideoDuration(file, maxDuration)
        if (!videoDuration) {
          message.warning('上传的视频应在15分钟内');
          return Upload.LIST_IGNORE;
        }

        return true
      }
    }
  //视频时长--用于限制上传时长
  const checkVideoDuration = (file, t) => {
    return new Promise((resolve, reject) => {
      const videoUrl = URL.createObjectURL(file)
      const videoObj = document.createElement('video')
      videoObj.preload = 'metadata'
      videoObj.src = videoUrl
      videoObj.onloadedmetadata = () => {
        URL.revokeObjectURL(videoUrl)
        let times = Math.round(videoObj.duration)
        console.log(times, 'times')
        if (parseFloat(times) > t) {
          resolve(false);
        } else {
          resolve(true);
        }
      }
    });
  }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值