使用参考文档进行 Mongoose 查询

问题描述 投票:0回答:1
我正在构建一个基于标签、价格、评级、排序和分页的复杂搜索路径(express.js、mongoose)。

这是产品架构:

import { Schema, model, SchemaTypes } from 'mongoose'; import { IProductDocument, IReviewDocument } from '../types.js'; const ProductSchema = new Schema<IProductDocument>( { name: { type: String, required: true, trim: true }, description: { type: String, required: true, trim: true }, price: { type: Number, required: true }, imagePath: { type: String, required: true }, tags: { type: [String], required: true, default: [] }, sold: { type: Number, required: true, default: 0 }, available: { type: Boolean, required: true, default: true }, reviews: { type: [SchemaTypes.ObjectId], ref: 'Review', default: [] } }, { timestamps: true, toJSON: { virtuals: true }, toObject: { virtuals: true } } ); ProductSchema.virtual('rating').get(async function () { if (this.reviews.length === 0) return 0; const reviews = this.reviews as unknown as IReviewDocument[]; const totalRating = reviews.reduce((acc, review) => { const reviewDocument = review; return acc + reviewDocument.rating; }, 0); const avgRating = totalRating / this.reviews.length; return Math.round(avgRating * 10) / 10; }); export default model<IProductDocument>('Product', ProductSchema);
这是路线:

router.get('/search', async (req: Request, res: Response) => { const tagsQuery = req.query.tags ? String(req.query.tags) : ''; const priceQuery = req.query.price ? String(req.query.price) : ''; const ratingQuery = req.query.rating ? String(req.query.rating) : ''; const sortQuery = req.query.sort ? String(req.query.sort) : ''; let sort: Record<string, 1 | -1>; switch (sortQuery) { case 'price_asc': sort = { price: 1 }; break; case 'price_desc': sort = { price: -1 }; break; case 'recommend': sort = { sold: -1 }; break; case 'newest': sort = { createdAt: -1 }; break; case 'oldest': sort = { createdAt: 1 }; break; default: sort = { sold: -1 }; break; } // e.g. tags=tag1,tag2 => tags = ['tag1', 'tag2'] (tags includes tag1 or tag2) let tags = tagsQuery .split(',') .map((tag) => tag.trim()) .filter((tag) => Boolean(tag)); if (!tags || tags.length === 0) { tags = ['bedroom', 'bed', 'bookshelf', 'chair', 'desk', 'drawer', 'livingroom', 'sofa', 'table']; } // e.g. price=0,100 => price = [0, 100] (price >= 0 && price <= 100) const price = priceQuery .split(',') .map((p) => Number(p.trim())) .filter((p) => Boolean(p)); // e.g. rating=3,5 => rating = [3, 5] (rating >= 3 && rating <= 5) const rating = ratingQuery .split(',') .map((r) => Number(r.trim())) .filter((r) => Boolean(r)); const limit = req.query.limit ? Number(req.query.limit) : 10; const page = req.query.page ? Number(req.query.page) : 1; const skip = (page - 1) * limit; const pipeline = [ { $match: { tags: { $in: [...tags] }, price: { $gte: price[0] || -1, $lte: price[1] || Number.MAX_SAFE_INTEGER } } }, { $facet: { filteredProducts: [ { $lookup: { from: 'reviews', localField: '_id', foreignField: 'productId', as: 'reviews' } }, { $addFields: { rating: { $round: [{ $avg: '$reviews.rating' }, 2] } } }, { $unset: 'reviews' }, { $match: { rating: { $gte: rating[0] || -1, $lte: rating[1] || Number.MAX_SAFE_INTEGER } } }, { $sort: sort }, { $skip: skip }, { $limit: limit } ], totalCount: [ { $count: 'count' } ] } } ]; const results = await Product.aggregate(pipeline); const products = results[0].filteredProducts; const count = results[0].totalCount[0]?.count || 0; if (products === undefined) return res.sendStatus(404); res.json({ products, count, currPage: page, totPage: Math.ceil(count / limit) }); });
这无法按预期工作,因为像这样查询:

/search?limit=5&sort=recommend

,返回一个空的产品数组。调试了一下,我认为问题隐藏在评级过滤中,但我不确定。

另外,如果可能的话,我更愿意使用猫鼬查询方法而不是聚合框架,但我不知道在如此复杂的情况下如何做到这一点。

node.js mongodb mongoose search aggregation-framework
1个回答
0
投票
尝试这种稍微简化的方法,看看它是否符合您的要求。

const pipeline = [ { $match: { tags: { $in: tags }, price: { $gte: price[0] || 0, $lte: price[1] || Number.MAX_SAFE_INTEGER } } }, { $lookup: { from: 'reviews', localField: '_id', foreignField: 'productId', as: 'reviews' } }, { $addFields: { rating: { $round: [{ $avg: '$reviews.rating' }, 2] } } }, { $match: { rating: { $gte: rating[0] || 0, $lte: rating[1] || Number.MAX_SAFE_INTEGER } } }, { $sort: sort }, { $skip: skip }, { $limit: limit } ]
    
© www.soinside.com 2019 - 2024. All rights reserved.