<template>
<div>
<a-upload
:accept="accept"
:beforeUpload="beforeUpload"
:disabled="disabled"
:fileList="fileList"
:list-type="listType"
:multiple="true"
:remove="handleImageRemove"
@change="handleChange"
>
<a-button :disabled="disabled">
<a-icon type="upload" />
{{ text }}
</a-button>
</a-upload>
</div>
</template>
<script>
import md5 from "js-md5";
import {
getCheckFile,
getChunkFile,
getUploadPart,
getMergeFile,
getCheckCosFile,
getChunkCosFile,
getCosUploadPart,
getMergeCosFile
} from "@/api/common";
export default {
name: "ChuckUploadComponent",
props: {
isCosType: { type: Boolean, required: false, default: false },
disabled: { type: Boolean, required: false, default: false },
count: { type: Number, required: false, default: 1 },
uploadLists: {
type: Array,
required: false,
default: () => {
return [];
}
},
listType: { type: String, required: false, default: "text" },
text: { type: String, required: false, default: "选择文件" },
accept: {
type: String,
required: false,
default: "*"
},
ext: {
type: Array,
required: false,
default: () => {
return []; // ['image/jpeg', 'image/jpg'];
}
}
},
data() {
return {
fileList: [],
tempThreads: 5,
chunkRetry: 4,
chunkSize: 5 * 1024 * 1024
};
},
created() {},
watch: {
uploadLists(value, oldValue) {
this.fileList = value;
}
},
methods: {
beforeUpload(file) {
this.fileList = [...this.fileList, file];
file.status = "uploading";
this.checkFile(file);
return false;
},
// 判断文件是否上传过,获取fileId
checkFile(file) {
const md5 = this.getFileMD5(file);
file.md5 = md5;
const formData = new FormData();
formData.append("fileMd5", md5);
formData.append("fileName", file.name);
if (this.isCosType) {
formData.append("fileSize", file.size);
}
const http = this.isCosType
? getCheckCosFile(formData)
: getCheckFile(formData);
http.then(async res => {
if (res.code === 0) {
if (res.data.status === 1) {
// 文件存在
if (res.data.fileId) {
file.fileId = res.data.fileId;
this.fileList.forEach(item => {
if (item.md5 === md5) {
item.status = "done";
this.$emit("fileId", res.data.fileId);
this.$message.success(item.name + " 文件上传成功!");
}
});
} else {
// console.log(`fileId不存在`)
this.$message.error(res.msg);
}
} else {
// 文件不存在或不完整,发送该文件
await this.uploadChunks(file);
}
}
});
},
// 根据文件大小,分配上传分片大小
updateCchunkSize(file) {
if (file.size > 2000 * 1024 * 1024) {
this.chunkSize = 1024 * 1024 * 15;
} else if (file.size > 1000 * 1024 * 1024) {
this.chunkSize = 1024 * 1024 * 10;
} else if (file.size > 500 * 1024 * 1024) {
this.chunkSize = 1024 * 1024 * 8;
} else {
this.chunkSize = 2 * 1024 * 1024;
}
},
createFileChunk(file, size) {
const fileChunkList = [];
let count = 0;
while (count < file.size) {
fileChunkList.push({
file: file.slice(count, count + size)
});
count += size;
}
return fileChunkList;
},
async uploadChunks(file) {
this.updateCchunkSize(file);
const fileChunkList = this.createFileChunk(file, this.chunkSize);
file.chunkList = fileChunkList.map(({ file }, index) => ({
index,
source: file,
size: file.size
}));
var chunkData = file.chunkList;
return new Promise((resolve, reject) => {
const requestDataList = chunkData.map(value => {
const formData = new FormData();
formData.append("fileMd5", file.md5);
formData.append("chunk", value.index);
formData.append("file", value.source);
return { formData, index: value.index, md5: file.md5, file };
});
try {
const ret = this.sendRequest(requestDataList);
// console.log('上传结束,参数:', ret, chunkData, file.md5)
resolve(ret);
} catch (error) {
this.$message.error("上传失败,请重试");
reject("sendRequest 失败", error);
}
}).then(async res => {
if (res == file.md5) {
// console.log('--- ' + file.name + ' 文件开始合并----')
await this.mergeRequest(file.md5);
}
});
},
// 并发,重试请求
async sendRequest(list) {
var finished = 0;
const retryArr = []; // retryArr.length代表请求数,值代表重试次数
var currentFileInfo;
const total = list.length;
// 所有请求都存放这个promise中
return new Promise((resolve, reject) => {
const handler = () => {
if (list.length) {
const formInfo = list.shift();
const index = formInfo.index;
const http = this.isCosType
? getChunkCosFile(formInfo.formData)
: getChunkFile(formInfo.formData);
http
.then(res => {
// if (res.code === 0) {
// //分片存在,跳过. 待测试
// finished++;
// handler();
// } else {
const http = this.isCosType
? getCosUploadPart(formInfo.formData)
: getUploadPart(formInfo.formData);
// 分块不存在或不完整,重新发送该分块内容
http
.then(res => {
if (res.code === 0) {
// const date = new Date();
// console.log(formInfo.file.name + 'UploadPart', formInfo, date.getTime(), res)
currentFileInfo = formInfo;
}
return res;
})
.then(res => {
if (res.code === 0) {
finished++;
handler();
} else {
this.$message.error(res.msg);
}
})
.catch(e => {
if (typeof retryArr[index] !== "number") {
retryArr[index] = 1;
}
if (retryArr[index] >= this.chunkRetry) {
return reject("重试失败", retryArr);
}
// console.log(`${formInfo.file.name}--文件的第 ${index} 个分块,开始进行第 ${retryArr[index]} 次重试`);
retryArr[index]++; // 累加
this.tempThreads++; // 释放当前占用的通道
list.push(formInfo); // 将失败的重新加入队列
handler();
});
// }
})
.catch(e => {
this.$message.error("上传失败,请重试");
});
}
if (finished >= total) {
resolve(currentFileInfo.md5); // 输出当前完成上传的文件信息
}
};
// 控制并发
for (let i = 0; i < this.tempThreads; i++) {
handler();
}
});
},
async mergeRequest(fileMd5) {
return new Promise((resolve, reject) => {
const mergeFormData = new FormData();
mergeFormData.append("fileMd5", fileMd5);
const http = this.isCosType
? getMergeCosFile(mergeFormData)
: getMergeFile(mergeFormData);
http
.then(res => {
if (res.code === 0) {
this.fileList.forEach(item => {
if (item.md5 === fileMd5) {
item.status = "done";
item.fileId = res.data.fileId;
this.$emit("fileId", item.fileId);
this.$message.success(item.name + " 文件上传成功!");
// console.log(item.name + ' 文件上传成功!')
resolve("sucess");
}
});
} else {
this.$message.error(res.msg);
}
})
.catch(err => {
// console.log('mergeRequest -> err', err);
reject("合并失败", err);
});
});
},
getFileMD5(file) {
return md5(file.name + file.size + file.lastModifiedDate);
// return md5(file.name + file.size + file.lastModifiedDate + Math.random(1000) + new Date().getTime());
// const reader = new FileReader();
// reader.onload = function (e) {
// file.fileMD5 = md5(e.target.result);
// console.log(e.target.result);
// }
// reader.readAsText(file);
},
handleChange(info) {
this.$set(this, "fileList", info.fileList);
const status = info.file.status;
if (status === "done") {
this.$message.success("上传成功");
} else if (status === "uploading") {
// this.$message.info(`${info.file.name} 上传中...`);
} else if (status === "error") {
this.$message.error(`${info.file.name} 上传失败`);
}
},
handleImageRemove(res) {
this.fileList.forEach((item, idx) => {
if (item.uid === res.uid) {
this.fileList.splice(idx, 1);
this.$message.success("删除成功");
}
});
this.$emit("getFileList", this.fileList);
}
}
};
</script>
<style>
/* 隐藏预览功能 */
span.ant-upload-list-item-actions a {
display: none;
}
</style>
ant-design-vue 文件分块、控制并发、重试上传
©著作权归作者所有,转载或内容合作请联系作者
- 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
- 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
- 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
推荐阅读更多精彩内容
- 最近在使用ant-design-vue做表格时,遇到要做一个可伸缩列表格的需求,但是官网示例的代码并不能直接使用,...
- 前言 分块上传和断点下载很像,就是讲文件分为多份来传输,从而实现暂停和继续传输。区别是断点下载的进度保存在客户端,...
- ant-design-vue表格中有个rowClassName属性: 1)首先,在table属性里定义rowCla...
- 黑色的海岛上悬着一轮又大又圆的明月,毫不嫌弃地把温柔的月色照在这寸草不生的小岛上。一个少年白衣白发,悠闲自如地倚坐...