You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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);
    }
  }
});
问题分析与解决方案

错误点

  1. 字段名错误:更新操作中误用了users字段,但模型里对应的点赞数组是likes,这是导致操作无效的核心原因。
  2. 查询逻辑冗余:判断用户是否在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 07:10:33