AJAX技术实现浏览器分片上传文件

AJAX技术实现浏览器分片上传文件

本技术一定要支持XMLHttpRequest Level 2的浏览器环境下运行(简单来说是IE9以下不支持),设计的主要思路如下:

  • File对象转换为FormData对象
  • 对FormData对象进行切片
  • 使用AJAX技术上传分片并监控上传信息

File对象转换为FormData对象

file对象
file对象是我们从input[type=”file”]标签(常用的文件输入框)与drop事件(常用的拖拽文件动作)中获取到的,记录文件信息的对象,我们可以从中获取到诸如文件名称name,文件大小size,文件类型type,文件内容file等属性值。

本质上,file对象是一种升级后的blob对象(表示一个不可变的, 原始数据的类似文件对象),我们可以采用同样的办法将某些文件中获取到的底层二进制流按照此办法进行分片传输,从而提高我们传输文件的效率和可操作性(比如某些时候我们一个文件夹中存储了多个图片、记录等二进制数组信息,我们可以按照文件标准对blob进行slice操作截取出需要进行上传的某一片目标段落进行上传,避免浪费大量资源在传输多余的信息内容上)。

FormData对象
FormData则是我们进行表单提交时产生的对象,效果与form标签整理页面数据的功能相同,本质上FormData就是form标签从html页面上提取输入信息后整理出的包含数据的对象。XMLHttpRequest Level 2接口的引入,使得在新式浏览器上,我们可以通过new FormData();的方式初始化一个空白的表单数据对象,通过append方法为其中添加数据。

//下面这个formData表单数据对象中就会包含有文件大小size,文件类型type,文件名称name和文件内容file信息
function fileToFormData (file){
    var formData = new FormData();
    formData.append("file",file);
    formData.append("name",file.name);
    formData.append("type",file.type);
    formData.append("size",file.size);
    return formData;
}

formData既然是表单数据,我们在传输文件数据流的时候大可以一并捎带上我们的其他需要传输的页面获取信息,便于我们通过这些附加信息区分文件归属,建立文件与数据之间的关联。

//下面这个formData表单数据对象中就会包含有文件大小size,文件类型type,文件名称name和文件内容file信息
//经过改进后,FormData获取方法可以满足文件上传、数据上传、文件+数据上传的功能了
function getFormData (data=null,file=null){
    var formData = new FormData();
    if(data!=null){
        formData.append("data",JSON.stringify(data));
    }
    if(file!=null){
        formData.append("file",file);
        formData.append("name",file.name);
        formData.append("type",file.type);
        formData.append("size",file.size);
    }
    return formData;
}

对formData对象进行切片

对于头像等等size较小的文件,我们可以通过常规的提交方式进行提交。占用网络的资源较小,等待的时间较少,我们几乎可以在上传的数秒至一二分钟里收到执行情况,决定我们下一步是完成上传了还是需要重新提交。但是对于视频等较大信息量的文件,1G、2G信息等待时间非常漫长,期间,还极有可能因为网络连接的暂时中断而使整个上传操作前功尽弃。因此,为了保证我们的上传稳定,“化整为零”分而传之,是上传大型文件必须具备的功能。

formData对象本身不具备分片方法,也不存在切formData对象的逻辑(不可能将formData的头、尾分别传送,这样会造成服务端完全无法读懂前端传来的数据)。我们的分片逻辑是针对file对象而言的,将file存储的二进制数据切片,在后端可以通过file的拼接方法实现对片段内容的拼接,诸如PHP的file_put_contents()。

但是,引入了分片概念,就不得不需要再去处理“拼接概念”,服务端收到的分片是离散的、甚至是无序的。为了提高我们的传输效率,我们在AJAX提交时也采用并行提交的方式。分片到达服务端的时间是不一定的,有可能最后一片甚至要早于第一片到达。为了便于后端识别片段的先后,我们必须将分片的位置记录在formData对象中供后端对照。同时,为了避免并行上传时formData数量过多占用内存过大。我们可以采用“现切现传”的方式缓解资源浪费的情况(不传的片段不要切好等待,每一个formData对象都是要实际占用一定空间的)。

//shard是我们当前片段的位置,可以理解为当前需要准备的是第0,1,...,n-1片片段
//shardCount是总分片数,计算方式为shardCount=Math.ceil(file.size/shardSize);
//shardSize是分片大小,当然,目前考虑的还是固定shardSize的分片方式。也可以根据先前的上传情况生成动态的shardSize,便于适应不同用户的网络状况,但要重新更换为shardSize数组的概念来切片,不能在采用shardSize*shard的方式来定位分片位置了(毕竟前面的shardSize可能大于或者小于当前的shardSize)
function getFormData (shard=0,shardCount=0,shardSize=0,data=null,file=null){
    var formData = new FormData();
    if(data!=null){
        formData.append("data",JSON.stringify(data));
    }
    if(file!=null){
        var start = shard*option.shardSize;
        var end   = Math.min(file.size,start+shardSize);//在file.size和start+shardSize中取最小值,避免切片越界
        formData.append("file",file.slice(start,end));
        formData.append("name",file.name);
        formData.append("type",file.type);
        formData.append("size",file.size);
        formData.append("shard",shard);
        formData.append("shardCount",shardCount);//shardCount传给服务端便于服务端判断文件上传是否完成
    }
    return formData;
}

使用AJAX技术上传分片并监控上传信息

AJAX技术作为我们上传工作的核心,提供了丰富的传输监控端口,对传输工作的封装也使得我们能够更加便捷地实现对多种信息的提交和对服务端响应的监控回调。AJAX技术是通过XMLHttpRequest对象统一封装实现的,因此不论是$.ajax还是其他诸多再封装的AJAX操作方式,我们都可以抓住本源XMLHttpRequest对象来实现定制我们需要的功能。

//id用于我们唯一定位一次ajax请求,可以采用自增量的方式设置。通过id我们可以唯一查找当前XMLHttpRequest事件对应的XMLHttpRequest对象。

function ajax(id,url,data,onprogress,onloadstart,onload,onerror,onabort){
    var xhr = new XMLHttpRequest();
    var dateTime = new Date().getTime();
    xhr.upload.onprogress = function(event){
        onprogress(event,dateTime,id,xhr);//onprogress是上传状态监控端口
    }
    xhr.onloadstart = function(event){
        onloadstart(event,dateTime,id,xhr);//onloadstart是上传开始监控端口
    }
    xhr.onload = function(event){
        onload(event,dateTime,id,xhr,JSON.parse(xhr.responseText));//onload是上传结束监控端口
    }
    xhr.onerror = function(event){
        var end = false;
        if(typeof(onerror)=="function"){
            //onerror是上传错误监控端口
            end = onerror(event,dateTime,id,xhr);
        }
        if(end){
            //当回调表示需要重传的时候,进行当前请求的重新提交
            xhr.open('POST',url,true);
            xhr.send(data);
        }else{
            return;
        }
    }
    xhr.onabort = function(event){
        onabort (event,dateTime,id,xhr);//onabort 是上传终止监控端口
    }
    xhr.open('POST',url,true);//open开启数据连接
    xhr.send(data);//send发送数据
    return;
}

完成了如上AJAX接口的封装,我们的分片上传文件方法就可以通过简洁的调用实现了

function sendFileByAjax(file,data,url,ajaxid){
    var shardSize = 3*(1<<20);//3Mb一片
    var shardCount=Math.ceil(file.size/shardSize);
    for(var shard = 0;shard < shardCount;shard ++){
        ajax(ajaxid,url,getFormData(shard,shardCount,shardSize,data,file));
    }
}


以上技术还有许多可以扩展的地方,诸如动态分片大小,中断分情况重传,监控上传进度,测算上传速度等。均可以通过XMLHttpRequest提供的端口实现。第一次写播,希望大家能够给予指点纠正。

你可能感兴趣的:(AJAX技术实现浏览器分片上传文件)