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:

相关推荐
m0_748236111 分钟前
Calcite Web 项目常见问题解决方案
开发语言·前端·rust
Watermelo61714 分钟前
详解js柯里化原理及用法,探究柯里化在Redux Selector 的场景模拟、构建复杂的数据流管道、优化深度嵌套函数中的精妙应用
开发语言·前端·javascript·算法·数据挖掘·数据分析·ecmascript
m0_7482489415 分钟前
HTML5系列(11)-- Web 无障碍开发指南
前端·html·html5
m0_7482356127 分钟前
从零开始学前端之HTML(三)
前端·html
一个处女座的程序猿O(∩_∩)O2 小时前
小型 Vue 项目,该不该用 Pinia 、Vuex呢?
前端·javascript·vue.js
hackeroink5 小时前
【2024版】最新推荐好用的XSS漏洞扫描利用工具_xss扫描工具
前端·xss
迷雾漫步者7 小时前
Flutter组件————FloatingActionButton
前端·flutter·dart
向前看-7 小时前
验证码机制
前端·后端
燃先生._.8 小时前
Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)
前端·javascript·vue.js