react大文件上传

目录

大文件上传优点:

大文件上传缺点:

大文件上传原理:

为什么要用md5

实现流程:

部分代码1:

部分代码2:​


大文件上传 优点:

  1. 文件太大分片上传能加快上传速度,提高用户体验
  2. 断点续传 如果上次上传失败或者中途离开的话下一次上传过的就不用重头开始了
  3. 已经上传过的文件根据HASH查询直接秒传

大文件上传缺点:

1.后台可能设置了请求时长限制,太久会上传失败(解决:后端不设置上传时长)

2.NGINX可能设置了文件上传的最大限制导致失败(解决:比如分片25M,nginx设置文件上传最大限度50M)

大文件上传原理:

  1. 用户选择要上传的大文件,计算整个文件的MD5。
  2. 前端根据分片大小将文件切分成多个小块,计算每个分片文件的MD5。
  3. 逐个上传每个小块到服务器端。
  4. 服务器端接收并保存每个小块。
  5. 在服务器端,根据上传的小块将它们合并成完整的文件。

为什么要用md5

因为每个文件都会有自己专属独立的md5值,就像是每个人的身份证,比如我们在某个平台发布视频,将视频文件二次上传的时候就会遇到不容易过审的原因,同一个MD5就有很大的机率显示搬运被退回。刚好后端同学也可以通过MD5这种特性来判断上传的文件是否完整。

如何快速计算文件的 md5 值呢? 我们使用 js-spark-md5 这个库

实现流程:

在upload组件上传文件的钩子函数beforeUpload() 中:

  1. 获取切片文件:设置切片文件大小、每次上传的开始字节,每次上传的结尾字节。文件切片的核心是使用Blob 对象的 slice 方法:

    javascript 复制代码
    var blob = file.slice([start [, end [, contentType]]]};

    start 和 end 代表 Blob 里的下标,表示被拷贝进新的 Blob 的字节的起始位置和结束位置。contentType 会给新的 Blob 赋予一个新的文档类型,很少使用。

  2. 计算分片文件的MD5

  3. 上传分片文件,断点续传(如何实现断点续传,关键点是后端需要记录文件文件切片的信息。用户在上传一个文件之前,先询问服务器,当前文件是否存在已经上传完毕的切片,如果存在的话,需要返回切片信息。前端根据返回的信息,调整当前的进度,上传未完成的切片)

  4. 检验分片数量及上传的结果,全部上传,文件合并:

    1. 前端发送切片完成后,发送一个合并请求,后端收到请求后,将之前上传的切片文件合并。(上面展示的代码采用 这个)
    2. 后台记录切片文件上传数据,当后台检测到切片上传完成后,自动完成合并。
    3. 创建一个和源文件大小相同的文件,根据切片文件的起止位置直接将切片写入对应位置。

部分代码1:

javascript 复制代码
export default class Project extends React.PureComponent {
    
    construction (props){
        this.state({
            file: {},
            fileplanNumber: 0, // 上传文件进度的百分比
            fileUploadState: '', // 上传文件的状态,fail失败, success成功
            interFaceStart: '', // 文件上传时的分片文件下标, 作用于断点续传
        })
    }


beforeUpload = (file) => {
        this.setState({
            file,  // 把file存起来
        })
        let reader = new FileReader();
        let md5 = '';
        reader.onload = (event) => {
            const spark = new SparkMD5.ArrayBuffer;
            spark.append(e.target.value);
            md5 = spark.end();
            axios.post('', {
              md5,
              id, // 后端需要的
            }).then((res) => {
                const { fileId, start, finish, message } = res.data.data;
                if(res.data.code !== 200){
                    message.error(message);
                }
                if(finish && finish === 'true'){
                    this.setState({
                        file: {}, // 等于true,表示上传完成了
                        fileplanNumber: 0, // 上传文件进度的百分比
                    })
                    message.error(message);
                    return;
                }
                // 请求成功后
                this.setState({
                    fileId,
                    interFaceStart: start, // 文件上传时,分片文件下标,为了断点续传
                }, () => {
                    this.getSliceFile() // 获取文件切片
                })
            })
        }
        reader.readArrayBufffer(file);
        return false;
}

    
   // 获取文件切片
getSliceFile = async () => {
       const { search, cataList } = this.props;  
       const { file, fileId, interFaceStart } = this.state;
       const archiveId = cataList && cataList[cataList.length - 1].archiveId;
       const fileSize = file.size; // 文件的大小
       const piece = 1024 *1024 * 25; // 每片25M

       let start = 0; // 每次上传开始字节
        let index = 1;

       let end = start + piece; // 每次上传的结尾字节
        
        const chunksList = [];
        while(start < fileSize){
          const current = Math.min(end, fileSize) // 两者中取最小的
          const blob = file.slice.call(file, start, current);
          
          // 计算分片文件的MD5
          let sliceFileMD5 = '';
          sliceFileMD5 = await this.getSliceFileMD5(blob );
          // 拼接分片信息数据
          chunksList.push({
            file: blod,
            index,
            sliceFileMD5,
          });
          start = current;
          end = start + piece;
          index += 1;
            
        }

         // 检验分片数量,开始上传
        if (chunksList && chunksList.length) {
            const chunks = chunksList.slice(interFaceStart); 
            // 分片上传的结果
            let resultList = 0;
            // 循环分片数据
            for(const item of chunks){
                // 调用接口上传分片内容
                const resultFile  = await this.uploadSliceFile(item, chunks);
                //记录上传结果
                resultList += 1;
                // 一次失败,结束后续上传
                if(!resultFile){
                    break;
                }
            }
            
            // 检验分片数量,分片上传结果数量,全部上传完成,调用合并接口
            if(resultList === chunks.length){
                const { fileList } = this.state;
                axios.post('', { 
                    fileId,
                    fileName: file.name,
                    businessId: archiveId,
                    businessType: 'archive',
                }).then((res) => {
                    if(res.data.code !== 200){
                        message.error(res.data.message)
                    }
                    message.success('上传成功!')

                    // 调接口,刷新页面
                    axios.post('', {
                        pageNum: 1,
                        pageSize: 10,
                        archiveId,
                        orderBy: '',
                        sort: '',  
                    }).then(res => {
                       if(res.code !== 200){
                            message.error(res.message)
                        };
                        
                    })

                    this.setState({
                        file: {},
                        fileId: '',
                        fileUploadState: null,
                        fileplanNumber: 0,
                        fileList,
                        interFaceStart: 0,
                    });
                })
            } else {
                this.setState({
                    fileUploadState: fail,
                });
            }
        }
}

    // 计算分片文件的MD5
    getSliceFileMD5 = (blob) => {
        return new Promise((resolve, reject) => {
            const sliceFileReader = new FileReader();
            let sliceFileMD5  = '';
            sliceFileReader.onerror = reject;
            sliceFileReader.onload = (event) => {
                const spark = new SparkMD5.ArrayBuffer();
                spark.append(event.target.result)
                sliceFileMD5 = spark.end();
                // 返回分片MD5
                resolve(sliceFileMD5);
            }
            sliceFileReader.readAsArrayBuffer(blob);
        })
    }


    // 上传分片文件
     uploadSliceFile = (fileMap, chunks) => {
        return new Promise((resolve, reject) => {
            const { sliceFileMD5, file, index } = fileMap;
            const fileBlob = new File([file], 'AAA.exe', { type: 'application/x-msdownload' })
            const { fileId } = this.state;
            const formData = new FormData();
            formData.append('fileId', fileId);
            formData.append('MD5', sliceFileMD5);
            formData.append('partSequence', index);
            formData.append('fileBlob', fileBlob);
            axios.post('', {
                formData,
                headers: {
                    'Content-Type': 'multipart/form-data',
                }
            }).then(res => {
                if(res.data.code !== 200){
                    message.error(res.data.message)
                    return false;
                }
                const fileplanNumber = (index / chunks.length) * 100;
                this.setState({
                    fileplanNumber,
                });
                resolve(true);
            })
        })
     }




}

部分代码2:

相关推荐
懒大王爱吃狼21 分钟前
Python教程:python枚举类定义和使用
开发语言·前端·javascript·python·python基础·python编程·python书籍
小牛itbull1 小时前
ReactPress:重塑内容管理的未来
react.js·github·reactpress
逐·風4 小时前
unity关于自定义渲染、内存管理、性能调优、复杂物理模拟、并行计算以及插件开发
前端·unity·c#
Devil枫5 小时前
Vue 3 单元测试与E2E测试
前端·vue.js·单元测试
尚梦5 小时前
uni-app 封装刘海状态栏(适用小程序, h5, 头条小程序)
前端·小程序·uni-app
GIS程序媛—椰子6 小时前
【Vue 全家桶】6、vue-router 路由(更新中)
前端·vue.js
前端青山6 小时前
Node.js-增强 API 安全性和性能优化
开发语言·前端·javascript·性能优化·前端框架·node.js
毕业设计制作和分享7 小时前
ssm《数据库系统原理》课程平台的设计与实现+vue
前端·数据库·vue.js·oracle·mybatis
清灵xmf9 小时前
在 Vue 中实现与优化轮询技术
前端·javascript·vue·轮询
大佩梨9 小时前
VUE+Vite之环境文件配置及使用环境变量
前端