首页 > IT职业 > IT职场 > 大文件上传原理及实现方案
一般,我们传送大文件是指传送大于100M的文件,而普通文件是指小于100M,常见的是20M、30M和50M,两者主要的区别在于文件大小上,还有传送速度上。
1.指定上传的接口地址。
1.前后端上传请求超时限制,一次性传输大小限制。
2.网络抖动等,失败后需要重新上传。
3.http1.1版本, TCP连接默认是open的,所有请求都通过同一个连接进行数据传输,如果前面的请求被阻塞了,后面的请求也得不到响应,也叫HTTP/1.1 中的队头阻塞问题,除非建立多个连接,但是多个连接会浪费资源。
4.无进度条,用户体验极差。
前端
获取文件的二进制内容,然后对其内容拆分成指定大小的切片文件,最后将每个切片上传到服务端即可。
流程:获取文件 ➡️ 分片 ➡️ 上传
需要优化的点
中断后无需重新上传(断点续传)
上传过的文件无需上传(秒传)
后端
根据切片文件的唯一标识在后端将多个相同文件的切片还原成一个文件
流程:获取分片文件 ➡️ 还原分片 ➡️ 返回拼接好的文件信息
需要优化的点
还原切片时需要注意的问题
解决办法
1)如何识别多个切片是来自于同一个文件的?
这个可以在发送请求时,为每个切片传递一个相同文件的identifier参数。
2)如何将多个切片还原成一个文件?
什么时候开始拼接:确认所有切片都已上传完后开始进行拼接,这个可以通过客户端在切片全部上传后调用后端定义的mkfile接口来通知服务端进行拼接,或者前端传递切片的总数totalChunks, 服务端判断接收的切片数量如果等于totalChunks的值就开始进行拼接,无须前端通知后端进行拼接。
怎么按顺序拼接:可以在每个切片上标记一个位置索引值,找到同一个context下的所有切片,根据chunkNumber确认每个切片的顺序,这个按顺序拼接切片,还原成文件
上面有几个重要的参数:identifier ,chunkNumber,totalChunks
identifier :我们需要获取为一个文件的唯一标识,可以通过下面两种方式获取
1. 根据文件名、文件长度等基本信息进行拼接,为了避免多个用户上传相同的文件,可以再额外拼接用户信息如uid等保证唯一性
2. 根据文件的二进制内容计算文件的hash,这样只要文件内容不一样,则标识也会不一样,缺点在于计算量比较大.
chunkNumber:当前切片的索引
totalChunks:总的切片数
// 获取identifier,同一个文件会返回相同的值function createIdentifiert(file) {return file.name + file.size}let file = document.querySelector("[name=file]").files[0];const LENGTH = 1024 * 1024 * 1;//1MBlet chunks = slice(file, LENGTH);// 获取对于同一个文件,获取其identifierlet identifier = createIdentifier(file);let tasks = [];chunks.forEach((chunk, index) => {let fd = new FormData();//传递file对象fd.append("file",chunk);// 传递identifierfd.append("identifier", identifier);// 传递切片索引值fd.append("chunkNumber", index + 1);// 传递切片总数fd.append(“totalChunks”, chunks.length);tasks.push(post("/mkblk.php", fd));});// 所有切片上传完毕后,调用mkfile接口Promise.all(tasks).then(res => {let fd = new FormData();fd.append("identifier", identifier);fd.append("totalChunks",chunks.length);post("/mkfile.php", fd).then(res => {console.log(res);})});
// mkblk.php接口$identifier = $_POST['identifier'];$path = './upload/' . $identifier;if(!is_dir($path)){mkdir($path);}// 把同一个文件的切片放在相同的目录下$filename = $path . '/' . $_POST['chunkNumber’];// 清除保存的切片$res = move_uploaded_file($_FILES['file']['tmp_name'], $filename);//接下来是mkfile.php接口的实现,这个接口会在所有切片上传后调用用来合并文件// mkfile.php接口$identifier = $_POST['identifier'];$totalChunks= (int)$_POST['totalChunks'];//合并后的文件名$filename = './upload/' . $identifier . '/file.jpg’;// 开始合并文件for($i = 1; $i <= $totalChunks; ++$i){$file = './upload/'.$ identifier. '/' .$i; // 读取单个切块// 获取文件内容$content = file_get_contents($file);if(!file_exists($filename)){//创建一个用于读写的空文件$fd = fopen($filename, "w+");}else{//追加到一个文件,写操作向文件末尾追加数据。如果文件不存在,则创建文件。$fd = fopen($filename, "a");}fwrite($fd, $content);// 将切块合并到一个文件上}
在切片上传成功后,保存已上传的切片信息
当下次传输相同文件时,遍历切片列表,只选择未上传的切片进行上传
所有切片上传完毕后,再调用mkfile接口通知服务端进行文件合并
因此问题就落在了如何保存已上传切片的信息了,保存一般有两种策略
1.可以通过locaStorage等方式保存在前端浏览器中,这种方式不依赖于服务端,实现起来也比较方便,缺点在于如果用户清除了本地文件,会导致上传记录丢失
// 获取已上传切片记录function getUploadSliceRecord(context){let record = localStorage.getItem(context)if(!record){return []}else {return JSON.parse(record)}}// 保存已上传切片function saveUploadSliceRecord(context, sliceIndex){let list = getUploadSliceRecord(context)list.push(sliceIndex)localStorage.setItem(context, JSON.stringify(list))}let context = createContext(file);// 获取上传记录let record = getUploadSliceRecord(context);let tasks = [];chunks.forEach((chunk, index) => {// 已上传的切片则不再重新上传if(record.includes(index)){return}let fd = new FormData();fd.append("file", chunk);fd.append("context", context);fd.append("chunk", index + 1);let task = post("/mkblk.php", fd).then(res=>{// 上传成功后保存已上传切片记录saveUploadSliceRecord(context, index)record.push(index)})tasks.push(task);});...
由于篇幅关系,上传进度和暂停的功能这里就先不实现了。
目前社区已经存在一些成熟的大文件上传解决方案,如七牛SDK,腾讯云SDK等,也许并不需要我们手动去实现一个简陋的大文件上传库,但是了解其原理还是十分有必要的。
推荐的前端vue组件:vue-simple-uploader,支持vue2,vue3
vue-simple-uploader是基于simple-Uploader.js封装的大文件上传组件,具有以下优点:
1. 支持单文件、多文件、文件夹上传;支持拖拽文件、文件夹上传
2. 可暂停、继续上传
3. 错误处理
4. 支持“秒传”,通过文件判断服务端是否已存在从而实现“秒传”
5. 分块上传
6. 支持进度、预估剩余时间、出错自动重试、重传等操作
vue-simple-uploader 内部的实现也很简单,有兴趣的同学可以去看一下源码
本文首先介绍了什么是大文件,以及大文件跟普通文件在上传时的区别,最后通过分析大文件上传的原理和思路给出简单的实现方案,并且推荐了一个成熟的vue大文件上传组件:vue-simple-uploader,希望对大家有所帮助。
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70027824/viewspace-3007526/ ,如需转载,请注明出处,否则将追究法律责任。