首先要知道我们上传文件时需要修改form表单的 enctype='multipart/form-data'
产生问题:
form表单提交之后会刷新页面
form表单上传大文件时,很容易遇见服务器超时
<form action="http:localhost:8080/uploadFile" method="POST" enctype="multipart/form-data"> <input type="file" name="myfile"> <input type="submit"> </form>
通过canvas讲图片装成base64,然后在服务端进行解码。
base64会将原本的体积转成4/3的体积,so会增大请求体加,浪费带宽,上传和解析的时间会明显增加。
<input type="file" id='file'> <canvas id='canvas'></canvas> <img src="" id='target-img'> <script> let canvas = document.getElementById("canvas"), targetImg = document.getElementById('target-img'), file = document.getElementById('file'), context = canvas.getContext('2d') file.onchange = function() { let URL = window.URL || window.webkitURL let dataURL = URL.createObjectURL(this.files[0]) // 创建URL对象 let img = new Image() img.crossOrigin = "anonymous" // 只有服务器模式打开, 才有效 img.src = dataURL img.onload = function() { URL.revokeObjectURL(this.src) // img加载完成后,主动释放URL对象 canvas.width = img.width canvas.height = img.height context.drawImage(img, 0, 0, img.width, img.height) let dataBase64Url = canvas.toDataURL('img/png') targetImg.src = dataBase64Url } } </script>
除了进行base64编码,还可以在前端直接读取文件内容后以二进制格式上传
关键api:
参考
FileReader:对象允许Web应用程序异步读取存储在用户计算机上的文件(或原始数据缓冲区)的内容,使用 File 或 Blob 对象指定要读取的文件或数据。
File:对象可以是来自用户在一个<input>
元素上选择文件后返回的files对象
readAsBinaryString: 方法会读取指定的 Blob 或 File 对象,当读取完成的时候,readyState 会变成DONE(已完成),并触发 loadend (en-US) 事件,同时result 属性将包含所读取文件原始二进制格式
Blob: 前端的一个专门用于支持文件操作的二进制对象
ArrayBuffer:前端的一个通用的二进制缓冲区,类似数组,但在API和特性上却有诸多不同
Buffer:Node.js提供的一个二进制缓冲区,常用来处理I/O操作
Uint8Array:类型数组表示的8位无符号整数数组
二进制上传
文件路径格式转二进制
var reader = new FileReader();//① reader.readAsBinaryString(file);// 把从input里读取的文件内容,放到fileReader的result字段里 reader.onload = function(){ readBinary(this.result) // 读取result或直接上传 } // 读取二进制文件 function readBinary(text){ var data = new ArrayBuffer(text.length);//创建一个长度为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) }
二进制下载
在向后端发起请求时,需要在请求头中加上
responseType: 'blob'
这样在返回data中可以得到一个浏览器可以解析的blob数据
const downURL = window.URL.createObjectURL(new Blob([data])); // data 为获取到的二进制数据 const listNode = document.createElement("a"); // 这里注意 : 非同源a标签的download去命名没有用 listNode.download = '合同公允价错误文件下载.xlsx'; listNode.style.display = "none"; listNode.href = downURL;
低版本浏览器上,xhr请求不支持formdata上传,只能form表单上传。
form表单上传,出现的问题上文已经提到,会本身进行页面跳转,产生原因为target属性导致
target我们或多或少有些了解,a标签也有改属性:
_self:默认值,在相同的窗口中打开响应页面
_blank:在新窗口打开
_parent:在父窗口打开
_top:在最顶层的窗口打开
实现方案
实现异步上传的感觉,自理我们就要用到framename去置顶名字的iframe中打开,也就是<iframe name='formtarget'></iframe>
,<form target='formtarget'>
,这样一来返回的数据会被iframe接收,就不会出现刷新问题,而返回的内容可以通过iframe文本拿到。
问题:预览图片只有先传给后台,后台再返回一个线上的地址
<iframe id="iframe1" name="formtarget" style="display: none"></iframe> <form id="fm1" action="/app04/ajax1/" method="POST" target="formtarget" enctype="multipart/form-data"> <input type="file" name="k3"/> <input type="submit"> </form> <script> file.onchange = function() { let iframe = document.getElementById('iframe1') iframe.addEventListener("load", function() { var content = this.contents(). var data = JSON.parse(content) }) } </script>
利用FormData模拟表单数据,通过ajax进行提交,可以更加灵活地发送Ajax请求。可以使用FormData来模拟表单提交。
let files = e.target.files // 获取input的file对象 let formData = new FormData(); formData.append('file', file); axios.post(url, formData);
在同一个请求中,要上传大量的数据,导致整个过程会比较漫长,且失败后需要重头开始上传。
大文件上传我们需要考虑三个方面:
识别切片来源
保证切片拼接顺序
实现
根据文件名、文件长度等基本信息进行拼接,为了避免多个用户上传相同的文件,可以再额外拼接用户信息如uid等保证唯一性
根据文件的二进制内容计算文件的hash,这样只要文件内容不一样,则标识也会不一样,缺点在于计算量比较大.
将文件拆分成piece大小的分块,然后每次请求只需要上传这一个部分的分块即可
let file = document.querySelector("[name=file]").files[0]; const LENGTH = 1024 * 1024 * 0.1; let chunks = sliceFile(file, LENGTH); // 首先拆分切片 chunks.forEach((chunk,index) => { let fd = new FormData(); fd.append("file", chunk); // 传递context fd.append("context", file.name + file.length); // 传递切片索引值 fd.append("chunk", index + 1); upload(fd) })
function sliceFile(file, piece = 1024 * 1024 * 5) { let totalSize = file.size; // 文件总大小 let start = 0; // 每次上传的开始字节 let end = start + piece; // 每次上传的结尾字节 let chunks = [] while (start < totalSize) { // 根据长度截取每次需要上传的数据 // File对象继承自Blob对象,因此包含slice方法 let blob = file.slice(start, end); chunks.push(blob) start = end; end = start + piece; } return chunks }
请求
/** * 文件上传 * @param {} params */ export function upload (params) { const data = new FormData(); data.append('file', params.file); data.append('type', params.type); return $axios({ method: 'post', url: "/api/Files/upload", data: data, headers: { 'Content-Type': 'multipart/form-data', } }) }
我们在上传或者下载文件的时候,如果已经进行了一部分,这时候网络故障、页面关闭的情况下,不需要从头开始操作,而是从指定位置继续进行操作,这种处理方式就是所说的“断点续传”
断点:的由来是在下载过程中,将一个下载文件分成了多个部分,同时进行多个部分一起的下载,当某个时间点,任务被暂停了,此时下载暂停的位置就是断点了。
续传:一个任务从暂停到开始时,会从上一次任务暂停处开始(可以每次传输成功后加一个表示为告诉前端传输进度)。
实现思路:
实现方案:
进度:我们可以利用xhr.upload.onprogress = Function方法做进度的监听
xhr.upload.onprogress = function(e) { if (e.lengthComputable) { var percent = Math.floor( e.loaded / e.total * 100);//进度计算 if(percent == 100){ }else{ } } };
暂停:如果该请求已被发出,XMLHttpRequest.abort() 方法将终止该请求,实现上传暂停的效果。
继续:和断点继传类似,先获取传输的列表,然后重新发送未上传的切片。