最近,我遇到了需要上传大型文件的需求,并研究了七牛和腾讯云的切片分段上传功能。因此,我整理了前端大型文件上传相关功能的实现。
在某些业务中,大文件上传是一个更重要的交互场景,如上传到仓库Excel表格数据、上传影音文件等。如果文件体积比较大,或者网络条件不好时,上传的时间会比较长(要传输更多的报文,丢包重传的概率也更大),用户不能刷新页面,只能耐心等待请求完成。
下面从文件上传的方式入手,整理大文件上传的思路,给出相关实例代码,因为PHP文件拆分便的文件拆分和拼接方法,因此使用服务代码PHP编写示例。
本文相关示例代码位于github上,主要参考
谈大文件上传
切割上传大文件
上传文件的几种方式
首先,让我们来看看文件上传的几种方式。
上传普通表格
使用PHP来展示常规的表单上传是一个不错的选择。首先构建文件上传的表单,并指定表单的提交内容类型为enctype="multipart/form-data",二进制数据需要上传表单。
然后编写index.php上传文件接收代码,使用move_uploaded_file方法即可(php大法好…)
form当表单上传大文件时,服务器很容易超时。xhr,前端还可以异步上传文件,一般有两种思路。
上传文件编码
第一个想法是编码文件,然后在服务端解码。之前写过一篇博客,在前端压缩上传图片。实现的主要原理是将图片转换为base64进行传递
varimgURL = URL.createObjectURL(file);
ctx.drawImage(imgURL, 0, 0);
//获取图片的编码,然后将图片作为一个长字符串传输
vardata= 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知识4知识Base64笔记。
除了进行base64编码也可以在前端直接读取文件内容后以二进制格式上传
//读取二进制文件
functionreadBinary(text){
vardata = newArrayBuffer(text.length);
varui8a = newUint8Array(data, 0);
for( vari = 0; i < text.length; i ){
ui8a[i] = (text.charCodeAt(i) & 0xff);
}
console.log(ui8a)
}
varreader = newFileReader;
reader. = function{
readBinary( this.result) //读取result或直接上传
}
//把从input读取的文件内容放在里面fileReader的result字段里
reader.readAsBinaryString(file);
formData异步上传
FormData对象主要用于组装一组 发送请求的键/值对可以更灵活地发送Ajax请求。可以使用FormData提交模拟表单。
letfiles = e.target.files //获取input的file对象
letformData = newFormData;
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获取内部文本。
functionupload{
varnow = newDate
varid = 'frame' now
$( "body").append( `<iframe style="display:none;" name="${id}" id="${id}" />`);
var$form = $( "#myForm")
$form.attr({
"action": '/index.php',
"method": "post",
"enctype": "multipart/form-data",
"encoding": "multipart/form-data",
"target": id
}).submit
$( "#" id).on( "load", function{
varcontent = $( this).contents.find( "body").text
try{
vardata = JSON.parse(content)
} catch(e){
console.log(e)
}
})
}
大文件上传
现在我们来看看上面提到的大文件上传会遇到的超时问题,
表单上传和iframe无刷新页面上传实际上是通过form标签上传文件,将整个请求完全交给浏览器。上传大文件时,可能会遇到请求加班的情况
通过fromData,其实也是xhr用于模拟表单请求的中包装一组请求参数,无法避免大文件超时上传的问题
编码上传,我们可以灵活控制上传的内容
大文件上传的主要问题是:在相同的请求中,上传大量大量的数据,导致整个过程相对较长,失败后需要重新开始上传。想象一下,如果我们将这个请求分为多个请求,每个请求的时间就会缩短,如果一个请求失败,我们只需要重新发送这个请求,而不需要从头开始能解决大文件上传的问题吗?
基于以上问题,上传大文件似乎需要满足以下需求
支持拆分上传请求(即切片)
支持断点续传
支持显示上传进度和暂停上传
接下来,让我们依次实现这些功能。最重要的功能应该是切片。
文件切片
参考: 切割上传大文件
在编码模式上传中,我们只需要在前端获取文件的二进制内容,然后拆分其内容,最后将每个切片上传到服务端。
在Java中,文件FIle对象是Blob对象的子类,Blob对象包含一种重要的方法slice,通过这种方法,我们可以拆分二进制文件。
以下是拆分文件的例子。up6.开发人员不需要关心拆分的细节,而是在控件的帮助下实现,开发人员只需要关心业务逻辑。
上传控件时,每个文件块的数据都会添加相关信息,开发人员在服务端接收数据后可自行处理。
接收到这些切片后,服务器可以拼接它们。PHP拼接切片的示例代码
对于up开发人员不需要拼接,up提供了示例代码,实现了这一逻辑。
为了将为每个文件块添加信息,如块索引、块索引等MD5,文件MD5
断点续传
up6自带续传功能,up6.文件信息已保存在服务端,文件进度信息也保存在客户端。上传时,控件会自动加载文件进度信息,开发人员不需要关注这些细节。文件块的处理逻辑只需要遵循件块索引来识别即可。
此时上传时刷新页面或者关闭浏览器,再次上传相同文件时,之前已经上传成功的切片就不会再重新上传了。
服务端实现断点续传的逻辑基本相似,只要在getUploadSliceRecord内部调用服务端的查询接口获取已上传切片的记录即可,因此这里不再展开。
此外断点续传还需要考虑切片过期的情况:如果调用了mkfile接口,则磁盘上的切片内容就可以清除掉了,如果客户端一直不调用mkfile的接口,放任这些切片一直保存在磁盘显然是不可靠的,一般情况下,切片上传都有一段时间的有效期,超过该有效期,就会被清除掉。基于上述原因,断点续传也必须同步切片过期的实现逻辑。
续传效果
上传进度和暂停
通过xhr.upload中的progress方法可以实现监控每一个切片上传进度。
上传暂停的实现也比较简单,通过xhr.abort可以取消当前未完成上传切片的上传,实现上传暂停的效果,恢复上传就跟断点续传类似,先获取已上传的切片列表,然后重新发送未上传的切片。
由于篇幅关系,上传进度和暂停的功能这里就先不实现了。
实现效果:
小结
目前社区已经存在一些成熟的大文件上传解决方案,如七牛SDK,腾讯云SDK等,也许并不需要我们手动去实现一个简陋的大文件上传库,但是了解其原理还是十分有必要的。
本文首先整理了前端文件上传的几种方式,然后讨论了大文件上传的几种场景,以及大文件上传需要实现的几个功能
通过Blob对象的slice方法将文件拆分成切片
整理了服务端还原文件所需条件和参数,演示了PHP将切片还原成文件
通过保存已上传切片的记录来实现断点续传
还留下了一些问题,如:合并文件时避免内存溢出、切片失效策略、上传进度暂停等功能,并没有去深入或一一实现,继续学习吧
后端代码逻辑大部分是相同的,目前能够支持MySQL,Oracle,SQL。在使用前需要配置一下数据库,可以参考我写的这篇文章:java http大文件断点续传上传 欢迎入群一起讨论:374992201