如何设置Mongoose Schema实现isClosed字段仅可修改一次的安全限制
解决方案
你可以通过数据库层+接口层的多层防护实现需求,以下是可直接落地的实现方式:
方案1:使用Mongoose字段immutable属性(最简实现)
Mongoose 5.6及以上版本支持immutable属性,可通过函数动态判断字段是否允许修改,直接修改你的Schema定义即可:
isClosed: { type: Boolean, // 当当前文档的isClosed已经为true时,该字段不可修改 immutable: (doc) => doc.isClosed === true }
该方式会自动忽略所有对已为true的isClosed字段的修改请求,不会报错也不会把无效修改写入数据库,适配你初始字段无值、第一次可设置为true的需求。
方案2:添加Mongoose Pre钩子做全局拦截
如果需要更灵活的控制(比如修改时抛出明确错误),可以给所有更新操作加钩子拦截,在Schema定义后添加以下代码:
// 拦截save类更新操作 bidSchema.pre('save', function(next) { if (this.isModified('isClosed') && this.$locals.originalDoc?.isClosed === true) { return next(new Error('已关闭的竞价无法修改状态')) } next() }) // 拦截findOneAndUpdate、updateOne等所有update类操作 const updatePreHook = function(next) { const updateParams = this.getUpdate() // 仅当更新参数包含isClosed字段时做校验 if (updateParams.isClosed !== undefined) { this.model.findOne(this.getQuery()).then(doc => { // 如果当前记录已经是关闭状态,直接删除更新参数里的isClosed字段 if (doc && doc.isClosed === true) { delete updateParams.isClosed // 也可以选择直接抛出错误:return next(new Error('已关闭的竞价无法修改状态')) } next() }).catch(err => next(err)) } else { next() } } bidSchema.pre('findOneAndUpdate', updatePreHook) bidSchema.pre('updateOne', updatePreHook)
该方案在数据库操作层做全局拦截,就算接口层漏了校验也不会出现非法修改的问题。
方案3:接口层做前置校验(双重保障)
你也可以在处理PUT请求的业务逻辑层加一层校验,提前拦截非法请求,示例代码如下:
// 竞价修改接口逻辑示例 async function updateBidHandler(req, res) { const { bidId } = req.params const updateData = req.body // 先查询当前竞价的状态 const targetBid = await bidModel.findById(bidId) if (!targetBid) return res.status(404).send('目标竞价不存在') // 如果竞价已关闭,直接删除请求里的isClosed参数,避免修改 if (targetBid.isClosed === true && Reflect.has(updateData, 'isClosed')) { delete updateData.isClosed // 也可以选择直接返回错误:return res.status(403).send('已关闭的竞价不允许修改状态') } // 执行剩余更新逻辑 const updatedBid = await bidModel.findByIdAndUpdate(bidId, updateData, { new: true }) return res.send(updatedBid) }
建议三种方案组合使用,实现多层防护,避免校验逻辑被绕过。
内容的提问来源于stack exchange,提问作者20CS039 - Gratus Richard A
相关产品推荐
相关产品推荐

