You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
var imgURL = URL.createObjectURL(file);
ctx.drawImage(imgURL, 0, 0);
// 获取图片的编码,然后将图片当做是一个很长的字符串进行传递
var data = canvas.toDataURL("image/jpeg", 0.5);
复制代码
// 读取二进制文件
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);
复制代码
function upload(){
var now = +new Date()
var id = '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(){
var content = $(this).contents().find("body").text()
try{
var data = JSON.parse(content)
}catch(e){
console.log(e)
}
})
}
复制代码
在某些业务中,大文件上传是一个比较重要的交互场景,如上传入库比较大的Excel表格数据、上传影音文件等。如果文件体积比较大,或者网络条件不好时,上传的时间会比较长(要传输更多的报文,丢包重传的概率也更大),用户不能刷新页面,只能耐心等待请求完成。
下面从文件上传方式入手,整理大文件上传的思路,并给出了相关实例代码,由于PHP内置了比较方便的文件拆分和拼接方法,因此服务端代码使用PHP进行示例编写。
本文相关示例代码位于github上,主要参考
文件上传的几种方式
首先我们来看看文件上传的几种方式。
普通表单上传
使用PHP来展示常规的表单上传是一个不错的选择。首先构建文件上传的表单,并指定表单的提交内容类型为
enctype="multipart/form-data"
,表明表单需要上传二进制数据。然后编写
index.php
上传文件接收代码,使用move_uploaded_file
方法即可(php大法好...)form表单上传大文件时,很容易遇见服务器超时的问题。通过xhr,前端也可以进行异步上传文件的操作,一般由两个思路。
文件编码上传
第一个思路是将文件进行编码,然后在服务端进行解码,之前写过一篇在前端实现图片压缩上传的博客,其主要实现原理就是将图片转换成base64进行传递
在服务端需要做的事情也比较简单,首先解码base64,然后保存图片即可
base64编码的缺点在于其体积比原图片更大(因为Base64将三个字节转化成四个字节,因此编码后的文本,会比原文本大出三分之一左右),对于体积很大的文件来说,上传和解析的时间会明显增加。
更多关于base64的知识,可以参考Base64笔记。
除了进行base64编码,还可以在前端直接读取文件内容后以二进制格式上传
formData异步上传
FormData对象主要用来组装一组用 XMLHttpRequest发送请求的键/值对,可以更加灵活地发送Ajax请求。可以使用FormData来模拟表单提交。
服务端处理方式与直接form表单请求基本相同。
iframe无刷新页面
在低版本的浏览器(如IE)上,xhr是不支持直接上传formdata的,因此只能用form来上传文件,而form提交本身会进行页面跳转,这是因为form表单的target属性导致的,其取值有
framename
,在指定名字的iframe中打开如果需要让用户体验异步上传文件的感觉,可以通过
framename
指定iframe来实现。把form的target属性设置为一个看不见的iframe,那么返回的数据就会被这个iframe接受,因此只有该iframe会被刷新,至于返回结果,也可以通过解析这个iframe内的文本来获取。大文件上传
现在来看看在上面提到的几种上传方式中实现大文件上传会遇见的超时问题,
大文件上传最主要的问题就在于:在同一个请求中,要上传大量的数据,导致整个过程会比较漫长,且失败后需要重头开始上传。试想,如果我们将这个请求拆分成多个请求,每个请求的时间就会缩短,且如果某个请求失败,只需要重新发送这一次请求即可,无需从头开始,这样是否可以解决大文件上传的问题呢?
综合上面的问题,看来大文件上传需要实现下面几个需求
接下来让我们依次实现这些功能,看起来最主要的功能应该就是切片了。
文件切片
参考: 大文件切割上传
编码方式上传中,在前端我们只要先获取文件的二进制内容,然后对其内容进行拆分,最后将每个切片上传到服务端即可。
在JavaScript中,文件FIle对象是Blob对象的子类,Blob对象包含一个重要的方法
slice
,通过这个方法,我们就可以对二进制文件进行拆分。下面是一个拆分文件的示例
将文件拆分成
piece
大小的分块,然后每次请求只需要上传这一个部分的分块即可服务器接收到这些切片后,再将他们拼接起来就可以了,下面是PHP拼接切片的示例代码
测试时记得修改nginx的server配置,否则大文件可能会提示
413 Request Entity Too Large
的错误。上面这种方式来存在一些问题
因此接下来我们来看看应该如何在服务端还原切片。
还原切片
在后端需要将多个相同文件的切片还原成一个文件,上面这种处理切片的做法存在下面几个问题
context
参数mkfile
接口来通知服务端进行拼接上面有一个重要的参数,即
context
,我们需要获取为一个文件的唯一标识,可以通过下面两种方式获取修改上传代码,增加相关参数
在
mkblk.php
接口中,我们通过context
来保存同一个文件相关的切片除了上面这种简单通过目录区分切片的方法之外,还可以将切片信息保存在数据库来进行索引。接下来是
mkfile.php
接口的实现,这个接口会在所有切片上传后调用这样就解决了上面的两个问题:
断点续传
即使将大文件拆分成切片上传,我们仍需等待所有切片上传完毕,在等待过程中,可能发生一系列导致部分切片上传失败的情形,如网络故障、页面关闭等。由于切片未全部上传,因此无法通知服务端合成文件。这种情况下可以通过断点续传来进行处理。
断点续传指的是:可以从已经上传部分开始继续上传未完成的部分,而没有必要从头开始上传,节省上传时间。
由于整个上传过程是按切片维度进行的,且
mkfile
接口是在所有切片上传完成后由客户端主动调用的,因此断点续传的实现也十分简单:mkfile
接口通知服务端进行文件合并因此问题就落在了如何保存已上传切片的信息了,保存一般有两种策略
下面让我们通过在本地保存已上传切片记录,来实现断点上传的功能
然后对上传逻辑稍作修改,主要是增加上传前检测是已经上传、上传后保存记录的逻辑
此时上传时刷新页面或者关闭浏览器,再次上传相同文件时,之前已经上传成功的切片就不会再重新上传了。
服务端实现断点续传的逻辑基本相似,只要在
getUploadSliceRecord
内部调用服务端的查询接口获取已上传切片的记录即可,因此这里不再展开。此外断点续传还需要考虑切片过期的情况:如果调用了
mkfile
接口,则磁盘上的切片内容就可以清除掉了,如果客户端一直不调用mkfile
的接口,放任这些切片一直保存在磁盘显然是不可靠的,一般情况下,切片上传都有一段时间的有效期,超过该有效期,就会被清除掉。基于上述原因,断点续传也必须同步切片过期的实现逻辑。上传进度和暂停
通过xhr.upload中的
progress
方法可以实现监控每一个切片上传进度。上传暂停的实现也比较简单,通过
xhr.abort
可以取消当前未完成上传切片的上传,实现上传暂停的效果,恢复上传就跟断点续传类似,先获取已上传的切片列表,然后重新发送未上传的切片。由于篇幅关系,上传进度和暂停的功能这里就先不实现了。
小结
目前社区已经存在一些成熟的大文件上传解决方案,如七牛SDK,腾讯云SDK等,也许并不需要我们手动去实现一个简陋的大文件上传库,但是了解其原理还是十分有必要的。
本文首先整理了前端文件上传的几种方式,然后讨论了大文件上传的几种场景,以及大文件上传需要实现的几个功能
slice
方法将文件拆分成切片Other resource
浏览器端js有如何为本机生成固定的uuid
Anonymous browser fingerprint
从 Fetch 到 Streams —— 以流的角度处理网络请求
大规格文件的上传优化
SparkMD5
The text was updated successfully, but these errors were encountered: