如何使用Mongoose的pre中间件对findOneAndUpdate做数据类型校验
问题根因
- 你开启的
runValidators默认仅校验Schema中配置的必填、自定义校验规则等,Mongoose默认会对更新字段做静默类型转换,比如传入数字给String类型字段会自动转成字符串,不会触发错误 - 你写的
pre("save")钩子仅对save()操作生效,对findOneAndUpdate、updateMany等更新类查询不生效 - 代码存在笔误:你定义的是
User模型,但更新接口中调用的是Post模型,需要先修正
解决方案
方案1:开启类型转换失败抛错(最简实现)
Mongoose 5.11+版本支持给字段或整个Schema配置cast: 'throw',类型不匹配无法转换时直接抛出校验错误,配合你已开启的全局runValidators即可生效,修改Schema如下:
const UserSchema = new Schema ({ nome: { type: String, required: true, trim: true, cast: 'throw' // 新增:类型转换失败直接抛错 }, email: { type: String, required: true, trim: true, cast: 'throw' }, morada: { type: String, required: true, trim: true, cast: 'throw' } }, { // 也可以全局配置给当前Schema所有字段生效,不需要每个字段单独加 cast: 'throw' });
方案2:使用findOneAndUpdate专属前置钩子(自定义校验逻辑)
如果需要自定义校验规则,直接针对findOneAndUpdate事件写前置钩子,注意更新钩子中要通过this.getUpdate()获取待更新的数据,而不是直接拿this(this在更新钩子中是查询实例,不是文档实例):
User.pre('findOneAndUpdate', function(next) { const updateData = this.getUpdate(); // 兼容普通更新和带$set的更新写法 const nome = updateData.$set?.nome ?? updateData.nome; const email = updateData.$set?.email ?? updateData.email; const morada = updateData.$set?.morada ?? updateData.morada; // 校验nome类型 if (nome !== undefined && typeof nome !== 'string') { this.invalidate('nome', 'nome必须为字符串类型'); return next(new Error('nome必须为字符串类型')); } // 同理校验其他字段 if (email !== undefined && typeof email !== 'string') { this.invalidate('email', 'email必须为字符串类型'); return next(new Error('email必须为字符串类型')); } if (morada !== undefined && typeof morada !== 'string') { this.invalidate('morada', 'morada必须为字符串类型'); return next(new Error('morada必须为字符串类型')); } next(); });
额外优化建议
调用更新接口时建议显式开启runValidators(避免全局配置未生效的情况),同时建议明确指定查询条件,不要直接传整个req.params:
const update = async (req, res) => { try { // 显式指定查询条件、开启runValidators let user = await User.findOneAndUpdate( { _id: req.params.id }, req.body, { new: true, runValidators: true } ); res.json(user) } catch (e) { res.status(400).json({ message: e.message }) // 校验错误属于客户端参数错误,用400状态码更规范 } }
内容的提问来源于stack exchange,提问作者mlila_p
相关产品推荐
相关产品推荐

