1.选择文件
我们可以通过 来获得文件,也可以通过拖拽来获得文件,还有就是通过new Blob来创建一个文件,前两种就不说了,今天就介绍一下Blob,详情查看Blob这一章节
第一,创建一个文件,如下代码所示:
//使用blob创建一个二进制文件 2个字节,所以blob.size = 2
function blobToFile() {
let blob = new Blob(["ab"],{ type: 'text/plain' })
blob.lastModifiedDate = new Date();
blob.name = 'a.txt';
return blob;
}
第二,使用formData,添加进去
这里使用的是blob创建的文件,也可以直接使用file类型的文件
const formData = new FormData();
formData.append("file",blobToFile());
formData.append("name","yzy");
第三,就是创建一个ajax
注意:这里是不能设置content-type的,手动设置时没有边界boundary进行分割的,数据不需要再进行转换,直接使用formData对象,第三这个方法只支持POST是不支持GET的,如下代码是我的简写
function ajax (config){
let xhr = new XMLHttpRequest();
let {url,data,success,error} = config;
xhr.open("POST", url, true);
xhr.send(data);
xhr.onreadystatechange=function() {
if(xhr.readyState==4){
if(xhr.status>=200 && xhr.status<300){
success(xhr.responseText);
}else{
if(error!=null){error("请求失败,错误代码:"+xhr.status);}
}
}
};
}
第四,就是调用ajax去异步请求,到这里就结束了
ajax({
url:"http://192.168.2.138:1083/api/upload",
data:formData,
success(text){
console.log(text);
},
error(){}
})
这里以Node koa为例,介绍一下后台:
//使用koa-body 接收表单
const koaBody = require('koa-body');
app.use(koaBody({
multipart: true,
formidable: {
maxFileSize:20*1024*1024 // 设置上传文件大小最大限制,默认2M
}
}));
//使用koa-router处理请求接口
router.post("/upload",async (ctx,next)=>{
//获取文件
console.log(ctx.request.files.file)
//获取其他的参数
console.log(ctx.request.body.name)
//返回结果,响应200
return ctx.body = {
};
})
2.文件上传进度
在XML请求章节中介绍了onpropress事件,这个事件就是上传进度的事件,在上传期间间隔性的触发,通过其属性可以计算出上传的百分比的,如下代码所示,一定要放在open方法执行前,loaded属性是上传的字节数,total是总的字节数,它们的比值就是上传的进度
xhr.onprogress = function(ev) {
if (ev.total > 0) {
console.log(ev.loaded / ev.total * 100);
}
}
3. 文件切片
切片上传
**思路:**前台通过Blob.slice方法对文件经行切片,然后追个进行上传切片这个切片可以放在后台的某个文件夹下,名称可以使用编号顺序命名,然后待切片全部上传成功后,前台再发送一个请求到后台,让后台进行切片的合并,然后返回成功就行了
断点续传
**思路:**我们在切片上传时,每个切片上传成功就标记成功,如果有失败的则标记失败,再下次继续上传时,只对没有上传成功的切片进行上传就可以了
代码实现
简单的实现,只是介绍一下思路,代码是还可以更加优化的,具体情况要根据具体的项目区实现,其中断点续传是将其切片信息保存起来,以便于以后经行直接展示已经上传成功的和从新上传切片失败的,避免了重复上传
let blob = blobToFile();
//标记切片顺序
let count = 0;
//blob开始切片的位置
let start = 0;
//每次切片限制的大小
let limit = 2;
//存储切片信息,及是否上传成功
let blobList = [];
while(start<blob.size){
const formData = new FormData();
let b = blob.slice(start,start+limit);
formData.append("file",b);
formData.append("sort",count);
ajax({
url:"http://192.168.2.138:1083/api/upload",
data:formData,
success(text){
blobList.push({
start:start,
end:start+limit,
sort:count,
isOk:true
})
},
error(){
blobList.push({
start:start,
end:start+limit,
sort:count,
isOk:false
})
}
})
start = start+limit;
count++;
}
4.完整实现
如下代码所示,在进行切片上传时,值得注意的是xhr.upload.onprogress才是上传监听事件,xhr.onprogress是下载的监听事件,还有一点就是要创建一个工厂函数,将i值传递进去,并返回一个函数,这个返回的函数每一个都是独立的,可以作为每个进度监听事件的处理函数
let input = document.querySelector("input");
let p = {
//将每个切片的大小存放进这个数组,当其变化时覆盖其对应的值
load:[],
//总的切片上传大小
loaded:0
}
input.addEventListener("change",(ev)=>{
//获取文件
let file = ev.target.files[0],
i = 0, //定义索引,标记切片的顺序
limit = 400*1024, //限制切片的大小
length = Math.ceil(file.size/limit), //求出切片的个数
start = 0 //切片的开始位置
;
//接触p.loaded的变化,当其变化时求解切片的上传数量
let value = p.loaded;
Object.defineProperty(p,"loaded",{
get(){
return value
},
set(val){
value =val;
//求解切片的上传大小之和
let sum = p.load.reduce((prev,cur)=>{
return prev+cur;
});
//求解上传进度
console.log("进度:"+parseInt(sum/file.size*100)/100)
}
});
//对每个切片进行上传
while(i<length){
const formData = new FormData();
let f = file.slice(start,start+limit);
formData.append(`f_${i}`,f);
ajax({
url:"http://192.168.2.138:1083/api/upload",
data:formData,
success(text){
console.log("成功");
},
//给每个进度事件分配一个独立的处理函数
updateProgress:updateProgress(i),
error(){
}
});
start += limit;
i++
}
})
//创建的工厂函数,生成一个函数
function updateProgress(i){
return e => {
p.load[i] = e.loaded;
p.loaded = e.loaded;
}
}
function ajax (config){
let xhr = new XMLHttpRequest();
let {url,data,success,error,updateProgress} = config;
xhr.upload.onprogress = updateProgress;
xhr.timeout = 10000;
xhr.open("POST", url, true);
xhr.send(data);
xhr.onreadystatechange=function() {
if(xhr.readyState==4){
if(xhr.status>=200 && xhr.status<300){
success(xhr.responseText);
}else{
if(error!=null){error("请求失败,错误代码:"+xhr.status);}
}
}
};
}
5.结果
如下就是进度的百分比,如果你使用Vue,可以使用计算属性,在这里我利用的是Object.definePropert进行数据的劫持,但在实现的过程中发现一个问题就是文件切片的大小,和通过formData之后Progress事件中的ev.total之和不是一样的,每个ev.total比原切片要大一些,如果要精确计算,可以在对象P中新建一个total数组用来存放每个ev.total的值,最后相加计算总的文件大小
进度:0.08
进度:0.15
进度:0.17
进度:0.19
进度:0.2
进度:0.22
进度:0.23
进度:0.29
进度:0.36
进度:0.43
进度:0.48
成功
进度:0.57
成功
进度:0.65
4成功
进度:0.73
进度:0.81
进度:0.89
进度:0.97
成功
进度:1
6成功