【jeecg项目积累】-大附件上传-分片上传-断点续传-并发请求-失败重发

需求

1、解决项目开发的时候,遇到超大附件上传,需要上传的时间很久,网络抖动时,上传的到一半被迫终止,需要重新上传。

2、超大文件上传,整个文件上传需要很久的时间,把文件切片,整理成小文件上传,从而节约时间。

实现思路

1、第一步结合后端,获取当前附件的标识。(次步鄹调用后端接口,后端返回标识即可)。

2、获取到标识之后,开始分割文件,放在一个数组里面。

3、调用接口传递向后端传递单片文件(需要注意并发请求,和单片文件失败之后的处理方式,如果后端需要,还要计算单片文件的MD5)。

4、调用后端合并的接口,文件上传成功。

本次的重点在于第3步,如何处理并发请求,失败重发,控制并发请求接口的个数,控制上传失败之后的逻辑。

重点步鄹

前期准备

js 复制代码
需要确定的变量                           
 chunksize: 5* 1024* 1024 //单片文件的大小
 errorNumber: 5 // 文件上传失败重试的次数
 actionNumber: 5 // 并发请求接口数

2 分割文件

js 复制代码
把整个文件切割成为单片文件,因为是文件流不可以使用forEach,要使用原生的for循环
/****
    data: 附件信息   
    res: 第一步后端的标识   
    fileChunkdeList: 切片的数组
  ****/
  
fileSlice(deta, res, fileChunkdeList) {
    if(!data) return
    // 获取分片的个数,注意:向上取整
    data.total = Math.ceil(data.file.size/ this.chunkSize)
    for(let i =0; i < data.total; i++){
        let tmp
        if(i === 0){
            tmp = data.file.slice(i, Math.min(i + this.chunkSize, data.file.size))
        } else {
            tmp = data.file.slice(i * this.chunkSize, Math.min(i * this.chunkSize + this.chunkSize, data.file.size))
        }
        let obj = {
            file: tmp,
            filesIndex: i + 1
         }
        fileChunkdeList.push(obj)
    }
    
  //这一步是给合并的接口准备参数
 data.comPleteMultipartUploadParms = this.getcomPleteMultipartUploadParms(data, res)
     
   // 切片成功之后,调用单片文件上传的接口  第3步操作的函数
   this.fileSliceAction(data, fileChunkdeList, res).then(res => {
    if(上传失败的条件){
            整理失败的逻辑: 是否要出现重启按钮啊,页面展示啊,页面提示啊等...
            }
        })
    }
}

疑惑:

既然是把大文件切片分割成为小文件上传,为什么不在切割文件的时候,就直接调用上传单个文件的接口呢?

直接调用上传单个文件的接口就已经可以实现大附件切割小附件,已经可以起到加速上传文件的效果了。

解答: 直接调用接口会造成一个问题,不能控制并发请求接口数量,会造成两个极端。

1、并发请求全部的数据,就是有几个单片文件,就同时发送几个请求,这肯定不行。

2、我可以添加 async 把接口请求设置成为同步的请求,这样就是一个请求结束,在发送下一个请求,可以请求上一个问题,但是这个方法并不是最优解。

3、并发请求,失败重发

js 复制代码
    /***
    arr : 切片之后的数组fileChunkdeList,
    callback: 所有单片文件上传成功之后的文件合并接口回调, 
    data:附件信息, 
    res :第一步后端返回的附件标识 , 
    num : 记录失败重试的次数,为后续的失败重发,断点续传做准备
    ****/
    异步并发控制,同一时间点有5个任务在同时请求
    sendRequest(arr, callback, data, res, num){
        let fetchArr = []  // 正在请求的数据
        let toFetch = () => {
            // 如果异步任务都已经开始执行,剩下最后一组,则结束并发控制
            if(!arr.length){
                return Promise.resolve()
            }
            const chunItem = arr.shift()
            // 执行异步任务
            let it = this.fileSliceOnlyAction(chunItem, data, res, a)
            // 添加异步事件的完成处理
            it.then((res) => {
                // 接口上传成功
                if(res.code === 200){
                    // 有一次上传成功,就把失败的计数重置为0
                    num = 0
                    fetchArr.splice(fetchArr,indexOf(it), 1)  // 成功从任务列队中删除
                } else {
                    arr.unshift(chunItem) // 失败之后重新放回总队列中
                }
            }).catch(e => {
                arr.unshift(chunItem) // 失败之后重新放回总队列中
            })
            
            // 添加新的任务
            fetchArr.push(it)
            
            let p = Promise.resolve()
            if(fetchArr.length >= this.actionNumber) { // 如果并发达到最大数,则等其中的一个异步任务完成再添加
                p = Promise.race(fetchArr) 
            }
            
            return p.then(() => toFetch()) // 执行递归
        }
        
        toFetch().then(() => {
            Promise.all(fetchArr).then((e) => {
            // 最后一组全部执行完在执行回调函数,只是当前的5片数据全部上传完成之后的回调 就会进行合并接口
            callback(data, res) // 调用合并接口
            }).catch((e) => { // 断网失败 或者接口失败
            // 可以记录失败的次数,判断是否到达上传失败重试次数  errorNumber, 是否重新执行调用上传函数
            })
        }).catch((e) => { // 断网失败 或者接口失败
            // 可以记录失败的次数,判断是否到达上传失败重试次数  errorNumber, 是否重新执行调用上传函数
            })
    }

单片上传的接口

js 复制代码
fileSliceOnlyAction(){
    // 准备参数
    // 参数里面可能会有 hash 计算 
    // 调用后端接口
    //callback  封装在 axios里面 获取文件上传的进度
    return postFile (this.url.part, 参数,callback, true, data, a*chunkSize) 
}


// hash 计算方法
computeMD5(data){
    return new Promise((resolve, reject) => {
        let fileReader = new FileReader()
        let spark = new SparkMD5()  // SparkMD5 是一个插件 spark-md5
        fileReader.readAsBinaryString(data)
        fileReader.onload = e => {
            spark.appendBinary(e.target.result, 16)
            const md5 = spark.end()
            resolve(md5)
        }
    })
}
相关推荐
秃头女孩y33 分钟前
【React】条件渲染——逻辑与&&运算符
前端·react.js·前端框架
学无止境鸭1 小时前
vue读取本地excel文件并渲染到列表页面
前端·javascript·vue.js
不cong明的亚子1 小时前
在vue中,完成@wangeditor/editor组件的大数据量加载,解决卡顿
前端·vue.js
原机小子1 小时前
Spring Boot编程训练系统:前端与后端集成
前端·spring boot·后端
小满zs1 小时前
React第十五章(useEffect)
前端·react.js
爱米的前端小笔记1 小时前
前端学习八股资料CSS(一)
前端·css·经验分享·学习·职场和发展
丁总学Java2 小时前
npm list -g --depth=0(用来列出全局安装的所有 npm 软件包而不显示它们的依赖项)
前端·npm·node.js
丁总学Java2 小时前
前端开发中常用的包管理器(npm、yarn、pnpm、bower、parcel)
前端·npm·node.js
提笔惊蚂蚁2 小时前
java-web-苍穹外卖-day1:软件开发步骤简化版+后端环境搭建
java·开发语言·前端·程序人生
灌江口话事人2 小时前
html(超文本标记语言)
前端·html