Files
djangoadmin-wiki/DjangoAdmin_FastAPI_EleVue/zh/volume4/business/role.md
T
2026-09-12 23:12:15 +08:00

194 lines
7.0 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.
:::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. 灵活组合:一个角色可分配多个菜单,一个用户可拥有多个角色
```