如何在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 }); } };
代码说明
- $match:精准过滤当前登录用户的购物车记录,减少后续处理的数据量。
- $lookup:分别关联
users和products集合,获取用户和商品的完整信息,注意from参数必须是MongoDB中的集合名称(mongoose默认会将模型名转为小写复数)。 - $unwind:将
$lookup返回的数组类型字段展开为单个对象,因为每个购物车项只会对应一个用户和一个商品,展开后方便后续分组和字段提取。 - $group:按用户ID分组,使用
$first提取用户的姓名和邮箱(同一用户的所有购物车项中这些信息一致),使用$push将每个购物车项的信息收集为数组。 - $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
相关产品推荐
相关产品推荐

