Mongoose findOneAndUpdate的pre hook修改字段无效,求正确实现方案
使用Mongoose的findOneAndUpdate pre钩子修改字段的正确方式
我在使用Mongoose的pre钩子处理findOneAndUpdate操作时,希望在文档保存到数据库前将password字段转为MD5值,但当前代码未生效,文档无任何变化。以下是实际文档、期望文档及代码,求正确实现方式:
实际文档
{ _id: new ObjectId("622457f5555562da89b7a1dd"), id: '5982ca552aeb2b12344eb6cd', name: 'Test User', configuration: [ { email: 'test2@gmail.com', password: 'p@ssw0rd', _id: new ObjectId("9473l58f2ad34efb816963dd"), }, { email: 'test3@gmail.com', password: 'trUstN0oNe', _id: new ObjectId("8674884cec1877c59c8838e0") } ], __v: 0 }
期望文档
{ _id: new ObjectId("622457f5555562da89b7a1dd"), id: '5982ca552aeb2b12344eb6cd', name: 'Test User', configuration: [ { email: 'test2@gmail.com', password: '0f359740bd1cda994f8b55330c86d845', _id: new ObjectId("9473l58f2ad34efb816963dd"), }, { email: 'test3@gmail.com', password: '3dba7872281dfe3900672545356943ce', _id: new ObjectId("8674884cec1877c59c8838e0") } ], __v: 0 }
当前代码
const UserSchema = new Schema({ id: { type: String, required: [true, "'id' value is required"] }, name: { type: String, required: [true, "'name' value is required"] }, configuration: [ConfigModel.schema] }); const ConfigSchema = new Schema({ email: { type: String, required: [true, "Email is required"] }, password: { type: String, required: [true, "Password is required"] } }); UserSchema.pre('findOneAndUpdate', async function(next) { const docToUpdate = await this.model.findOne(this.getQuery()); docToUpdate.configuration.forEach((item,i) => { docToUpdate.configuration[i].password = md5(item.password); }); return next(); });
问题分析
你的代码问题在于:虽然查询到了要更新的文档并修改了密码,但没有将修改后的内容应用到findOneAndUpdate的更新操作中。findOneAndUpdate的pre钩子默认是基于你传入的更新参数执行操作,而非你修改后的查询文档。
解决方案
方案一:直接修改更新操作的参数(推荐)
这种方式更高效,符合findOneAndUpdate的设计逻辑,无需额外的数据库查询和保存操作。核心是获取当前更新操作中的configuration数据,对密码进行MD5加密后替换原数据:
const md5 = require('md5'); // 确保已安装并引入md5库 UserSchema.pre('findOneAndUpdate', function(next) { // 检查更新操作中是否包含configuration字段的修改 if (this._update.$set && this._update.$set.configuration) { // 遍历configuration数组,加密每个密码 this._update.$set.configuration = this._update.$set.configuration.map(item => ({ ...item, password: md5(item.password) })); } next(); });
方案二:修改查询到的文档并手动保存
如果你的需求是更新数据库中已存在的文档密码(而非传入新的configuration数组),可以修改查询到的文档后手动保存,但需注意取消原findOneAndUpdate操作避免重复更新:
const md5 = require('md5'); UserSchema.pre('findOneAndUpdate', async function(next) { const docToUpdate = await this.model.findOne(this.getQuery()); if (!docToUpdate) return next(); let hasChanged = false; docToUpdate.configuration.forEach((item, i) => { const encryptedPwd = md5(item.password); if (item.password !== encryptedPwd) { docToUpdate.configuration[i].password = encryptedPwd; hasChanged = true; } }); if (hasChanged) { await docToUpdate.save(); // 取消原findOneAndUpdate操作,避免重复更新 this._update = {}; this.setOptions({ overwrite: false }); } next(); });
注意事项
- 确保已正确安装并引入
md5库(可通过npm install md5安装)。 - 如果你的更新操作使用了其他MongoDB操作符(如
$push、$addToSet),需要对应调整钩子中的处理逻辑。
内容的提问来源于stack exchange,提问作者MaryCoding
相关产品推荐
相关产品推荐

