权限树必须只包含启用节点(status=1),需在模型scope统一定义active范围、用静态方法buildtree单次查全量并排序、递归收集rule生成扁平数组、缓存时用cache::tag精准清除。

递归查询权限节点时,where 条件漏掉 status=1 导致脏数据
权限树必须只包含启用状态的节点,否则前端渲染会显示已禁用菜单或按钮,甚至引发越权访问。ThinkPHP 的 with 关联 + 递归查询容易在子查询中丢失主表过滤条件。
- 在模型的
scope中统一定义active查询范围:return $query->where('status', 1); - 调用
with(['children' => function ($q) { $q->active(); }]),而不是直接写$q->where('status', 1)—— 否则嵌套层级深时可能被覆盖 - 如果用原生
Db::name()手动递归,每次查子节点都得显式加->where('status', 1),漏一次就出问题
think\model\Relation 的 hasMany 关系默认不支持无限递归加载
ThinkPHP 原生的关联关系只能查一级子节点,所谓“递归加载”其实是靠 PHP 层手动循环查库,不是 ORM 自动展开。强行在 with 里写 children.children.children 不仅难维护,还会触发 N+1 查询。
- 用模型静态方法封装递归逻辑,例如
Permission::buildTree($parentId = 0),内部用单次select查全量再 PHP 分组 - 查全量时按
pid和sort排序:order('pid, sort'),方便后续用数组键映射快速挂载子节点 - 避免在循环里反复调用
find()或select(),哪怕加了缓存,数据库连接和解析开销也扛不住三级以上权限结构
前端需要扁平化权限标识(如 user:edit),但数据库是树形结构
RBAC 控制动作级权限时,后端鉴权函数(比如 Auth::check($rule))通常接收字符串规则,而树形数据里存的是 ID 和路径字段,两者不能直接对上。
- 在构建树的同时,用递归收集所有叶子节点的
rule字段(假设字段叫rule),生成一维数组:['user:list', 'user:edit', 'role:assign'] - 不要依赖
path字段拼接规则(如admin/user/edit),因为权限校验逻辑和 URL 路由未必对齐,语义易混淆 - 如果权限节点本身不存
rule,而是靠角色-权限中间表关联,则必须先查出当前用户所有有效rule,再与请求路由比对——此时树结构只用于后台管理展示,不参与运行时鉴权
缓存权限树时,Cache::tag() 没绑定模型名导致更新失效
权限变动频繁,缓存必须可精准清除。用 Cache::remember() 直接存树,改了一条权限就得清全部缓存,影响太大。
- 写入缓存时打标签:
Cache::tag('permission_tree')->set($key, $tree, 3600) - 在权限模型的
afterWrite钩子里执行:Cache::tag('permission_tree')->clear() - 别用
Cache::rm()清单个 key —— 树有多个缓存 key(按用户角色、按顶级节点等),漏一个就会返回过期数据
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











