最近遇见一个需要上传百兆大文件的需求,调研了七牛和腾讯云的切片分段上传功能,因此在此整理前端大文件上传相关功能的实现。

在某些业务中,大文件上传是一个比较重要的交互场景,如上传入库比较大的Excel表格数据、上传影音文件等。如果文件体积比较大,或者网络条件不好时,上传的时间会比较长(要传输更多的报文,丢包重传的概率也更大),用户不能刷新页面,只能耐心等待请求完成。

下面从文件上传方式入手,整理大文件上传的思路,并给出了相关实例代码,由于PHP内置了比较方便的文件拆分和拼接方法,因此服务端代码使用PHP进行示例编写。

文件上传的几种方式

首先我们来看看文件上传的几种方式。

普通表单上传

使用PHP来展示常规的表单上传是一个不错的选择。首先构建文件上传的表单,并指定表单的提交内容类型为enctype="multipart/form-data",表明表单需要上传二进制数据。

然后编写index.php上传文件接收代码,使用move_uploaded_file方法即可(php大法好...)

$imgName = IMG.time()...str_replace(image/,,$_FILES["myfile"][type]);

$fileName = upload/.$imgName;

// 移动上传文件至指定upload文件夹下,并根据返回值判断操作是否成功

if (move_uploaded_file($_FILES[myfile][tmp_name], $fileName)){

echo $fileName;

}else {

echo "nonn";

}

form表单上传大文件时,很容易遇见服务器超时的问题。通过xhr,前端也可以进行异步上传文件的操作,一般由两个思路。

文件编码上传

第一个思路是将文件进行编码,然后在服务端进行解码,其主要实现原理就是将图片转换成base64进行传递

var imgURL = URL.createObjectURL(file);

ctx.drawImage(imgURL, 0, 0);

// 获取图片的编码,然后将图片当做是一个很长的字符串进行传递

var data = canvas.toDataURL("image/jpeg", 0.5);

在服务端需要做的事情也比较简单,首先解码base64,然后保存图片即可

$imgData = $_REQUEST[imgData];

$base64 = explode(,, $imgData)[1];

$img = base64_decode($base64);

$url = ./test.jpg;

if (file_put_contents($url, $img)) {

exit(json_encode(array(

url => $url

)));

}

base64编码的缺点在于其体积比原图片更大(因为Base64将三个字节转化成四个字节,因此编码后的文本,会比原文本大出三分之一左右),对于体积很大的文件来说,上传和解析的时间会明显增加。

除了进行base64编码,还可以在前端直接读取文件内容后以二进制格式上传

// 读取二进制文件

function readBinary(text){

var data = new ArrayBuffer(text.length);

var ui8a = new Uint8Array(data, 0);

for (var i = 0; i < text.length; i++){

ui8a[i] = (text.charCodeAt(i) & 0xff);

}

console.log(ui8a)

}

var reader = new FileReader();

reader.onload = function(){

readBinary(this.result) // 读取result或直接上传

}

// 把从input里读取的文件内容,放到fileReader的result字段里

reader.readAsBinaryString(file);

formData异步上传

FormData对象主要用来组装一组用 XMLHttpRequest发送请求的键/值对,可以更加灵活地发送Ajax请求。可以使用FormData来模拟表单提交。

let files = e.target.files // 获取input的file对象

let formData = new FormData();

formData.append(file, file);

axios.post(url, formData);

服务端处理方式与直接form表单请求基本相同。

iframe无刷新页面

在低版本的浏览器(如IE)上,xhr是不支持直接上传formdata的,因此只能用form来上传文件,而form提交本身会进行页面跳转,这是因为form表单的target属性导致的,其取值有

_self,默认值,在相同的窗口中打开响应页面_blank,在新窗口打开_parent,在父窗口打开_top,在最顶层的窗口打开framename,在指定名字的iframe中打开

如果需要让用户体验异步上传文件的感觉,可以通过framename指定iframe来实现。把form的target属性设置为一个看不见的iframe,那么返回的数据就会被这个iframe接受,因此只有该iframe会被刷新,至于返回结果,也可以通过解析这个iframe内的文本来获取。

function upload(){

var now = +new Date()

var id = frame + now

$("body").append(`