简单分片上传的实现
一、实现逻辑
①选择文件,将文件后缀传给后端,获取该文件的ID(在后面向后端传每个分片时,都需要将这个ID带上);②根据文件大小判断是否需要上传(本文以5M为例),③如需,将文件进行切片④将文件转换成formData格式⑤分组上传切片(如果一个切片上传3次,就放弃上传)⑥所有切片上传完成,合并切片。
二、实现样式
三、实现代码
1.样式(iviewui组件)
<div v-if="!formItem.url">
<Upload :before-upload="handleBeforeUpload" action="">
<Button icon="ios-cloud-upload-outline">
选择文件上传
</Button>
</Upload>
<div v-if="file !== null">
<div>
<span>{{ file.name }}</span>
<Icon type="ios-trash-outline" @click="removeHandle()"/>
</div>
<Button @click="upload(file)" :loading="loadingStatus" >
{{ loadingStatus ? "上传中..." : "点击确认上传" }}
</Button>
</div>
</div>
<div v-else>{{ formItem.url }}
<Icon v-if="title != 'detail'" type="ios-trash-outline" @click="removeHandle()"/>
</div>
2.获取文件后缀
handleBeforeUpload(file) {
this.file = file;
let str = "";
str = this.file.name.substr(this.file.name.lastIndexOf('.') + 1);
if (str) {
createUploadId({
fileSuffix: str,
}).then((res) => {
if (res.data.code == 200) {
//获取分片上传文件的id
this.uploadId = res.data.data;
} else {
this.uploadId = ''
}
});
}
},
3.上传分片
async upload(file) {
this.loadingStatus = true;
//1.先判断拥有分片上传的id
if (this.uploadId) {
//2.分片
let ChunkSize= 5 * 1024 * 1024;
const chunkList = this.createFileChunk(file,ChunkSize) //切割分片函数
3.处理切片
this.requestList = chunkList.map(({ file }, index) => ({
file,
index,
partSize: file.size,//当前片大小
partNumber: index + 1,//当前片数
totalPart: chunkList.length,//总片数
uploadId: this.uploadId,//分片上传id
}));
await this.uploadChunks(); // 执行上传切片的操作
} else {
this.$Message.info("分片上传Id不能为空,请删除文件重试!");
this.loadingStatus = false;
}
},
3.1 切割分片
createFileChunk(file, size) {
//切割文件,size是每片的大小
const fileChunkList = []
let cur = 0
while (cur < file.size) {
// 使用slice方法切片
fileChunkList.push({ file: file.slice(cur, cur + size) })
cur += size
}
return fileChunkList
},
3.2 上传每个分片
async uploadChunks(uploadedList = []) {
//转成formData格式
const requestList = this.requestList
.filter((chunk) => !uploadedList.includes(chunk.file))
.map(({ file, partSize, partNumber, uploadId, totalPart, index }) => {
console.log('item==', file, partSize, partNumber, uploadId, totalPart, index);
const formData = new FormData();
formData.append("file", file);
formData.append("partSize", partSize);
formData.append("partNumber", partNumber);
formData.append("totalPart", totalPart);
formData.append("uploadId", uploadId);
return { formData, index };
});
await this.sendRequest(requestList, 4);
// chunk 全部发送完成了需要通知后台去合并切片
if (uploadedList.length + requestList.length === this.requestList.length) {
await this.mergeChunks();
}
},
3.3 控制并发数量
async sendRequest(forms, max) {
//forms,切片列表
//max最大并发数
//Status,每个切片的状态
const Status = { wait: 1, error: 2, done: 3, fail: 4 };
return new Promise((resolve, reject) => {
const len = forms.length; //长度
let counter = 0; // 已经发送成功的请求
const retryArr = []; // 记录错误的次数
// 一开始将所有的表单状态置为等待
forms.forEach((item) => (item.status = Status.wait));
const start = async () => {
// 有请求,有通道
while (counter < len && max > 0) {
max--; // 占用通道
// 只要是没有完成的我们就重发
let idx = forms.findIndex((v) => v.status == Status.wait || v.status == Status.error);
if (idx == -1) {
// 找不到失败状态和等待状态
return reject();
}
let { formData, index } = forms[idx];
await UZadeefillnoop(
formData,
).then((res) => {
console.log('res.data.data', res.data.code);
if (res.data.code == 200) {
forms[idx].status = Status.done;
max++; // 释放通道
counter++;
if (counter === len) {
resolve();
}
} else {
forms[idx].status = Status.error;
if (typeof retryArr[index] !== 'number') {
this.$message.error(`第 ${index} 个块上传失败,系统准备重试`);
retryArr[index] = 0;
}
// 次数累加
retryArr[index]++;
// 一个请求报错3次的
if (retryArr[index] > 3) {
this.$message.error(`第 ${index} 个块重试多次无效,放弃上传`);
forms[idx].status = Status.fail;
}
max++; // 释放通道
}
}).catch(() => {
forms[idx].status = Status.error;
if (typeof retryArr[index] !== 'number') {
this.$message.error(`第 ${index} 个块上传失败,系统准备重试`);
retryArr[index] = 0;
}
// 次数累加
retryArr[index]++;
// 一个请求报错3次的
if (retryArr[index] > 3) {
this.$message.error(`第 ${index} 个块重试多次无效,放弃上传`);
forms[idx].status = Status.fail;
}
max++; // 释放通道
});
}
};
start();
});
},
3.4 合并切片
mergeChunks() {
let obj = {}
obj.uploadId = this.uploadId
completeUpload(obj).then((res) => {
if (res.data.code == 200) {
this.$Message.success("上传成功");
for (let item of this.$refs.formItem.fields) {
if (item.prop === "url") {
item.resetField();
break;
}
}
this.formItem.url = res.data.data;
this.loadingStatus = false;
}
}).catch(() => {
})
},