数据权限需区分操作权限与数据权限,必须通过scope_type和scope_id字段实现上下文感知的精细化控制,校验须下沉至模型或service层,规则命名应带策略后缀并严格解析。

数据权限控制不是加个 checkPermission() 就完事的,它必须能区分「能不能看订单列表」和「能不能看张三的订单」——前者是操作权限,后者才是数据权限。直接复用 RBAC 的路由级规则会失效。
role_access 表必须带 scope_type 和 scope_id 字段
只靠 role_id + rule_id 关联,根本无法表达「某角色仅可查看本部门订单」或「仅可编辑自己创建的商品」这类约束。真实业务里,数据权限永远绑定上下文。
-
scope_type值应限定为枚举:比如'global'(全系统)、'dept'(部门)、'user'(个人)、'order'(某条记录) -
scope_id在scope_type = 'dept'时存部门 ID,在scope_type = 'user'时可为空(表示“当前用户”),在scope_type = 'order'时存具体订单 ID - 查询时不能只写
where('role_id', $roleId),得组合判断:where(['role_id' => $roleId, 'scope_type' => 'dept', 'scope_id' => $deptId]) - 别把
scope_id设为字符串类型存 JSON——后期无法走索引,查 10 万条数据会慢到超时
数据权限校验不能放在中间件里统一做
中间件适合拦截「能否访问 /admin/order/index」,但没法决定「这个接口返回的数据要不要过滤掉非本部门的订单」。数据权限必须下沉到模型查询层或 Service 层。
- 在
OrderService::getList()里,先调用$this->getDataScope($user)获取当前用户的数据范围条件,再拼进Db::name('order')->where($scopeWhere) - 不要在控制器里手动
where('dept_id', $user['dept_id'])—— 一旦用户有多个部门身份或跨部门权限,逻辑立刻崩溃 - 如果用了软删除,注意
scope_type = 'global'的规则要显式排除已删除数据,避免权限绕过 - API 返回前对集合做二次过滤(如
array_filter($list, fn($item) => $this->canViewData($user, $item)))只适用于小数据量,大数据集必须靠 SQL 层过滤
Rule 名必须带数据策略后缀,且解析逻辑要独立
把「查看订单」和「查看自己订单」写成两个规则,而不是靠 condition 字段动态判断——TP6.1+ 已移除 condition 的 eval() 支持,硬写等于埋雷。
- 规则名严格使用冒号分隔策略:
'admin/order/view:own'、'admin/order/edit:dept'、'api/v1/user/profile:update:global' - 校验时先取路由基础名(如
'admin/order/view'),再根据当前数据上下文拼出完整规则名,再查数据库 - 别在
Auth::check()里传整个对象或数组——它只认字符串,传错类型会静默失败 - 如果用 Casbin(
think-authz),策略字段要用p, role, resource, action, effect四元组,其中resource可填order:dept:123这类带上下文的标识
最易被忽略的一点:数据权限的「生效时机」不是请求进来那一刻,而是数据组装完成前最后一刻。缓存、Eager Loading、分页偏移这些环节都可能绕过你的过滤逻辑——尤其当用了 with('relation') 或 cache(true) 时,务必确认关联数据也受同套 scope 规则约束。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











