From f21006000ac1f0e9c8e5fcf364b46ed9a5580149 Mon Sep 17 00:00:00 2001 From: Wu Clan Date: Fri, 14 Jul 2023 17:01:47 +0800 Subject: [PATCH] Update user role interface to standalone (#177) * Update user role interface to standalone * Fix the userinfo schema department field type * fix update userinfo values * fix update user avatar value * update readme docs * update test data sql * update readme typo * update readme docs --- README.md | 20 ++++++++++++++++++++ README.zh-CN.md | 22 +++++++++++++++++++++- backend/app/api/v1/user.py | 24 +++++++++++++++++++----- backend/app/crud/base.py | 3 ++- backend/app/crud/crud_dept.py | 2 +- backend/app/crud/crud_user.py | 17 +++++++++-------- backend/app/models/sys_dept.py | 2 +- backend/app/schemas/user.py | 8 ++++++-- backend/app/services/user_service.py | 27 ++++++++++++++++++++------- backend/app/utils/redis_info.py | 1 - backend/sql/init_test_data.sql | 1 + 11 files changed, 100 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index e49f5b46..0a283fd4 100644 --- a/README.md +++ b/README.md @@ -135,6 +135,11 @@ Execute tests via pytest pytest -vs --disable-warnings ``` +## Contributors + + + + ## Thanks - [FastAPI](https://fastapi.tiangolo.com/) @@ -144,6 +149,21 @@ Execute tests via pytest - [Ruff](https://beta.ruff.rs/docs/) - ...... +## Sponsor + +> If this program has helped you, you can sponsor the author with some coffee beans :coffee: + + + + + + + + +
+ +
Wechat PayAli Pay
+ ## License This project is licensed under the terms of the MIT license diff --git a/README.zh-CN.md b/README.zh-CN.md index 90e18b38..ac8b1311 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -131,7 +131,12 @@ ```shell pytest -vs --disable-warnings - ``` + ``` + +## 贡献者 + + + ## 鸣谢 @@ -142,6 +147,21 @@ - [Ruff](https://beta.ruff.rs/docs/) - ...... +## 赞助 + +> 如果此项目能够帮助到你,你可以赞助作者一些咖啡豆表示鼓励 :coffee: + + + + + + + + +
+ +
微信支付宝
+ ## 许可证 本项目根据 MIT 许可证的条款进行许可 diff --git a/backend/app/api/v1/user.py b/backend/app/api/v1/user.py index 72074f06..ba8e96e2 100644 --- a/backend/app/api/v1/user.py +++ b/backend/app/api/v1/user.py @@ -9,7 +9,15 @@ from backend.app.common.jwt import DependsJwtAuth from backend.app.common.pagination import paging_data, PageDepends from backend.app.common.response.response_schema import response_base from backend.app.database.db_mysql import CurrentSession -from backend.app.schemas.user import CreateUser, GetAllUserInfo, ResetPassword, UpdateUser, Avatar, GetCurrentUserInfo +from backend.app.schemas.user import ( + CreateUser, + GetAllUserInfo, + ResetPassword, + UpdateUser, + Avatar, + GetCurrentUserInfo, + UpdateUserRole, +) from backend.app.services.user_service import UserService from backend.app.utils.serializers import select_to_json @@ -31,7 +39,7 @@ async def password_reset(request: Request, obj: ResetPassword): @router.get('/me', summary='获取当前用户信息', dependencies=[DependsJwtAuth]) -async def current_userinfo(request: Request): +async def get_current_userinfo(request: Request): data = GetCurrentUserInfo(**select_to_json(request.user)) return await response_base.success(data=data, exclude={'password'}) @@ -51,6 +59,12 @@ async def update_userinfo(request: Request, username: str, obj: UpdateUser): return await response_base.fail() +@router.put('/{username}/role', summary='更新用户角色', dependencies=[DependsRBAC]) +async def update_user_role(request: Request, username: str, obj: UpdateUserRole): + await UserService.update_role(request=request, username=username, obj=obj) + return await response_base.success() + + @router.put('/{username}/avatar', summary='更新头像', dependencies=[DependsJwtAuth]) async def update_avatar(request: Request, username: str, avatar: Avatar): count = await UserService.update_avatar(request=request, username=username, avatar=avatar) @@ -72,7 +86,7 @@ async def get_all_users( return await response_base.success(data=page_data) -@router.post('/{pk}/super', summary='修改用户超级权限', dependencies=[DependsRBAC]) +@router.put('/{pk}/super', summary='修改用户超级权限', dependencies=[DependsRBAC]) async def super_set(request: Request, pk: int): count = await UserService.update_permission(request=request, pk=pk) if count > 0: @@ -80,7 +94,7 @@ async def super_set(request: Request, pk: int): return await response_base.fail() -@router.post('/{pk}/action', summary='修改用户状态', dependencies=[DependsRBAC]) +@router.put('/{pk}/status', summary='修改用户状态', dependencies=[DependsRBAC]) async def status_set(request: Request, pk: int): count = await UserService.update_status(request=request, pk=pk) if count > 0: @@ -88,7 +102,7 @@ async def status_set(request: Request, pk: int): return await response_base.fail() -@router.post('/{pk}/multi', summary='修改用户多点登录状态', dependencies=[DependsRBAC]) +@router.put('/{pk}/multi', summary='修改用户多点登录状态', dependencies=[DependsRBAC]) async def multi_set(request: Request, pk: int): count = await UserService.update_multi_login(request=request, pk=pk) if count > 0: diff --git a/backend/app/crud/base.py b/backend/app/crud/base.py index dfd37da0..7af9da0a 100644 --- a/backend/app/crud/base.py +++ b/backend/app/crud/base.py @@ -36,7 +36,8 @@ class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]): :param del_flag: :return: """ - assert pk is not None or name is not None, '查询错误, pk 和 name 参数不能同时为空' + assert pk is not None or name is not None, '查询错误, pk 和 name 参数不能同时存在' + assert pk is None or name is None, '查询错误, pk 和 name 参数不能同时为空' where_list = [self.model.id == pk] if pk is not None else [self.model.name == name] if status is not None: assert status in (0, 1), '查询错误, status 参数只能为 0 或 1' diff --git a/backend/app/crud/crud_dept.py b/backend/app/crud/crud_dept.py index 9f78f994..b283f048 100644 --- a/backend/app/crud/crud_dept.py +++ b/backend/app/crud/crud_dept.py @@ -16,7 +16,7 @@ class CRUDDept(CRUDBase[Dept, CreateDept, UpdateDept]): return await self.get_(db, pk=dept_id, del_flag=0) async def get_by_name(self, db: AsyncSession, name: str) -> Dept | None: - return await self.get_(db, name=name) + return await self.get_(db, name=name, del_flag=0) async def get_all( self, db: AsyncSession, name: str = None, leader: str = None, phone: str = None, status: int = None diff --git a/backend/app/crud/crud_user.py b/backend/app/crud/crud_user.py index 6c0236e2..efe5794d 100644 --- a/backend/app/crud/crud_user.py +++ b/backend/app/crud/crud_user.py @@ -11,7 +11,7 @@ from sqlalchemy.sql import Select from backend.app.common import jwt from backend.app.crud.base import CRUDBase from backend.app.models import User, Role -from backend.app.schemas.user import CreateUser, UpdateUser, Avatar +from backend.app.schemas.user import CreateUser, UpdateUser, Avatar, UpdateUserRole class CRUDUser(CRUDBase[User, CreateUser, UpdateUser]): @@ -35,13 +35,15 @@ class CRUDUser(CRUDBase[User, CreateUser, UpdateUser]): role_list = [] for role_id in create.roles: role_list.append(await db.get(Role, role_id)) - new_user.roles.append(*role_list) + new_user.roles.extend(role_list) db.add(new_user) async def update_userinfo(self, db: AsyncSession, input_user: User, obj: UpdateUser) -> int: - user = await db.execute( - update(self.model).where(self.model.id == input_user.id).values(**obj.dict(exclude={'roles'})) - ) + user = await db.execute(update(self.model).where(self.model.id == input_user.id).values(**obj.dict())) + return user.rowcount + + @staticmethod + async def update_role(db: AsyncSession, input_user: User, obj: UpdateUserRole) -> None: # 删除用户所有角色 for i in list(input_user.roles): input_user.roles.remove(i) @@ -49,11 +51,10 @@ class CRUDUser(CRUDBase[User, CreateUser, UpdateUser]): role_list = [] for role_id in obj.roles: role_list.append(await db.get(Role, role_id)) - input_user.roles.append(*role_list) - return user.rowcount + input_user.roles.extend(role_list) async def update_avatar(self, db: AsyncSession, current_user: User, avatar: Avatar) -> int: - user = await db.execute(update(self.model).where(self.model.id == current_user.id).values(avatar=avatar)) + user = await db.execute(update(self.model).where(self.model.id == current_user.id).values(avatar=avatar.url)) return user.rowcount async def delete(self, db: AsyncSession, user_id: int) -> int: diff --git a/backend/app/models/sys_dept.py b/backend/app/models/sys_dept.py index bcc487b1..b76ba63f 100644 --- a/backend/app/models/sys_dept.py +++ b/backend/app/models/sys_dept.py @@ -14,7 +14,7 @@ class Dept(Base): __tablename__ = 'sys_dept' id: Mapped[id_key] = mapped_column(init=False) - name: Mapped[str] = mapped_column(String(50), unique=True, comment='部门名称') + name: Mapped[str] = mapped_column(String(50), comment='部门名称') level: Mapped[int] = mapped_column(default=0, comment='部门层级') sort: Mapped[int] = mapped_column(default=0, comment='排序') leader: Mapped[str | None] = mapped_column(String(20), default=None, comment='负责人') diff --git a/backend/app/schemas/user.py b/backend/app/schemas/user.py index 481f4ed2..45a22883 100644 --- a/backend/app/schemas/user.py +++ b/backend/app/schemas/user.py @@ -21,7 +21,7 @@ class AuthLogin(Auth): class CreateUser(Auth): - dept_id: int + dept_id: int | None = None roles: list[int] nickname: str email: str = Field(..., example='user@example.com') @@ -36,7 +36,7 @@ class CreateUser(Auth): class _UserInfoBase(SchemaBase): - dept_id: int + dept_id: int | None = None username: str nickname: str email: str = Field(..., example='user@example.com') @@ -58,6 +58,10 @@ class _UserInfoBase(SchemaBase): class UpdateUser(_UserInfoBase): + pass + + +class UpdateUserRole(SchemaBase): roles: list[int] diff --git a/backend/app/services/user_service.py b/backend/app/services/user_service.py index b4c6b945..5ce23937 100644 --- a/backend/app/services/user_service.py +++ b/backend/app/services/user_service.py @@ -15,7 +15,7 @@ from backend.app.crud.crud_role import RoleDao from backend.app.crud.crud_user import UserDao from backend.app.database.db_mysql import async_db_session from backend.app.models import User -from backend.app.schemas.user import CreateUser, ResetPassword, UpdateUser, Avatar +from backend.app.schemas.user import CreateUser, ResetPassword, UpdateUser, Avatar, UpdateUserRole class UserService: @@ -82,17 +82,30 @@ class UserService: dept = await DeptDao.get(db, obj.dept_id) if not dept: raise errors.NotFoundError(msg='部门不存在') - for role_id in obj.roles: - role = await RoleDao.get(db, role_id) - if not role: - raise errors.NotFoundError(msg='角色不存在') count = await UserDao.update_userinfo(db, input_user, obj) return count + @staticmethod + async def update_role(*, request: Request, username: str, obj: UpdateUserRole) -> None: + async with async_db_session.begin() as db: + if not request.user.is_superuser: + if request.user.username != username: + raise errors.ForbiddenError(msg='你只能修改自己的角色') + input_user = await UserDao.get_with_relation(db, username=username) + if not input_user: + raise errors.NotFoundError(msg='用户不存在') + for role_id in obj.roles: + role = await RoleDao.get(db, role_id) + if not role: + raise errors.NotFoundError(msg='角色不存在') + await UserDao.update_role(db, input_user, obj) + @staticmethod async def update_avatar(*, request: Request, username: str, avatar: Avatar) -> int: async with async_db_session.begin() as db: - await jwt.superuser_verify(request) + if not request.user.is_superuser: + if request.user.username != username: + raise errors.ForbiddenError(msg='你只能修改自己的头像') input_user = await UserDao.get_by_username(db, username) if not input_user: raise errors.NotFoundError(msg='用户不存在') @@ -111,7 +124,7 @@ class UserService: raise errors.NotFoundError(msg='用户不存在') else: if pk == request.user.id: - raise errors.ForbiddenError(msg='禁止修改自身权限') + raise errors.ForbiddenError(msg='禁止修改自身管理员权限') count = await UserDao.set_super(db, pk) return count diff --git a/backend/app/utils/redis_info.py b/backend/app/utils/redis_info.py index fbc82531..54442a86 100644 --- a/backend/app/utils/redis_info.py +++ b/backend/app/utils/redis_info.py @@ -5,7 +5,6 @@ from backend.app.utils.server_info import server_info class RedisInfo: - @staticmethod async def get_info(): info = await redis_client.info() diff --git a/backend/sql/init_test_data.sql b/backend/sql/init_test_data.sql index 51b4df88..65016232 100644 --- a/backend/sql/init_test_data.sql +++ b/backend/sql/init_test_data.sql @@ -22,6 +22,7 @@ values (1, 'test', 0, 0, null, null, 0, null, null, 1, null, null, '2023-06-26 (18, 'monitor', 0, 88, 'icon-computer', null, 0, null, null, 1, null, null, '2023-07-11 20:20:20', null, 1, 1, '系统监控'), (19, 'Redis', 0, 0, null, null, 1, '/monitor/redis/index.vue', null, 1, null, 18, '2023-07-11 20:21:28', null, 1, 1, 'Redis监控'), (20, 'Server', 0, 0, null, 'server', 1, '/monitor/server/index.vue', null, 1, null, 18, '2023-07-11 20:23:43', null, 1, 1, '服务器监控'); + (21, 'SysUser', 0, 0, null, 'sys-user', 1, '/admin/user/index.vue', null, 1, null, 8, '2023-07-13 03:32:47', null, 1, 1, '用户管理'); INSERT INTO fba.sys_role (id, name, data_scope, status, remark, created_time, updated_time) VALUES (1, 'test', 2, 1, null, '2023-06-26 17:13:45', null);