Skip to content

Commit 8a18186

Browse files
authored
Add role-related interfaces (#89)
1 parent d4f4f3f commit 8a18186

File tree

9 files changed

+172
-16
lines changed

9 files changed

+172
-16
lines changed

backend/app/api/v1/role.py

+51-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,56 @@
11
#!/usr/bin/env python3
22
# -*- coding: utf-8 -*-
3-
from fastapi import APIRouter
3+
from typing import Annotated
4+
5+
from fastapi import APIRouter, Query, Request
6+
7+
from backend.app.common.casbin_rbac import DependsRBAC
8+
from backend.app.common.jwt import DependsJwtAuth
9+
from backend.app.common.pagination import PageDepends, paging_data
10+
from backend.app.common.response.response_schema import response_base
11+
from backend.app.database.db_mysql import CurrentSession
12+
from backend.app.schemas.role import GetAllRole, CreateRole, UpdateRole
13+
from backend.app.services.role_service import RoleService
14+
from backend.app.utils.serializers import select_to_json
415

516
router = APIRouter()
617

7-
# TODO: 添加 role 相关接口
18+
19+
@router.get('/{pk}', summary='获取角色详情', dependencies=[DependsJwtAuth])
20+
async def get_role(pk: int):
21+
role = await RoleService.get(pk=pk)
22+
data = GetAllRole(**select_to_json(role))
23+
return response_base.success(data=data)
24+
25+
26+
@router.get('', summary='(模糊条件)分页获取所有角色', dependencies=[DependsJwtAuth, PageDepends])
27+
async def get_all_roles(
28+
db: CurrentSession,
29+
name: Annotated[str | None, Query()] = None,
30+
data_scope: Annotated[int | None, Query()] = None,
31+
):
32+
role_select = await RoleService.get_select(name=name, data_scope=data_scope)
33+
page_data = await paging_data(db, role_select, GetAllRole)
34+
return response_base.success(data=page_data)
35+
36+
37+
@router.post('', summary='创建角色', dependencies=[DependsRBAC])
38+
async def create_role(request: Request, obj: CreateRole):
39+
await RoleService.create(obj=obj, user_id=request.user.id)
40+
return response_base.success()
41+
42+
43+
@router.put('/{pk}', summary='更新角色', dependencies=[DependsRBAC])
44+
async def update_role(request: Request, pk: int, obj: UpdateRole):
45+
count = await RoleService.update(pk=pk, obj=obj, user_id=request.user.id)
46+
if count > 0:
47+
return response_base.success()
48+
return response_base.fail()
49+
50+
51+
@router.delete('', summary='(批量)删除角色', dependencies=[DependsRBAC])
52+
async def delete_role(pk: Annotated[list[int], Query(...)]):
53+
count = await RoleService.delete(pk=pk)
54+
if count > 0:
55+
return response_base.success()
56+
return response_base.fail()

backend/app/crud/crud_menu.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66

77

88
class CRUDMenu(CRUDBase[Menu, CreateMenu, UpdateMenu]):
9-
# TODO: 添加 menu 相关数据库操作
10-
pass
9+
async def get(self, db, menu_id: int) -> Menu | None:
10+
return await self.get_(db, menu_id)
1111

1212

1313
MenuDao: CRUDMenu = CRUDMenu(Menu)

backend/app/crud/crud_role.py

+59-2
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,70 @@
11
#!/usr/bin/env python3
22
# -*- coding: utf-8 -*-
3+
from typing import NoReturn
4+
5+
from sqlalchemy import select, update, delete
6+
from sqlalchemy.orm import selectinload
7+
38
from backend.app.crud.base import CRUDBase
4-
from backend.app.models import Role
9+
from backend.app.models import Role, Menu
510
from backend.app.schemas.role import CreateRole, UpdateRole
611

712

813
class CRUDRole(CRUDBase[Role, CreateRole, UpdateRole]):
9-
async def get(self, db, role_id: int):
14+
async def get(self, db, role_id: int) -> Role | None:
1015
return await self.get_(db, role_id)
1116

17+
async def get_with_relation(self, db, role_id: int) -> Role | None:
18+
role = await db.execute(
19+
select(self.model)
20+
.options(selectinload(self.model.menus))
21+
.where(self.model.id == role_id)
22+
)
23+
return role.scalars().first()
24+
25+
async def get_all(self, name: str = None, data_scope: int = None):
26+
se = select(self.model).options(selectinload(self.model.menus)).order_by(self.model.created_time.desc())
27+
where_list = []
28+
if name:
29+
where_list.append(self.model.name.like(f'%{name}%'))
30+
if data_scope:
31+
where_list.append(self.model.data_scope == data_scope)
32+
if where_list:
33+
se = se.where(*where_list)
34+
return se
35+
36+
async def get_by_name(self, db, name: str) -> Role | None:
37+
role = await db.execute(select(self.model).where(self.model.name == name))
38+
return role.scalars().first()
39+
40+
async def create(self, db, obj_in: CreateRole, user_id: int) -> NoReturn:
41+
new_role = self.model(**obj_in.dict(exclude={'menus'}), create_user=user_id)
42+
menu_list = []
43+
for menu_id in obj_in.menus:
44+
menu_list.append(await db.get(Menu, menu_id))
45+
new_role.menus.append(*menu_list)
46+
db.add(new_role)
47+
48+
async def update(self, db, role_id: int, obj_in: UpdateRole, user_id: int) -> int:
49+
role = await db.execute(
50+
update(self.model)
51+
.where(self.model.id == role_id)
52+
.values(**obj_in.dict(exclude={'menus'}), update_user=user_id)
53+
)
54+
current_role = await self.get_with_relation(db, role_id)
55+
# 删除角色所有菜单
56+
for i in list(current_role.menus):
57+
current_role.menus.remove(i)
58+
# 添加角色菜单
59+
menu_list = []
60+
for menu_id in obj_in.menus:
61+
menu_list.append(await db.get(Menu, menu_id))
62+
current_role.menus.append(*menu_list)
63+
return role.rowcount
64+
65+
async def delete(self, db, role_id: list[int]) -> int:
66+
roles = await db.execute(delete(self.model).where(self.model.id.in_(role_id)))
67+
return roles.rowcount
68+
1269

1370
RoleDao: CRUDRole = CRUDRole(Role)

backend/app/models/sys_dept.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,6 @@ class Dept(Base):
2020
phone: Mapped[str | None] = mapped_column(String(11), default=None, comment='手机')
2121
email: Mapped[str | None] = mapped_column(String(50), default=None, comment='邮箱')
2222
status: Mapped[bool] = mapped_column(default=True, comment='部门状态(0停用 1正常)')
23-
del_flag: Mapped[bool] = mapped_column(default=True, comment='删除标志(0删除 1存在)')
23+
del_flag: Mapped[bool] = mapped_column(default=False, comment='删除标志(0删除 1存在)')
2424
# 用户部门一对多
2525
users: Mapped['User'] = relationship(init=False, back_populates='dept') # noqa: F821

backend/app/models/sys_menu.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class Menu(Base):
2222
menu_type: Mapped[int] = mapped_column(default=0, comment='菜单类型(0目录 1菜单 2按钮)')
2323
icon: Mapped[str | None] = mapped_column(String(100), default='#', comment='菜单图标')
2424
remark: Mapped[str | None] = mapped_column(LONGTEXT, default=None, comment='备注')
25-
del_flag: Mapped[bool] = mapped_column(default=True, comment='删除标志(0删除 1存在)')
25+
del_flag: Mapped[bool] = mapped_column(default=False, comment='删除标志(0删除 1存在)')
2626
# 菜单角色多对多
2727
roles: Mapped[list['Role']] = relationship( # noqa: F821
2828
init=False, secondary=sys_role_menu, back_populates='menus'

backend/app/models/sys_role.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,8 @@ class Role(Base):
1515

1616
id: Mapped[id_key] = mapped_column(init=False)
1717
name: Mapped[str] = mapped_column(String(20), unique=True, comment='角色名称')
18-
sort: Mapped[int] = mapped_column(default=0, comment='显示顺序')
1918
data_scope: Mapped[int | None] = mapped_column(default=2, comment='数据范围(1:全部数据权限 2:自定数据权限)')
20-
del_flag: Mapped[bool] = mapped_column(default=True, comment='删除标志(0删除 1存在)')
19+
del_flag: Mapped[bool] = mapped_column(default=False, comment='删除标志(0删除 1存在)')
2120
# 角色用户多对多
2221
users: Mapped[list['User']] = relationship( # noqa: F821
2322
init=False, secondary=sys_user_role, back_populates='roles'

backend/app/schemas/role.py

+2-3
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010

1111
class RoleBase(BaseModel):
1212
name: str
13-
sort: int = Field(default=0, ge=0, description='排序')
1413
data_scope: int | None = Field(default=RoleDataScope.custom, description='数据范围(1:全部数据权限 2:自定数据权限)') # noqa: E501
1514
del_flag: bool
1615

@@ -22,11 +21,11 @@ def check_data_scope(cls, v):
2221

2322

2423
class CreateRole(RoleBase):
25-
menu_ids: list[int]
24+
menus: list[int]
2625

2726

2827
class UpdateRole(RoleBase):
29-
menu_ids: list[int]
28+
menus: list[int]
3029

3130

3231
class GetAllRole(RoleBase):

backend/app/services/api_service.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
class ApiService:
1313
@staticmethod
1414
async def get(*, pk: int) -> Api:
15-
async with async_db_session.begin() as db:
15+
async with async_db_session() as db:
1616
api = await ApiDao.get(db, pk)
1717
if not api:
1818
raise errors.NotFoundError(msg='接口不存在')

backend/app/services/role_service.py

+54-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,59 @@
11
#!/usr/bin/env python3
22
# -*- coding: utf-8 -*-
3+
from sqlalchemy import Select
4+
5+
from backend.app.common.exception import errors
6+
from backend.app.crud.crud_menu import MenuDao
7+
from backend.app.crud.crud_role import RoleDao
8+
from backend.app.database.db_mysql import async_db_session
9+
from backend.app.models import Role
10+
from backend.app.schemas.role import CreateRole, UpdateRole
311

412

513
class RoleService:
6-
# TODO: 添加 role 相关服务
7-
pass
14+
@staticmethod
15+
async def get(pk: int) -> Role:
16+
async with async_db_session() as db:
17+
role = await RoleDao.get_with_relation(db, pk)
18+
if not role:
19+
raise errors.NotFoundError(msg='角色不存在')
20+
return role
21+
22+
@staticmethod
23+
async def get_select(*, name: str = None, data_scope: int = None) -> Select:
24+
return await RoleDao.get_all(name=name, data_scope=data_scope)
25+
26+
@staticmethod
27+
async def create(*, obj: CreateRole, user_id: int) -> None:
28+
async with async_db_session.begin() as db:
29+
role = await RoleDao.get_by_name(db, obj.name)
30+
if role:
31+
raise errors.ForbiddenError(msg='角色已存在')
32+
for menu_id in obj.menus:
33+
menu = await MenuDao.get(db, menu_id)
34+
if not menu:
35+
raise errors.ForbiddenError(msg='菜单不存在')
36+
await RoleDao.create(db, obj, user_id)
37+
38+
@staticmethod
39+
async def update(*, pk: int, obj: UpdateRole, user_id: int) -> int:
40+
async with async_db_session.begin() as db:
41+
role = await RoleDao.get(db, pk)
42+
if not role:
43+
raise errors.NotFoundError(msg='角色不存在')
44+
if role.name != obj.name:
45+
role = await RoleDao.get_by_name(db, obj.name)
46+
if role:
47+
raise errors.ForbiddenError(msg='角色已存在')
48+
for menu_id in obj.menus:
49+
menu = await MenuDao.get(db, menu_id)
50+
if not menu:
51+
raise errors.ForbiddenError(msg='菜单不存在')
52+
count = await RoleDao.update(db, pk, obj, user_id)
53+
return count
54+
55+
@staticmethod
56+
async def delete(*, pk: list[int]) -> int:
57+
async with async_db_session.begin() as db:
58+
count = await RoleDao.delete(db, pk)
59+
return count

0 commit comments

Comments
 (0)