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;