基于Mongoose+Express实现电影API收藏功能的技术问题
电影API收藏功能实现指南
一、独立收藏集合的合理性
你的选择完全正确:
- 把收藏数据存在独立集合,比嵌套在User文档里更靠谱。如果把收藏列表塞在User文档里,用户收藏大量电影时,文档会持续膨胀,很快逼近MongoDB的16MB文档大小限制,而且查询、更新收藏列表的性能会越来越差。
- 你给
{ movie: 1, user: -1 }加了复合索引,这步很关键,能直接把“判断用户是否收藏某电影”“查询用户所有收藏”这类操作的效率拉满,避免全集合扫描。
二、解决虚拟属性不实时更新的问题
你之前的pre(/^find/)钩子写法有问题——虚拟属性是Schema级别的,一旦定义就会被Mongoose缓存,所以移除收藏后不会实时更新,必须重启服务器才生效。给你两个可行的解决方案:
方案1:查询电影时实时关联收藏状态(推荐)
不用依赖虚拟属性,在获取电影详情的接口里,直接根据当前登录用户ID查询Favorit集合,把收藏状态加到返回结果里。示例代码(假设用Express,当前用户ID存在req.user._id):
// 电影详情路由 router.get('/movies/:id', async (req, res) => { try { // 查电影基础信息 const movie = await Movie.findById(req.params.id); if (!movie) { return res.status(404).json({ message: '电影不存在' }); } // 判断当前用户是否收藏该电影 const isFavorited = await Favorit.exists({ user: req.user._id, movie: req.params.id }); // 把状态合并到返回结果 res.json({ ...movie.toObject(), addedToList: !!isFavorited }); } catch (err) { res.status(500).json({ message: err.message }); } });
方案2:给Movie模型加静态方法封装查询
如果想把逻辑封装到模型层,可以给Movie加一个静态方法,专门返回带收藏状态的电影信息:
// 在Movie Schema中定义静态方法 movieSchema.statics.getWithFavoriteStatus = async function(movieId, userId) { const movie = await this.findById(movieId); if (!movie) return null; const isFavorited = await Favorit.exists({ user: userId, movie: movieId }); return { ...movie.toObject(), addedToList: !!isFavorited }; }; // 接口中调用 router.get('/movies/:id', async (req, res) => { try { const movie = await Movie.getWithFavoriteStatus(req.params.id, req.user._id); if (!movie) { return res.status(404).json({ message: '电影不存在' }); } res.json(movie); } catch (err) { res.status(500).json({ message: err.message }); } });
三、收藏/取消收藏的核心接口实现
- 收藏(POST请求):先检查是否已收藏,未收藏则创建记录
router.post('/favorites', async (req, res) => { try { const { movieId } = req.body; // 先判断是否已收藏 const existing = await Favorit.findOne({ user: req.user._id, movie: movieId }); if (existing) { return res.status(400).json({ message: '已经收藏过这部电影了' }); } // 创建收藏记录 const favorite = await Favorit.create({ user: req.user._id, movie: movieId }); res.status(201).json(favorite); } catch (err) { res.status(500).json({ message: err.message }); } });
- 取消收藏(DELETE请求):根据用户ID和电影ID删除收藏记录
router.delete('/favorites/:movieId', async (req, res) => { try { const deleted = await Favorit.findOneAndDelete({ user: req.user._id, movie: req.params.movieId }); if (!deleted) { return res.status(404).json({ message: '你还没收藏这部电影' }); } res.json({ message: '取消收藏成功' }); } catch (err) { res.status(500).json({ message: err.message }); } });
内容的提问来源于stack exchange,提问作者Abdullah Moubayed
相关产品推荐
相关产品推荐

