ThinkPHP多角色权限需手动合并各角色规则,因Auth::check()默认仅查首角色权限;正确做法是查用户全部角色ID,批量获取启用的rule_id并匹配路由名,同时用Redis按role_id缓存权限集并及时清理。

多角色权限在 ThinkPHP 后台不是开箱即用的功能,原生 Auth 类和主流扩展(如 think-auth)默认只查用户第一个角色的权限,必须手动合并多个角色的规则才能生效。
为什么 Auth::check() 总是返回 false 或不准
因为 Auth::check() 底层只取 $user->getRoleList() 的第一个 role_id 去查 role_access 表,其余角色被忽略。哪怕数据库里用户绑了「运营」+「审核」两个角色,也只校验「运营」的权限。
- 别依赖
Auth::check('admin/user/edit')原生调用,它不支持多角色叠加 - 检查
user_role表是否真有多个role_id关联该用户,而不是只插了一条 - 确认中间件中没提前 return,导致后续权限逻辑根本没执行
- 若用了
think-auth,看它配置里有没有开启 multi-role 模式(多数版本默认关)
如何正确合并多个角色的权限规则
核心是绕过框架封装,直接查所有角色对应的 rule_id,再判断当前请求是否命中其中任一节点。关键点不在“怎么查”,而在“查完怎么用”。
- 用
Db::name('user_role')->where('user_id', $userId)->column('role_id')拉出全部角色 ID - 用
Db::name('role_access')->where('role_id', 'in', $roleIds)->where('status', 1)->column('rule_id')批量拉权限节点 ID - 再查
auth_rule表,用where('id', 'in', $ruleIds)->where('name', $routeName)->value('id')判断是否存在 - 务必加
status = 1过滤,否则禁用的权限也会生效 - 如果要支持通配符(如
admin/user/*),不能靠 SQL LIKE,得在 PHP 层解析字符串匹配
中间件里怎么避免 N+1 和缓存污染
每次请求都走全量数据库查询会拖垮后台首页——典型表现是打开一个页面触发 40+ 次权限查询。必须引入按角色维度的缓存,且 key 必须带 role_id。
立即学习“PHP免费学习笔记(深入)”;
- 缓存 key 设计为
auth_rules_by_role_{$roleId},值为该角色所有启用的rule_name数组(Redis Set 最佳) - 中间件中先通过当前用户拿到所有
role_id,再批量cache()->getMultiple([...])获取各角色规则集 - 权限变更时(如后台改角色权限),必须主动清理对应
auth_rules_by_role_*缓存,不能等自然过期 - 别把全量权限塞进 Session,用户角色一变,Session 就失效;更别用
$request->has('auth_checked')跳过校验 - 路由名要用
$request->rule()->getName()拿,不是$request->url()或手动拼接
rule_name 命名和表结构最容易翻车的地方
权限节点名写错,等于整个 RBAC 失效。它不是功能描述,而是路由的精确映射,大小写、分隔符、前缀一个都不能错。
-
rule_name必须和路由定义完全一致:Route::get('admin/user/list', ...) → 对应admin/user/list,不是admin.user.list或user_list - 权限表字段别冗余加
condition,除非真需要动态 SQL 条件(极易 SQL 注入),否则留空 - 关联表(如
permission_role)必须是复合主键(role_id, permission_id),不能只加自增 ID - 若需数据级权限(如“仅能看本部门订单”),在
permission_role表加scope_type和scope_id字段,别硬塞进 rule_name - 菜单和操作要分开:type=1 存菜单,type=2 存按钮/接口,混在一起会导致权限回收时误删
最常被忽略的是权限变更后的缓存清理时机——不是用户登出才清,而是管理员在后台点“保存权限”的那一刻,就要主动 cache()->delete('auth_rules_by_role_'.$roleId)。否则新权限永远不生效,排查时容易误判成代码问题。



















