From 4ed1899f8f49d3472b9e181e4c4edec940ba6aa1 Mon Sep 17 00:00:00 2001 From: Wu Clan Date: Fri, 21 Jul 2023 17:11:24 +0800 Subject: [PATCH] Update role menu interface is standalone (#182) * Update role directory interface is standalone * fix data type conversion error * update the body to the schema type * fix menu permission verification --- README.md | 14 ++++++------- README.zh-CN.md | 2 +- backend/app/api/v1/role.py | 10 ++++++++- backend/app/common/casbin_rbac.py | 4 +++- backend/app/crud/crud_role.py | 20 ++++++++---------- .../app/middleware/opera_log_middleware.py | 6 ++++++ backend/app/schemas/role.py | 6 +++++- backend/app/services/role_service.py | 21 ++++++++++++------- 8 files changed, 53 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 0a283fd4..8e2dc5c8 100644 --- a/README.md +++ b/README.md @@ -17,13 +17,13 @@ the way to go But in python development, there is no universal standard for the concept of a three-tier architecture, so here I call it a pseudo three-tier architecture -| workflow | java | fastapi_best_architecture | -|-----------------|----------------|---------------------------| -| view | controller | api | -| data validation | dto | schema | -| business logic | service + impl | service | -| data access | dao / mapper | crud | -| model | model / entity | model | +| workflow | java | fastapi_best_architecture | +|----------------|----------------|---------------------------| +| view | controller | api | +| data transmit | dto | schema | +| business logic | service + impl | service | +| data access | dao / mapper | crud | +| model | model / entity | model | ## Features diff --git a/README.zh-CN.md b/README.zh-CN.md index ac8b1311..7d6594c6 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -17,7 +17,7 @@ | 工作流程 | java | fastapi_best_architecture | |------|----------------|---------------------------| | 视图 | controller | api | -| 数据验证 | dto | schema | +| 数据传输 | dto | schema | | 业务逻辑 | service + impl | service | | 数据访问 | dao / mapper | crud | | 模型 | model / entity | model | diff --git a/backend/app/api/v1/role.py b/backend/app/api/v1/role.py index ae334acd..3744eba1 100644 --- a/backend/app/api/v1/role.py +++ b/backend/app/api/v1/role.py @@ -9,7 +9,7 @@ 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.schemas.role import GetAllRole, CreateRole, UpdateRole, UpdateRoleMenu from backend.app.services.role_service import RoleService from backend.app.utils.serializers import select_to_json, select_to_list @@ -63,6 +63,14 @@ async def update_role(pk: int, obj: UpdateRole): return await response_base.fail() +@router.put('/{pk}/menu', summary='更新角色菜单', dependencies=[DependsRBAC]) +async def update_role_menu(pk: int, menu_ids: UpdateRoleMenu): + count = await RoleService.update_menus(pk=pk, menu_ids=menu_ids) + if count > 0: + return await response_base.success() + return await response_base.fail() + + @router.delete('', summary='(批量)删除角色', dependencies=[DependsRBAC]) async def delete_role(pk: Annotated[list[int], Query(...)]): count = await RoleService.delete(pk=pk) diff --git a/backend/app/common/casbin_rbac.py b/backend/app/common/casbin_rbac.py index f7deff03..f3bb8809 100644 --- a/backend/app/common/casbin_rbac.py +++ b/backend/app/common/casbin_rbac.py @@ -52,7 +52,9 @@ class RBAC: menu_perms = [] for role in user_roles: menu_perms.extend([menu.perms for menu in role.menus]) - if not menu_perms or path_auth not in settings.MENU_EXCLUDE: + if path_auth in set(settings.MENU_EXCLUDE): + return + if path_auth not in set(menu_perms): raise AuthorizationError else: # casbin 权限校验 diff --git a/backend/app/crud/crud_role.py b/backend/app/crud/crud_role.py index ba749eff..982c22c0 100644 --- a/backend/app/crud/crud_role.py +++ b/backend/app/crud/crud_role.py @@ -2,12 +2,12 @@ # -*- coding: utf-8 -*- from typing import NoReturn -from sqlalchemy import select, update, delete, desc +from sqlalchemy import select, delete, desc from sqlalchemy.orm import selectinload from backend.app.crud.base import CRUDBase from backend.app.models import Role, Menu, User -from backend.app.schemas.role import CreateRole, UpdateRole +from backend.app.schemas.role import CreateRole, UpdateRole, UpdateRoleMenu class CRUDRole(CRUDBase[Role, CreateRole, UpdateRole]): @@ -46,20 +46,18 @@ class CRUDRole(CRUDBase[Role, CreateRole, UpdateRole]): return role.scalars().first() async def create(self, db, obj_in: CreateRole) -> NoReturn: - new_role = self.model(**obj_in.dict(exclude={'menus'})) - menus = await db.execute(select(Menu).where(Menu.id.in_(obj_in.menus))) - new_role.menus = menus.scalars().all() - db.add(new_role) + await self.create_(db, obj_in) async def update(self, db, role_id: int, obj_in: UpdateRole) -> int: - role = await db.execute( - update(self.model).where(self.model.id == role_id).values(**obj_in.dict(exclude={'menus'})) - ) + rowcount = await self.update_(db, pk=role_id, obj_in=obj_in) + return rowcount + + async def update_menus(self, db, role_id: int, menu_ids: UpdateRoleMenu) -> int: current_role = await self.get_with_relation(db, role_id) # 更新菜单 - menus = await db.execute(select(Menu).where(Menu.id.in_(obj_in.menus))) + menus = await db.execute(select(Menu).where(Menu.id.in_(menu_ids.menus))) current_role.menus = menus.scalars().all() - return role.rowcount + return len(current_role.menus) async def delete(self, db, role_id: list[int]) -> int: roles = await db.execute(delete(self.model).where(self.model.id.in_(role_id))) diff --git a/backend/app/middleware/opera_log_middleware.py b/backend/app/middleware/opera_log_middleware.py index 0e46c3e0..9294922e 100644 --- a/backend/app/middleware/opera_log_middleware.py +++ b/backend/app/middleware/opera_log_middleware.py @@ -159,6 +159,12 @@ class OperaLogMiddleware: body_data = await request.body() if body_data: json_data = await request.json() + if not isinstance(json_data, dict): + json_data = { + f'{type(json_data)}_to_dict_data': json_data.decode('utf-8') + if isinstance(json_data, bytes) + else json_data + } args.update(json_data) return args diff --git a/backend/app/schemas/role.py b/backend/app/schemas/role.py index 191cdadf..17e19eb1 100644 --- a/backend/app/schemas/role.py +++ b/backend/app/schemas/role.py @@ -17,10 +17,14 @@ class RoleBase(SchemaBase): class CreateRole(RoleBase): - menus: list[int] + pass class UpdateRole(RoleBase): + pass + + +class UpdateRoleMenu(SchemaBase): menus: list[int] diff --git a/backend/app/services/role_service.py b/backend/app/services/role_service.py index fd692955..9fa7ed40 100644 --- a/backend/app/services/role_service.py +++ b/backend/app/services/role_service.py @@ -7,7 +7,7 @@ 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 +from backend.app.schemas.role import CreateRole, UpdateRole, UpdateRoleMenu class RoleService: @@ -41,10 +41,6 @@ class RoleService: 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) @staticmethod @@ -57,11 +53,20 @@ class RoleService: role = await RoleDao.get_by_name(db, obj.name) if role: raise errors.ForbiddenError(msg='角色已存在') - for menu_id in obj.menus: + count = await RoleDao.update(db, pk, obj) + return count + + @staticmethod + async def update_menus(*, pk: int, menu_ids: UpdateRoleMenu) -> int: + async with async_db_session.begin() as db: + role = await RoleDao.get(db, pk) + if not role: + raise errors.NotFoundError(msg='角色不存在') + for menu_id in menu_ids.menus: menu = await MenuDao.get(db, menu_id) if not menu: - raise errors.ForbiddenError(msg='菜单不存在') - count = await RoleDao.update(db, pk, obj) + raise errors.NotFoundError(msg='菜单不存在') + count = await RoleDao.update_menus(db, pk, menu_ids) return count @staticmethod