# 6.2 角色管理 :::tip 概述 角色管理是 RBAC 权限体系的核心,通过角色关联菜单实现权限分配。`RoleService` 继承 `BaseTenantService`(角色按租户隔离),并提供菜单授权、权限缓存清理等专用能力。 ::: ## 模块特点 | 特性 | 说明 | |------|------| | 基类 | `BaseTenantService`(租户隔离,角色含 `tenant_id`) | | 关联 | 与 Menu 多对多(通过 `RoleMenu` 中间表) | | 唯一校验 | `code` 按租户分组唯一(`['code','tenant_id']`) | | 数据权限 | 范围由 `HasDataScope` 解析(当前表无 `data_scope` 字段,默认全部数据) | | 缓存联动 | 菜单授权后 `clearRolePermissions()` 清除该角色权限缓存(配合 `RoleMenuObserver`) | ## 套餐化角色预置 系统内置三套租户角色模板,用于按套餐等级快速授权: | 角色名称 | 定位 | 菜单范围 | |----------|------|----------| | 租户-标准版 | 基础功能 | 核心业务模块 | | 租户-专业版 | 进阶功能 | 核心 + 高级模块 | | 租户-旗舰版 | 全功能 | 全部菜单(最全) | 创建租户后,通过「角色管理 → 授权菜单」为对应角色分配菜单权限。详见 [6.13 租户管理](/zh/modules/tenant.html)。 ## API 接口 | 方法 | 路径 | 权限码 | 说明 | |------|------|--------|------| | GET | `/api/role/page` | sys:role:page | 分页列表 | | GET | `/api/role/list` | sys:role:list | 全量列表 | | GET | `/api/role/detail/{id}` | sys:role:detail | 详情 | | POST | `/api/role/add` | sys:role:add | 新增 | | PUT | `/api/role/update` | sys:role:update | 修改 | | DELETE | `/api/role/delete/{id}` | sys:role:delete | 删除 | | DELETE | `/api/role/batchDelete` | sys:role:batchDelete | 批量删除 | | GET | `/api/role/menu/list/{id}` | — | 获取角色菜单(含选中状态) | | POST | `/api/role/menu/save` | sys:role:savePermission | 保存角色菜单 | ## Service 属性配置 ```php // app/Services/RoleService.php(来自项目实际代码) class RoleService extends BaseTenantService { // HasDataScope(数据权限 + 租户隔离)由 BaseTenantService 继承提供 use HasCrudOperations, HasQueryScopes, HasUniqueCheck; protected string $modelClass = Role::class; protected array $pageLikeFields = ['name', 'code']; protected array $pageEqFields = []; protected array $pageOrderBy = ['field' => 'sort', 'type' => 'asc']; // code 在同一租户内唯一 protected array $uniqueFields = [['code', 'tenant_id']]; } ``` ## 菜单权限分配 ### 专用方法 | 方法 | 说明 | |------|------| | `getMenuListWithChecked($roleId)` | 获取平铺菜单列表(`id` / `name` / `parent_id` / `checked`),供前端授权弹窗回显 | | `saveMenus($roleId, $menuIds)` | **全量覆盖式**保存角色菜单(事务保护) | | `getMenuIds($roleId)` | 获取角色已有菜单 ID 数组 | | `clearRolePermissions($roleId)` | 清除该角色的权限缓存(授权即时生效) | ### 保存流程(saveMenus) ```text saveMenus($roleId, $menuIds) │ ▼ DB::transaction { 1. 删除该角色原有全部 role_menu 关联(全量覆盖) 2. 遍历新菜单 ID,组装关联记录(含审计字段) 3. 批量写入 role_menu 4. clearRolePermissions() 清除该角色权限缓存 } │ 任一步骤异常 → 自动回滚 ▼ 返回成功 ``` :::tip 授权即时生效 保存成功即清除该角色在 Redis(`perms:{uid}`)与 Laravel Cache 中的权限缓存,拥有该角色的用户**下一次请求**即按新权限校验,无需重新登录。 ::: ### 控制器(menuSave) ```php // app/Http/Controllers/Api/RoleController.php(行为说明) #[OperationLog('角色管理-保存菜单权限', OperationLog::TYPE_AUTHORIZE, '保存角色ID:{roleId}的菜单权限')] #[Permission('sys:role:savePermission', '授权角色菜单')] public function menuSave(): JsonResponse { $data = $this->getJsonBody(); $roleId = $data['roleId'] ?? 0; $menuIds = $data['menuIds'] ?? []; if (empty($roleId)) { return $this->fail('缺少roleId参数'); } $this->roleService->saveMenus($roleId, $menuIds); return $this->success(null, '保存成功'); } ``` **请求参数:** | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | `roleId` | int | 是 | 角色 ID | | `menuIds` | array | 是 | 菜单 ID 数组(含目录/菜单/按钮) | ## 删除保护(beforeDelete / afterDelete 钩子) ```php // 删除前:校验业务约束(如角色已被用户使用时阻止删除) protected function beforeDelete(int $id): void { // 检查 user_role 是否存在关联 → 存在则抛 BizException 阻止删除 } // 删除后:清理 role_menu 关联 + 清除权限缓存 protected function afterDelete(int $id): void { RoleMenu::where('role_id', $id)->delete(); $this->clearRolePermissions($id); } ``` ## 数据权限 数据范围语义由 `HasDataScope` 解析(详见 [5.9 数据权限](/zh/core/data-scope.html)): | 值 | 范围 | |----|------| | 1 | 全部数据(当前默认) | | 2 | 本部门数据 | | 3 | 仅本人数据(无角色用户默认) | :::info 当前版本现状 `laravel_role` 表不包含 `data_scope` 字段(按当前表设计),`getRoleDataScope()` 读取缺省为 1——角色一律按全部数据处理。 :::