MongoDB数组条件更新:实现单帖用户仅可点赞或点踩一次
问题根源
你的查询条件失效核心是对数组字段错误使用了$nin操作符。$nin的语义是匹配字段值不在给定候选列表中的文档,作用在数组字段上时,它的匹配逻辑是「数组没有任何元素完全匹配候选列表值」,这个逻辑在数组存在多元素、类型隐式转换场景下很容易出现非预期匹配,直接导致投票拦截逻辑失效。
另外你当前的逻辑只覆盖了未投票用户的首次投票场景,既没有对更新结果做校验,也没有兼容多数论坛常见的「切换投票」需求。
修复方案
判断数组中是否不存在指定元素,直接使用$ne操作符即可,这是Mongo官方明确推荐的数组元素查询写法,行为稳定无歧义,且不需要额外包裹$and(Mongo顶层多条件默认就是且逻辑)。
基础版:仅允许单次投票,不可切换
完全符合你当前的业务规则:每个用户只能选点赞/点踩其中一种,不可重复投、不可同时投。
// 点赞操作 const vote_result = await Poll.updateOne( { _id: poll_id, likes: { $ne: MY_ID }, dislikes: { $ne: MY_ID } }, { $addToSet: { likes: MY_ID } } ) // 必须校验匹配条数,没匹配到说明用户已经投过票 if (vote_result.matchedCount === 0) { throw new Error("不可重复投票") }
点踩操作只需要把$addToSet的字段从likes换成dislikes即可,查询条件保持不变。
进阶版:支持切换投票(可选)
如果需要兼容用户从点赞改点踩、点踩改点赞的常见需求,可以用原子更新一步完成,不需要先查后改,避免并发问题:
// 切换为点赞 const vote_result = await Poll.updateOne( { _id: poll_id, likes: { $ne: MY_ID } // 防止重复点赞 }, { // 先从点踩列表移除当前用户(存在才删,不存在无影响) $pull: { dislikes: MY_ID }, // 加入点赞列表 $addToSet: { likes: MY_ID } } )
点踩逻辑只需要互换$pull和$addToSet对应的字段即可。
场景验证
修复后可以覆盖所有测试场景:
- 初始状态
likes: []、dislikes: []:点赞、点踩均可正常执行 - 已点赞状态
likes: [MY_ID]、dislikes: []:重复点赞直接被拦截,若使用进阶版逻辑,发起点踩会自动将用户从点赞列表移到点踩列表 - 已点踩状态
likes: []、dislikes: [MY_ID]:重复点踩直接被拦截,点赞逻辑同理
内容的提问来源于stack exchange,提问作者DaMatt91
相关产品推荐
相关产品推荐

