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

如何在Mongoose中关联多集合并获取指定字段(MERN电商场景)

MERN电商平台购物车聚合查询优化方案

问题场景

开发基于MERN栈的电商平台,包含User、Product、Cart三个Schema,其中Cart Schema关联User和Product。需要实现购物车查询,按用户分组后返回以下信息:

  • 购物车ID、商品数量(unit)
  • 用户姓名(name)、邮箱(email)
  • 商品标题(title)、价格(price)
    返回格式需为数组,方便React前端用map方法渲染。

现有Schema结构

cartModel.js

const mongoose = require("mongoose");
const { Schema, model } = mongoose;

const cartSchema = new Schema(
  {
    user: { type: Schema.Types.ObjectId, ref: "User", required: true },
    product: { type: Schema.Types.ObjectId, ref: "Product", required: true },
    unit: { type: Number, required: true },
  },
  { timestamps: true, versionKey: false }
);

const cartModel = model("Cart", cartSchema);
module.exports = cartModel;

productModel.js

const mongoose = require("mongoose");
const { Schema, model } = mongoose;

const productSchema = new Schema(
  {
    title: String,
    short_des: String,
    price: Number,
    discount_price: Number,
    image: String,
    stock: String,
    star: String,
  },
  { timestamps: true, versionKey: false }
);

const productModel = model("Product", productSchema);

module.exports = productModel;

userModel.js

const mongoose = require("mongoose");
const { Schema, model } = mongoose;

const userSchema = new Schema(
  {
    name: { type: String, required: true },
    email: { type: String, unique: true, required: true },
    password: { type: String, required: true },
  },
  { timestamps: true, versionKey: false }
);

const userModel = model("User", userSchema);

module.exports = userModel;

原查询代码(未达预期)

exports.cartList = async (req, res) => {
  try {
    let userId = new mongoose.Types.ObjectId(req.user._id);

    let result = await cartModel.aggregate([
      { $match: { user: userId } },
      {
        $lookup: {
          from: "users", // Name of the User model collection
          localField: "user",
          foreignField: "_id",
          as: "userDetails",
        },
      },
      {
        $lookup: {
          from: "products", // Name of the Product model collection
          localField: "product",
          foreignField: "_id",
          as: "productDetails",
        },
      },
      {
        $group: {
          _id: "$user",
        },
      },
    ]);
    res.status(200).json({ status: true, data: result });
  } catch (error) {
    res.status(200).json({ status: false, data: error.message });
  }
};

购物车示例数据

[
  {
    "_id": { "$oid": "6592be024fc8de3f1771a9e1" },
    "product": { "$oid": "657fab1586f102e3657bf034" },
    "user": { "$oid": "6559696bfa9b7df357161a81" },
    "createdAt": { "$date": "2024-01-01T13:28:34.452Z" },
    "unit": 4,
    "updatedAt": { "$date": "2024-01-01T13:28:46.865Z" }
  },
  {
    "_id": { "$oid": "6592dc014fc8de3f1771bc3f" },
    "product": { "$oid": "65801d5e2c366e8773ada21f" },
    "user": { "$oid": "6559696bfa9b7df357161a81" },
    "createdAt": { "$date": "2024-01-01T15:36:33.420Z" },
    "unit": 3,
    "updatedAt": { "$date": "2024-01-01T15:36:37.161Z" }
  }
]

优化后的聚合查询实现

exports.cartList = async (req, res) => {
  try {
    const userId = new mongoose.Types.ObjectId(req.user._id);

    const result = await cartModel.aggregate([
      // 1. 过滤当前用户的购物车数据
      { $match: { user: userId } },
      // 2. 关联User集合,获取用户详情
      {
        $lookup: {
          from: "users",
          localField: "user",
          foreignField: "_id",
          as: "userDetails"
        }
      },
      // 3. 关联Product集合,获取商品详情
      {
        $lookup: {
          from: "products",
          localField: "product",
          foreignField: "_id",
          as: "productDetails"
        }
      },
      // 4. 展开用户详情数组(每个购物车项对应一个用户,展开后变成对象)
      { $unwind: "$userDetails" },
      // 5. 展开商品详情数组(每个购物车项对应一个商品,展开后变成对象)
      { $unwind: "$productDetails" },
      // 6. 按用户ID分组,收集用户信息和购物车商品列表
      {
        $group: {
          _id: "$user",
          userName: { $first: "$userDetails.name" },
          userEmail: { $first: "$userDetails.email" },
          cartItems: {
            $push: {
              cartId: "$_id",
              unit: "$unit",
              productTitle: "$productDetails.title",
              productPrice: "$productDetails.price"
            }
          }
        }
      },
      // 7. 整理输出格式,移除默认_id,改用更清晰的字段名
      {
        $project: {
          _id: 0,
          userId: "$_id",
          userName: 1,
          userEmail: 1,
          cartItems: 1
        }
      }
    ]);

    res.status(200).json({ status: true, data: result[0] || {} });
  } catch (error) {
    res.status(500).json({ status: false, data: error.message });
  }
};

代码说明

  1. $match:精准过滤当前登录用户的购物车记录,减少后续处理的数据量。
  2. $lookup:分别关联users和products集合,获取用户和商品的完整信息,注意from参数必须是MongoDB中的集合名称(mongoose默认会将模型名转为小写复数)。
  3. $unwind:将$lookup返回的数组类型字段展开为单个对象,因为每个购物车项只会对应一个用户和一个商品,展开后方便后续分组和字段提取。
  4. $group:按用户ID分组,使用$first提取用户的姓名和邮箱(同一用户的所有购物车项中这些信息一致),使用$push将每个购物车项的信息收集为数组。
  5. $project:调整输出格式,隐藏默认的_id,新增userId字段,让返回结构更清晰。

返回数据示例

{
  "status": true,
  "data": {
    "userId": "6559696bfa9b7df357161a81",
    "userName": "张三",
    "userEmail": "zhangsan@example.com",
    "cartItems": [
      {
        "cartId": "6592be024fc8de3f1771a9e1",
        "unit": 4,
        "productTitle": "商品A",
        "productPrice": 99
      },
      {
        "cartId": "6592dc014fc8de3f1771bc3f",
        "unit": 3,
        "productTitle": "商品B",
        "productPrice": 199
      }
    ]
  }
}

在React前端可以直接通过data.cartItems.map(item => ...)来渲染购物车列表,完全适配需求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 05:24:57