Mongoose更新验证异常:合法折扣值触发验证错误问题排查
Mongoose更新时折扣价验证异常问题排查与解决
问题重现
Schema定义
price: { type: Number, required: [true, 'A tour must have a price'], }, priceDiscount: { type: Number, validate: function (val) { return val < this.price; } }
异常现象
- 创建新Tour时,折扣价验证规则正常生效
- 执行更新操作时,即使传入合法参数(如
{"price": 997,"priceDiscount":10}),仍触发验证错误,错误信息如下:
"status": "FAIL", "message": { "errors": { "priceDiscount": { "name": "ValidatorError", "message": "Validator failed for path `priceDiscount` with value `10`", "properties": { "message": "Validator failed for path `priceDiscount` with value `10`", "type": "user defined", "path": "priceDiscount", "value": 10 }, "kind": "user defined", "path": "priceDiscount", "value": 10 } }, "_message": "Validation failed", "name": "ValidationError", "message": "Validation failed: priceDiscount: Validator failed for path `priceDiscount` with value `10`" }
更新接口代码
exports.UpdateTour = async (req, res) => { try { const upTour = await Tour.findByIdAndUpdate(req.params.id, req.body, { new: true, runValidators: true, }); res.status(200).json({ status: 'success', data: { tour: `UPDATE TOUR #${req.params.id} ${upTour}`, }, }); } catch (err) { res.status(400).json({ status: 'FAIL', message: err, }); } };
原因分析
核心问题在于Mongoose更新类方法(如findByIdAndUpdate)中,字段验证器的this指向不是文档实例:
- 创建文档时,验证器的
this指向新创建的文档实例,能正常访问this.price - 更新文档时,即使设置了
runValidators: true,验证器的this指向的是Mongoose的Query对象,而非目标文档实例,此时this.price为undefined,导致val < undefined返回false,触发验证错误。
解决方案
方案1:改用先查询再保存的方式
放弃findByIdAndUpdate,先获取文档实例,修改后调用save()方法,此时验证器的this会正确指向文档实例:
exports.UpdateTour = async (req, res) => { try { // 先获取目标文档 const tour = await Tour.findById(req.params.id); if (!tour) { return res.status(404).json({ status: 'FAIL', message: 'Tour not found' }); } // 合并更新字段 Object.assign(tour, req.body); // 调用save触发完整验证 const upTour = await tour.save(); res.status(200).json({ status: 'success', data: { tour: upTour, }, }); } catch (err) { res.status(400).json({ status: 'FAIL', message: err, }); } };
方案2:使用异步验证器
通过异步验证器查询原文档,对比折扣价与价格(支持同时更新价格和折扣价的场景):
修改Schema中的priceDiscount验证规则:
priceDiscount: { type: Number, validate: { validator: async function(val) { // 获取更新的query,拿到文档ID const tourId = this.getQuery()._id; // 获取本次更新的字段 const update = this.getUpdate(); // 查询原文档 const tour = await this.model.findById(tourId); // 确定对比的价格:若更新了price则用新值,否则用原文档价格 const targetPrice = update.price !== undefined ? update.price : tour.price; return val < targetPrice; }, message: 'Discount price ({VALUE}) must be lower than the tour price' } }
方案3:使用pre('findOneAndUpdate')中间件手动验证
在Schema上添加预更新中间件,手动验证折扣价规则:
TourSchema.pre('findOneAndUpdate', async function(next) { const update = this.getUpdate(); // 仅当更新包含priceDiscount时验证 if (update.priceDiscount !== undefined) { // 获取原文档 const tour = await this.model.findById(this.getQuery()._id); // 确定对比的价格:若更新了price则用新值,否则用原文档价格 const price = update.price !== undefined ? update.price : tour.price; if (update.priceDiscount >= price) { return next(new Error('Discount price must be lower than the tour price')); } } next(); });
内容的提问来源于stack exchange,提问作者Mahmoud Mohammed Aboelella
相关产品推荐
相关产品推荐

