Laravel中避免用户重复投票的上下投票系统优化问询
上下投票系统的资源高效实现方案
针对你已经在Vote、Post、Comment间实现多态关联的场景,要解决帖子列表页的票数展示、用户投票状态标记问题,同时减少冗余数据开销,直接在后端查询时注入所需字段是最优解,不用拉取所有投票记录。
核心思路
不用with('votes')拉取所有用户的投票数据,而是通过子查询聚合直接计算帖子净票数,同时针对登录用户单独查询其投票状态,把vote_count(净票数)、has_voted(是否投过票)、vote_direction(投票方向)直接注入到Post模型实例中,前端拿到数据就能直接用,不用嵌套遍历处理冗余数据。
具体实现代码
基础查询逻辑
直接在帖子列表查询时添加所需字段:
$userId = auth()->id(); $posts = Post::query() // 计算净票数:up票加1,down票减1,求和得到总票数 ->withCount([ 'votes as vote_count' => function ($query) { $query->select(DB::raw('SUM(CASE WHEN direction = "up" THEN 1 WHEN direction = "down" THEN -1 ELSE 0 END)')); } ]) // 仅当用户登录时,注入当前用户的投票状态 ->when($userId, function ($query) use ($userId) { $query->addSelect([ // 判断是否投过票:存在则返回1,否则为null 'has_voted' => Vote::selectRaw('1') ->where('votable_type', Post::class) ->where('votable_id', DB::raw('posts.id')) ->where('user_id', $userId) ->limit(1), // 获取投票方向:up/down,未投票则为null 'vote_direction' => Vote::select('direction') ->where('votable_type', Post::class) ->where('votable_id', DB::raw('posts.id')) ->where('user_id', $userId) ->limit(1) ]); }) ->get();
模型层封装(复用性优化)
把这段逻辑封装到Post模型的作用域里,后续调用更方便:
// app/Models/Post.php public function scopeWithVoteStatus($query) { $userId = auth()->id(); return $query->withCount([ 'votes as vote_count' => function ($query) { $query->select(DB::raw('SUM(CASE WHEN direction = "up" THEN 1 WHEN direction = "down" THEN -1 ELSE 0 END)')); } ])->when($userId, function ($query) use ($userId) { $query->addSelect([ 'has_voted' => Vote::selectRaw('1') ->where('votable_type', static::class) ->where('votable_id', DB::raw('posts.id')) ->where('user_id', $userId) ->limit(1), 'vote_direction' => Vote::select('direction') ->where('votable_type', static::class) ->where('votable_id', DB::raw('posts.id')) ->where('user_id', $userId) ->limit(1) ]); }); } // 调用时只需一行 $posts = Post::withVoteStatus()->get();
前端与后端交互优化
用户投票/取消投票时,不用重新拉取整个列表,只需调用接口(比如POST /posts/{id}/vote),后端处理投票的创建/删除/更新后,返回更新后的vote_count、has_voted、vote_direction三个字段,前端直接更新对应帖子的状态即可。
性能补充
给Vote表的user_id、votable_type、votable_id三个字段建立联合索引,能大幅加速子查询的投票状态判断,避免数据库全表扫描:
CREATE INDEX votes_user_votable ON votes(user_id, votable_type, votable_id);
内容的提问来源于stack exchange,提问作者Max Pattern
相关产品推荐
相关产品推荐

