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

Laravel:indexQuery与筛选器叠加导致role_id重复IN的问题解决

问题场景

我在Laravel中自定义了indexQuery,代码如下:

$resourceTable = 'soft';
$user = Auth::user();

$orderBy = $request->get('orderBy');
$dir = $request->get('orderByDirection');

$query->select(DB::raw("{$resourceTable}.*"));

if (!$user->isGlobalAdmin) {
    $roles = json_decode($user->getRoleNames());
    $rolesArray = [];
    foreach ($roles as $roleName) {
        $role = json_decode(Role::findByName($roleName));
        $rolesArray[] = $role->id;
    }

    $query->join('model_has_roles', 'model_id', "=", "{$resourceTable}.id")
        ->where('model_type', '=', 'App\Models\Soft')
        ->whereIn('role_id', $rolesArray);
}

加载资源列表页时,生成的SQL为:

select soft.* from `soft` 
inner join `model_has_roles` on `model_id` = `soft`.`id` 
where `model_type` = 'App\\Models\\Soft' and `role_id` in (2, 3)

同时设置了角色筛选器,其apply代码如下:

$roles = $value;
$rolesArray = [];
foreach ($roles as $roleName) {
    $role = json_decode(Role::findByName($roleName));
    $rolesArray[] = $role->id;
}

return $query->whereIn('role_id', $rolesArray);

当选择单个角色进行筛选时,生成的SQL出现了两个role_id IN条件:

select soft.* from `soft` 
inner join `model_has_roles` on `model_id` = `soft`.`id` 
where `role_id` in (3) and `model_type` = App\\Models\\Soft and `role_id` in (2, 3)

需要修改代码,让SQL中只保留一个role_id IN条件。

解决方案

核心思路是合并用户默认的角色范围和筛选器的角色范围,而非追加重复条件,以下是两种可行实现方式:

方式一:在筛选器中直接合并角色集合

修改筛选器的apply方法,先获取用户自身的角色ID集合,再和筛选的角色ID取交集(非全局管理员场景),最后替换原有的role_id条件:

$roles = $value;
$user = Auth::user();
$filterRoleIds = [];
foreach ($roles as $roleName) {
    // 无需json_decode,直接取Eloquent模型的ID
    $role = Role::findByName($roleName);
    $filterRoleIds[] = $role->id;
}

if (!$user->isGlobalAdmin) {
    // 获取用户自身的角色ID集合
    $userRoles = json_decode($user->getRoleNames());
    $userRoleIds = [];
    foreach ($userRoles as $roleName) {
        $role = Role::findByName($roleName);
        $userRoleIds[] = $role->id;
    }
    // 取交集,确保筛选结果在用户权限范围内
    $finalRoleIds = array_intersect($userRoleIds, $filterRoleIds);
    
    // 移除原有role_id的In条件
    $query->getQuery()->wheres = collect($query->getQuery()->wheres)
        ->filter(fn($where) => !($where['type'] === 'In' && $where['column'] === 'role_id'))
        ->values()
        ->all();
    
    // 添加合并后的条件
    if (!empty($finalRoleIds)) {
        $query->whereIn('role_id', $finalRoleIds);
    }
} else {
    // 全局管理员直接使用筛选的角色
    $query->whereIn('role_id', $filterRoleIds);
}

return $query;

方式二:在indexQuery中存储角色范围,筛选器复用

先在indexQuery里把用户的角色ID集合存为查询的自定义属性,再在筛选器中直接合并:

  1. 修改indexQuery代码:
$resourceTable = 'soft';
$user = Auth::user();

$orderBy = $request->get('orderBy');
$dir = $request->get('orderByDirection');

$query->select(DB::raw("{$resourceTable}.*"));

if (!$user->isGlobalAdmin) {
    $roles = json_decode($user->getRoleNames());
    $userRoleIds = [];
    foreach ($roles as $roleName) {
        $role = Role::findByName($roleName);
        $userRoleIds[] = $role->id;
    }

    // 将用户角色ID存为查询的自定义属性
    $query->userRoleIds = $userRoleIds;
    
    $query->join('model_has_roles', 'model_id', "=", "{$resourceTable}.id")
        ->where('model_type', '=', 'App\Models\Soft')
        ->whereIn('role_id', $userRoleIds);
}
  1. 修改筛选器的apply方法:
$roles = $value;
$filterRoleIds = [];
foreach ($roles as $roleName) {
    $role = Role::findByName($roleName);
    $filterRoleIds[] = $role->id;
}

// 检查是否存在用户角色范围限制
if (property_exists($query, 'userRoleIds')) {
    // 合并用户角色与筛选角色的交集
    $finalRoleIds = array_intersect($query->userRoleIds, $filterRoleIds);
    
    // 移除原有role_id的In条件
    $query->getQuery()->wheres = collect($query->getQuery()->wheres)
        ->filter(fn($where) => !($where['type'] === 'In' && $where['column'] === 'role_id'))
        ->values()
        ->all();
    
    if (!empty($finalRoleIds)) {
        $query->whereIn('role_id', $finalRoleIds);
    }
} else {
    $query->whereIn('role_id', $filterRoleIds);
}

return $query;

额外优化建议

  • 避免重复查询角色:可以在用户模型中添加访问器,直接返回角色ID集合,或者缓存查询结果,减少数据库请求。
  • 移除不必要的json_decode:Role::findByName返回的是Eloquent模型对象,直接调用$role->id即可,无需转JSON再解码。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 07:52:46