桀桀桀,前端大文件切片上传你还不会吗?

220 阅读2分钟

有一天原本平静的前端开发突然受到不明需求的袭击!“快逃!”“可是……” 快找出大文件切片上传的方法,欢迎收看我的开发日记之大文件上传

前言

前端在上传一个较大的文件时, 由于文件过大,需要占用大量的时间,而前端常用的方法是切片上传,也就是将大的文件分成多个数据流, 分别上传至后端,然后再将他们合并为同一个,如此来解决大文件上传时占用时间过长的问题。切片上传简单俩说分为三步:文件切片、文件上传、文件合并。

需要的参数

  • file: 单个切片文件流
  • md5:文件唯一的id
  • chunkTotal: 切片总数
  • name: 文件名
  • numbel:当前切片在整个切片数组种的位置(建议于文件md5合并生成为同一参数)
  • id:每个切片的标识,可以通过总的md5和当前文件在切片数组种的位置合并生成

文件切片

将文件分成我们预先设置的大小

const SIZE = 2 * 1024 * 1024; // 切片大小
​
createFileChunk(file, size = SIZE) {
    const fileChunkList = [];
    let cur = 0;
    while (cur < file.size) {
        fileChunkList.push({ file: file.slice(cur, cur + size) });
        cur += size;
    }
    return fileChunkList;
},

获取文件的md5值

确定我们上传文件的唯一id,利用spark-md5实现,可以利用worker计算

//fileChunkList 切片文件数组
const spark = new self.SparkMD5.ArrayBuffer();
let percentage = 0;
let count = 0;
const loadNext = index => {
    // 新建读取器
    const reader = new FileReader();
    // 设定读取数据格式并开始读取
    console.log(fileChunkList[index]);
    reader.readAsArrayBuffer(fileChunkList[index].file);
    // 监听读取完成
    reader.onload = e => {
        count++;
        // 获取读取结果并交给spark计算hash
        spark.append(e.target.result);
        if (count === fileChunkList.length) {
            self.postMessage({
                percentage: 100,
                // 获取最终hash
                hash: spark.end()
            });
            self.close();
        } else {
            percentage += 100 / fileChunkList.length;
            self.postMessage({
                percentage
            });
            // 递归计算下一个切片
            loadNext(count);
        }
    };
};
loadNext(0);

文件上传

将切片的文件上传,实际操作种往往需要控制并发量

updataChunkMulti(updataList, multiple = 2){ 
    // apiList 的副本,避免 shift 方法对参数造成影响
    let list = [...updataList];
    let map = new Map(); //利用map存返回值
    //let count = 0; //记录已上传的文件数量,用于进度条
    // 递归调用
    const run = () => {
        if (list.length) {
            const params = list.shift();
            let formData = new FormData();
            Object.keys(params).map(key=>{ //将参数改为后端需要的形式
                console.log(key);
                formData.append(key, params[key]);
            });
            return postAction(SystemManageApi.notice.uploadChunkFile,formData).then(res => { //文件上传接口
                map.set(params, res);
                //count++;
                //this.percentage = Number.parseInt(count/updataList.length * 100); //percentage为进度条
                return run();
            }).catch(res=>{  //用于捕获单个切片文件上传失败
                map.set(params, res);
                return Promise.reject(res); //单个文件上传失败后不在继续上传
                //return run() //单个文件上传后继续上传
            });
        }
        return null;
    };
    //控制并发
    const promiseList = Array(Math.min(updataList.length, multiple)).fill(Promise.resolve()).map(promise => promise.then(run));

    return Promise.all(promiseList).then(() => {
        return updataList.map(c => map.get(c));
    }).catch((res)=>{
        return Promise.reject();
    });
},

文件合并

调通合并接口, 通知后端将上传的切片文件进行合并

结尾

本文仅仅只是文件上传的基本步骤,实际项目中的文件切片上传,往往还有断点续传、暂停、文件秒传功能,其中断点续传和文件秒传,都是通过后端通过md5鉴别当前文件是否已经上传还是已经上传了部分切片,而上传暂停则是利用axios实现。桀桀桀,前端大文件切片上传不知道你会了没???