Mongoose添加商品评论时rating字段NaN类型转换失败问题求助
商品评论添加时的ValidationError问题排查与修复
问题描述
给关联商品添加评论时,通过Postman调用API始终报错,错误信息:
ValidationError: Product validation failed: rating: Cast to Number failed for value "NaN" (type number) at path "rating"
错误栈信息
ValidationError: Product validation failed: rating: Cast to Number failed for value "NaN" (type number) at path "rating" [0] at Document.invalidate (E:\Web\Shop.co\node_modules\mongoose\lib\document.js:3197:32) [0] at model.$set (E:\Web\Shop.co\node_modules\mongoose\lib\document.js:1456:12) [0] at model.set [as rating] (E:\Web\Shop.co\node_modules\mongoose\lib\helpers\document\compile.js:205:19) [0] at E:\Web\Shop.co\backend\controller\productController.js:108:20 [0] at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
相关代码
Product Schema代码
const mongoose = require("mongoose"); const reviewSchema = mongoose.Schema( { name: { type: String, required: true }, rating: { type: Number, required: true, default: 0 }, comment: { type: String, required: true }, user: { type: mongoose.Schema.Types.ObjectId, required: true, ref: "User", }, }, { timestamps: true, } ); const productSchema = mongoose.Schema( { user: { type: mongoose.Schema.Types.ObjectId, required: true, ref: "User", }, name: { type: String, required: true, }, image: { type: String, required: true, }, brand: { type: String, required: true, }, category: { type: String, required: true, }, description: { type: String, required: true, }, price: { type: Number, required: true, default: 0, }, review: [reviewSchema], rating: { type: Number, required: true, default: 0, }, numReviews: { type: Number, required: true, default: 0, }, countInStock: { type: Number, required: true, default: 0, }, }, { timestamps: true, } ); const Product = mongoose.model("Product", productSchema); module.exports = Product;
控制器createReview函数代码
const createReview = asyncHandler(async (req, res) => { const { rating, comment } = req.body; const product = await Product.findById(req.params.id); if (product) { const alreadyReviewed = product.review.find( (r) => r.user.toString() === req.user._id.toString() ); if (alreadyReviewed) { res.status(400).json({ message: " Product already reviewed" }); throw new Error("Product already reviewed"); } const reviews = { name: req.user.name, rating: Number(rating), comment, user: req.user._id, }; product.review.push(reviews); product.numReviews = product.review.length; product.rating = product.review.reduce((item, acc) => item.rating + acc, 0) / product.review.length; await product.save(); res.status(100).json({ message: " Review Added" }); } else { res.status(400).json({ message: " Product not found" }); throw new Error("Product not found"); } });
路由代码
router.route("/:id/reviews").post(protect, createReview);
错误原因分析
- reduce参数顺序错误:计算商品平均评分时,
reduce回调函数的参数顺序写反了。正确顺序应为(acc, item)(累加器在前,当前元素在后),但代码中写成(item, acc),导致第一次累加时0 + 评论对象得到NaN,最终product.rating为NaN,触发Mongoose类型验证错误。 - 缺少输入校验:未对
req.body.rating做有效性校验,如果前端未传该字段或传入非数字值,Number(rating)会转为NaN,直接存入评论的rating字段,后续计算也会出错。
修复方案
1. 修正reduce参数顺序
修改控制器中平均评分计算代码:
product.rating = product.review.reduce((acc, item) => acc + item.rating, 0) / product.review.length;
2. 添加输入有效性校验
在创建评论前校验rating是否为有效数字:
const createReview = asyncHandler(async (req, res) => { const { rating, comment } = req.body; // 新增输入校验 if (!rating || isNaN(Number(rating)) || Number(rating) < 1 || Number(rating) > 5) { res.status(400).json({ message: "Invalid rating value, must be a number between 1 and 5" }); throw new Error("Invalid rating value"); } const product = await Product.findById(req.params.id); // 后续代码不变... });
3. 处理空评论列表边界情况(可选)
预防无评论时除以0的情况:
product.rating = product.review.length === 0 ? 0 : product.review.reduce((acc, item) => acc + item.rating, 0) / product.review.length;
4. 修正HTTP状态码
- 评论添加成功返回
201 Created,而非100 - 商品不存在返回
404 Not Found,而非400 Bad Request
修改后的响应代码:
// 评论添加成功 res.status(201).json({ message: "Review Added" }); // 商品不存在 res.status(404).json({ message: "Product not found" });
改进建议
- 使用Joi或express-validator做完善的请求参数校验,覆盖必填字段的格式、范围校验
- 在Schema中给评论的rating字段添加范围限制:
rating: { type: Number, required: true, default: 0, min: 1, max: 5 }, - 提取平均评分计算逻辑为单独函数,提高代码复用性
- 保存商品时添加错误捕获,返回更友好的错误信息
内容的提问来源于stack exchange,提问作者Nouman Sajjad
相关产品推荐
相关产品推荐

