一、背景
-
日常业务中难免出现前端需要向后端传输大型文件的情况,这时单次的请求不能满足传输大文件的需求,就需要用到分片上传
-
前端:
vue2.x
+Element
组件+axios
二、流程图
三、技术准备
-
主要技术
1.el-upload、axious 、 vue2
四、选择需要上传的文件
1.文件类型筛选
-
本身minio是没有文件类型筛选,文件大小和数量以及多文件这些功能;这些其实是在input上设置实现accept来实现的;文件的file对应的支持方式为下面几种:
imgAcceptOption: [
//4图片:png、jpg、bmp、jpeg
'image/png',
'image/x-png',
'image/jpeg',
'image/jpg',
'image/pjpeg',
'image/webp',
'image/bmp',
],
textAcceptOption: [
//2文件: txt、json、xml、dat
'text/plain',
'application/json ',
'text/xml',
'application/xml',
'.dat',
],
tableAcceptOption: [
//3表格:xls、xlsx、csv
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'application/vnd.ms-excel',
'application/msexcel',
'.xls',
'.xlsx',
'text/csv',
],
folderAcceptOption: ['.zip', 'application/zip', '.7z', '.rar'], //1文件夹zip rar
2.自定义上传方式,不采用el-upload自带的上传方式
<el-upload
ref="uploadRef"
class="upload-demo"
:accept="currentAcceptOption.toString()"
action="/"
multiple
:limit="100"
:auto-upload="false"
:show-file-list="false"
:on-change="handleFileChange"
>
<div class="add-file-btn">
<span>添加文件上传</span>
<i class="upload-icon"></i>
</div>
</el-upload>
五、获取预签名
-
前端支持批量上传,并将获取的文件进行获取,然后自动请求预签名接口;
-
后端生成presigned url(预签名url,里面包含上传到AWS S3所需要的一些认证标识信息)以及每个的partSize和currentNumber给到前端,前端通过这个URL,以及大小将文件分片上传到minio服务上,
-
具体切片处理:
const start = Number(partSize) * (partItem.currentNumber - 1); const end = start + Number(partSize); const blob = fileList[filesIndex].raw.slice(start, end); //切片后的文件
-
循环请求模块的代码,代码上传完成后根据请求的requist的coinfig里面的链接参数中的partNumber来判断当前片段是否成功:
// 判断链接
const queryURLParams = (url) => {
if (!url) {
return;
}
const askIn = url.indexOf('?');
let wellIn = url.indexOf('#');
let askText = '';
let wellText = '';
// #不存在
wellIn === -1 ? (wellIn = url.length) : null;
// ?存在
askIn >= 0 ? (askText = url.substring(askIn + 1, wellIn)) : null;
wellText = url.substring(wellIn + 1);
const result = {};
wellText !== '' ? (result['HASH'] = wellText) : null;
if (askText !== '') {
const ary = askText.split('&');
ary.forEach((item) => {
const aryText = item.split('=');
result[aryText[0]] = aryText[1];
});
}
return result.partNumber;
};
5.表格合并
-
// 进行中的文件和已经成功的记录进行表格合并去重 mergeTable(fileList) { try { this.upList = []; fileList.forEach((e) => { this.upList.push({ fileName: e.name, fileSize: e.size, fileSizeString: this.formatFileSize(e.size), fileStatus: e.status, uid: e.uid, fileType: e.type || this.getBit(e.name), percent: e.percent, isComplete: -1, }); }); // 缓存列表 this.cacheCurrentUploadList = [ ...this.cacheCurrentUploadList, ...fileList, ]; this.tableData = this.unique([...this.upList, ...this.tableData]); } catch (e) { console.log(e); } },
6.axios循环请求预签名
// 获取预签名接口
async handleHttpRequest(fileList, paramsDataSetFilesArr) {
this.$refs.uploadRef.uploadFiles = [];
const params = {
dataSetFiles: paramsDataSetFilesArr,
datasetId: this.currentDatasetId,
};
const { code, data, msg } = await preSignUrlFileUploadCheck(params);
if (code === 200) {
// 空间状态spaceStatus: 0、剩余空间足够支撑上传 1、剩余空间不支持上传 2、 文件类型校验失败
if (data.spaceStatus === 0) {
this.mergeTable(fileList); //当前的表格
data.fileInfos.forEach((filesItem, filesIndex) => {
// 文件检查状态checkStatus: 0、需要上传 1、已存在,秒上传 2、失败
if (filesItem.checkStatus === 1) {
this.fileUploadCompleteConfirm(filesItem, true);
} else {
// 依据预签名列表,分片上传
const { partSize } = filesItem;
const bathSuccessPartNumberList = [];
filesItem.filePartInfos &&
filesItem.filePartInfos.forEach(async (partItem) => {
const start = Number(partSize) * (partItem.currentNumber - 1);
const end = start + Number(partSize);
const blob = fileList[filesIndex].raw.slice(start, end);
await axios
.request({
url: partItem.fileUrl,
method: 'PUT',
data: blob,
headers: {
'Content-Type': 'application/octet-stream',
},
})
.then((response) => {
const obj = {};
obj[`${queryURLParams(response.config.url)}`] = true;
bathSuccessPartNumberList.push(obj);
// 上传完成
if (
bathSuccessPartNumberList.length ===
filesItem.filePartInfos.length
) {
this.fileUploadCompleteConfirm(filesItem, true);
} else {
this.tableData.find((item, index) => {
if (item.fileName === filesItem.fileName) {
this.tableData[index].percent = (
(100 / Number(filesItem.filePartInfos.length)) *
(index + 1)
).toFixed(0);
this.$refs.tableRef.doLayout();
}
});
}
})
.catch((error) => {
console.log(error);
// this.fileUploadCompleteConfirm(filesItem, false);
});
});
}
});
} else if (data.spaceStatus === 1) {
this.$message.error('剩余空间不支持上传');
} else if (data.spaceStatus === 2) {
this.$message.error('文件类型校验失败');
}
} else {
this.$message.error(msg || '服务异常~');
}
},
六、完成上传合并
// 文件上传成功确认
async fileUploadCompleteConfirm(options, isSuccess) {
try {
const meargeParams = {
fileName: options.fileName,
datasetId: this.currentDatasetId,
isSuccess: isSuccess,
};
const { code, msg } = await fileUploadCompleteConfirm(meargeParams);
if (code === 200) {
this.$refs.uploadRef.uploadFiles = [];
this.tableData.find((item, index) => {
if (item.fileName === options.fileName) {
this.tableData[index].isComplete = 1;
this.$refs.tableRef.doLayout();
}
});
this.queryPageByDatasetId();
this.$refs.uploadRef.uploadFiles = [];
} else {
this.$message.error(msg || '文件上传错误');
}
} catch (e) {
console.log(e);
}
},