最近有一个上传视频到服务器的功能,然后发现视频太大了,比如1个G的视频文件基本都是上传失败的,我之前都是上传阿里云的,所以面对大文件上传服务器就做了分片上传和断点续传。
首先解释什么是分片上传:比如一个文件是22M,我令5M为一片那么就可以分5片,一片一片上传给后端然后都上传完成开始合并这个文件。
断点续传:还是以上面的例子,有5片小文件,比如我传第一片的时侯没问题,第二片,第三片因为网络啊什么的原因打断了,第四片,第五片又上传成功了;那我下一次传相同的文件时,后端会返回给我那几片没成功,那么我只要传没成功的分片就可以,全部传完就可以调用合并文件接口。
接下来我就说说自己项目这个大文件视频是怎么进行分片断点续传的,代码里有注释,菜鸡写的不好,轻点喷
1.上传文件然后自定义上传
<el-upload
class="upload-demo"
action="#"
:http-request="requestUpload"
:show-file-list="false"
:before-upload="beforeUpload"
drag
:accept="'video/*'"
v-if="file === null"
>
<i class="el-icon-upload"></i>
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
<div class="el-upload__tip" slot="tip">只能上传视频文件</div>
</el-upload>
<div v-else>
<p>{{ file.name }}</p>
<el-progress :percentage="percentage" v-if="percentage !== 0"></el-progress>
</div>
// 覆盖默认的上传行为
requestUpload() {},
//上传前的操作,可以对文件做一些限制
beforeUpload(file) {
if (
file.type != "video/mp4"
) {
this.$modal.msgError("视频上传只支持Mp4格式");
return false;
} else {
this.file = file;
}
},
然后点击确定进行上传到服务器
/** 确定提交按钮 */
submitForm() {
this.$refs["form"].validate((valid) => {
if (valid) {
if (this.file != null) {
//点击确定显示loading效果
this.confirmLoading = true;
//重置进度条为0
this.percentage = 0;
//这里走分片上传逻辑
this.uploadByPieces({
file: this.file, //视频实体
pieceSize: 5, //分片大小
success: (data) => {
this.confirmLoading = false;
this.percentage = 100;
this.$modal.msgSuccess("新增成功");
this.open = false;
this.getList();
},
error: (e) => {
this.confirmLoading = false;
this.file = null;
this.percentage = 0;
this.$modal.msgError("分片上传视频失败");
},
});
} else {
this.$modal.msgError("请上传视频");
}
}
});
},
//断点分片上传
uploadByPieces({ file, pieceSize = 2, success, error }) {
// 上传过程中用到的变量
let fileMD5 = ""; // md5加密文件的标识
const chunkSize = pieceSize * 1024 * 1024; // 分片大小
const chunkCount = Math.ceil(file.size / chunkSize); // 总片数
//得到某一片的分片
const getChunkInfo = (file, currentChunk, chunkSize) => {
let start = currentChunk * chunkSize;
let end = Math.min(file.size, start + chunkSize);
let chunk = file.slice(start, end);
return chunk;
};
// 第一步
const readFileMD5 = () => {
// 读取视频文件的md5
console.log("获取文件的MD5值");
//得到第一片和最后一片
const startChunk = getChunkInfo(file, 0, chunkSize);
const endChunk = getChunkInfo(file, chunkCount - 1, chunkSize);
console.log(startChunk, endChunk);
//对第一片进行转码然后md5加密,网上很多是直接对整个文件转码加密得到标识,但是我发现大文件尤其是几个G的文件会崩溃,所以我是先分片然后取第一片加密
let fileRederInstance = new FileReader();
fileRederInstance.readAsBinaryString(startChunk);
fileRederInstance.addEventListener("load", (e) => {
let fileBolb = e.target.result;
fileMD5 = md5(fileBolb);
//检查文件有没有上传过的状态
uploadCheckAxios({ identifier: fileMD5, totalChunks: chunkCount })
.then((res) => {
if (res.data.needMerge == true) {
console.log("文件都已上传,现在需要合并");
//调用合并接口,合并接口后端做了异步处理不然会超时
let time = new Date().getTime();
mergeFileAxios({
name: this.form.name,
subjectName: this.form.subjectName,
teacherName: this.form.teacherName,
categoryId: this.form.categoryId,
description: this.form.description,
identifier: fileMD5,
totalSize: file.size,
filename: time + "_" + file.name,
})
.then((res) => {
console.log("文件合并成功");
success && success(res);
})
.catch((e) => {
console.log(e, "文件合并错误");
error && error(e);
});
} else {
//这里很多博客都是直接for循环,可我实际联调发现for循环不等返回,虽然上传对顺序没影响但是很多分片其实是超时了会上传失败,所以我处理成数组,从需要上传的数组第一个分片上传有了返回后就从前面删掉数组元素直到数组为[]
//文件未被上传
if (res.data.status === 0) {
console.log("文件未被上传");
//整理需要上传的分片的数组
let needUploadList = [];
for (let i = 0; i < chunkCount; i++) {
needUploadList.push(i);
}
console.log(needUploadList);
readChunkMD5(needUploadList);
}
//文件已被上传过一部分
else {
let arr = res.data.uploaded;
//如果上传过,进度条开始的数据要计算一下
let per = 100 / chunkCount;
this.percentage = Number((arr.length * per).toFixed(2));
console.log(this.percentage);
console.log(arr);
let needUploadList = [];
console.log("文件已被上传过一部分");
//整理需要上传的分片的数组
for (let i = 0; i < chunkCount; i++) {
if (!arr.includes(i)) {
needUploadList.push(i);
}
}
console.log(needUploadList);
readChunkMD5(needUploadList);
}
}
})
.catch((e) => {
error && error(e);
});
});
};
// 针对每个分片文件进行上传处理
const readChunkMD5 = (needUploadList) => {
if (needUploadList.length > 0) {
let i = needUploadList[0];
console.log(i);
//得到当前需要上传的分片文件
const chunk = getChunkInfo(file, i, chunkSize);
let fetchForm = new FormData();
//后端需要的参数
fetchForm.append("chunkNumber", i);
fetchForm.append("chunkSize", chunkSize);
fetchForm.append("currentChunkSize", chunk.size);
fetchForm.append("file", chunk);
fetchForm.append("filename", fileMD5 + "-" + i);
fetchForm.append("identifier", fileMD5);
fetchForm.append("totalChunks", chunkCount);
fetchForm.append("totalSize", file.size);
//上传接口
uploadVideoChunkAxios(fetchForm)
.then((res) => {
console.log(res);
//都上传了,等待合并
if (res.data === true) {
console.log("文件开始合并");
let time = new Date().getTime();
mergeFileAxios({
name: this.form.name,
subjectName: this.form.subjectName,
teacherName: this.form.teacherName,
categoryId: this.form.categoryId,
description: this.form.description,
identifier: fileMD5,
totalSize: file.size,
filename: time + "_" + file.name,
})
.then((res) => {
console.log("文件合并成功");
success && success(res);
})
.catch((e) => {
console.log(e, "文件合并错误");
error && error(e);
});
} else {
//每上传一个就在进度条上加数据
let per = 100 / chunkCount;
console.log(per, this.percentage);
let totalPrecent = Number((this.percentage + per).toFixed(2));
if (totalPrecent > 100) {
this.percentage === 100;
} else {
this.percentage = totalPrecent;
}
console.log(this.percentage);
let newArr = JSON.parse(JSON.stringify(needUploadList));
if (newArr.length > 0) {
newArr.shift();
readChunkMD5(newArr);
}
}
})
.catch((e) => {
error && error(e);
});
} else {
console.log("上传结束");
}
};
readFileMD5(); // 开始执行代码
},
就到这里啦,结束