目标
表单中实现文件分片上传
实现思路
阻止Upload组件的默认上传,将文件流按照大小分割成数组,再逐条将其创建成单个文件进行上传。
具体实现
阻止默认上传
iView的Upload组件上传文件之前的钩子是before-upload,参数是上传的文件,通过返回 false 或者 Promise 停止上传。
Upload组件默认生成的uid为时间戳,可以使用md5加密为文件的唯一标识。
<Upload :before-upload="handleUpload">
</Upload>
......
handleUpload(file){
file.id = CryptoJs.MD5(file.uid).toString(); // 生成id
this.fileCutting(file); // 文件切片
return false; // 阻止默认上传
},
文件切片
使用file.slice()方法将文件按照分片的大小切片,并存入数组中。
fileCutting(file){
let chunkList = []; // 存储分片的文件流数据
let chunkSize = 1 * 1024 * 1024; // 分片大小1M
let startIndex = 0, endIndex = Math.min(chunkSize, file.size); // 切片开始位置,切片结束位置
do { // 文件分片
let blob = file.slice(startIndex, endIndex); // 文件流截取
startIndex += chunkSize; // 开始位置后移
endIndex = Math.min(endIndex + chunkSize, file.size); // 结束位置后移
if(!blob.size){ // 如果截取不到了则说明已完成分片
break;
}
chunkList.push(blob)
} while (true);
this.fileChunkHandle(chunkList, file); // 分片完成,对每个切片的参数进行处理
},
文件上传
- 处理接口传参
将二进制文件流转换成File格式,并将接口所需参数转换成FromData格式;遍历切片数组,逐条发送请求。
async fileChunkHandle(chunkList, file){
let chunks = chunkList.length;// 切片个数
let fileName = file.name;// 文件名
let fileType = this.getFileType(fileName); // 根据后缀获取文件类型
let retry = 3; // 失败重试次数
let formData = new FormData();// 参数表单
let params = {
...this.uploadData,// 接口所需的其他参数
chunks,
chunk: null,
name: file.name,
uuid: file.id,
file: null,
}
for(let key in params){
formData.append(key, params[key]);
};
for(let i = 0; i < chunkList.length; i++){
let fileChunk = new File(chunkList[i], fileName, {type: fileType});
formData.set("file",fileChunk);
formData.set("chunk",i);
await this.uploadFile(formData, i, chunks, retry + 1);
}
},
// 通过文件后缀获取文件类型
getFileType(name){
let splitNameList = name.split(".");
return splitNameList[splitNameList.length - 1];
},
- 调用接口上传文件
uploadFile(formData, chunk, chunks, tryCount){ // 接口参数,切片索引,切片总数,失败尝试次数
return new Promise(resolve =>{
$.ajax({
url: this.url,
data: formData,
contentType: false,
cache: false,
processData: false,
type: 'post',
success: res =>{
if(chunk < chunks - 1){
resolve()
} else if(chunk == chunks - 1){// 已全部上传完毕
console.log("success"); // 成功
}
},
error: err =>{
if(tryCount <= 1){
console.log("error"); // 失败
} else {
this.uploadFile(formData, chunk, chunks, tryCount-1)
}
}
})
})
}
完整代码
<template>
<div id="app">
<Upload ref="upload"
:before-upload="handleUpload"
:action="url">
<Button>文件上传</Button>
</Upload>
</div>
</template>
<script>
export default {
name: "App",
data() {
return {
url: "http://localhost:8081/file",
uploadData: {},
};
},
methods: {
handleUpload(file){
file.id = CryptoJs.MD5(file.uid).toString(); // upload组件默认生成的uid为时间戳,使用md5加密为唯一id
this.fileCutting(file); // 文件切片
return false; // 阻止默认上传
},
fileCutting(file){
let chunkList = []; // 存储分片的文件流数据
let chunkSize = 1 * 1024 * 1024; // 分片大小1M
let startIndex = 0, endIndex = chunkSize; // 切片开始位置,切片结束位置
do { // 文件分片
let blob = file.slice(startIndex, endIndex); // 文件流截取
startIndex += chunkSize; // 开始位置后移
endIndex += chunkSize; // 结束位置后移
if(!blob.size){ // 如果截取不到了则说明已完成分片
break;
}
chunkList.push(blob)
} while (true);
this.fileChunkHandle(chunkList, file); // 分片完成,对每个切片的参数进行处理
},
async fileChunkHandle(chunkList, file){
let chunks = chunkList.length;// 切片个数
let fileName = file.name;// 文件名
let fileType = this.getFileType(fileName); // 根据后缀获取文件类型
let retry = 3; // 失败重试次数
let formData = new FormData();// 参数表单
let params = {
...this.uploadData,// 接口所需的其他参数
chunks,
chunk: null,
name: file.name,
uuid: file.id,
file: null,
}
for(let key in params){
formData.append(key, params[key]);
};
for(let i = 0; i < chunkList.length; i++){
let fileChunk = new File([chunkList[i]], fileName, {type: fileType});
formData.set("file",fileChunk);
formData.set("chunk",i);
await this.uploadFile(formData, i, chunks, retry + 1);
}
},
// 通过文件后缀获取文件类型
getFileType(name){
let splitNameList = name.split(".");
return splitNameList[splitNameList.length - 1];
},
uploadFile(formData, chunk, chunks, tryCount){
return new Promise(resolve =>{
$.ajax({
url: this.url,
data: formData,
contentType: false,
cache: false,
processData: false,
type: 'post',
success: res =>{
if(chunk < chunks - 1){
resolve()
} else if(chunk == chunks - 1){
console.log("success")
}
},
error: err =>{
if(tryCount <= 1){
console.log("error")
} else {
this.uploadFile(formData, chunk, chunks, tryCount-1)
}
}
})
})
}
}
};
</script>