Files
rxthinkcmf-wiki/RXThinkCMF_Laravel13_EleVue/zh/modules/role.md
T
2026-10-08 17:50:29 +08:00

155 lines
5.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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——角色一律按全部数据处理。
:::