You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何查询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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 01:25:12