194 lines
7.0 KiB
Markdown
194 lines
7.0 KiB
Markdown
:::tip 角色管理
|
||
角色是 RBAC 权限模型的核心,连接用户和菜单(权限)。通过为角色分配菜单权限,再将角色分配给用户,实现灵活的权限管理。角色删除时需清理关联的用户角色和角色菜单数据,并即时失效受影响用户的权限缓存。
|
||
:::
|
||
|
||
## 模块结构
|
||
|
||
```
|
||
src/modules/system/role/
|
||
├── models.py # 角色模型
|
||
├── schemas.py # 表单验证
|
||
├── repository.py # 数据访问层
|
||
└── service.py # 业务逻辑层
|
||
|
||
src/modules/system/role_menu/
|
||
├── models.py # 角色-菜单关联模型
|
||
├── repository.py # 关联数据访问
|
||
└── service.py # 关联业务逻辑
|
||
```
|
||
|
||
## 角色模型
|
||
|
||
```python
|
||
# src/modules/system/role/models.py
|
||
# ============================================================
|
||
# 角色模型
|
||
# ============================================================
|
||
class Role(base_model, base_db):
|
||
"""角色模型类"""
|
||
# ============================================================
|
||
# 表名配置
|
||
# ============================================================
|
||
__tablename__ = DB_PREFIX + "role"
|
||
__table_comment__ = "角色表"
|
||
|
||
# ============================================================
|
||
# 字段定义
|
||
# ============================================================
|
||
# 角色名称
|
||
name = Column(String(150), nullable=False, index=True, comment="角色名称")
|
||
# 角色编码
|
||
code = Column(String(30), nullable=False, index=True, comment="角色编码")
|
||
# 角色排序
|
||
sort = Column(Integer, default=0, server_default=text('0'), comment="角色排序")
|
||
# 角色备注
|
||
note = Column(String(255), nullable=True, comment="角色备注")
|
||
|
||
# ============================================================
|
||
# 内置方法
|
||
# ============================================================
|
||
def __str__(self):
|
||
"""返回角色名称作为字符串表示"""
|
||
return '角色{}:'.format(self.name)
|
||
```
|
||
|
||
## 角色-菜单关联
|
||
|
||
角色与菜单通过 `fastapi_role_menu` 关联表实现多对多关系:
|
||
|
||
```python
|
||
# src/modules/system/role_menu/models.py
|
||
# ============================================================
|
||
# 角色菜单关联模型
|
||
# ============================================================
|
||
class RoleMenu(base_model, base_db):
|
||
"""角色菜单关联模型类"""
|
||
# ============================================================
|
||
# 表名配置
|
||
# ============================================================
|
||
__tablename__ = DB_PREFIX + "role_menu"
|
||
__table_comment__ = "角色菜单关联表"
|
||
|
||
# ============================================================
|
||
# 字段定义
|
||
# ============================================================
|
||
# 角色ID
|
||
role_id = Column(Integer, default=0, server_default=text('0'), index=True, comment="角色ID")
|
||
# 菜单ID
|
||
menu_id = Column(Integer, default=0, server_default=text('0'), index=True, comment="菜单ID")
|
||
|
||
# ============================================================
|
||
# 内置方法
|
||
# ============================================================
|
||
def __str__(self):
|
||
"""返回角色ID作为字符串表示"""
|
||
return "角色菜单表{}".format(self.role_id)
|
||
```
|
||
|
||
## 自定义 Service
|
||
|
||
角色模块使用自定义 service 函数,因为涉及唯一性校验(名称+编码双字段)、内置角色保护、关联数据清理等复杂逻辑:
|
||
|
||
```python
|
||
# src/modules/system/role/service.py 关键片段
|
||
def add_role(request, data: RoleForm):
|
||
"""新增角色"""
|
||
# 唯一性校验(名称 + 编码,编码重复会导致角色识别/鉴权混乱)
|
||
if role_repo.get_one(name=data.name):
|
||
return R.failed("角色名称不能重复")
|
||
if role_repo.get_one(code=data.code):
|
||
return R.failed("角色编码不能重复")
|
||
|
||
role_repo.create(**data.model_dump(exclude={'id'}), create_user=get_realname(request))
|
||
return R.ok(msg="添加成功")
|
||
```
|
||
|
||
## 删除与缓存清理
|
||
|
||
角色删除是异步操作,需在删除前收集受影响用户 ID,删除后逐一清理权限缓存:
|
||
|
||
```python
|
||
# src/modules/system/role/service.py
|
||
|
||
async def delete_role(role_id):
|
||
def _delete():
|
||
id_list = parse_id_list(role_id)
|
||
|
||
# 内置角色禁止删除
|
||
if BUILTIN_ROLE_IDS & set(id_list):
|
||
return R.failed("内置角色不允许删除"), None
|
||
|
||
records = role_repo.filter(Role.id.in_(id_list), Role.is_delete == 0).all()
|
||
for record in records:
|
||
record.is_delete = 1
|
||
|
||
db = get_db_session()
|
||
|
||
# 收集受影响用户ID(必须在删除 UserRole 关联之前查)
|
||
affected_user_ids = [
|
||
row.user_id for row in db.query(UserRole.user_id).filter(UserRole.role_id.in_(id_list)).all()
|
||
]
|
||
|
||
# 清理角色关联
|
||
db.query(UserRole).filter(UserRole.role_id.in_(id_list)).delete(synchronize_session=False)
|
||
db.query(RoleMenu).filter(RoleMenu.role_id.in_(id_list)).delete(synchronize_session=False)
|
||
|
||
return None, (id_list, records, affected_user_ids)
|
||
|
||
err, result = await asyncio.to_thread(_delete)
|
||
if err:
|
||
return err
|
||
|
||
id_list, records, affected_user_ids = result
|
||
|
||
# 清理被删角色下所有用户的权限缓存
|
||
for uid in affected_user_ids:
|
||
await invalidate_user_perms(uid)
|
||
|
||
return R.ok(msg="本次共删除{0}条数据".format(len(records)))
|
||
```
|
||
|
||
:::warning 删除顺序
|
||
必须在删除 `UserRole` 关联**之前**收集受影响用户 ID,否则删后无法回查。`invalidate_user_perms` 会因查不到关联而失效失败,导致历史权限残留最长 2 小时。
|
||
:::
|
||
|
||
## API 接口
|
||
|
||
| 接口 | 方法 | 权限节点 | 说明 |
|
||
|------|------|---------|------|
|
||
| `/api/v1/role/page` | GET | `sys:role:page` | 角色分页列表 |
|
||
| `/api/v1/role/list` | GET | `sys:role:list` | 角色列表(无分页) |
|
||
| `/api/v1/role/detail/{id}` | GET | `sys:role:detail` | 角色详情 |
|
||
| `/api/v1/role/add` | POST | `sys:role:add` | 新增角色 |
|
||
| `/api/v1/role/update` | PUT | `sys:role:update` | 编辑角色 |
|
||
| `/api/v1/role/delete/{id}` | DELETE | `sys:role:delete` | 删除角色 |
|
||
| `/api/v1/role/batchDelete` | DELETE | `sys:role:batchDelete` | 批量删除 |
|
||
| `/api/v1/role/auth` | POST | `sys:role:auth` | 分配菜单权限 |
|
||
|
||
## 前端权限分配
|
||
|
||
角色管理页面通常包含菜单树组件,用于勾选分配权限:
|
||
|
||
```vue
|
||
<el-tree
|
||
ref="menuTree"
|
||
:data="menuTreeData"
|
||
show-checkbox
|
||
node-key="id"
|
||
:default-checked-keys="checkedMenuIds"
|
||
/>
|
||
```
|
||
|
||
## 总结
|
||
|
||
角色管理模块具备以下特点:
|
||
|
||
```
|
||
1. RBAC 核心:连接用户和权限的桥梁
|
||
2. 双字段唯一性:name + code 均需唯一性校验
|
||
3. 内置角色保护:BUILTIN_ROLE_IDS 中的角色禁止删除
|
||
4. 级联清理:删除角色时硬删 user_role / role_menu 关联
|
||
5. 缓存失效:删除前收集受影响用户,删除后逐一清理权限缓存
|
||
6. 灵活组合:一个角色可分配多个菜单,一个用户可拥有多个角色
|
||
```
|