如何在Mongoose中实现单文档ratings数组内user_id唯一,无需全局唯一校验
解决方案
你遇到的问题根源是:Mongoose中给数组内字段加unique属性会创建集合级别的唯一索引,会校验整个集合内所有数组元素的该字段唯一,和你要的单文档内数组唯一的需求不符。你可以通过下面两种方案实现需求:
方案1:使用Mongoose自定义校验器
先修改Schema,移除user_id的unique属性,新增数组内user_id唯一的自定义校验:
const ReviewSchema = new mongoose.Schema({ "blog_id": {type: Number, required: true, unique: true}, ratings: [{ "user_id": {type: String, required: true}, "rating": {type: Number, required: true} }] }) // 新增自定义校验:同一文档的ratings数组内user_id不重复 ReviewSchema.path('ratings').validate(function(ratings) { const userIds = ratings.map(item => item.user_id) // 去重后长度和原长度一致说明没有重复 return new Set(userIds).size === userIds.length }, '同一博客下同一用户只能提交一次评分')
该方案的优势是校验逻辑和Schema耦合,只要是通过Mongoose操作这个模型的写入请求都会自动触发校验,不需要在业务代码里重复写判断。
方案2:写入时加查询条件实现原子性校验
如果你需要更高的并发安全性,避免校验和写入之间的竞态条件,可以在新增评分的更新语句里加ratings.user_id不存在的判断:
// 示例:给blog_id为1的文档新增user_id为3的评分 const updateResult = await ReviewModel.updateOne( { blog_id: 1, // 加这个条件:当前文档的ratings数组里没有要插入的user_id 'ratings.user_id': { $ne: '3' } }, { $push: { ratings: { user_id: '3', rating: 5 } } } ) // 判断更新结果,如果matchedCount为0说明要么blog不存在,要么用户已经评过分 if (updateResult.matchedCount === 0) { throw new Error('该用户已对当前博客提交过评分,请勿重复提交') }
该方案是原子操作,不会有并发问题,适合高并发的业务场景。
内容的提问来源于stack exchange,提问作者Aman Gupta
相关产品推荐
相关产品推荐

