参考文章: Nestjs 实现大文件分片上传在本文中,我们将探讨如何使用NestJS框架来实现大文件分片上传,并且将文件分片数据存储 - 掘金
文件上传一共分为这几步
- 获取大文件上传签名
- 上传大文件分片
- 获取大文件分片上传状态
- 取消大文件上传
- 合并大文件
获取大文件上传签名
该步骤是创建一个长传会话, 用来保存文件的一些基本信息
interface ILargeFileSession {
// 总的分片数据
chunkCount: number;
// 文件hash
hash: string;
// 文件名称
fileName: string;
// 文件大小
fileSize: number;
// 当前上传文件的用户id
userId: number;
// 文件的保存路径
folderPath: string;
// 保存到数据库的文件id
fileId: number;
}
其中 chunkCount ,hash, fileSize, 都是用来最后合并的时候校验的, 看文件是否上传出错。
保存文件到动态文件夹
考虑到文件是分片上传,所以需要用一个文件夹来存放这些分片。文件夹的路径设置规则我设置的是年月日+uuid, 这个根据业务进行设置。 要注意的点是MulterModule 怎么将文件放到对应的文件夹里面去。
在上面的会话信息中, 我保存了一个folderPath 的字段, 这就是要存放的文件夹路径。我使用的是redis保存信息, 所以inject了一个Redis的service, 然后在 destination 可以读取到request请求。 可以从请求体里面获取到会话id,然后从redis 里面查找存放路径。
MulterModule.registerAsync({
inject: [AppRedisService],
async useFactory(redisService: AppRedisService) {
return {
storage: multer.diskStorage({
destination: async (req, file, cb) => {
console.log('req', req);
const { session } = req.body as FilePartUploadRequest;
const id = `${req.user.type}-${req.user.userId}`;
const redisKey = mergeRedisKey(LARGE_FILE, `${req.user.type}`, `${req.user.userId}`, session);
console.log("🚀 ~ destination: ~ id:", id)
const folder = await redisService.client.hget(redisKey, 'folderPath');
cb(null, folder);
},
filename: (req, file, cb) => {
const { partIndex } = req.body as FilePartUploadRequest;
cb(null, `${partIndex}`);
}
})
};
},
}),
合并文件
主要代码如下
这里使用了stream 进行文件合并, 不需要一次性的将文件全部加载到内存里面。 然后每次写入的时候都会包装成一个Promise,这样可以避免递归和回调的写法。
for (let currentPartIndex = 0; currentPartIndex < files.length; currentPartIndex++) {
const readStream = fs.createReadStream(path.join(folderPath, files[currentPartIndex]));
// 使用 pipe 将读取的流写入目标文件
await new Promise<void>((resolve, reject) => {
readStream.pipe(ws);
readStream.on('end', resolve);
readStream.on('error', reject);
});
}