签入版本
This commit is contained in:
@@ -1,16 +1,18 @@
|
||||
# 6.2 角色管理
|
||||
|
||||
:::tip 概述
|
||||
角色管理是 RBAC 权限体系的核心,通过角色关联菜单实现权限分配。
|
||||
角色管理是 RBAC 权限体系的核心,通过角色关联菜单实现权限分配。`RoleService` 继承 `BaseTenantService`(角色按租户隔离),并提供菜单授权、权限缓存清理等专用能力。
|
||||
:::
|
||||
|
||||
## 模块特点
|
||||
|
||||
| 特性 | 说明 |
|
||||
|------|------|
|
||||
| 基类 | BaseLogic(系统共享,不隔离) |
|
||||
| 关联 | 与 Menu 多对多(通过 RoleMenu 中间表) |
|
||||
| 数据权限 | data_scope 字段控制(1=全部 2=本部门 3=仅本人) |
|
||||
| 基类 | `BaseTenantService`(租户隔离,角色含 `tenant_id`) |
|
||||
| 关联 | 与 Menu 多对多(通过 `RoleMenu` 中间表) |
|
||||
| 唯一校验 | `code` 按租户分组唯一(`['code','tenant_id']`) |
|
||||
| 数据权限 | 范围由 `HasDataScope` 解析(当前表无 `data_scope` 字段,默认全部数据) |
|
||||
| 缓存联动 | 菜单授权后 `clearRolePermissions()` 清除该角色权限缓存(配合 `RoleMenuObserver`) |
|
||||
|
||||
## 套餐化角色预置
|
||||
|
||||
@@ -22,7 +24,7 @@
|
||||
| 租户-专业版 | 进阶功能 | 核心 + 高级模块 |
|
||||
| 租户-旗舰版 | 全功能 | 全部菜单(最全) |
|
||||
|
||||
创建租户后,通过「角色管理 → 授权菜单」为对应角色分配菜单权限。详见 [租户管理](/zh/modules/tenant.html)。
|
||||
创建租户后,通过「角色管理 → 授权菜单」为对应角色分配菜单权限。详见 [6.13 租户管理](/zh/modules/tenant.html)。
|
||||
|
||||
## API 接口
|
||||
|
||||
@@ -30,154 +32,123 @@
|
||||
|------|------|--------|------|
|
||||
| GET | `/api/role/page` | sys:role:page | 分页列表 |
|
||||
| GET | `/api/role/list` | sys:role:list | 全量列表 |
|
||||
| GET | `/api/role/detail/:id` | sys:role:detail | 详情 |
|
||||
| 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/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:authorize | 保存角色菜单 |
|
||||
| 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/logic/RoleLogic.php
|
||||
// app/Http/Controllers/Api/RoleController.php(行为说明)
|
||||
|
||||
/**
|
||||
* 保存角色菜单关联(事务保护)
|
||||
*
|
||||
* 处理流程:
|
||||
* 1. 开启事务
|
||||
* 2. 先删除该角色原有的全部菜单关联(全量覆盖式保存)
|
||||
* 3. 遍历新菜单 ID,组装关联记录
|
||||
* 4. 批量写入关联记录
|
||||
* 5. 提交事务
|
||||
*/
|
||||
public function saveMenus(int $roleId, array $menuIds): bool
|
||||
#[OperationLog('角色管理-保存菜单权限', OperationLog::TYPE_AUTHORIZE, '保存角色ID:{roleId}的菜单权限')]
|
||||
#[Permission('sys:role:savePermission', '授权角色菜单')]
|
||||
public function menuSave(): JsonResponse
|
||||
{
|
||||
Db::startTrans();
|
||||
try {
|
||||
RoleMenu::where('role_id', $roleId)->delete();
|
||||
|
||||
$data = [];
|
||||
foreach ($menuIds as $menuId) {
|
||||
$data[] = [
|
||||
'role_id' => $roleId,
|
||||
'menu_id' => $menuId,
|
||||
'create_user' => request()->userInfo->username ?? '',
|
||||
'create_time' => date('Y-m-d H:i:s'),
|
||||
];
|
||||
}
|
||||
|
||||
if (!empty($data)) {
|
||||
(new RoleMenu())->saveAll($data);
|
||||
}
|
||||
|
||||
Db::commit();
|
||||
return true;
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
throw $e;
|
||||
$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, '保存成功');
|
||||
}
|
||||
```
|
||||
|
||||
### Controller 代码
|
||||
**请求参数:**
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `roleId` | int | 是 | 角色 ID |
|
||||
| `menuIds` | array | 是 | 菜单 ID 数组(含目录/菜单/按钮) |
|
||||
|
||||
## 删除保护(beforeDelete / afterDelete 钩子)
|
||||
|
||||
```php
|
||||
// app/controller/RoleController.php
|
||||
|
||||
class RoleController extends BaseController
|
||||
// 删除前:校验业务约束(如角色已被用户使用时阻止删除)
|
||||
protected function beforeDelete(int $id): void
|
||||
{
|
||||
protected RoleLogic $logic;
|
||||
// 检查 user_role 是否存在关联 → 存在则抛 BizException 阻止删除
|
||||
}
|
||||
|
||||
protected function initialize(): void
|
||||
{
|
||||
$this->logic = new RoleLogic();
|
||||
}
|
||||
|
||||
#[Log('角色管理-查询分页记录', Log::TYPE_QUERY)]
|
||||
#[Permission('sys:role:page', '角色分页')]
|
||||
public function page(): Json
|
||||
{
|
||||
return parent::_index($this->logic);
|
||||
}
|
||||
|
||||
#[Log('角色管理-查询全量列表', Log::TYPE_QUERY)]
|
||||
public function list(): Json
|
||||
{
|
||||
return parent::_list($this->logic);
|
||||
}
|
||||
|
||||
#[Log('角色管理-查询详情', Log::TYPE_QUERY, '查询角色ID:{id}')]
|
||||
public function detail(int $id): Json
|
||||
{
|
||||
return parent::_detail($this->logic, $id);
|
||||
}
|
||||
|
||||
#[Log('角色管理-新增记录', Log::TYPE_ADD, '新增角色:{name}')]
|
||||
#[Permission('sys:role:add', '添加角色')]
|
||||
public function add(): Json
|
||||
{
|
||||
return parent::_add($this->logic, $this->getJsonBody());
|
||||
}
|
||||
|
||||
#[Log('角色管理-修改记录', Log::TYPE_UPDATE, '修改角色ID:{id}')]
|
||||
#[Permission('sys:role:update', '修改角色')]
|
||||
public function update(): Json
|
||||
{
|
||||
$data = $this->getJsonBody();
|
||||
$id = $data['id'] ?? 0;
|
||||
if (empty($id)) {
|
||||
return $this->fail('缺少id参数');
|
||||
}
|
||||
return parent::_edit($this->logic, $id, $data);
|
||||
}
|
||||
|
||||
#[Log('角色管理-删除记录', Log::TYPE_DELETE, '删除角色ID:{id}')]
|
||||
#[Permission('sys:role:delete', '删除角色')]
|
||||
public function delete(int $id): Json
|
||||
{
|
||||
return parent::_remove($this->logic, $id);
|
||||
}
|
||||
|
||||
#[Log('角色管理-批量删除记录', Log::TYPE_DELETE)]
|
||||
#[Permission('sys:role:delete', '删除角色')]
|
||||
public function batchDelete(): Json
|
||||
{
|
||||
$ids = $this->getJsonBody()['ids'] ?? [];
|
||||
return parent::_batchRemove($this->logic, $ids);
|
||||
}
|
||||
|
||||
// 获取角色菜单(带选中状态)
|
||||
#[Log('角色管理-查询菜单权限', Log::TYPE_QUERY, '查询角色ID:{id}的菜单权限')]
|
||||
public function menuList(int $id): Json
|
||||
{
|
||||
$result = $this->logic->getMenuListWithChecked($id);
|
||||
return $this->success($result);
|
||||
}
|
||||
|
||||
// 保存角色菜单
|
||||
#[Log('角色管理-保存菜单权限', Log::TYPE_AUTHORIZE, '保存角色ID:{roleId}的菜单权限')]
|
||||
#[Permission('sys:role:authorize', '授权角色菜单')]
|
||||
public function menuSave(): Json
|
||||
{
|
||||
$data = $this->getJsonBody();
|
||||
$roleId = $data['roleId'] ?? 0;
|
||||
$menuIds = $data['menuIds'] ?? [];
|
||||
if (empty($roleId)) {
|
||||
return $this->fail('缺少roleId参数');
|
||||
}
|
||||
try {
|
||||
$this->logic->saveMenus($roleId, $menuIds);
|
||||
return $this->success(null, '保存成功');
|
||||
} catch (\Exception $e) {
|
||||
return $this->fail($e->getMessage());
|
||||
}
|
||||
}
|
||||
// 删除后:清理 role_menu 关联 + 清除权限缓存
|
||||
protected function afterDelete(int $id): void
|
||||
{
|
||||
RoleMenu::where('role_id', $id)->delete();
|
||||
$this->clearRolePermissions($id);
|
||||
}
|
||||
```
|
||||
|
||||
:::warning 权限生效
|
||||
权限分配后,拥有该角色的用户需重新登录才能获取最新权限。权限变更时相关缓存会自动失效。
|
||||
:::
|
||||
## 数据权限
|
||||
|
||||
数据范围语义由 `HasDataScope` 解析(详见 [5.9 数据权限](/zh/core/data-scope.html)):
|
||||
|
||||
| 值 | 范围 |
|
||||
|----|------|
|
||||
| 1 | 全部数据(当前默认) |
|
||||
| 2 | 本部门数据 |
|
||||
| 3 | 仅本人数据(无角色用户默认) |
|
||||
|
||||
:::info 当前版本现状
|
||||
`laravel_role` 表不包含 `data_scope` 字段(按当前表设计),`getRoleDataScope()` 读取缺省为 1——角色一律按全部数据处理。
|
||||
:::
|
||||
|
||||
Reference in New Issue
Block a user