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

如何在MongoDB树形结构中单独获取所有评论文档?

问题背景

我在MongoDB中有如下结构的评论文档:

{
   "_id": { "$oid": "61acd4474bbaa88d12c38199" },
   "textContent": "这条评论有2条回复",
   "replies": [
     { "$oid": "61acd57fe06fed9e70462139" },
     { "$oid": "61acd57fe06fed9e7046213a" },
   ],
   "__v": 0
},
{
   "_id": { "$oid": "61acd57fe06fed9e70462139" },
   "textContent": "这条评论没有回复",
   "replies": [],
   "__v": 0
},
{
   "_id": { "$oid": "61acd57fe06fed9e7046213a" },
   "textContent": "这条评论没有回复",
   "replies": [],
   "__v": 0
}

所有评论都是同类型文档,replies数组存储其他评论的ID,构成树形结构但每个文档独立存储。

我希望从服务器返回数据时,评论仍保持独立文档的形式,仅保留对其他评论的ID引用(和存储结构一致)。现在需要单次查询获取所有相关评论,且保持文档独立,注意嵌套层级可能比示例更深,而且预先不知道顶级评论的ID。

我知道Mongoose的populate可以查询嵌套文档,但它会返回嵌套的大对象,而不是独立文档:

this.populate({ path: 'replies' })

它的输出是:

{ _id: "61acd4474bbaa88d12c38199",
  textContent: "这条评论有2条回复",
  replies: [
    { _id: "61acd57fe06fed9e70462139",
      textContent: "这条评论没有回复",
      replies: []
    },
    { _id: "61acd57fe06fed9e7046213a",
      textContent: "这条评论没有回复",
      replies: []
    }
  ]
}

我的期望输出是:

[
   { _id: "61acd4474bbaa88d12c38199",
     textContent: "这条评论有2条回复",
     replies: [ "61acd57fe06fed9e70462139", "61acd57fe06fed9e7046213a" ]
   },
   { _id: "61acd57fe06fed9e70462139",
     textContent: "这条评论没有回复",
     replies: [ ]
   },
   { _id: "61acd57fe06fed9e7046213a",
     textContent: "这条评论没有回复",
     replies: [ ]
   }
]
解决方案

方法1:使用MongoDB聚合查询(推荐)

利用$graphLookup可以一次性递归获取所有层级的关联评论,同时保持文档独立:

const allComments = await Comment.aggregate([
  // 递归查询所有关联的评论ID,覆盖所有嵌套层级
  {
    $graphLookup: {
      from: "comments", // 替换为你的评论集合名称
      startWith: "$_id",
      connectFromField: "replies",
      connectToField: "_id",
      as: "relatedIds"
    }
  },
  // 筛选出顶级评论(未被任何其他评论引用的)
  {
    $match: {
      $expr: { $not: { $in: ["$_id", "$relatedIds.replies"] } }
    }
  },
  // 收集所有关联ID并去重
  {
    $group: {
      _id: null,
      allCommentIds: { $addToSet: "$relatedIds._id" }
    }
  },
  // 展开ID数组
  { $unwind: "$allCommentIds" },
  { $unwind: "$allCommentIds" },
  // 根据ID查询对应评论文档
  {
    $lookup: {
      from: "comments",
      localField: "allCommentIds",
      foreignField: "_id",
      as: "comment"
    }
  },
  { $unwind: "$comment" },
  // 将replies中的ObjectId转为字符串,移除__v字段
  {
    $addFields: {
      "comment.replies": {
        $map: {
          input: "$comment.replies",
          as: "replyId",
          in: { $toString: "$$replyId" }
        }
      }
    }
  },
  { $project: { "comment.__v": 0 } },
  // 整理为期望的评论数组格式
  { $replaceRoot: { newRoot: "$comment" } }
]);

如果不需要筛选顶级评论,直接查询所有评论并格式化:

const allComments = await Comment.aggregate([
  { $match: {} },
  {
    $addFields: {
      replies: {
        $map: {
          input: "$replies",
          as: "replyId",
          in: { $toString: "$$replyId" }
        }
      }
    }
  },
  { $project: { __v: 0 } }
]);

方法2:Mongoose查询+简单后处理

如果聚合逻辑复杂,可先查询所有评论,再做格式转换:

// 查询所有评论,排除__v字段
const rawComments = await Comment.find({}, { __v: 0 });
// 转换replies中的ObjectId为字符串
const formattedComments = rawComments.map(comment => ({
  ...comment.toObject(),
  replies: comment.replies.map(id => id.toString())
}));

若仅需获取关联评论(而非全部),可先递归收集所有关联ID再批量查询:

// 找出所有顶级评论
const topComments = await Comment.find({
  _id: { $nin: await Comment.distinct("replies") }
});

// 递归收集所有关联评论ID
const collectAllIds = async (ids) => {
  const comments = await Comment.find({ _id: { $in: ids } });
  const childIds = comments.flatMap(c => c.replies);
  if (!childIds.length) return ids;
  const nestedIds = await collectAllIds(childIds);
  return [...new Set([...ids, ...nestedIds])];
};

const allIds = await collectAllIds(topComments.map(c => c._id));
// 批量查询并格式化
const formattedComments = (await Comment.find({ _id: { $in: allIds } }, { __v: 0 })).map(comment => ({
  ...comment.toObject(),
  replies: comment.replies.map(id => id.toString())
}));

关键说明

  • 若输出允许保留ObjectId类型,可去掉所有$map或map中的字符串转换逻辑。
  • $graphLookup支持MongoDB 3.4+,能高效处理深层嵌套的树形结构查询。
  • 如需限定查询范围(比如某篇文章的评论),只需在$match或find中添加对应条件(如articleId: 目标ID)。

内容的提问来源于stack exchange,提问作者Candleout

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 23:42:51