- 介绍常见的文件上传方式
- 大文件上传的通病:容易超时。介绍
文件分片
和断点续传
方法。
文件上传
编码后上传
图片转base64上传
前端将需要上传的图片进行base64编码,然后提交到服务端。
var imgURL = URL.createObjectURL(file);
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
ctx.drawImage(imgURL, 0, 0);
// 获取图片的编码,然后将图片当做是一个很长的字符串进行传递
var data = canvas.toDataURL("image/jpeg", 0.5);
服务端接收到文件后,进行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);
form表单上传
使用form标签,并指定标签的enctype="multipart/form-data"
,表明表单需要上传二进制数据,并设置method="POST"
,。
<form action="http://localhost:8080" method="POST" enctype="multipart/form-data">
<input type="file" name="file1">
<input type="submit">
</form>
使用type=submit
上传文件,体验上的缺点很明显,上传完毕会刷新页面,导致页面数据和状态丢失。
早期会把表单使用iframe内嵌到页面里,提交完只刷新iframe,实现局部刷新的效果。
通过xhr,前端也可以进行异步上传文件的操作,实现无刷新上传。
FormData上传
使用FormData
对象管理表单数据,再将表单数据进行异步提交。
let files = e.target.files // 获取input的file对象
let formData = new FormData(); // 构造FormData对象
formData.append('file', file);
axios.post(url, formData);
大文件上传
以上的传输方式在文件不大时运行良好,但是在文件很大的情况下,比如一个视频文件几百上千兆,就会出现问题:
在同一个请求中,上传的数据量太大,容易导致链接超时失效,或是超过服务器可接收最大字段。而上传失败后整个文件需要重传。
文件分片
解决大文件上传问题,关键技术就是将大的文件分割成一个个小的文件,并行上传。
还原分片
接收端在接收完所有分片文件后,将一个个小文件按照顺序拼接还原。
断点续传
在传输过程中意外中断时,我们并不希望将所有文件都进行重传,而是只把上传失败的部分进行重传。
文件分片
Blob对象
:表示一个不可变、原始数据的类文件对象。它包含一个重要的方法slice()
,通过这个方法,我们就可以对二进制文件进行拆分。
File对象
:File对象是特殊类型的Blob,它继承blob接口的方法。
文件分片关键步骤:
- 前端将大文件进行分片,分割成一个个小文件
- 前端并行发送分片文件
- 服务端接收分片文件
- 分片文件传送完毕后,前端发送一个标志结束的请求
- 服务端接收到结束标志后,把分片文件进行合并
- 合并完成后,删除分片文件
分片代码示例:
function sliceInPieces(file, size = 2 * 1024 * 1024) {
const totalSize = file.size; // 文件总大小
const chunks = [];
let start = 0;
let end = 0;
while (start < totalSize) {
end = start + size;
const blob = file.slice(start, end); // 调用对象上的slice方法
chunks.push(blob);
start = end;
}
return chunks;
}
上传分片代码示例:(如果分片比较多,并发请求的数量需要控制一下)
const file = document.querySelector("[name=file]").files[0]; // 读取文件
const chunks = sliceInPieces(file); // 分片
const context = uuid(); // 文件唯一标识
const promiseList = [];
chunks.forEach((chunk, index) => {
let fd = new FormData();
fd.append("file", chunk);
fd.append("context", context); // 带上标识
fd.append("index", index); // 带上位置序号
promiseList.push(axios.post(url, fd));
})
Promise.all(promiseList).then((res) => {
...
// 全部上传完毕后通知接收端结束
let fd = new FormData();
fd.append("context", context);
fd.append("chunks", chunks.length);
axios.post(doneUrl, fd).then(res => {
...
});
})
还原分片
接收端处理分片需要注意的问题:
- 如何识别分片文件来自同一个源文件?
- 如何将多个分片还原成一个文件?
区分来自同一个源文件
对源文件生成一个文件唯一标识context
参数,标志文件分片来自同一个源文件。在每个切片请求上把context
参数带上,通知结束的接口也带上这个标记值, 接收端根据这个标记值确认接收到的分片属于同一个文件。
这个context
值是文件的唯一标识,下面例举几种生成方式:
- 用文件名等作为标识,但是为了避免不同用户取了相同的文件名,可以再拼接上用户信息,比如uid保证唯一性。
- 用md5生成文件hash作为文件的唯一标识。
触发还原分片
在所有分片上传完毕后,还会额外再发送一个请求通知接收端进行拼接。
接收端根据请求中的context
值,找到所有带此context
标志的分片,确认分片的顺序(可以通过分片请求中加的index参数,有些也会直接拼接在context后面,接收端进行处理),根据顺序合并分片文件。
断点续传
上面我们已经了解大文件上传的方法,大文件进行分片并上传,接收端再合并还原成大文件。但在等待分片上传的过程中,我们仍然很有可能发生一些意外的情况导致部分分片上传失败,如断网或者页面关闭/刷新等。由于分片没有全部上传成功,因此无法通知接收端进行文件还原。如果再次全部重新上传,已上传成功的分片就浪费了。因此我们可以通过断点续传来进行处理。
断点续传:只对上传失败的分片进行重传。关键点是客户端如何感知哪些分片已上传成功。 前端触发重传时,根据以上传成功的分片进行筛选,只对未成功的分片进行上传。全部上传完毕后再进行合并的额外请求。
那前端如何感知已上传的分片的信息呢?
- 客户端记录,通过locaStorage等方式保存在客户端上。
- 优点:方便实现,且不依赖服务端。缺点:存在客户端上不保险,用户清除缓存记录就会丢失
- 服务端记录,服务端额外提供一个查询接口给前端调用。
- 优点:由服务端根据已接受到的分片给客户端开一个已上传的记录接口,客户端重传前进行调用。记录不容易丢失。缺点:额外的接口开销。
分片过期:在分片的步骤中,最后一步是合并完成后删除分片。如果客户端一直不调用通知上传完成的接口,这些分片就一直会保存在磁盘中,这显然是不合理的。因此,分片还需要带上有效期,超期需要被清理掉。在断点续传时,也同样需要考虑到过期的问题。
最后
微信搜索公众号Eval Studio,关注更多动态。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!