如何查询MongoDB中类文件系统的递归文档?
递归类文件系统MongoDB(Typegoose)查询方案
问题背景
我正在开发一款用MongoDB(基于typegoose)存储类文件系统(递归结构)的应用,文档由文件和文件夹组成,但不知道如何针对这类Schema做查询。可用查询条件有:
- 用户_id
- 有序的文件夹名称数组(示例:
['root', 'nestedFolder1', 'nestedFolder2', ...]) - 选中的最后一个文件夹的_id
Schema定义
import { Types } from "mongoose"; interface File { _id: Types.ObjectId; fileName: string; isDir: false; content: string; title: string; description: string; } interface FileSystem { _id: Types.ObjectId; folderName: string; isDir: true; files: File[] | []; folders: FileSystem[] | []; } interface Project { _id: Types.ObjectId; projectName: string; fileSystem: FileSystem; } interface User { _id: Types.ObjectId; projects: Project[]; }
场景1:通过用户ID + 文件夹名称路径查询目标文件夹
针对有序的文件夹名称数组,可通过动态构建MongoDB嵌套查询路径定位目标文件夹,核心是根据路径层级逐层匹配folderName。
实现代码
先将interface转为Typegoose类模型,再实现查询逻辑:
import { getModelForClass, prop, modelOptions } from "@typegoose/typegoose"; class FileClass { @prop({ required: true }) public fileName!: string; @prop({ required: true, default: false }) public isDir!: false; // 省略content、title等其他字段定义 } class FileSystemClass { @prop({ required: true }) public folderName!: string; @prop({ required: true, default: true }) public isDir!: true; @prop({ type: () => [FileClass], default: [] }) public files!: FileClass[]; @prop({ type: () => [FileSystemClass], default: [] }) public folders!: FileSystemClass[]; } class ProjectClass { @prop({ required: true }) public projectName!: string; @prop({ type: () => FileSystemClass, required: true }) public fileSystem!: FileSystemClass; } @modelOptions({ schemaOptions: { collection: "users" } }) class UserClass { @prop({ type: () => [ProjectClass], default: [] }) public projects!: ProjectClass[]; } const UserModel = getModelForClass(UserClass); // 根据路径查询文件夹 async function getFolderByPath(userId: Types.ObjectId, folderPath: string[]) { let queryPath = "projects.fileSystem"; const queryCondition: any = { "_id": userId }; // 逐层构建匹配条件 folderPath.forEach((folderName, index) => { if (index === 0) { queryCondition[`${queryPath}.folderName`] = folderName; } else { queryPath += `.folders`; queryCondition[`${queryPath}.$.folderName`] = folderName; } }); // 只返回目标文件夹数据 const user = await UserModel.findOne(queryCondition, { [`${queryPath}`]: 1, "_id": 0 }); // 提取深层嵌套的目标文件夹 if (!user) return null; let target = user.projects[0].fileSystem; for (let i = 1; i < folderPath.length; i++) { target = target.folders[0]; } return target; }
场景2:通过用户ID + 目标文件夹ID查询
利用MongoDB聚合管道的自定义函数,递归查找嵌套结构中指定_id的文件夹。
实现代码
async function getFolderById(userId: Types.ObjectId, folderId: Types.ObjectId) { const result = await UserModel.aggregate([ // 匹配目标用户 { $match: { _id: userId } }, // 展开projects数组 { $unwind: "$projects" }, // 递归查找目标文件夹 { $addFields: { targetFolder: { $function: { body: function findFolder(fs) { if (fs._id.toString() === folderId.toString()) return fs; for (const subFolder of fs.folders) { const found = findFolder(subFolder); if (found) return found; } return null; }, args: ["$projects.fileSystem"], lang: "js" } } } }, // 过滤出存在目标文件夹的结果 { $match: { targetFolder: { $ne: null } } }, // 仅返回目标文件夹 { $project: { targetFolder: 1, _id: 0 } } ]); return result.length > 0 ? result[0].targetFolder : null; }
性能优化建议
- 给高频查询字段添加索引:
UserModel.schema.index({ "projects.fileSystem._id": 1 }); UserModel.schema.index({ "projects.fileSystem.folderName": 1 }); - 若文件夹层级极深或数据量较大,建议将递归结构改为**物化路径(Materialized Path)或嵌套集(Nested Set)**模式,降低查询复杂度。
内容的提问来源于stack exchange,提问作者Nugget
相关产品推荐
相关产品推荐

