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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 20:50:26