Skip to content

角色管理

角色是 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)))

删除顺序

必须在删除 UserRole 关联之前收集受影响用户 ID,否则删后无法回查。invalidate_user_perms 会因查不到关联而失效失败,导致历史权限残留最长 2 小时。

API 接口

接口方法权限节点说明
/api/v1/role/pageGETsys:role:page角色分页列表
/api/v1/role/listGETsys:role:list角色列表(无分页)
/api/v1/role/detail/{id}GETsys:role:detail角色详情
/api/v1/role/addPOSTsys:role:add新增角色
/api/v1/role/updatePUTsys:role:update编辑角色
/api/v1/role/delete/{id}DELETEsys:role:delete删除角色
/api/v1/role/batchDeleteDELETEsys:role:batchDelete批量删除
/api/v1/role/authPOSTsys: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. 灵活组合:一个角色可分配多个菜单,一个用户可拥有多个角色

小蚂蚁云团队 · 提供技术支持