Node.js+MongoDB实现Opportunity模型点赞/取消点赞功能求助
问题描述
我有一个Opportunity模型,其中包含likes字段,该字段是由用户ObjectId组成的数组。需要实现帖子的点赞/取消点赞逻辑:先检查用户是否已存在于likes数组中,若存在则将其从数组中移除,若不存在则将其添加到数组中。
我的Opportunity模型代码
const mongoose = require("mongoose"); const OpportunityModel = new mongoose.Schema({ user: { type: mongoose.Schema.Types.ObjectId, ref: "User", }, content: { type: String, required: true, trim: true, }, likes: [ { type: mongoose.Schema.Types.ObjectId, ref: "User", }, ], image: [ { type: Object, }, ], share_with_thoughts: { type: mongoose.Schema.Types.ObjectId, ref: "Shares", }, comments: { type: mongoose.Schema.Types.ObjectId, ref: "Comment", }, is_opportunity_applied: [ { type: mongoose.Schema.Types.ObjectId, ref: "User", }, ], }); const Opportunity = mongoose.model("Opportunity", OpportunityModel); module.exports = Opportunity;
我尝试但未成功的代码
const likeOrUnlikeOpportunity = expressAsyncHandler(async (req, res) => { let userId = req.user._id; let opportunityId = req.params.opportunityId; let isUserExist = await Opportunity.find({ $and: [{ _id: opportunityId }, { likes: { $elemMatch: { $eq: userId } } }], }).populate("user", "user_id user_name"); if (isUserExist.length > 0) { const unliked = await Opportunity.findByIdAndUpdate( opportunityId, { $pull: { users: userId }, }, { new: true } ).populate("user", "user_id user_name"); if (!unliked) { res.status(500).send({ ErrMessaage: "an error occured" }); } else { res.status(200).json(unliked); } } else { const added = await Opportunity.findByIdAndUpdate( opportunityId, { $push: { users: userId }, }, { new: true } ).populate("user", "user_id user_name"); if (!added) { res.status(500).send({ ErrMessaage: "an error occured" }); } else { res.status(200).json(added); } } });
问题分析与解决方案
错误点
- 字段名错误:更新操作中误用了
users字段,但模型里对应的点赞数组是likes,这是导致操作无效的核心原因。 - 查询逻辑冗余:判断用户是否在
likes数组中不需要用$and和$elemMatch,直接用likes: userId即可完成匹配。
优化方案一(原子操作,性能更优)
通过一次数据库操作尝试取消点赞,失败则执行点赞,避免两次查询的性能损耗:
const likeOrUnlikeOpportunity = expressAsyncHandler(async (req, res) => { const userId = req.user._id; const opportunityId = req.params.opportunityId; // 先尝试取消点赞:移除当前用户ID let updatedOpportunity = await Opportunity.findOneAndUpdate( { _id: opportunityId, likes: userId }, { $pull: { likes: userId } }, { new: true } ).populate("user", "user_id user_name"); // 若未找到匹配文档(用户未点赞),则执行点赞操作 if (!updatedOpportunity) { updatedOpportunity = await Opportunity.findOneAndUpdate( { _id: opportunityId }, { $push: { likes: userId } }, { new: true } ).populate("user", "user_id user_name"); } if (!updatedOpportunity) { return res.status(404).json({ ErrMessage: "未找到对应的Opportunity" }); } res.status(200).json(updatedOpportunity); });
优化方案二(逻辑直观,先判断再操作)
先查询目标文档明确用户点赞状态,再执行对应操作:
const likeOrUnlikeOpportunity = expressAsyncHandler(async (req, res) => { const userId = req.user._id; const opportunityId = req.params.opportunityId; // 查询目标Opportunity const opportunity = await Opportunity.findById(opportunityId); if (!opportunity) { return res.status(404).json({ ErrMessage: "未找到对应的Opportunity" }); } let updatedOpportunity; const hasLiked = opportunity.likes.includes(userId); if (hasLiked) { // 取消点赞 updatedOpportunity = await Opportunity.findByIdAndUpdate( opportunityId, { $pull: { likes: userId } }, { new: true } ).populate("user", "user_id user_name"); } else { // 添加点赞 updatedOpportunity = await Opportunity.findByIdAndUpdate( opportunityId, { $push: { likes: userId } }, { new: true } ).populate("user", "user_id user_name"); } res.status(200).json(updatedOpportunity); });
注意事项
- 确保
req.user._id是有效的ObjectId类型,若为字符串,可通过mongoose.Types.ObjectId(userId)转换。 - 区分“资源不存在”和“服务器错误”的状态码与提示信息,便于前端处理。
内容的提问来源于stack exchange,提问作者MAXWEL OCHIENG
相关产品推荐
相关产品推荐

