前端-文件上传核心思想

x33g5p2x  于2022-04-02 转载在 其他  
字(5.6k)|赞(0)|评价(0)|浏览(221)

文件上传三种方案:1. form表单上传,2. iframe,3. FormData,base64上传文件,二进制流上传文件,二进制流下载文件。异步上传,大文件上传--切片:拆分上传请求断点续传显示上传进度和暂停上传**

一、文件上传几种方式

  1. form表单上传
  2. iframe
  3. FormData异步上传

1、from 表单上传

首先要知道我们上传文件时需要修改form表单的 enctype='multipart/form-data'
产生问题:
form表单提交之后会刷新页面
form表单上传大文件时,很容易遇见服务器超时

1.1 普通上传
<form action="http:localhost:8080/uploadFile" method="POST" enctype="multipart/form-data">
    <input type="file" name="myfile">
    <input type="submit">
</form>
1.2异步上传
方案1:base64上传

通过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>
方案2:二进制形式

除了进行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;

2、frame上传

低版本浏览器上,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>

3、FormData异步上传

利用FormData模拟表单数据,通过ajax进行提交,可以更加灵活地发送Ajax请求。可以使用FormData来模拟表单提交。

let files = e.target.files // 获取input的file对象
let formData = new FormData();
formData.append('file', file);
axios.post(url, formData);

二、大文件上传

在同一个请求中,要上传大量的数据,导致整个过程会比较漫长,且失败后需要重头开始上传

大文件上传我们需要考虑三个方面:

  • 切片:拆分上传请求
  • 断点续传
  • 显示上传进度和暂停上传

1、切片

识别切片来源
保证切片拼接顺序

  • 我们一般采用编码的方式进行上传,获取文件对应的二进制内容。

  • 计算出内容的总大小,根据文件大小切成对应的分片。

  • 上传时标识出当前文件,告诉后端上传到了第几个(可以用时间戳形式)。

  • 不加表示的话后端在追加切片时,无法识别切片顺序

  • 接口异常的情况下无法正确拼接

实现
根据文件名、文件长度等基本信息进行拼接,为了避免多个用户上传相同的文件,可以再额外拼接用户信息如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',
    }
  })
}

2、断点续传

我们在上传或者下载文件的时候,如果已经进行了一部分,这时候网络故障、页面关闭的情况下,不需要从头开始操作,而是从指定位置继续进行操作,这种处理方式就是所说的“断点续传”

**断点:**的由来是在下载过程中,将一个下载文件分成了多个部分,同时进行多个部分一起的下载,当某个时间点,任务被暂停了,此时下载暂停的位置就是断点了。
续传:一个任务从暂停到开始时,会从上一次任务暂停处开始(可以每次传输成功后加一个表示为告诉前端传输进度)。

实现思路:

  • 保存已上传的切片信息
  • 选择未上传的切片进行上传
  • 全部上传成功后后端进行文件合并

实现方案:

  1. 本地存储:我们可以利用localstorage,cookie等方式存储在浏览器内,这种情况下我们不依赖于后端,直接本地读取就行。清理了本地文件,会导致上传记录丢失。
  2. 其实服务器知道我们已经传输到了哪些切片,那些进度,我们通过接口去传输为上传的切片即可。

3、上传进度和暂停

进度:我们可以利用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() 方法将终止该请求,实现上传暂停的效果。
继续:和断点继传类似,先获取传输的列表,然后重新发送未上传的切片。

相关文章