Laravel 11.x中用无限级分类作权限节点的核心是:以邻接表建模节点(含name、parent_id等字段),通过角色中转绑定权限,利用祖先查询和通配符实现继承与范围判断,并动态渲染授权菜单树。

在 Laravel 11.x 中,把无限级分类用作权限节点(比如菜单、功能模块、API 路由分组等),核心思路是:**让分类结构承载权限语义,同时复用其树形关系支撑权限继承与范围控制**。这不是单纯展示分类,而是把它变成权限系统的“骨架”。下面从建模、关联、使用三个关键环节讲清楚怎么做。
用邻接表模型定义权限节点表
权限节点本质是带层级的资源,适合用 邻接表(Adjacency List) 实现,字段精简但语义明确:
- id:主键
-
name:节点标识(如
user.manage、order.export),建议用点号分隔,便于后续通配符匹配 - title:中文名(如“用户管理”、“导出订单”)
- parent_id:可空外键,指向自身,必须设为 nullable(),否则根节点无法插入
-
type:可选字段,区分节点类型(
menu/api/button) - sort、status:排序与启用状态
迁移示例:
$table->id();
$table->string('name', 100)->unique();
$table->string('title', 100);
$table->foreignId('parent_id')->nullable()->constrained('permission_nodes')->nullOnDelete();
$table->string('type')->default('menu');
$table->tinyInteger('sort')->default(0);
$table->boolean('status')->default(true);
$table->timestamps();
建立节点与角色/用户的权限绑定关系
权限节点本身不直接赋给用户,而是通过角色中转(RBAC 模式),支持灵活继承和批量管理:
- 创建中间表
role_permission_node(角色-节点多对多) - Role 模型中定义关系:
public function permissionNodes()<br>{<br> return $this->belongsToMany(PermissionNode::class, 'role_permission_node');<br>} - 为角色批量分配节点时,用
sync()或syncPermissions()(若已集成 spatie/laravel-permission) - 用户通过角色间接获得节点权限,无需在 User 模型里再加节点关系
这样设计后,一个「运营主管」角色可以被赋予 campaign.* 节点,而「运营专员」继承该角色时,自动拥有其全部节点权限(配合通配符或自定义检查逻辑)。
利用节点层级实现权限继承与范围判断
无限级结构天然支持「父节点授权 → 子节点默认可用」的继承逻辑,但需主动实现判断,不能依赖 Eloquent 自动处理:
- 在
PermissionNode模型中添加辅助方法:
public function getAllAncestorsAttribute()<br>{<br> return $this->ancestors()->pluck('name')->push($this->name);<br>}
配合递归查询获取完整路径(如['system', 'system.user', 'system.user.create']) - 权限检查时,不仅查当前节点,也查其所有祖先节点是否被授权:
$user->roles->flatMap(fn($r) => $r->permissionNodes)->pluck('name')->contains($targetNode)
或更高效地用 SQL 查询:PermissionNode::where('id', $nodeId)->with(['ancestors' => fn($q) => $q->select('name')])->first() - 若需支持通配符(如
system.*),建议启用spatie/laravel-permission的enable_wildcard_permission配置,并将节点 name 设计为兼容格式
前端渲染菜单时按权限过滤并保持层级
后台菜单通常需按用户权限动态生成树形结构,推荐两步走:
- 先查出用户所有可访问的节点 ID 列表(含祖先)
- 再查这些节点,并用集合递归构建树:
$nodes = PermissionNode::whereIn('id', $accessibleIds)<br> ->where('status', true)<br> ->orderBy('parent_id')->get();<br>$tree = $nodes->where('parent_id', null)->map(fn($n) => $n->toArray() + ['children' => $this->buildChildren($nodes, $n->id)]);
这样既保证了层级正确,又不会暴露未授权节点。


















