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

如何统计分类下文章数量?Mongoose实现方案咨询

实现分类文章数量统计的两种方案

一、你的现有方案:维护冗余字段(posts_Number)

合理性分析

这种方案的优点是查询分类列表时性能高,直接读取字段即可展示;但缺点也很明显:

  • 需要在文章创建、删除、修改分类时同步更新posts_Number,一旦某个环节出错(比如创建文章失败但分类计数已增加),就会出现数据不一致;
  • 如果分类名称修改,文章里的categories字段(当前是字符串)无法自动同步,会导致计数统计错误。

代码实现(修改创建文章的路由)

要实现创建文章时分类计数自增,需在文章保存成功后,找到对应分类并更新posts_Number,建议用Mongoose的findOneAndUpdate做原子操作,同时处理异常:

// CREATE POST
router.post("/", async (req, res) => {
  try {
    // 1. 创建并保存文章
    const newPost = new Post(req.body);
    const savedPost = await newPost.save();

    // 2. 如果文章指定了分类,更新对应分类的文章数
    if (savedPost.categories) {
      // 按用户名+分类名匹配,避免跨用户混淆
      await PostsCategory.findOneAndUpdate(
        { name: savedPost.categories, username: savedPost.username },
        { $inc: { posts_Number: 1 } }, // 原子自增操作
        { upsert: false, new: true } // upsert设为true则分类不存在时自动创建,按需调整
      );
    }

    res.status(200).json(savedPost);
  } catch (err) {
    res.status(500).json(err);
    console.log(err);
  }
});

注意:后续如果有文章删除、修改分类的操作,也要同步更新对应分类的posts_Number——比如删除文章时减1,修改分类时给旧分类减1、新分类加1。

二、更健壮的方案:实时统计(不维护冗余字段)

方案思路

放弃维护posts_Number字段,在查询分类列表时,通过MongoDB的聚合查询实时统计每个分类对应的文章数量。这种方案彻底避免数据不一致问题,适合数据量不是特别大的后台管理系统。

优化模型关联(可选但推荐)

建议把Post模型的categories字段从字符串改为ObjectId,关联到PostsCategory的_id,这样即使分类名称修改,关联关系也不会断裂:

// 修改后的Post模型
const PostSchema = new mongoose.Schema(
  {
    username: {
      type: String,
      required: true,
    },
    title: {
      type: String,
      required: true,
    },
    categories: {
      type: mongoose.Schema.Types.ObjectId,
      ref: "PostsCategory", // 关联PostsCategory模型
      required: false,
    },
  },
  { timestamps: true }
);

分类列表查询的聚合代码

在获取分类列表的路由中,用aggregate统计每个分类的文章数:

// 获取带文章数量的分类列表
router.get("/categories", async (req, res) => {
  try {
    const { username } = req.query; // 按用户名过滤,避免跨用户查询
    const categoriesWithCount = await PostsCategory.aggregate([
      // 1. 过滤当前用户的分类
      { $match: { username } },
      // 2. 关联文章集合,匹配分类ID
      {
        $lookup: {
          from: "posts", // 注意这里是Post模型对应的集合名称(一般为小写复数)
          localField: "_id",
          foreignField: "categories",
          as: "postsList",
        },
      },
      // 3. 统计文章数量,保留分类其他字段
      {
        $project: {
          username: 1,
          name: 1,
          catImage: 1,
          createdAt: 1,
          updatedAt: 1,
          posts_Number: { $size: "$postsList" }, // 统计关联的文章数组长度
        },
      },
    ]);
    res.status(200).json(categoriesWithCount);
  } catch (err) {
    res.status(500).json(err);
    console.log(err);
  }
});

如果不修改模型,仍用字符串匹配分类名称,聚合查询可改为:

router.get("/categories", async (req, res) => {
  try {
    const { username } = req.query;
    const categoriesWithCount = await PostsCategory.aggregate([
      { $match: { username } },
      {
        $lookup: {
          from: "posts",
          let: { catName: "$name", user: "$username" },
          pipeline: [
            {
              $match: {
                $expr: {
                  $and: [
                    { $eq: ["$categories", "$$catName"] },
                    { $eq: ["$username", "$$user"] },
                  ],
                },
              },
            },
          ],
          as: "postsList",
        },
      },
      {
        $project: {
          username: 1,
          name: 1,
          catImage: 1,
          createdAt: 1,
          updatedAt: 1,
          posts_Number: { $size: "$postsList" },
        },
      },
    ]);
    res.status(200).json(categoriesWithCount);
  } catch (err) {
    res.status(500).json(err);
    console.log(err);
  }
});

方案选择建议

  • 如果系统文章和分类数量极大、对查询性能要求极高,选第一种方案,但要确保所有修改文章分类的操作都同步更新计数;
  • 大多数后台管理系统数据量不大,推荐第二种方案,数据一致性更有保障,代码维护更简单。

内容的提问来源于stack exchange,提问作者sultan.h

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 12:40:16