NestJS下Mongoose findOneAndUpdate预钩子this.set覆盖嵌套字段问题求解
问题产生的核心原因
- 在
findOneAndUpdate的预钩子中,this指向的是Mongoose Query实例,调用this.set()本质是向本次更新操作追加$set指令。如果你传入的是嵌套结构的对象,比如{ company: { name: "X3jfks" } },Mongoose会执行整体替换company字段的逻辑——直接用你传入的只有name的新对象,覆盖数据库中原本存的完整company对象,自然会把原本的number字段冲掉。 - 你业务代码里的调用写法本身也存在隐患:
...updateData展开后,实际传给findOneAndUpdate的更新体是{ company: "xyz" },这种不带操作符的写法会被Mongoose自动包装成$set: { company: "xyz" },同样会造成嵌套对象被整体替换的问题,只是之前被预钩子的set操作覆盖了,没有暴露出来。
符合业务约束的解决方案
因为业务要求必须在预钩子内通过this.set实现加密,只要避开“直接传嵌套整体对象做set”的写法,改用Mongoose支持的嵌套路径点语法做局部字段更新,就能在不改变钩子逻辑要求的前提下,解决嵌套字段被整体覆盖的问题。
第一步:调整预钩子的set写法
把原来直接传嵌套对象的逻辑,改成用点路径精准指定要修改的子字段:
schema.pre('findOneAndUpdate', function () { // 用点路径定位具体要加密的嵌套子字段,不传入完整的company对象 this.set({ 'company.name': "X3jfks" // 这里替换成实际的加密逻辑即可 }); });
这种写法的优势:
- 完全满足“在预钩子内部使用this.set实现加密”的业务约束,不需要修改钩子的触发时机和调用方式
- 点路径形式的
$set操作只会精准更新指定的company.name字段,不会触碰company节点下的其他原有字段(比如之前存储的number: 123),自然不会出现整体覆盖丢失字段的问题
第二步:修正业务侧的更新调用逻辑
原来的业务代码存在参数展开错误,会造成额外的字段覆盖风险,需要同步调整:
// 错误的原写法 const updateData = { company: "xyz" } await this.model.findOneAndUpdate( {_id: "x8"}, ...updateData // 展开后实际传入的是company: "xyz",会整体替换嵌套对象 ) // 修正后的写法 const updateData = { 'company.name': "xyz" // 同样用点路径指定要更新的子字段,避免整体替换 } await this.model.findOneAndUpdate( {_id: "x8"}, updateData // 直接传入更新对象即可,不需要多余的展开操作 )
动态场景的兼容处理
如果加密逻辑需要读取业务侧传入的原始字段值,不需要额外查询数据库,可以通过Query实例提供的方法获取当前更新上下文的内容,做兼容处理:
schema.pre('findOneAndUpdate', function () { // 获取本次操作已经传入的全部更新内容 const rawUpdate = this.getUpdate(); // 清除可能存在的整体company替换逻辑,避免覆盖 if (rawUpdate.company) { this.unset('company'); } // 拿到原始的name值执行加密,通过点路径设置回具体字段 const rawNameValue = rawUpdate.company?.name || rawUpdate['company.name']; this.set({ 'company.name': encryptFunction(rawNameValue) // 替换成实际的加密方法 }); });
内容的提问来源于stack exchange,提问作者kims akka
相关产品推荐
相关产品推荐

