- 一、文件上传几种方式- 1、from 表单上传- 1.1 普通上传
- 1.2异步上传- 方案1:base64上传
- 方案2:二进制形式
 
 
- 2、frame上传
- 3、FormData异步上传
 
- 1、from 表单上传
- 二、大文件上传- 1、切片
- 2、断点续传
- 3、上传进度和暂停
 
- form表单上传
- iframe
- FormData异步上传
首先要知道我们上传文件时需要修改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',
    }
  })
}
我们在上传或者下载文件的时候,如果已经进行了一部分,这时候网络故障、页面关闭的情况下,不需要从头开始操作,而是从指定位置继续进行操作,这种处理方式就是所说的“断点续传”
断点:的由来是在下载过程中,将一个下载文件分成了多个部分,同时进行多个部分一起的下载,当某个时间点,任务被暂停了,此时下载暂停的位置就是断点了。
续传:一个任务从暂停到开始时,会从上一次任务暂停处开始(可以每次传输成功后加一个表示为告诉前端传输进度)。
实现思路:
- 保存已上传的切片信息
- 选择未上传的切片进行上传
- 全部上传成功后后端进行文件合并
实现方案:
- 本地存储:我们可以利用localstorage,cookie等方式存储在浏览器内,这种情况下我们不依赖于后端,直接本地读取就行。清理了本地文件,会导致上传记录丢失。
- 其实服务器知道我们已经传输到了哪些切片,那些进度,我们通过接口去传输为上传的切片即可。
进度:我们可以利用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() 方法将终止该请求,实现上传暂停的效果。
继续:和断点继传类似,先获取传输的列表,然后重新发送未上传的切片。
