文件上传的方式有多种,在此做个总结,以后用到时方便查询。
首先我们先来看一下 form 表单的格式 multipart/form-data,它表示互联网上的混合资源,意思是资源是有多种元素组成的。multipart/form-data 是在 post 方法的基础上演变来的,具体如下:
- 基于 post 方法,必须和 post 组合实现
- 与普通的 post 不同的是 request header 和 request body
- 请求头必须带上 Context-Type: multipart/form-data,同时还要规定一个分隔符,用于分割表单中的多个项
比如
请求头
Content-Type: multipart/form-data; boundary=--------------------56423498738365
这个 boundary 后面定义的值就是分隔符,会在 body 中用到
请求体
--------------------56423498738365
Content-Disposition: form-data; name="id"
--------------------56423498738365
Content-Disposition: form-data; name="file000"; filename="xxx.png"
Content-Type: image/png
--------------------56423498738365
可以看到表单的每一项都是以分隔符开头,接着是描述信息,在最后再来一个分隔符,表示整个表单的结束。
简单介绍了下 multipart/form-data 的含义,其实不管它是什么类型,只要它是个 http请求,它就逃不出状态行、消息头、消息体这三块内容。下面结束几种文件上传的方式
form 表单
form 表单上传文件应该是最原始也是最简单的方式了
<form method="post" action="后端接口地址" enctype="multipart/form-data">
<input type="file" name="idcard" id="idcard"/>
<button type="submit">上传</button>
</form>
如果是要多文件一起上传,<input> 元素上加上 multiple 属性就行了。那么传到后端的这个 idcard 就是一个数组,每个元素是一个文件,相应的后端也要做一点改动
ajax 上传
var file = document.getElementById('idcard').file;
var data = new FormData();
data.append('idcard', file);
// 创建xhr
xhr.send(data);
上传进度
//接着上面
xhr.onprocess = uploadProcess;
xhr.upload.onprocess = uploadProcess;
function uploadProcess (e) {
if (e.lengthComputable) {
var percent = (e.loaded / e.total * 100).toFixed(2)
}
}
xhr.send(data)
这里需要注意几点:
- e.lengthComputable 是一个状态,标识发送的长度有了变化,可以计算
- e.loaded 表示发送了多少字节
- e.total 表示文件的总字节数
- xhr.upload.onprocess 需要写在 send 之前,不然 e.lengthComputable 不会变化
拖拽上传
<div id="drop-box" ondrop="drop">把文件拖到这里</div>
<button id="submit">上传</button>
function drop (e) {
e.preventDefault(); // 取消浏览器默认拖拽行为
var file = e.dataTransfer.files; // 获取拖拽中的文件对象
// 再按上面的xhr去上传
}
这里知道一点就行了,e.dataTransfer.files 可以获取拖拽中的文件对象
大文件-分片上传
文件过大的时候,单个文件上传可能会超时,也会超得出服务端允许的最大文件限制。这个时候我们就可以把大文件分割成一个个小片段,用 md5 给每个片段加上一个标志。服务端在接收到所有片段后,根据这个标志、顺序、类型把所有的片段在合并成一个大文件,然后删掉这些片段,如此这般就完成了大文件的分片上传功能。
var chunkSize = 1 * 1024 * 1024; // 每个分片的大小 1M
var file = document.getElementById('idcard');
var chunks = []; // 保存分片数据
var start = end = 0;
while (true) {
end += chunkSize;
var blob = file.slice(start, end);
start += chunkSize;
if (!blob.size) { // 截取的数据为空,结束
break;
}
chunks.push(blob);
}
for (let i=0, l=chunks.length; i<l; i++) {
var data = new FormData();
data.append('token', md5()); // 片段标志
data.append('idcard', chunks[i]); // 所有的片段都放在 idcard 这一项里
data.append('index', i); // 标识片段的顺序,服务端会根据这个标志来合并所有的片段
xhr.send(data, function() {
// 记数,等到所有片段都发送完,发送一个合并请求
})
}
这里知道一点就好做了,file 文件对象提供了一个 slice 方法,让我们可以像操作数组一样来操作文件对象。该方法用来截取一段文件对象的二进制内容 blob。
大文件-断点续传
基于文件分片上传,维护一个数组,在每个片段上传成功之后,将当前片段的index放入这个数组,然后存入 localStorage。当下次上传时,先读取这个数组,然后将大文件分片,片段下标已经在这个数组中的就可以不用上传了