React实现文件极速上传, 分片上传功能

1,826 阅读1分钟

前言

开发一个文件管理系统模块时, 对文件上传进行的优化(极速上传, 分片断点续传功能)

流程概览

uploadFile.jpg

代码实现

    // UploadFile
    import React, { useRef } from 'react';
    import { Upload, Button } from 'antd';
    import tool from 'src/tool.js';
    import api from 'src/App.api.js';
    function UploadFile() {
        const fileInfoRef = useRef(null);
        const beforeUpload = (file) => {
            // 文件上传前钩子
            return tool.md5File(file).then(fileInfo => {
                const { md5Key, fileSliceInfo } = fileInfo;
                return api.checkMd5(md5Key).then(res => {
                    // 验证文件key值是否有对应的值
                    const { data } = res;
                    if (data.isDone) {
                        // 找到文件并且文件已经上传完成
                        return Promise.reject(res);
                    }
                    // 没有找到文件或上传被中断
                    fileInfoRef.current = {
                        ...fileSliceInfo,
                        responseData: data,
                    }
                    return Promise.resolve();
                }) 
            })
        }
        
        const customRequest = () => {
            // 覆盖Upload默认上传行为
            const getApis = (list, existList) => {
                return list.fillter(file => {
                    // 筛选出服务器端不存在的文件
                    return !existList.includes(file.key);
                }).map((file, index) => {
                    const formData = new FormData();
                    formData.append('size', size); // 每次传输文件要带上文件总大小
                    formData.append('key', file.key);
                    formData.append('file', file.file);
                    return api.uploadFile(formData);
                })
            }
            const { size, fileList, responseData } = fileInfoRef.current;
            const existList = responseData.exist || [];
            let apiList = getApis(fileList, existList);
            Promise.all(apiList).then(res => {
                console.log('上传成功');
                fileInfoRef.current = null;
            })
        }
        return  <Upload
            beforeUpload={beforeUpload}
            customRequest={customRequest}
        >
            <Button>上传文件</Button>
        </Upload>
    }
    // tool
    import SparkMD5 from 'spark-md5';

    /**
     * 
     * @param {*} file 文件信息
     * @param {*} size 文件分片大小 
     * @returns 
     * md5Key 文件加密后key值
     * fileInfo 分片文件信息
     */
    function md5File(file, size = 2 * 1024 * 1024) {
        let fileList = [];
        // 文件分片长度
        const len = Math.ceil(file.size / size);
        const blobSlice =
            File.prototype.mozSlice ||
            File.prototype.webkitSlice ||
            File.prototype.slice;
        let spark = new SparkMD5.ArrayBuffer();
        const fileReader = new FileReader();
        let current = 0;

        const loadNext = (size) => {
            // 切片主要方法
            let start = current * size;
            let end = start + size >= file.size ? file.size : start + size;
            let sliceFile = blobSlice.call(file, start, end);
            // 将切片文件保存
            fileList.push({
                key: current,
                file: sliceFile,
            });
            fileReader.readAsArrayBuffer(sliceFile);
        };

        return new Promise((resolve, reject) => {
            try {
                loadNext(size)
            } catch (err) {
                reject(err)
            }
            // 文件读取完毕之后的处理
            fileReader.onload = (e) => {
                try {
                    spark.append(e.target.result);
                    current += 1;
                    if (current < len) {
                        // 文件递归读取
                        loadNext(size)
                    } else {
                        // 文件全部读取完, 返回对应信息;
                        const res = {
                            md5Key: spark.end(), // 文件加密key值
                            fileInfo: {
                                size: file.size, //文件总大小
                                fileList, // 切片文件列表
                            }
                        }
                        resolve(res);
                    }
                } catch (err) {
                    reject(err)
                }
            }
        })
    }