权限校验必须在控制器实时执行,禁用中间件缓存;rule名须精确到路由节点并小写无空格;role_permission表需增加scope_type/scope_id字段支持数据级权限;白名单路由须用option('allow_anonymous')标记,避免字符串匹配;需批量预加载权限规则防n+1查询。

权限系统不是加个中间件就完事——真实项目里,90%的权限失效、跳转异常、白名单失灵,都源于校验时机错位、规则命名模糊或关联表缺字段。
权限校验必须在控制器里实时执行,别信中间件缓存
ThinkPHP 默认中间件(包括 think-auth 类扩展)常把 checkPermission() 结果塞进 Session 或 Request 属性,用户刚被分配新角色,刷新页面还是提示“无权限”。这不是 Bug,是设计使然。
- 所有权限判断逻辑必须写在控制器方法内,例如
if (!$this->auth->check('admin/user/index')) { $this->error('无访问权限'); } - 禁用任何类似
$this->request->has('auth_checked')的跳过机制;每次请求都要重新查数据库或 Redis(带 role 更新监听的缓存) - 若用
Auth::getUser()->getRoles(),确保它没从 Session 里硬读role_id,而是实时查role_user表
rule 名必须精确到路由节点,不能用模块名或中文
写成 'user' 或 '用户管理' 看似省事,上线后根本没法做最小粒度回收。想取消「导出」权限,却连「查看」也一起丢了。
- Rule 名严格按 ThinkPHP 路由定义来:
'admin/user/index'、'api/v1/order/export'、'admin/goods/edit' - 涉及数据上下文控制时,用冒号后缀:
'admin/order/view:own'、'admin/order/delete:dept',解析时再分发策略 - 所有 rule 字段(如
permissions.name)必须小写、无空格、无中文,否则AuthRule::check()查不到记录
role_permission 关联表要加 scope_type/scope_id 字段
只靠 role_id + permission_id 两字段,撑不起部门级、用户级、订单级的数据权限。一加新需求就得改表结构、重写校验逻辑。
- 在
permission_role表中增加scope_type(值为'global'、'dept'、'user')和scope_id(对应部门 ID 或用户 ID) - 查权限时用组合条件:
where(['role_id' => $roleId, 'scope_type' => 'dept', 'scope_id' => $deptId]),而非简单IN匹配 - 别把「能导出报表」和「能导出哪些部门的报表」混在一个 rule 里——前者走
permissions表,后者走关联表的scope_*字段
白名单路由必须用 option('allow_anonymous') 标记,别字符串匹配 URL
用 $request->url() 做 strpos() 判断,会因查询参数、大小写、路由重写失败。更糟的是,资源路由(Route::resource())每个动作都得单独配,漏一个就裸奔。
- 登录注册类接口统一打标:
Route::post('login', 'LoginController@login')->option(['allow_anonymous' => true]); - 资源路由需逐动作设置:
Route::resource('captcha', 'CaptchaController')->only(['index'])->option(['allow_anonymous' => true]); - 中间件里统一用
$request->route()->option('allow_anonymous')判断,稳定且可 debug
最易被忽略的一点:N+1 查询。用户打开后台首页卡顿,debug 显示执行了 47 次权限查询——问题不在 SQL 写得慢,而在没批量预加载规则 ID,每次 check() 都单独查一次 permission_role 表。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











