From 293f3ba937bf4aee7ad3a5e150f7a724a5317a11 Mon Sep 17 00:00:00 2001 From: xtaodada Date: Wed, 13 Nov 2024 15:27:41 +0800 Subject: [PATCH] feat: user admin add --- src/frontend/sa-view/user/user-add.html | 6 +-- src/frontend/sa-view/user/user-list.html | 23 +++++---- src/route/users.py | 60 ++++++++++++++++-------- src/route/users_admin.py | 41 +++++++++++++++- src/route/users_update.py | 6 +++ src/services/users/repositories.py | 9 +++- src/services/users/schemas.py | 27 ++++++++++- src/services/users/services.py | 25 ++++++++-- src/utils/__init__.py | 2 +- 9 files changed, 158 insertions(+), 41 deletions(-) diff --git a/src/frontend/sa-view/user/user-add.html b/src/frontend/sa-view/user/user-add.html index ad12c7f..d2e2fed 100644 --- a/src/frontend/sa-view/user/user-add.html +++ b/src/frontend/sa-view/user/user-add.html @@ -84,9 +84,9 @@ methods: { // ok ok: function() { - sa.ajax2('/user/add', function(res) { - sa.alert('数据添加, 参数为:' + JSON.stringify(this.m)); - }.bind(this)) + sa.ajax('/user/admin/add', this.m, function(res) { + sa.alert('数据添加成功'); + }.bind(this), {}) } } }) diff --git a/src/frontend/sa-view/user/user-list.html b/src/frontend/sa-view/user/user-list.html index b7ce748..16806c3 100644 --- a/src/frontend/sa-view/user/user-list.html +++ b/src/frontend/sa-view/user/user-list.html @@ -113,7 +113,7 @@ @@ -165,6 +165,7 @@ sa.ajax('/user/admin/list', this.p, function(res){ this.dataList = res.data.data; // 数据 for (let i = 0; i < this.dataList.length; i++) { + this.dataList[i].avatar = this.dataList[i].avatar ? this.dataList[i].avatar : '/user/avatar/default'; this.dataList[i].status = this.dataList[i].is_active ? 1 : 0; } this.dataCount = res.data.count; // 分页 @@ -202,12 +203,16 @@ }, // 删除 del: function(data) { - sa.confirm('是否删除,此操作不可撤销', function() { - sa.ajax2('/user/delete?id=' + data.id, function(res) { - sa.arrayDelete(this.dataList, data); - sa.ok('删除成功'); - sa.f5TableHeight(); // 刷新表格高度 - }.bind(this)) + sa.confirm('是否修改,此操作可撤销', function() { + sa.ajax('/user/admin/change_status', {user_id: data.id, is_active: !data.is_active}, function(res) { + // sa.arrayDelete(this.dataList, data); + if (data.is_active) { + sa.ok('禁用成功'); + } else { + sa.ok('启用成功'); + } + this.f5(); + }.bind(this), {}) }.bind(this)); }, // 批量删除 @@ -219,8 +224,8 @@ return sa.msg('请至少选择一条数据') } // 提交删除 - sa.confirm('是否批量删除选中数据?此操作不可撤销', function() { - sa.ajax2('/SysType/deleteByIds', {ids: ids.join(',')}, function(res) { + sa.confirm('是否批量禁用选中用户?此操作可撤销', function() { + sa.ajax2('/user/admin/change_status', {ids: ids}, function(res) { sa.arrayDelete(this.dataList, selection); sa.ok('删除成功'); sa.f5TableHeight(); // 刷新表格高度 diff --git a/src/route/users.py b/src/route/users.py index 26f3462..7c5324d 100644 --- a/src/route/users.py +++ b/src/route/users.py @@ -1,5 +1,5 @@ import datetime -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Union from fastapi_amis_admin.crud import BaseApiOut @@ -17,6 +17,7 @@ from src.services.users.schemas import ( UserLoginOut, UserRoleEnum, CreateTypeEnum, + UserAdminAdd, ) from src.services.users.services import UserServices, UserRoleServices @@ -33,30 +34,31 @@ class UserRoutes(Plugin): self.user_services = user_services self.user_role_services = user_role_services - @handler.post("/register", admin=False) - async def register(self, data: UserRegIn): - if data.username.upper() in SystemUserEnum.__members__: + async def before_reg(self, username: str, student_id: str, phone: str): + if username.upper() in SystemUserEnum.__members__: return BaseApiOut(status=500, msg="用户名已被注册") - user = await self.user_services.get_user(username=data.username) + user = await self.user_services.get_user(username=username) if user: return BaseApiOut(status=500, msg="用户名已被注册") - role = UserRoleEnum.STUDENT.value - create_type = CreateTypeEnum.STUDENT - if not (data.student_id or data.phone): + if not (student_id or phone): return BaseApiOut(status=500, msg="学号或手机号至少填写一项") - if data.student_id: - user = await self.user_services.get_user(student_id=data.student_id) + if student_id: + user = await self.user_services.get_user(student_id=student_id) if user: return BaseApiOut(status=500, msg="学号已被注册") - role = UserRoleEnum.STUDENT.value - create_type = CreateTypeEnum.STUDENT - if data.phone: - user = await self.user_services.get_user(phone=data.phone) + if phone: + user = await self.user_services.get_user(phone=phone) if user: return BaseApiOut(status=500, msg="手机号已被注册") - role = UserRoleEnum.OUT.value - create_type = CreateTypeEnum.PHONE - # 检查通过,注册用户 + return None + + async def create_user( + self, + data: Union[UserRegIn, UserAdminAdd], + create_type: CreateTypeEnum, + role: UserRoleEnum, + ): + sex = "" if not hasattr(data, "sex") else data.sex try: user = await self.user_services.register_user( username=data.username, @@ -64,19 +66,39 @@ class UserRoutes(Plugin): student_id=data.student_id, phone=data.phone, real_name=data.real_name, + sex=sex, create_type=create_type.value, ) if not await self.user_role_services.is_user_in_role_group( - data.username, role + data.username, + role.value, ): await self.user_role_services.add_user_to_role_group( - data.username, role + data.username, + role.value, ) + return user except Exception as e: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Error Execute SQL:{e}", ) from e + + @handler.post("/register", admin=False) + async def register(self, data: UserRegIn): + check = await self.before_reg(data.username, data.student_id, data.phone) + if check: + return check + role = UserRoleEnum.STUDENT + create_type = CreateTypeEnum.STUDENT + if data.student_id: + role = UserRoleEnum.STUDENT + create_type = CreateTypeEnum.STUDENT + if data.phone: + role = UserRoleEnum.OUT + create_type = CreateTypeEnum.PHONE + # 检查通过,注册用户 + user = await self.create_user(data, create_type, role) # 注册成功,设置用户信息 token_info = UserLoginOut.model_validate(user) token_info.access_token = await self.user_services.login_user(user) diff --git a/src/route/users_admin.py b/src/route/users_admin.py index a9a1652..4accbca 100644 --- a/src/route/users_admin.py +++ b/src/route/users_admin.py @@ -4,18 +4,27 @@ from starlette import status from src.plugin import handler from src.plugin.plugin import Plugin -from src.services.users.schemas import UserList +from src.route.users import UserRoutes +from src.services.users.schemas import ( + UserList, + DisableOrEnableUser, + UserAdminAdd, + UserRoleEnum, + CreateTypeEnum, +) from src.services.users.services import UserServices -class UserRoutes(Plugin): +class UserAdminRoutes(Plugin): _prefix = "/user/admin" def __init__( self, user_services: UserServices, + user_routes: UserRoutes, ): self.user_services = user_services + self.user_routes = user_routes @handler.post("/list", student=True) async def get_user_list(self, data: UserList): @@ -50,3 +59,31 @@ class UserRoutes(Plugin): status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Error Execute SQL:{e}", ) from e + + @handler.post("/change_status", student=True) + async def disable_or_enable_user(self, data: DisableOrEnableUser): + try: + if data.is_active: + result = await self.user_services.enable_user(data.user_id) + else: + result = await self.user_services.disable_user(data.user_id) + if not result: + return BaseApiOut(code=500, msg="操作失败") + return BaseApiOut(code=0, msg="请求成功", data={}) + except Exception as e: + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"Error Execute SQL:{e}", + ) from e + + @handler.post("/add", student=True) + async def add_user(self, data: UserAdminAdd): + check = await self.user_routes.before_reg( + data.username, data.student_id, data.phone + ) + if check: + return check + role = data.role_id + create_type = CreateTypeEnum.ADMIN + user = await self.user_routes.create_user(data, create_type, role) + return BaseApiOut(code=0, msg="注册成功", data=user) diff --git a/src/route/users_update.py b/src/route/users_update.py index a3d0c06..3ddb014 100644 --- a/src/route/users_update.py +++ b/src/route/users_update.py @@ -11,6 +11,7 @@ from src.plugin.plugin import Plugin from src.services.users.models import UserModel from src.services.users.schemas import UserUpdate, UserUpdateAvatar from src.services.users.services import UserServices, UserRoleServices +from src.utils import AVATAR_DATA_PATH from src.utils.upload_file import get_avatar, save_avatar, check_avatar @@ -25,6 +26,7 @@ class UserUpdateRoutes(Plugin): self.user_services = user_services self.user_role_services = user_role_services self.avatar_path = "/user/avatar/" + self.avatar_path_default = AVATAR_DATA_PATH / "avatar.jpg" @handler.get("/me", student=True, out=True) async def get_me(self, request: Request): @@ -69,6 +71,10 @@ class UserUpdateRoutes(Plugin): detail=f"Error Execute SQL:{e}", ) from e + @handler.get("/avatar/default", student=True, out=True) + async def get_default_avatar(self): + return FileResponse(self.avatar_path_default) + @handler.get("/avatar/{uid}/{file_path}", student=True, out=True) async def get_avatar(self, request: Request, uid: int, file_path: str): can_see = False diff --git a/src/services/users/repositories.py b/src/services/users/repositories.py index 42915a8..2e9e7b3 100644 --- a/src/services/users/repositories.py +++ b/src/services/users/repositories.py @@ -46,6 +46,7 @@ class UserRepo(AsyncInitializingComponent): student_id: Optional[str], phone: Optional[str], real_name: Optional[str], + sex: Optional[str], create_type: int, ): password = self.AUTH.pwd_context.hash(password.get_secret_value()) @@ -55,6 +56,7 @@ class UserRepo(AsyncInitializingComponent): "student_id": student_id, "phone": phone, "real_name": real_name, + "sex": sex, "create_type": create_type, } user = self.user_model.model_validate(values) @@ -66,12 +68,15 @@ class UserRepo(AsyncInitializingComponent): async def get_user( self, + user_id: Optional[int] = None, username: Optional[str] = None, student_id: Optional[str] = None, phone: Optional[str] = None, ) -> Optional[UserModel]: async with AsyncSession(self.engine) as session: statement = select(self.user_model) + if user_id: + statement = statement.where(self.user_model.id == user_id) if username: statement = statement.where(self.user_model.username == username) if student_id: @@ -159,8 +164,8 @@ class UserRepo(AsyncInitializingComponent): phone: str, student_id: str, create_type: int, - start_time: datetime, - end_time: datetime, + start_time: Optional[datetime], + end_time: Optional[datetime], page_no: int, page_size: int, ) -> Tuple[Sequence[UserModel], int]: diff --git a/src/services/users/schemas.py b/src/services/users/schemas.py index bda355f..264fef7 100644 --- a/src/services/users/schemas.py +++ b/src/services/users/schemas.py @@ -128,19 +128,42 @@ class UserList(BaseModel): create_type: CreateTypeEnum = CreateTypeEnum.ALL sortType: int = 1 - start_time: str - end_time: str + start_time: Optional[str] = "" + end_time: Optional[str] = "" pageNo: int = 1 pageSize: int = 10 @property def start(self): + if not self.start_time: + return None y, m, d = self.start_time.split("-") start_time = date(int(y), int(m), int(d)) return datetime.combine(start_time, datetime.min.time()) @property def end(self): + if not self.end_time: + return None y, m, d = self.end_time.split("-") end_time = date(int(y), int(m), int(d)) return datetime.combine(end_time, datetime.max.time()) + + +class DisableOrEnableUser(BaseModel): + user_id: int + is_active: bool = False + + +class UserAdminAdd( + UsernameMixin, + RealNameMixin, + PasswordMixin, + StudentIdMixin, + PhoneMixin, + SexMixin, +): + """管理员添加""" + + nickname: Optional[str] = Field("", title=_("Nickname"), max_length=40) + role_id: UserRoleEnum diff --git a/src/services/users/services.py b/src/services/users/services.py index 43fe30c..ac1b2f4 100644 --- a/src/services/users/services.py +++ b/src/services/users/services.py @@ -26,6 +26,7 @@ class UserServices(AsyncInitializingComponent): student_id: Optional[str], phone: Optional[str], real_name: Optional[str], + sex: Optional[str], create_type: int = 0, ): return await self.repo.register_user( @@ -34,6 +35,7 @@ class UserServices(AsyncInitializingComponent): student_id, phone, real_name, + sex, create_type, ) @@ -42,11 +44,12 @@ class UserServices(AsyncInitializingComponent): async def get_user( self, + user_id: Optional[int] = None, username: Optional[str] = None, student_id: Optional[str] = None, phone: Optional[str] = None, ) -> Optional[UserModel]: - return await self.repo.get_user(username, student_id, phone) + return await self.repo.get_user(user_id, username, student_id, phone) async def create_login_history( self, user: "UserModel", ip: str, ua: str, forwarded_for: str @@ -108,8 +111,8 @@ class UserServices(AsyncInitializingComponent): phone: str, student_id: str, create_type: int, - start_time: datetime, - end_time: datetime, + start_time: Optional[datetime], + end_time: Optional[datetime], page_no: int, page_size: int, ) -> Tuple[Sequence[UserModel], int]: @@ -127,6 +130,22 @@ class UserServices(AsyncInitializingComponent): page_size, ) + async def disable_user(self, user_id: int) -> bool: + user = await self.get_user(user_id) + if not user or not user.is_active: + return False + user.is_active = False + await self.repo.update_user(user) + return True + + async def enable_user(self, user_id: int) -> bool: + user = await self.get_user(user_id) + if not user or user.is_active: + return False + user.is_active = True + await self.repo.update_user(user) + return True + class UserRoleServices(AsyncInitializingComponent): __order__ = 1 diff --git a/src/utils/__init__.py b/src/utils/__init__.py index bff9985..fad82de 100644 --- a/src/utils/__init__.py +++ b/src/utils/__init__.py @@ -1 +1 @@ -from ._path import PROJECT_ROOT, SERVICES_PATH, FRONTEND_PATH +from ._path import PROJECT_ROOT, SERVICES_PATH, FRONTEND_PATH, AVATAR_DATA_PATH