如何计算集合中的所有文档并在控制器中使用 cont,MongoDB 和 Express.js?

How do I count all the documents in a collection and use the cont in a controller, with MongoDB and Express.js?

我正在开发 blogging application (click the link to see the GitHub repo) with Express (version 4.17.1), EJS 和 MongoDB(版本 4.0.10)。

尝试对帖子进行分页,我在控制器中执行了以下操作:

exports.getPosts = (req, res, next) => {

    const perPage = 5;

    const currPage = req.query.page ? parseInt(req.query.page) : 1;

    let postsCount = 0;

    const posts = Post.find({}, (err, posts) => {

            postsCount = posts.length;

            let pageDecrement = currPage > 1 ? 1 : 0;

            let pageIncrement = postsCount >= perPage ? 1 : 0;

            if (err) {
                console.log('Error: ', err);
            } else {
                res.render('default/index', {
                    moment: moment,
                    layout: 'default/layout',
                    website_name: 'MEAN Blog',
                    page_heading: 'XPress News',
                    page_subheading: 'A MEAN Stack Blogging Application',
                    currPage: currPage,
                    posts: posts,
                    pageDecrement: pageDecrement,
                    pageIncrement: pageIncrement
                });
            }
        })
        .sort({
            created_at: -1
        })
        .populate('category')
        .limit(perPage)
        .skip((currPage - 1) * perPage);
};

并且在视图中:

<a class="btn btn-primary <%= pageDecrement == 0 ? 'disabled' : '' %>" href="/?page=<%= currPage - pageDecrement %>">&larr; Newer Posts</a>

<a class="btn btn-primary <%= pageIncrement == 0 ? 'disabled' : '' %>" href="/?page=<%= currPage + pageIncrement %>">Older Posts &rarr;</a>

除非帖子数量等于 perPage x N,否则效果很好,其中 N 是一个整数,在这种情况下,"Older Posts" 按钮被禁用的时间太迟了一页。

那是因为postsCount = posts.length统计之后的帖子,它们受到.skip((currPage - 1) * perPage)的限制。

所以我需要计算来自 model/collection 的帖子并将计数变量 引入 控制器。

我的模型:

const mongoose = require('mongoose');

const postSchema = new mongoose.Schema({
    title: {
        type: String,
        required: true
    },
    short_description: {
        type: String,
        required: true
    },
    full_text: {
        type: String,
        required: true
    },
    category: {
        type: mongoose.Schema.Types.ObjectId,
        ref: 'Category'
    },
    post_image: {
        type: String,
        required: false
    },
    updated_at: {
        type: Date,
        default: Date.now()
    },
    created_at: {
        type: Date,
        default: Date.now()
    }
});

module.exports = mongoose.model('Post', postSchema);

如何计算帖子集合中的所有文档并在帖子控制器中使用该数字?

阅读$facet

New in version 3.4.

Processes multiple aggregation pipelines within a single stage on the same set of input documents. Each sub-pipeline has its own field in the output document where its results are stored as an array of documents.

示例: 参见 here

db.collection.aggregate([
  {
    $facet: {
      "count": [
        { $match: {} },
        { $count: "totalCount" }
      ],
      "data": [
        { $match: {} },
        { $sort: { _id: -1 } },
        { $skip: 1 },
        { $limit: 2 }
      ]
    }
  }
])

猫鼬版本:

Model.aggregate([
  {
    $facet: {
      "count": [
        { $match: {} },
        { $count: "totalCount" }
      ],
      "data": [
        { $match: {} },
        { $sort: { _id: -1 } },
        { $skip: 1 },
        { $limit: 2 }
      ]
    }
  }
]).
then(res => console.log(res)).
catch(error => console.error('error', error));

如果是猫鼬,你应该使用这个:

https://mongoosejs.com/docs/api.html#aggregate_Aggregate-facet

官方 Mongodb 文档:

https://docs.mongodb.com/manual/reference/operator/aggregation/facet

总体思路是执行聚合而不是多次调用(1 次用于获取所需信息 + 1 次用于获取文档总数)

您当然可以执行 2 个单独的调用,但这会影响您的性能(对于小数据量影响不大,但仍然...) 因此,您可以使用 .find() 获取所有需要的数据,然后像这样获取计数: https://mongoosejs.com/docs/api.html#model_Model.count

PS。顺便说一句,使用 async/await 而不是回调以避免回调地狱

使用 mongodb 聚合框架可以更轻松地完成此操作。

我们使用 $facet 聚合来获取分页数据以及文档总数。

在聚合框架中我们使用$lookup instead of mongoose populate. $lookup returns an array, to get the first item in array we use $arrayElemAt operator inside $addFields

Playground

下面是应用于您的应用的代码: (这里不需要第一个 $match 聚合,但我放在里面以防你将来可能需要它)

exports.getPosts = async (req, res, next) => {
    const perPage = 5;
    const currPage = req.query.page ? parseInt(req.query.page) : 1;
    const skip = (currPage - 1) * perPage;

    try {
        const result = await Post.aggregate([{
                $match: {},
            },
            {
                $sort: {
                    created_at: -1,
                },
            },
            {
                $lookup: {
                    from: "categories",
                    localField: "category",
                    foreignField: "_id",
                    as: "category",
                },
            },
            {
                $addFields: {
                    category: {
                        $arrayElemAt: ["$category", 0],
                    },
                },
            },
            {
                $facet: {
                    totalRecords: [{
                        $count: "total",
                    }, ],
                    data: [{
                            $skip: skip,
                        },
                        {
                            $limit: perPage,
                        },
                    ],
                },
            },
        ]);

        let postsCount = result[0].totalRecords[0].total;
        const pageCount = Math.ceil(postsCount / perPage);
        const pageDecrement = currPage > 1 ? 1 : 0;
        const pageIncrement = currPage < pageCount ? 1 : 0;
        const posts = result[0].data;

        res.render("default/index", {
            moment: moment,
            layout: "default/layout",
            website_name: "MEAN Blog",
            page_heading: "XPress News",
            page_subheading: "A MEAN Stack Blogging Application",
            currPage,
            posts,
            pageDecrement,
            pageIncrement,
        });
    } catch (err) {
        console.log("Error: ", err);
        res.status(500).send("something went wrong");
    }
};

顺便说一下,在 post 架构中,对于您使用 default: Date.now() 的日期字段,这将导致日期值始终是相同的值,它应该是这种格式:default: Date.now