Vue大文件切片上传 断点续传

发布时间 2023-10-31 15:20:13作者: Xproer-松鼠

一、中等文件上传解决方案-nginx放行
在我们工作中,上传功能最常见的就是excel的上传功能,一般来说,一个excel的大小在10MB以内吧,如果有好几十MB的excel,就勉强算是中等文件吧,此时,我们需要设置nginx的client_max_body_size值,将其放开,只不过一次上传一个几十MB的文件,接口会慢一些,不过也勉强能够接受

二、大文件上传解决方案 -- 文件切片上传 断点续传
思路:

1、把大文件切成每块10M(按照你自己的要求),然后依次上传(为了让你好理解,你可以理解成分页,一共89条数据,每页数10条,一共9页)

2、上传完成后端把文件封装好,返回上传的url地址 加上进度条

3、中途上传中断,如果再次上传如何回到原来的位置继续上传,解决办法就是每次上传前给后端请求接口查验下,该文件上传到第几片了,和后端对接好chunk参数,比如-1代表已经上传完,0代表第0片,以此类推。

完善各种情况:比如发生错误,是否可以尝试重新上传,离开页面以后取消上传操作(离开页面以后,上传还在继续,这种情况除了上传还有计时器的问题,这里就不多说了,总之离开之前先清掉的思路)

PS:一定要上传完一个才能上传完下一个,是串行不是并行,另外,ajax一定要是异步的不管是原生还是jq的ajax,因为我开始弄成同步,progress事件根本出不来

后台需要提供三个接口

1、上传文件的接口

2、上传之前查验文件是否上传过,上传到第几片的接口

3、上传文件完成merge的接口

具体逻辑 Vue

<template>
<div>
<input id="file" name="file" type="file"/>
<el-button size="small" type="primary" id="startBtn">上传视频</el-button>
<el-progress style="width: 400px" v-if="percentage==100 && !isError" :percentage="percentage" status="success"></el-progress>
<el-progress style="width: 400px" :percentage="percentage" v-else-if="percentage > 0 && !isError && percentage < 100"></el-progress>
<el-progress style="width: 400px" :percentage="percentage" status="exception" v-else-if="isError"></el-progress>
</div>
</template>

<script>
export default {
data() {
return {
percentage: 0, // 进度条
isError: false, // 是否发生错误
request: null // ajax请求
}
},
mounted() {
var pecent;
var start;
var end;
var file;
var name;
var size;
var shardCount;
var i = 0;
var shardSize;
var GUID;

var status = 0;
var _this = this;

var page = {
init: function(){
$("#startBtn").click($.proxy(this.upload, this));
},
upload: function(){
status = 0;


file = $("#file")[0].files[0]; //文件对象

if (!file) {
_this.$message.warning('请选择文件');
return;
}
name = file.name; //文件名
size = file.size; //总大小
GUID = this.guid(file.name, file.lastModified, file.size, file.type);
shardSize = 10 * 1024 * 1024; //以1MB为一个分片
shardCount = Math.ceil(size / shardSize); //总片数

// 获取当前的片数
let formData = new FormData();
formData.append("md5", GUID);
getCurrentFileChunk(formData).then(res => {
if (res.result.chunk < 0) {
_this.form.videoUrl = res.result.url;
_this.percentage = 100;
} else {
status = res.result.chunk;
start = res.result.chunk * shardSize;
end = Math.min(size, start + shardSize);
var partFile = file.slice(start,end);


var pecent=100*(start * shardSize)/file.size;
_this.percentage = parseInt(pecent);

this.partUpload(GUID,partFile,name,shardCount,status);
}
});
},
partUpload:function(GUID,partFile,name,chunks,chunk){
// 重新上传的时候
_this.isError = false;

//构造一个表单,FormData是HTML5新增的
var now = this;
var form = new FormData();
form.append("md5", GUID);
form.append("file", partFile); //slice方法用于切出文件的一部分
form.append("chunk", chunk); //当前是第几片
//form.append("chunks", chunks); //总片数
//Ajax提交
_this.request = $.ajax({
url: process.env.API_F_URL + "/files/uploadVideo",
type: "POST",
data: form,
async: true, //同步
processData: false, //很重要,告诉jquery不要对form进行处理
contentType: false, //很重要,指定为false才能形成正确的Content-Type
success: function(data){
status++;
if (status < chunks) {
start = status * shardSize,
end = Math.min(size, start + shardSize);
var partFile = file.slice(start,end);
now.partUpload(GUID,partFile,name,shardCount,status);
}

// if(data.code == 200){
// $("#output").html(status+ " / " + chunks);
// }
if(status==chunks){
now.mergeFile(GUID,name, chunks);
}
},
error: function(err) {
console.log('err', err);
if (err.statusText === 'abort') {
_this.$message.warning('已取消上传');
} else {
_this.isError = true;
_this.$message.error('上传失败,请重新上传');

// 上传失败,再次上传
// start = status * shardSize,
// end = Math.min(size, start + shardSize);
// var partFile = file.slice(start,end);
// now.partUpload(GUID,partFile,name,shardCount,status);
}
},
xhr: function () {
//获取ajax中的ajaxSettings的xhr对象 为他的upload属性绑定progress事件的处理函数
var myXhr = $.ajaxSettings.xhr();
if (myXhr.upload) {
//检查其属性upload是否存在
myXhr.upload.addEventListener("progress", function(ev){
if(ev.lengthComputable){
pecent=100*(ev.loaded+start)/file.size;
if(pecent>99){
pecent=99;
}

_this.percentage = parseInt(pecent);
}
}, false);
}
return myXhr;
},
});
},
mergeFile:function(GUID,name,chunks){
var formMerge = new FormData();
formMerge.append("md5", GUID);
formMerge.append("fileName", name);
formMerge.append("chunks", chunks);
$.ajax({
url: process.env.API_F_URL + "/files/mergeVideo",
type: "POST",
data: formMerge,
processData: false, //很重要,告诉jquery不要对form进行处理
contentType: false, //很重要,指定为false才能形成正确的Content-Type
success: function(res){
if (res.status == 'success') {
_this.isError = false;
_this.form.videoUrl = res.result.url;
_this.percentage = 100;
} else {
_this.isError = true;
_this.$message.error('上传失败,请重新上传');
}
},
error: function(err) {
_this.isError = true;
_this.$message.error('上传失败,请重新上传');
}
});
},
guid:function(name, lastModified, size, type){
return md5(name+'#'+lastModified+'#'+size+'#'+type);
}
};
$(function(){
page.init();
});
},
beforeDestroy() {
this.request.abort();
}
}
</script>

参考文章:http://blog.ncmem.com/wordpress/2023/10/31/vue%e5%a4%a7%e6%96%87%e4%bb%b6%e5%88%87%e7%89%87%e4%b8%8a%e4%bc%a0-%e6%96%ad%e7%82%b9%e7%bb%ad%e4%bc%a0/

欢迎入群一起讨论