切片上传

47 阅读2分钟

import $file from '@/api/file'; import CryptoJS from "crypto-js`

/**

  • 描述:获取和该关联的文件的唯一key
  • 参数:file
  • 返回值:该文件的唯一key */

function getFileKey(file) {

return new Promise((resolve, reject) => {
    // 将文件名和最后修改时间和文件大小拼接为一个字符串
    const metadataString = `${file.name}-${file.lastModified}-${file.size}`;

    // 使用CryptoJS SHA-256函数计算摘要
    const wordArray = CryptoJS.SHA256(metadataString);
    const key = wordArray.toString(CryptoJS.enc.Hex); // 返回十六进制字符串形式的key
    resolve(key)
});

}

/**

  • 描述:检查该文件的切片上传状态
  • 参数:
  • 返回值:对象{shardIndex:已经上传的切片下标,-1代表全部上传了}
    */

function checkUpload(key) {

return new Promise(async (resolve, reject) => {
    let result = {}
    let data = await $file.getList({
        key
    }).catch(err=>{
        reject({
            type:'checkUpload',
            msg:'查询文件失败'
        })
    })
    //如果不存在则从第一个分片开始上传
    if (data.length == 0) {
        result.shardIndex = 1;
    } else{
        let first=data[0];
        if(first.chunkIndex===first.chunkTotal){
          // 已上传分片 = 分片总数,说明已全部上传完,不需要再上传
          result.shardIndex = -1;
          result.uploadData = first;
        }else{
            result.shardIndex =first.chunkIndex+1;
        }
    }
    resolve(result)
})

}

/**

  • 描述:切片上传文件
  • 参数:对象{shardIndex:切片下标,shardTotal:总切片数,partFile:切片后的文件}
  • 返回值: */

function uploadFile(param, option = {}) {

return new Promise(async (resolve, reject) => {
    let { shardIndex, shardTotal, partFile,key } = param;
    const formData = new window.FormData();
    formData.append('chunkIndex', shardIndex);
    formData.append('chunkTotal', shardTotal);
    formData.append('file', partFile);
    formData.append('key', key);
    await $file.uploadMultipart(formData, {
        headers: {
            'Content-Type': 'multipart/form-data',
        },
        onUploadProgress: progressEvent => {
            if (option && option.onProgress) {
                option.onProgress(progressEvent.loaded / progressEvent.total);
            }
        }
    }).then(res=>{
        if(res.code==200){
            resolve({
                data:res.data
            })
        }else{
            resolve({
                err:{
                    type:'uploadFile',
                    msg:res.msg
                }
            })
        }
       
    }).catch(err=>{
        resolve({
            err:{
                type:'uploadFile',
                msg:'切片上传失败'
            }
        })
    })
})

}

// 切片上传文件

class MultipartUpload {

//最小的切片大小  小于该大小的文件不进行切片上传 使用直接上传
minPartSize =50* 1024 * 1024;

//切片大小 
partSize = 1 * 1024 * 1024;

constructor() {
}

/**
* 描述:
* 参数:file File文件 option={onProgress(progress:Number进度值1代表上传100%):文件上传进度回调函数}
* 返回值:
*/

upload(file, option = {}) {
    return new Promise(async (resolve, reject) => {
        if (file.size < this.minPartSize) {
            //直接通过接口上传 不用切片
            const formData = new window.FormData();
            formData.append('file', file);
            const config = {
                headers: {
                    'Content-Type': 'multipart/form-data',
                },
                notLoading:true,
                onUploadProgress: progressEvent => {
                    if (option && option.onProgress) {
                        option.onProgress(progressEvent.loaded / progressEvent.total);
                    }
                }
            };
            const data = await $file.uploadTemp(formData, config);
            resolve(data)
        } else {
            let key = await getFileKey(file)
            let { shardIndex, uploadData} = await checkUpload(key);
            if (shardIndex == -1) {
                //代表全部上传过了
                if (option && option.onProgress) {
                    option.onProgress(1);
                }
               return resolve(uploadData)
            }
            //切片从1开始计算 数组索引从0开始
            let partArr = this.divideParts(file.size);
            for (let i = shardIndex - 1; i < partArr.length; i++) {
                let part = partArr[i]
                const blobData = file.slice(part.start, part.end);
                const partFile = new File([blobData], file.name, );
                let shardIndex = i + 1;
                let shardTotal = partArr.length;
               let {data,err}=  await uploadFile({
                    partFile,
                    shardIndex,
                    shardTotal,
                    key
                }, {
                    onProgress(progress) {
                        if (option && option.onProgress) {
                            let _progress = (shardIndex - 1) / shardTotal+(1/shardTotal*progress);
                            //如果进度值大于maxProgress 则取maxProgress值,因为还要等待最后接口返回的时间 这段时间是不确定的
                            let maxProgress=0.99;
                            option.onProgress(_progress>=maxProgress  ? maxProgress : _progress );
                        }
                    }
                })
                if(err){
                  return  reject(err)
                }
                if(data.chunkIndex===data.chunkTotal){
                    if (option && option.onProgress) {
                        option.onProgress(1);
                    }
                    //代表切片全部上传了
                    resolve(data)
                }
            }
        }
    });
}

/**
* 描述:根据文件大小分成切片位置
* 参数:文件大小 fileSize
* 返回值:切片后的文件位置数组对象 [{start:Number,end:Number}]
*/
divideParts(fileSize) {
    const numParts = Math.ceil(fileSize / this.partSize);
    const partOffs = [];
    for (let i = 0; i < numParts; i++) {
        const start = this.partSize * i;
        const end = Math.min(start + this.partSize, fileSize);
        partOffs.push({
            index: i + 1,
            start,
            end
        });
    }
    return partOffs;
};

}

export default MultipartUpload;