Javascript大文件上传

100 阅读4分钟

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成功