当前位置 : 主页 > 网页制作 > React >

在React中实用WebUploader实现大文件分片上传的踩坑日记!

来源:互联网 收集:自由互联 发布时间:2021-06-15
前段时间公司项目有个大文件分片上传的需求,项目是用React写的,大文件分片上传这个功能使用了WebUploader这个组件。 具体交互是: 1. 点击上传文件button后出现弹窗,弹窗内有选择文

前段时间公司项目有个大文件分片上传的需求,项目是用React写的,大文件分片上传这个功能使用了WebUploader这个组件。

具体交互是:

1. 点击上传文件button后出现弹窗,弹窗内有选择文件和开始上传button。

2. 每个文件显示序号、文件名、进度条、上传操作按钮(开始/暂停、删除)。

3. 选择好文件之后点击开始上传,文件按照顺序自动从第一个开始上传。

4. 期间如果用户点了弹窗“X”关闭,则暂停任务,弹窗关闭。

5. 弹窗关闭之后重新点击上传文件button后将用户上次选择的未完成的文件展示出来,并可以继续上传。

6. 全部上传完成之后自动关闭弹窗。

 

开发过程中踩了不少坑,好在自己始终没有放弃,慢慢研究探索,终于是实现了需求,或许这就叫做匠人精神吧????。。

下面来分享一下开发过程中遇到的坑(博主React菜鸟一枚,写的不好勿喷,望各路大神指点??)

首先说一下实现以上交互需求的具体思路吧:

注册uploader,在uploader实例化之后,把uploader保存在state里,在上传过程中更新文件状态,当上传完成时再更新一下状态。

更新状态的目的是后面会根据这些文件的状态渲染按钮,“待开始”状态的渲染“开始”按钮,“上传中”状态的渲染“暂停”按钮,已完成渲染“成功”按钮,“异常”状态的渲染“错误”按钮。

部分代码如下:

//WebUploader hook var chunkSize = 10 * 1024 * 1024;//分片上传,每片5M,默认是5M var that = this;   //保存this指针 WebUploader.Uploader.register({   name:‘my-uploader‘,   ‘before-send-file‘: ‘beforeSendFile‘,   ‘before-send‘: ‘beforeSend‘   }, {   beforeSendFile: function (file) {     // console.log("beforeSendFile");     // Deferred对象在钩子回掉函数中经常要用到,用来处理需要等待的异步操作。     var task = new $.Deferred();     // 根据文件内容来查询MD5     uploader.md5File(file,0,chunkSize).progress(function (percentage) {})        .then(function (val) { // md5计算完成           // console.log(‘md5 result:‘, val);           file.md5 = val;           file.uid = WebUploader.Base.guid();           // 进行md5判断           $.post("后端checkMd5的url", {uid: file.uid, md5: file.md5, fileName:file.name},             function (data) {             // console.log(data,‘md5 res‘);               if(data.code==‘500‘){                 message.error(data.msg)                 let updateFileList = that.state.fileQueuedList;    //更新文件状态,所有选择的文件保存在fileQueuedList中                 let res = updateFileList.map(item=>{                   if(item.fileId === file.id){                     item.status = "ERROR";                     item.statusName = "错误";                   }                   return item                 })                 that.setState({                   fileQueuedList:res,                 })                  task.reject(); //遇到不符合要求的文件调用reject方法,可以上传后面正常的文件               }else{                 var status = data.status.value;                 task.resolve();                 if (status == 101) {                   // 文件不存在,那就正常流程                 }else if (status == 100) {                   // 文件存在 忽略上传过程,直接标识上传成功;                   message.error(file.name+data.msg);                   uploader.skipFile(file);                   file.pass = true;                 }else if (status == 102) {                   // 部分已经上传到服务器了,但是差几个模块。                   file.missChunks = data.data;                 }               }            }         );      });      return $.when(task);   },   beforeSend: function (block) {     var task = new $.Deferred();     var file = block.file;     var missChunks = file.missChunks;     var blockChunk = block.chunk;     // console.log("当前分块:" + blockChunk);     // console.log("missChunks:" + missChunks);     if (missChunks !== null && missChunks !== undefined && missChunks !== ‘‘) {       var flag = true;       for (var i = 0; i < missChunks.length; i++) {         if (blockChunk == missChunks[i]) {           // console.log(file.name + ":" + blockChunk + ":还没上传,现在上传去吧。");           flag = false;           break;         }       }       if (flag) {         task.reject();       } else {         task.resolve();       }     } else {       task.resolve();     }     return $.when(task);     }   });   // 实例化   var uploader = WebUploader.create({     pick: {       id:‘#picker‘,       multiple:true     },     formData: {       uid: 0,       md5: ‘‘,       chunkSize: chunkSize,     },     swf: ‘../webUploader/Uploader.swf‘, // swf文件路径     chunked: true, //是否要分片处理大文件上传     chunkSize: chunkSize,     threads: 3, //上传并发数。允许同时最大上传进程数。     server: ‘/dynamic/video/fileUpload‘, // 文件接收服务端。     auto: false,     duplicate:false,     withCredentials:true,     // accept: {        //   extensions: ‘avi,asf,avs,mpg,mov,mp4,m4a,3gp,ogg,flv,ps,ts,dav,rmvb,SV4,SV5,SSDV‘,     // },     // 禁掉全局的拖拽功能。这样不会出现图片拖进页面的时候,把图片打开。     disableGlobalDnd: true,     // fileNumLimit: 1024, //验证文件总数量, 超出则不允许加入队列。     // fileSizeLimit: 1024 * 1024 * 1024, // 1G 验证文件总大小是否超出限制, 超出则不允许加入队列。     // fileSingleSizeLimit: 20*1024 * 1024 * 1024 // 20G 验证单个文件大小是否超出限制, 超出则不允许加入队列。   });   that.setState({      //把实例保存到state中     uploader:uploader       })   // 当有文件被添加进队列的时候   uploader.on(‘fileQueued‘, function (file) {     let appendFile = that.state.fileQueuedList;     let res = appendFile.some(item=>{       return item.file.name==file.name     })     if(res){       // message.error(file.name+‘文件重复。‘)       return     }     appendFile.push({       file:file,    //把file对象也保存下来       fileId:file.id,       progress:‘0%‘,       status:‘START‘,       statusName:‘待开始‘,     })     that.setState({       fileQueuedList:appendFile,     })   });
  //当某个文件的分块在发送前触发,主要用来询问是否要添加附带参数,大文件在开起分片上传的前提下此事件可能会触发多次。   uploader.onUploadBeforeSend = function (obj, data) {     // console.log("onUploadBeforeSend");     var file = obj.file;     data.md5 = file.md5 || ‘‘;     data.uid = file.uid;   };   // 上传中   uploader.on(‘uploadProgress‘, function (file, percentage) {     let updateFileList = that.state.fileQueuedList;     let res = updateFileList.map(item=>{      //文件上传中时更新文件状态和进度条       if(item.fileId === file.id){         item.progress=Math.floor(percentage * 100) + ‘%‘;         item.status = "UPLOADING";         item.statusName = "上传中";       }       return item     })     that.setState({       fileQueuedList:res,     })     // console.log(Math.floor(percentage * 100) + ‘%‘,file.name,‘上传进度‘)
  });   // 上传返回结果   uploader.on(‘uploadSuccess‘, function (file) {     // console.log(‘success‘)     let updateFileList = that.state.fileQueuedList;     let res = updateFileList.map(item=>{    //文件上传成功更新状态       if(item.fileId === file.id){         item.progress=‘100%‘;         item.status = "UPLOADED";         item.statusName = "已完成"       }       return item     })      //判断是不是都上传完,可以将该判断放在uploadComplete函数中,uploadSuccess只监听的到已成功的文件,uploadComplete函数无论成功失败都可以监听到     let isAllCompleted = updateFileList.every(item=>{           return item.status==="UPLOADED"||item.status==="ERROR"     })     that.setState({       fileQueuedList:res,       isAllCompleted:isAllCompleted     })     if(isAllCompleted){ //都上传成功之后       that.props.onClose&&that.props.onClose() //关闭弹窗       that.props.getFileList&&that.props.getFileList() //刷新文件table     }     });     uploader.on(‘error‘, function (type,file) {     // message.error("上传出错!请检查后重新上传!错误代码"+type);     // if(type==‘F_DUPLICATE‘){        // message.error(file.name+‘文件重复‘)     // }   // if (type == "Q_TYPE_DENIED") {     // message.error("请上传视频格式文件");   // }else {     // message.error("上传出错!请检查后重新上传!错误代码"+type);   // }   });   }   //点击文件的"开始"Icon,obj为当前点击的文件对象,即currentItem in fileQueuedList fileUpload(obj){   const {uploader,fileQueuedList} = this.state;   uploader.upload(obj.file)   let updateObj = fileQueuedList;   let idx = fileQueuedList.indexOf(obj);   updateObj[idx].status = "UPLOADING";   updateObj[idx].statusName = "上传中";   this.setState({fileQueuedList:updateObj}) } //点击暂停Icon fileStop(obj){   const {uploader,fileQueuedList} = this.state;    uploader.cancelFile(obj.file) //此处为第一个坑,在API里暂停是调用stop方法,此处想要暂停指定文件,显然应该用stop(file)方法, 然而实践之后发现调用stop(file)方法会报错 “Cannot read property ‘file‘ of undefined”, 之后再点击继续发现无法继续上传,没有发出请求。到现在博主也没有弄明白这样调用错在哪里。。 后来经过各种尝试后采用了cancelFile方法,可以暂停并继续,但此方法会标记文件为已取消状态,可以再次手动选择添加进队列,从而不触发文件重复的error监听。     let idx = fileQueuedList.indexOf(obj);   let updateObj = fileQueuedList;   updateObj[idx].status = "PAUSE";   updateObj[idx].statusName = "已暂停";   this.setState({fileQueuedList:updateObj}) } //文件暂停时点击继续开始Icon fileContinue(obj){   const {uploader,fileQueuedList} = this.state;    uploader.retry(obj.file)  //继续上传可以采用retry方法也可以使用upload方法   let idx = fileQueuedList.indexOf(obj);   let updateObj = fileQueuedList;   updateObj[idx].status = "UPLOADING";   updateObj[idx].statusName = "上传中";   this.setState({fileQueuedList:updateObj})  //更新文件状态 } //点击文件删除Icon clickDeleteIcon(obj){   let that = this;   const {uploader,fileQueuedList} = that.state;   let updateObj = fileQueuedList;   let idx = fileQueuedList.indexOf(obj);   updateObj.splice(idx,1)   uploader.cancelFile(obj.file);   that.setState({fileQueuedList:updateObj}) } //点击开始上传按钮 startUpload(){   const{uploader,fileQueuedList} = this.state;   let PausedFile = fileQueuedList.filter(item=>{     return item.status==="PAUSE"   })   // console.log(PausedFile)   if(PausedFile&&PausedFile.length>0){    //如果有已暂停的文件则从已暂停的文件中第一个开始上传     uploader.upload(PausedFile[0].file)   }else{     uploader.upload()   } } //弹窗关闭 onClose(){   const {fileQueuedList,isAllCompleted,uploader} = this.state;   if(!isAllCompleted){   let res = fileQueuedList&&fileQueuedList.reduce((data,current)=>{  //把除了错误和上传完成的文件暂停     if(current.status!==‘UPLOADED‘||current.status!==‘ERROR‘){       current.status="PAUSE";         current.statusName="已暂停";       uploader.stop(true);       data.push(current)     }     return data     },[])     // console.log(res,‘res‘)     this.props.saveFileStatus&&this.props.saveFileStatus(res)  //把所有添加的文件状态保存下来传给父组件。再有父组件通过props传给子组件   }     this.props.onClose&&this.props.onClose()     this.props.getFileList() }   componentDidMount(){ //挂载完成后获取父组件的props保存的文件状态   const {savedFileList} = that.props;  //savedFileList保存了关闭弹窗后未上传完的任务列表   // console.log(savedFileList,‘saved‘)     this.uploadOperate()  //把WebUploader相关的代码统一写在了此函数中,挂载时调用,注册hook并生成WebUploader实例     if(savedFileList&&savedFileList.length>0){       this.setState({         fileQueuedList:savedFileList,    //赋值,显示未完成的文件列表       },()=>{         const {uploader,fileQueuedList} = that.state;         let files = fileQueuedList.map(item=>{         return item.file       })       for(let i = 0; i < files.length;i++){              uploader.removeFile(files[i],true)          }        uploader.addFiles(files) //遍历所有的未完成任务,移除任务后再重新添加,目的是这样会触发fileQ ueue事件,否则进来点继续上传只会触发uploadProgress函数,在这个函数里有setState方法,但是会报错“Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component.” 发现上传请求是正常进行的,但是页面进度条不渲染,这也是第二个坑点,博主当时也没有找到原因,因为componentDidMount函数已经触发了,uploader实例也生成了,为什么还是unmounted component呢?于是便各种尝试,最终衍生出了上述代码,解决了这个进度条不渲染的,需求到此也是都实现了。。。       })     }   } }
网友评论