参考:https://blog.csdn.net/aituochang1886/article/details/101167667
思路
- 把大文件切成每块10M(按照你自己的要求),然后依次上传(为了让你好理解,你可以理解成分页,一共89条数据,每页数10条,一共9页)。
- 上传完成后端把文件封装好,返回上传的url地址
- 加上进度条
- 中途上传中断,如果再次上传如何回到原来的位置继续上传,解决办法就是每次上传前给后端请求接口查验下,该文件上传到第几片了,和后端对接好chunk参数,比如-1代表已经上传完,0代表第0片,以此类推。
- 完善各种情况:比如发生错误,是否可以尝试重新上传,离开页面以后取消上传操作(离开页面以后,上传还在继续,这种情况除了上传还有计时器的问题,这里就不多说了,总之离开之前先清掉的思路)
PS:一定要上传完一个才能上传完下一个,是串行不是并行,另外,ajax一定要是异步的不管是原生还是jq的ajax,因为我开始弄成同步,progress事件根本出不来
我做这个的最大感悟就是一点点做起来的一个完善的东西,都是在开发中不断完善的,一开始不会考虑到那么多情况,或者你先不要考虑那么多,先把基础做了,慢慢叠加功能。我是先把1和2做了以后才慢慢加上3、4、5,这是一个很自然的过程,如果你一上来都要实现,对我而言亚历山大。
后台需要提供三个接口:
- 上传文件的接口
- 上传之前查验文件是否上传过,上传到第几片的接口
- 上传文件完成merge的接口
完整代码一:vue版本
(vue+js+ajax+md5+element-ui)
<input id="file" name="file" type="file"/>
<el-button size="small" type="primary" id="startBtn">上传视频</el-button>
<div>
<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>
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();
}
不完整代码二:只实现了分片上传+进度条(html+jq)
自己看vue版本的也能把这版弄完整,但是我太懒了,不想弄了。大家自己动手吧
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<script src="http://libs.baidu.com/jquery/2.1.4/jquery.min.js"></script>
<script src="http://www.gongjuji.net/Content/files/jquery.md5.js"></script>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
</head>
<body>
<div id="uploader">
<div class="btns">
<input id="file" name="file" type="file"/>
<br>
<br>
<button id="startBtn">
开始上传
</button>
<div id="upimg">
<div id="load"></div>
</div>
</br>
</br>
</div>
<div id="output">
</div>
</div>
</body>
<script type="text/javascript">
var des=document.getElementById('load');
var num=document.getElementById('upimg');
var pecent;
var start;
var file;
var name;
var size;
var shardCount;
var i = 0;
var shardSize;
var GUID;
var status = 0;
var page = {
init: function(){
$("#startBtn").click($.proxy(this.upload, this));
},
upload: function(){
status = 0;
file = $("#file")[0].files[0]; //文件对象
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); //总片数
start = 0,
end = Math.min(size, start + shardSize);
var partFile = file.slice(start,end);
this.partUpload(GUID,partFile,name,shardCount,0);
},
partUpload:function(GUID,partFile,name,chunks,chunk){
//构造一个表单,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提交
$.ajax({
url: "/api-f/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);
}
},
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>100){
pecent=100;
}
des.style.width=pecent+'%';
des.innerHTML = 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: "/api-f/files/mergeVideo",
type: "POST",
data: formMerge,
processData: false, //很重要,告诉jquery不要对form进行处理
contentType: false, //很重要,指定为false才能形成正确的Content-Type
success: function(data){
if(data.code == 200){
alert('上传成功!');
}
}
});
},
guid:function(name, lastModified, size, type){
return $.md5(name+'#'+lastModified+'#'+size+'#'+type);
// var counter = 0;
// var guid = (+new Date()).toString( 32 ),
// i = 0;
// for ( ; i < 5; i++ ) {
// guid += Math.floor( Math.random() * 65535 ).toString( 32 );
// }
// return (prefix || 'wu_') + guid + (counter++).toString( 32 );
}
};
$(function(){
page.init();
});
</script>
</html>