文件分片上传
文件分片上传的总体思路是 先将文件通过blob.slice()方法将文件切割成多个分片。然后循环上传分片将整个文件上传以后。调用接口合并文件; 大概步骤可以分为:
将文件切割成自己定义的大小(1M 2M 5M 10M)1M = 1024KB*1024KB
通过md5 获取到一个唯一的标识。
然后调用一个init的接口将这个文件的标识传给后台。后台返回给你一个文件的ID 和 刚刚传给后台的md5的值
然后循环上传分片的文件上传完成以后调用合并文件的接口 通知后台将上传文成的文件合并。
分片上传共涉及到四个接口 分别是
初始化的接口
文件上传的接口
合并的接口
删除文件的接口
当分片的文件上传失败的时候需要调用删除文件的接口将数据库里的文件清除掉。
还有一点需要优化的就是如果文件分的片数太多了就需要做节流每次只上传三个 或五个分片。如果同时上传太多的分片就会造成很大的并发。还会占用很大的带宽。
<ul>
<!-- 文件预览的位置 -->
<li class="proview" v-for="(item,index) in previewList" :key="index">
<div class="proview-zhezhao">
<i class="el-icon-close" @click="closeImg(item,index)"></i>
</div>
<p class="err">{{item.err}}</p>
<img :src="item.img" alt="">
<el-progress v-if="item.isSuccess" :percentage="item.uploadBar" status="success"></el-progress>
<el-progress v-else :percentage="item.uploadBar" status="exception"></el-progress>
</li>
<!-- 上传文件 -->
<li class="upload-box">
<form enctype="multipart/form-data">
<input id="xz-upload" class="hide" type="file" multiple="multiple" v-if="isShowInput" @change="upLoad($event)">
</form>
<label class="xz-label" for="xz-upload">
<span class="icon iconfont icon-jiahao"></span>
<p class="middle top-85">点击添加文件</p>
<p class="middle top-5">文件大小不超过2G</p>
</label>
</li>
</ul>
// 这里写到的是上传多个文件也可以分片的方法 如果想实现上传单个文件分片功能 直接从这里边拆出来就可以了。
// 上传文件的change事件 获取文件对象
upLoad(event) {
let that = this;
/*
this.isShowInput = false;
注意 file文件的值只能写一次 第二次选择文件后不能触发 onchange事件 通过 v-if 从新生成 input 节点
*/
this.isShowInput = false;
// 获取文件对象
let files = Array.from(event.target.files);
// 获取上传文件的总大小。一次不允许上传2G
files.forEach((item, index) => {
this.size += item.size;
// 每一个文件唯一的标识
item.fileKey = hex_md5(new Date().getTime() + item.name);
// 根据后缀判断文件的类型
let hz = item.name.split(".");
let hzm = hz[hz.length - 1];
// 校验文件的类型 如果不是这些类型就不上传。可以自行添加需要上传的类型
if (!/txt|doc|docx|xls|xlsx|ppt|pptx|pdf|jpg|jepg|png|gif|psd|avi|mp4|mp3|rar|zip|tar|7z/.test(hzm)) {
this.$alert(`文件类型不允许:${item.name}`);
this.isShowInput = true;
} else {
// 文件的预览 如果是图片就直接预览图片 如果不是图片就给一个默认的图片
var reader = new FileReader();
reader.readAsDataURL(item);
reader.onload = function(evente) {
that.previewList.push({
img: /jpg|png|jpeg|gif/.test(hzm)? this.result: testPic(null, hzm),
fileKey: item.fileKey,
uploadBar: 10, // 进度条的值 使用的是element的进度条
isSuccess: true //进度条的颜色。用来展示文件是成功还是失败。成功是绿色 失败为红色
});
};
}
});
// 判断文件的大小 如果上传多个文件 并且这个文件大于2G直接不上传 提示用户
if (this.size > 1024 * 1024 * 1024 * 2) {
this.$alert("您上传的文件太大了(文件应小于2G)");
this.isShowInput = true;
return;
}
// 循环文件对象 开始上传文件
files.forEach((item, index) => {
let hz = item.name.split(".");
let hzm = hz[hz.length - 1];
// 根据后缀名判断文件是否为图片。如果是图片就直接上传。不调用分片 如果不是图片就调用将文件分片上传
if (/jpg|png|jpeg|gif/.test(hzm)) {
this.uploadImg(item);
} else {
this.uploadSlice(item);
}
});
},
// 分片上传文件
uploadSlice(event) {
var fail = {}; // 记录失败的位置
var that = this;
var totalPieces = ""; // 文件的总片数
var bytesPerPiece = 1024*1024*10 // 将每一个分片的大小定位10M
var currentNum = 0; // 判断文件上传了几片。当currentNum == totalPieces 的时候就调用文件合并
var max = 3; // 每次最多同时发送三个请求
var blob = event; // 文件对象
let fileName = blob.name; // 文件的名称
var filesize = blob.size; // 文件的大小
var partNum = 0; // 用来计数每一个的分片
totalPieces = Math.ceil(filesize / this.bytesPerPiece); // 获取文件总的分片数
// 切割并上传文件 为了拿到文件对象就没有将这个函数提取出来(偷懒了)
function uploadPartFn(partNum, code, key, fileName) {
// 截取文件的其实位置
var start = 0 + that.bytesPerPiece * partNum;
// 截取文件的结束位置
var end;
end = start + that.bytesPerPiece;
// 如果结束位置大于文件的大小的时候就将文件的大小赋值到切割结束位置
if (end > filesize) {
end = filesize;
}
// 切割文件
var chunk = blob.slice(start, end);
// 传参
var formData = new FormData();
formData.append("file", chunk, fileName);
formData.append("uploadId", code);
formData.append("fileDisplayName", fileName);
formData.append("partNum", partNum);
formData.append("remoteKey", key);
// 设置请求参数
let config = {
processData: false,
contentType: false,
cache: false
};
uploadPart(formData, config).then(res => {
// 找到预览列表中的位置
let index = that.previewList.findIndex(el => {
return el.fileKey == res.data.data.fileKey;
});
// 如果返回的status 的值不为 1 标识上传失败
if (res.data.status != 1) {
// 记录上传失败的文件
fail[res.data.data.fileKey] = 1;
// 在预览中将进度条切换为失败的进度条
let index = that.previewList.findIndex(el => {
return el.fileKey == res.data.data.fileKey;
});
that.previewList[index].isSuccess = false;
// 调用删除文件的接口 清除数据库里的冗余文件
that.abort(code, key);
// 显示input 可以继续上传
that.isShowInput = true;
} else {
// 先判断有没有上传失败的文件 如果有的话就直接清除掉冗余文件 并且不再上传文件
if (fail[res.data.data.fileKey] == 1) {
that.abort(code, key);
return;
}
// 记录上传成功一个分片
currentNum++;
// 进度条
that.previewList[index].uploadBar += 90 / totalPieces;
// 当文件的总的分片数 等会上传成功的数据的时候表示文件已经全部长传完成了。可以合并了。
if (currentNum == totalPieces) {
// 合并文件
that.merge(code, key);
} else if (max < totalPieces) {
// 如果没有全部上传文成 就继续调用上传文件的函数
uploadPartFn(max++, code, key, fileName);
}
}
});
}
// 给后台传参 如果使用 URLSearchParams 这个API 有兼容性问题 可以使用 qs 模块来处理参数
let params = new URLSearchParams();
params = {
remoteKey: event.fileKey
};
// 初始化上传文件
initMultipartUpload(params).then(res => {
let code = res.data.data.uploadId;
let key = res.data.data.key;
// 每次最多发送max个请求 节流 防止并发太大服务器崩溃
for (let i = 0; i < (totalPieces > max ? max : totalPieces); i++) {
// 上传每一个分片
uploadPartFn(i, code, key, fileName);
}
});
},
// 上传图片
uploadImg(event) {
let formData = new FormData();
formData.append("file", event);
formData.append("fileDisplayName", event.name);
formData.append("fileKey", event.fileKey);
// 上传图片的接口
PostUploadFile(formData).then(res => {
// 显示input
this.isShowInput = true;
// 当上传图片失败的时候就切换进度条为失败状态
if (res.data.status == 0) {
let index = this.previewList.findIndex(el => {
return el.fileKey == res.data.data.fileKey;
});
this.previewList[index].isSuccess = false;
this.$alert(res.data.errMsg);
return;
}
// 记录所有图片的id
this.fileIds.push(res.data.data.id);
// 进度条功能
if (res.data.status == 1) {
let index = this.previewList.findIndex(el => {
return el.fileKey == res.data.data.fileKey;
});
this.previewList[index].id = res.data.data.id;
this.previewList[index].err = "";
this.previewList[index].uploadBar = 100;
this.previewList[index].name = res.data.data.fileName;
}
});
},
// 分片的合并接口 进度条功能 为文件赋值
merge(uploadId, remoteKey) {
let param = new URLSearchParams();
param = {
uploadId,
remoteKey
};
completeMultipartUpload(param).then(res => {
let index = this.previewList.findIndex(el => {
return el.fileKey == res.data.data.fileKey;
});
if (res.data.status != 1) {
this.previewList[index].isSuccess = false;
this.abort(code, key);
this.isShowInput = true;
return;
}
this.previewList[index].id = res.data.data.id;
this.previewList[index].err = "";
this.previewList[index].uploadBar = 100;
this.previewList[index].name = res.data.data.fileName;
this.isShowInput = true;
});
},
// 分片上传失败 清除文件
abort(uploadId, remoteKey) {
let param = new URLSearchParams();
param = {
uploadId,
remoteKey
};
abortMultipartUpload(param).then(res => {
console.log(res);
});
},
网友评论