大文件上传 问题解决三种方案

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

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

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

本文相关示例代码位于github上,主要参考

  • 聊聊大文件上传
  • 大文件切割上传

文件上传的几种方式

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

普通表单上传

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

 
  

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

 
  
  1. $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,然后保存图片即可

 
  
  1. $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
    
    )));
    
    }

  2. 复制代码

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

更多关于base64的知识,可以参考Base64笔记。

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

 
  
  1. // 读取二进制文件
    
    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来模拟表单提交。

 
  
  1. 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(`