thinkphp 6 的 auth 组件默认仅支持单角色权限校验,需自定义 permissionservice::check() 手动合并多角色权限,并通过中间件统一鉴权、缓存规则数组提升性能。

ThinkPHP 6 的 Auth 组件不支持多角色直接叠加权限
ThinkPHP 官方的 Auth 类(包括 think-auth 扩展)默认只取用户第一个角色的权限规则,getRoleList() 返回多个角色时,check() 内部仍只查第一个角色的 rule_id 关联记录。这不是 bug,是设计如此——它假设「一个用户一个主角色」。
真实业务中常需「运营+审核」双身份,或「管理员+部门负责人」复合权限,这时必须自己接管权限判定逻辑。
- 不要依赖
Auth::check('user/edit')原生调用,它底层走的是单角色查表 - 改用自定义门面或服务类封装权限检查,例如
PermissionService::check($user, 'user/edit') - 数据库需确保
role_access表(或你命名的关联表)中,同一role_id可对应多条rule_id,且不同role_id可重复指向同一rule_id
如何查出用户所有角色的权限并合并去重
核心是绕过 Auth 的单角色限制,手动拉取用户全部角色对应的权限规则 ID 列表,再判断当前操作是否在其中。
示例代码(放在 app/service/PermissionService.php):
public static function check($user, $ruleName)
{
$roleIds = Db::name('user_role')->where('user_id', $user['id'])->column('role_id');
if (empty($roleIds)) {
return false;
}
$ruleIds = Db::name('role_access')
->where('role_id', 'in', $roleIds)
->where('status', 1)
->column('rule_id');
// 查权限节点本身是否启用、是否匹配 name 字段
return Db::name('auth_rule')
->where('id', 'in', $ruleIds)
->where('status', 1)
->where('name', $ruleName)
->value('id') !== null;
}
-
user_role和role_access是你自定义的中间表,别硬套 ThinkPHP 默认表名 - 务必加
status = 1过滤,否则禁用的规则也会生效 - 如果权限节点有层级(如
user/*),需额外做通配符匹配,原生 SQL 不支持LIKE模糊查name,建议加字段is_wildcard或用 PHP 层解析
路由级权限控制:避免在每个控制器里写 PermissionService::check()
TP6 的中间件是最自然的切入位置。把权限校验下沉到中间件,比在每个 __construct() 或方法开头手写 if (!PermissionService::check(...)) 更可靠。
- 创建中间件
app/middleware/PermissionCheck.php,在handle()中获取当前路由名:$routeName = $request->routeInfo()['rule'] ?? ''; - 注意:不是所有路由都注册了 name,建议统一用
Route::get('user/edit', 'User/edit')->name('user.edit');方式显式命名 - 中间件中调用
PermissionService::check($user, $routeName),失败则返回json(['code'=>403, 'msg'=>'无权限'])并终止请求 - 别忘了在
app/middleware.php中给需要鉴权的分组或路由绑定该中间件
缓存权限数据能明显降低 DB 查询压力
每次请求都查 2 次数据库(角色 + 权限规则)太重,尤其当用户角色多、权限节点多时。缓存 key 应包含 user_id 和 timestamp(用于配合权限变更钩子失效)。
- 推荐用
Cache::tag('permission_'.$userId)->set('rules', $ruleNames, 3600),标签便于批量清除 - 在后台修改角色权限时(如分配新规则、禁用某条规则),触发
Cache::tag('permission_'.$userId)->clear() - 不要缓存「true/false」结果,而应缓存该用户拥有的全部
name数组,后续in_array($ruleName, $cachedRules)更快也更灵活 - Redis 比文件缓存更适合此场景,TP6 默认 File 缓存并发下可能锁文件
PermissionService::check() 里明确写死,不能交给数据库或中间件自动合并。php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











