From 8a18186a7c4fe59755812f1d98ff8fb52d58bd9c Mon Sep 17 00:00:00 2001 From: Wu Clan Date: Sat, 3 Jun 2023 10:07:55 +0800 Subject: [PATCH] Add role-related interfaces (#89) --- backend/app/api/v1/role.py | 53 +++++++++++++++++++++++- backend/app/crud/crud_menu.py | 4 +- backend/app/crud/crud_role.py | 61 +++++++++++++++++++++++++++- backend/app/models/sys_dept.py | 2 +- backend/app/models/sys_menu.py | 2 +- backend/app/models/sys_role.py | 3 +- backend/app/schemas/role.py | 5 +-- backend/app/services/api_service.py | 2 +- backend/app/services/role_service.py | 56 ++++++++++++++++++++++++- 9 files changed, 172 insertions(+), 16 deletions(-) diff --git a/backend/app/api/v1/role.py b/backend/app/api/v1/role.py index d8dc2ca5..bd8c8b90 100644 --- a/backend/app/api/v1/role.py +++ b/backend/app/api/v1/role.py @@ -1,7 +1,56 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -from fastapi import APIRouter +from typing import Annotated + +from fastapi import APIRouter, Query, Request + +from backend.app.common.casbin_rbac import DependsRBAC +from backend.app.common.jwt import DependsJwtAuth +from backend.app.common.pagination import PageDepends, paging_data +from backend.app.common.response.response_schema import response_base +from backend.app.database.db_mysql import CurrentSession +from backend.app.schemas.role import GetAllRole, CreateRole, UpdateRole +from backend.app.services.role_service import RoleService +from backend.app.utils.serializers import select_to_json router = APIRouter() -# TODO: 添加 role 相关接口 + +@router.get('/{pk}', summary='获取角色详情', dependencies=[DependsJwtAuth]) +async def get_role(pk: int): + role = await RoleService.get(pk=pk) + data = GetAllRole(**select_to_json(role)) + return response_base.success(data=data) + + +@router.get('', summary='(模糊条件)分页获取所有角色', dependencies=[DependsJwtAuth, PageDepends]) +async def get_all_roles( + db: CurrentSession, + name: Annotated[str | None, Query()] = None, + data_scope: Annotated[int | None, Query()] = None, +): + role_select = await RoleService.get_select(name=name, data_scope=data_scope) + page_data = await paging_data(db, role_select, GetAllRole) + return response_base.success(data=page_data) + + +@router.post('', summary='创建角色', dependencies=[DependsRBAC]) +async def create_role(request: Request, obj: CreateRole): + await RoleService.create(obj=obj, user_id=request.user.id) + return response_base.success() + + +@router.put('/{pk}', summary='更新角色', dependencies=[DependsRBAC]) +async def update_role(request: Request, pk: int, obj: UpdateRole): + count = await RoleService.update(pk=pk, obj=obj, user_id=request.user.id) + if count > 0: + return response_base.success() + return response_base.fail() + + +@router.delete('', summary='(批量)删除角色', dependencies=[DependsRBAC]) +async def delete_role(pk: Annotated[list[int], Query(...)]): + count = await RoleService.delete(pk=pk) + if count > 0: + return response_base.success() + return response_base.fail() diff --git a/backend/app/crud/crud_menu.py b/backend/app/crud/crud_menu.py index ac5adfc6..2868afd2 100644 --- a/backend/app/crud/crud_menu.py +++ b/backend/app/crud/crud_menu.py @@ -6,8 +6,8 @@ from backend.app.schemas.menu import CreateMenu, UpdateMenu class CRUDMenu(CRUDBase[Menu, CreateMenu, UpdateMenu]): - # TODO: 添加 menu 相关数据库操作 - pass + async def get(self, db, menu_id: int) -> Menu | None: + return await self.get_(db, menu_id) MenuDao: CRUDMenu = CRUDMenu(Menu) diff --git a/backend/app/crud/crud_role.py b/backend/app/crud/crud_role.py index 9aecae58..1dcda76a 100644 --- a/backend/app/crud/crud_role.py +++ b/backend/app/crud/crud_role.py @@ -1,13 +1,70 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- +from typing import NoReturn + +from sqlalchemy import select, update, delete +from sqlalchemy.orm import selectinload + from backend.app.crud.base import CRUDBase -from backend.app.models import Role +from backend.app.models import Role, Menu from backend.app.schemas.role import CreateRole, UpdateRole class CRUDRole(CRUDBase[Role, CreateRole, UpdateRole]): - async def get(self, db, role_id: int): + async def get(self, db, role_id: int) -> Role | None: return await self.get_(db, role_id) + async def get_with_relation(self, db, role_id: int) -> Role | None: + role = await db.execute( + select(self.model) + .options(selectinload(self.model.menus)) + .where(self.model.id == role_id) + ) + return role.scalars().first() + + async def get_all(self, name: str = None, data_scope: int = None): + se = select(self.model).options(selectinload(self.model.menus)).order_by(self.model.created_time.desc()) + where_list = [] + if name: + where_list.append(self.model.name.like(f'%{name}%')) + if data_scope: + where_list.append(self.model.data_scope == data_scope) + if where_list: + se = se.where(*where_list) + return se + + async def get_by_name(self, db, name: str) -> Role | None: + role = await db.execute(select(self.model).where(self.model.name == name)) + return role.scalars().first() + + async def create(self, db, obj_in: CreateRole, user_id: int) -> NoReturn: + new_role = self.model(**obj_in.dict(exclude={'menus'}), create_user=user_id) + menu_list = [] + for menu_id in obj_in.menus: + menu_list.append(await db.get(Menu, menu_id)) + new_role.menus.append(*menu_list) + db.add(new_role) + + async def update(self, db, role_id: int, obj_in: UpdateRole, user_id: int) -> int: + role = await db.execute( + update(self.model) + .where(self.model.id == role_id) + .values(**obj_in.dict(exclude={'menus'}), update_user=user_id) + ) + current_role = await self.get_with_relation(db, role_id) + # 删除角色所有菜单 + for i in list(current_role.menus): + current_role.menus.remove(i) + # 添加角色菜单 + menu_list = [] + for menu_id in obj_in.menus: + menu_list.append(await db.get(Menu, menu_id)) + current_role.menus.append(*menu_list) + return role.rowcount + + async def delete(self, db, role_id: list[int]) -> int: + roles = await db.execute(delete(self.model).where(self.model.id.in_(role_id))) + return roles.rowcount + RoleDao: CRUDRole = CRUDRole(Role) diff --git a/backend/app/models/sys_dept.py b/backend/app/models/sys_dept.py index ee6be0ed..b8c19e5d 100644 --- a/backend/app/models/sys_dept.py +++ b/backend/app/models/sys_dept.py @@ -20,6 +20,6 @@ class Dept(Base): phone: Mapped[str | None] = mapped_column(String(11), default=None, comment='手机') email: Mapped[str | None] = mapped_column(String(50), default=None, comment='邮箱') status: Mapped[bool] = mapped_column(default=True, comment='部门状态(0停用 1正常)') - del_flag: Mapped[bool] = mapped_column(default=True, comment='删除标志(0删除 1存在)') + del_flag: Mapped[bool] = mapped_column(default=False, comment='删除标志(0删除 1存在)') # 用户部门一对多 users: Mapped['User'] = relationship(init=False, back_populates='dept') # noqa: F821 diff --git a/backend/app/models/sys_menu.py b/backend/app/models/sys_menu.py index 3db75709..1aa9c4f7 100644 --- a/backend/app/models/sys_menu.py +++ b/backend/app/models/sys_menu.py @@ -22,7 +22,7 @@ class Menu(Base): menu_type: Mapped[int] = mapped_column(default=0, comment='菜单类型(0目录 1菜单 2按钮)') icon: Mapped[str | None] = mapped_column(String(100), default='#', comment='菜单图标') remark: Mapped[str | None] = mapped_column(LONGTEXT, default=None, comment='备注') - del_flag: Mapped[bool] = mapped_column(default=True, comment='删除标志(0删除 1存在)') + del_flag: Mapped[bool] = mapped_column(default=False, comment='删除标志(0删除 1存在)') # 菜单角色多对多 roles: Mapped[list['Role']] = relationship( # noqa: F821 init=False, secondary=sys_role_menu, back_populates='menus' diff --git a/backend/app/models/sys_role.py b/backend/app/models/sys_role.py index 3f516bc0..85887d20 100644 --- a/backend/app/models/sys_role.py +++ b/backend/app/models/sys_role.py @@ -15,9 +15,8 @@ class Role(Base): id: Mapped[id_key] = mapped_column(init=False) name: Mapped[str] = mapped_column(String(20), unique=True, comment='角色名称') - sort: Mapped[int] = mapped_column(default=0, comment='显示顺序') data_scope: Mapped[int | None] = mapped_column(default=2, comment='数据范围(1:全部数据权限 2:自定数据权限)') - del_flag: Mapped[bool] = mapped_column(default=True, comment='删除标志(0删除 1存在)') + del_flag: Mapped[bool] = mapped_column(default=False, comment='删除标志(0删除 1存在)') # 角色用户多对多 users: Mapped[list['User']] = relationship( # noqa: F821 init=False, secondary=sys_user_role, back_populates='roles' diff --git a/backend/app/schemas/role.py b/backend/app/schemas/role.py index 3f5f091f..eb5d210b 100644 --- a/backend/app/schemas/role.py +++ b/backend/app/schemas/role.py @@ -10,7 +10,6 @@ from backend.app.schemas.menu import GetAllMenu class RoleBase(BaseModel): name: str - sort: int = Field(default=0, ge=0, description='排序') data_scope: int | None = Field(default=RoleDataScope.custom, description='数据范围(1:全部数据权限 2:自定数据权限)') # noqa: E501 del_flag: bool @@ -22,11 +21,11 @@ class RoleBase(BaseModel): class CreateRole(RoleBase): - menu_ids: list[int] + menus: list[int] class UpdateRole(RoleBase): - menu_ids: list[int] + menus: list[int] class GetAllRole(RoleBase): diff --git a/backend/app/services/api_service.py b/backend/app/services/api_service.py index b0d06274..4da6caa0 100644 --- a/backend/app/services/api_service.py +++ b/backend/app/services/api_service.py @@ -12,7 +12,7 @@ from backend.app.schemas.api import CreateApi, UpdateApi class ApiService: @staticmethod async def get(*, pk: int) -> Api: - async with async_db_session.begin() as db: + async with async_db_session() as db: api = await ApiDao.get(db, pk) if not api: raise errors.NotFoundError(msg='接口不存在') diff --git a/backend/app/services/role_service.py b/backend/app/services/role_service.py index bbb2e951..fe6f6301 100644 --- a/backend/app/services/role_service.py +++ b/backend/app/services/role_service.py @@ -1,7 +1,59 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- +from sqlalchemy import Select + +from backend.app.common.exception import errors +from backend.app.crud.crud_menu import MenuDao +from backend.app.crud.crud_role import RoleDao +from backend.app.database.db_mysql import async_db_session +from backend.app.models import Role +from backend.app.schemas.role import CreateRole, UpdateRole class RoleService: - # TODO: 添加 role 相关服务 - pass + @staticmethod + async def get(pk: int) -> Role: + async with async_db_session() as db: + role = await RoleDao.get_with_relation(db, pk) + if not role: + raise errors.NotFoundError(msg='角色不存在') + return role + + @staticmethod + async def get_select(*, name: str = None, data_scope: int = None) -> Select: + return await RoleDao.get_all(name=name, data_scope=data_scope) + + @staticmethod + async def create(*, obj: CreateRole, user_id: int) -> None: + async with async_db_session.begin() as db: + role = await RoleDao.get_by_name(db, obj.name) + if role: + raise errors.ForbiddenError(msg='角色已存在') + for menu_id in obj.menus: + menu = await MenuDao.get(db, menu_id) + if not menu: + raise errors.ForbiddenError(msg='菜单不存在') + await RoleDao.create(db, obj, user_id) + + @staticmethod + async def update(*, pk: int, obj: UpdateRole, user_id: int) -> int: + async with async_db_session.begin() as db: + role = await RoleDao.get(db, pk) + if not role: + raise errors.NotFoundError(msg='角色不存在') + if role.name != obj.name: + role = await RoleDao.get_by_name(db, obj.name) + if role: + raise errors.ForbiddenError(msg='角色已存在') + for menu_id in obj.menus: + menu = await MenuDao.get(db, menu_id) + if not menu: + raise errors.ForbiddenError(msg='菜单不存在') + count = await RoleDao.update(db, pk, obj, user_id) + return count + + @staticmethod + async def delete(*, pk: list[int]) -> int: + async with async_db_session.begin() as db: + count = await RoleDao.delete(db, pk) + return count