如何用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
相关产品推荐
相关产品推荐

