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

如何用Node.js实现视频点赞计数更新及用户点赞状态验证

基于Likedislike模型更新视频点赞/点踩计数的实现方案

一、先优化模型(避免重复记录)

首先给likedislikeSchema添加唯一索引,确保同一用户对同一视频只会有一条点赞/点踩记录,防止重复操作:

const likedislikeSchema = mongoose.Schema(
  {
    videoId: {
      type: ObjectId,
      ref: 'video',
    },
    userId: {
      type: ObjectId,
      ref: 'user',
    },
    like: {
      type: Number,
      default: 0, // 0=未点赞,1=已点赞
    },
    dislike: {
      type: Number,
      default: 0, // 0=未点踩,1=已点踩
    },
  },
  {
    timestamps: true,
  }
);

// 添加唯一索引,约束同一用户+同一视频的记录唯一性
likedislikeSchema.index({ videoId: 1, userId: 1 }, { unique: true });

const LikedislikeModel = mongoose.model('Likedislike', likedislikeSchema);
module.exports = LikedislikeModel;

二、核心操作实现

以下是点赞、点踩、验证用户状态的具体函数,使用Mongoose事务保证数据一致性(两个模型的操作要么同时成功,要么同时回滚):

1. 点赞/取消点赞操作

const mongoose = require('mongoose');
const LikedislikeModel = require('./likedislikeModel');
const VideoModel = require('./videoModel');

async function handleVideoLike(userId, videoId) {
  const session = await mongoose.startSession();
  session.startTransaction();

  try {
    // 查询用户对该视频的已有操作记录
    const existingRecord = await LikedislikeModel.findOne({ userId, videoId }).session(session);

    if (existingRecord) {
      if (existingRecord.like === 1) {
        // 用户已点赞,执行取消点赞
        await LikedislikeModel.updateOne(
          { _id: existingRecord._id },
          { $set: { like: 0 } },
          { session }
        );
        await VideoModel.updateOne(
          { _id: videoId },
          { $inc: { likes: -1 } }, // 点赞数减1
          { session }
        );
      } else {
        // 用户之前点了踩,现在切换为点赞
        await LikedislikeModel.updateOne(
          { _id: existingRecord._id },
          { $set: { like: 1, dislike: 0 } },
          { session }
        );
        await VideoModel.updateOne(
          { _id: videoId },
          { $inc: { likes: 1, dislikes: -1 } }, // 点赞+1,点踩-1
          { session }
        );
      }
    } else {
      // 无记录,新增点赞记录
      await LikedislikeModel.create(
        [{ userId, videoId, like: 1 }],
        { session }
      );
      await VideoModel.updateOne(
        { _id: videoId },
        { $inc: { likes: 1 } },
        { session }
      );
    }

    await session.commitTransaction();
    session.endSession();
    return { success: true, msg: '操作成功' };
  } catch (err) {
    await session.abortTransaction();
    session.endSession();
    throw err;
  }
}

2. 点踩/取消点踩操作

async function handleVideoDislike(userId, videoId) {
  const session = await mongoose.startSession();
  session.startTransaction();

  try {
    const existingRecord = await LikedislikeModel.findOne({ userId, videoId }).session(session);

    if (existingRecord) {
      if (existingRecord.dislike === 1) {
        // 用户已点踩,执行取消点踩
        await LikedislikeModel.updateOne(
          { _id: existingRecord._id },
          { $set: { dislike: 0 } },
          { session }
        );
        await VideoModel.updateOne(
          { _id: videoId },
          { $inc: { dislikes: -1 } },
          { session }
        );
      } else {
        // 用户之前点了赞,现在切换为点踩
        await LikedislikeModel.updateOne(
          { _id: existingRecord._id },
          { $set: { dislike: 1, like: 0 } },
          { session }
        );
        await VideoModel.updateOne(
          { _id: videoId },
          { $inc: { dislikes: 1, likes: -1 } },
          { session }
        );
      }
    } else {
      // 无记录,新增点踩记录
      await LikedislikeModel.create(
        [{ userId, videoId, dislike: 1 }],
        { session }
      );
      await VideoModel.updateOne(
        { _id: videoId },
        { $inc: { dislikes: 1 } },
        { session }
      );
    }

    await session.commitTransaction();
    session.endSession();
    return { success: true, msg: '操作成功' };
  } catch (err) {
    await session.abortTransaction();
    session.endSession();
    throw err;
  }
}

3. 验证用户是否已点赞/点踩

async function checkUserVideoReaction(userId, videoId) {
  const record = await LikedislikeModel.findOne({ userId, videoId });
  if (!record) {
    return { isLiked: false, isDisliked: false };
  }
  return {
    isLiked: record.like === 1,
    isDisliked: record.dislike === 1
  };
}

三、关键注意事项

  • 事务保证一致性:使用Mongoose事务避免出现Likedislike记录更新了,但Video计数没更新的情况
  • 原子操作防并发:用$inc操作符修改计数,是MongoDB的原子操作,能避免多用户同时操作导致的计数错误
  • 互斥逻辑:当前实现中点赞和点踩是互斥的,用户不能同时点赞和点踩,若需要支持可调整字段逻辑

内容的提问来源于stack exchange,提问作者Venkatesh

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 11:20:34