🔥vue3+node 全栈实战 讲解 大文件分片上传原理(文末尾附有完整源码)
🚀 技术栈
前端: vue3 + ant-design-vue(版本4)
后端: express
nodejs必备后端框架【express/koa2/nestjs】、数据库【mysql/mongodb/redis】
这里用expressjs演示分片上传
🚀 内容核心
-
文件切片:
- 通过读取文件的二进制内容,将大文件按照固定大小
切分成多个块
,并且给每个块分配索引
。
- 通过读取文件的二进制内容,将大文件按照固定大小
-
断点续传:
- 根据中断时记录的已上传切片信息,从
下一个待上传的切片开始
,将所有剩余的都上传到服务器。
- 根据中断时记录的已上传切片信息,从
-
文件合并:
- 在客户端上传完所有切片后,会
发一个合并的请求到服务器
,通知服务器该文件切片上传已完成需合并
。
- 在客户端上传完所有切片后,会
-
并发控制:
同时上传多个切片
,来提高上传效率和速度;限制并发上传数量
,避免网络堵塞和服务器负载过高。
🚀 项目规划
1. antd 上传按钮
先用antd的upload组件搞一个上传按钮,customRequest
这是重点,我们自己定义上传方法,用这个方法覆盖本身默认上传的行为。
2. 上传事件规划
首先,先给文件流的唯一标识(这里我们用
spark-md5
去获取文件流的md5);
接着,向后端请求获取这个唯一标识的文件已经上传过的文件列表的接口,如果本身就有这个文件了,就直接拿来用,不用再次上传了(这种情况相当于秒传);如果没有那就继续下面的操作;
接着,给文件按块大小分成块,返回块数组;
接着,切片块数组筛选出没有被上传过的部分,循环获取FormData,然后每个小块循环返回一个请求上传单块的接口的数组;通过
Promise.all
来并发控制请求;
最后,等待
Promise.all
全部完成,向后端发起这个唯一标识的文件合并的请求。
2.1 文件流唯一标识(md5)
import SparkMD5 from "spark-md5";
通过FileReader
对象来读取文件内容,然后通过spark-md5
模块进行md5计算。
2.2 获取该文件已经上传过的块
// 获取文件已经上传过的块
const res = await getUploadedChunks({ md5, suffix });
注意这里的参数md5
、suffix
这两个参数,在上面文件流唯一标识
那里已经得到了,这里就只是向后端请求一个接口去敲门得到已经上传过的这个文件流的块
即可。这里重点放在后端。
const getUploadedChunks = async (params = {}) => {
const res = await Upload.getUploadedChunks(params);
return res;
};
前端就是请求一个接口而已,来看看后端的实现。
后端接口
—— 获取上传的该文件已上传的块
这里后端我们用expressjs来做。首先先创一个文件夹express-project
,然后npm init
一直回车(这里可以根据个人情况来看着选)完成后可以看到package.json
。
接着新建一个index.js
文件。
let express = require('express')
let app = express(),
PORT = 5000,
HOST = 'http://localhost',
HOSTNAME = `${HOST}:${PORT}`;
app.listen(PORT, () => {
console.log(`服务器在端口 ${PORT} 启动~`)
})
然后node index.js
跑起来,得到服务器在端口 5000 启动~
。
已知上面说到前端首先需要一个获取已经上传完的切片
的接口,也就是说从我们后端这里判断这个文件是不是已经上传过了,上传完了还是说就上传一部分了。那么就需要用到node的内置fs模块
去判断文件在不在或者文件夹在不在。
node的fs文件系统模块
是内置模块,用来读写文件的。用来进行文件目录的创建、删除、查询、以及文件内容的读取和写入等等,并且提供了异步和同步的方法。
const UPLOAD_DIR = ${__dirname}/upload
// 在文件根目录下创建一个upload
的文件夹,用来放前端上传上来的块或者通过块合并成单个文件的文件目录。
请求一下发现这个报错:
碰到这种问题,不要怕不要怀疑,一般就是跨域,跨域让后端配一下就解决了。具体怎么配取决于应用的需求和技术栈。这里我们用的是express.js
, express的跨域配置
如下:
app.all('*', (req, resp, next) => {
// 设置允许跨域的域名,*代表允许任意域名跨域
resp.header('Access-Control-Allow-Origin','http://localhost:5173');
// 允许的header类型
resp.header('Access-Control-Allow-Headers','*');
// 跨域允许的请求方式
resp.header('Access-Control-Allow-Methods','DELETE,PUT,POST,GET,OPTIONS');
// 让options尝试请求快速结束
if(req.method.toLowerCase() == 'options') {
resp.send(200);
} else {
next();
}
})
get请求,传参md5
和suffix
http://localhost:5000/get_uploaded_chunks?md5=20edfe0208b4f44536b2b7126c7594a8&suffix=pdf
2.3 给文件按块大小分成块,返回块数组
const chunks = await getFileAllChunks(file, md5, suffix);
这里我为了方便演示,定义了1兆字节(即: 1 * 1024 * 1024 = 1MB),最大的块数100块,如果超过这个块数,就用该文件的大小除以100块数,来得到每块的字节大小。最终得到一个chunks
块数组。
2.4 并发上传每个文件块
通过Promise.all()
实现并发控制的上传,控制同时上传多个文件,限制并发数为3。这样确保同时上传的切片数量不会超过设定的并发数。
这里的filter就是去除已经上传过的那些块,然后每个块通过FormData处理
一下:
然后再进行上传到后端
给后端处理,那么这里就涉及到上传方法uploadChunk
了。
再回来到后端express这边,处理一下文件块也即是切片上传接口
后端接口
——上传文件切片
我们通过multiparty.Form
实例来处理form-data
格式的数据,然后通过form.parse
方法解析请求,并在回调函数中获取文件信息和其他表单字段。
其中multipartyFormData
方法是用用来解析前端传过来的form-data
数据的。
let multiparty = require('multiparty')
将文件块写入文件夹目录中
上传一个pdf
文件(12.6 MB)
2.5 合并文件夹里所有文件块
后端接口——合并块
合并文件夹下所有的文件块
最后得出这个文件的目录路径:
🚀 总结
该文章是vue3+express项目,演示了切片上传的原理和实现。其他框架同理。
☎️ 希望对大家有所帮助,如有错误,望不吝赐教,欢迎评论区留言互相学习。
转载自:https://juejin.cn/post/7268802459382890554