From 547a4eeae6d922a625dfc89f22380283238b1d76 Mon Sep 17 00:00:00 2001 From: zy7y <13271962515@163.com> Date: Tue, 4 Oct 2022 18:19:26 +0800 Subject: [PATCH] Refactor backend MVC (#2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * docs(requirements.txt):升级fastapi、uvicorn版本 * refactor(user):重构用户router、service * ref: role list api * doc: 1 * refactor(backend): mvc ref --- backend/controller/__init__.py | 1 - backend/controller/common.py | 36 ------- backend/controller/menu.py | 40 -------- backend/controller/role.py | 78 --------------- backend/controller/user.py | 89 ----------------- backend/core/dbhelper.py | 61 ++++++++++-- backend/core/log.py | 16 +-- backend/core/middleware.py | 18 +++- backend/core/security.py | 18 ++-- backend/core/service.py | 63 ++++++++++++ backend/dbhelper/menu.py | 66 ------------- backend/dbhelper/role.py | 79 --------------- backend/dbhelper/user.py | 121 ----------------------- backend/main.py | 4 +- backend/mini.db | Bin 4096 -> 0 bytes backend/mini.db-shm | Bin 32768 -> 0 bytes backend/mini.db-wal | Bin 622152 -> 0 bytes backend/requirements.txt | 1 + backend/{dbhelper => router}/__init__.py | 0 backend/router/auth.py | 19 ++++ backend/router/menu.py | 30 ++++++ backend/router/role.py | 46 +++++++++ backend/router/user.py | 53 ++++++++++ backend/schemas/__init__.py | 5 +- backend/service/__init__.py | 0 backend/service/auth.py | 26 +++++ backend/service/menu.py | 26 +++++ backend/service/role.py | 61 ++++++++++++ backend/service/user.py | 90 +++++++++++++++++ 29 files changed, 496 insertions(+), 551 deletions(-) delete mode 100644 backend/controller/__init__.py delete mode 100644 backend/controller/common.py delete mode 100644 backend/controller/menu.py delete mode 100644 backend/controller/role.py delete mode 100644 backend/controller/user.py create mode 100644 backend/core/service.py delete mode 100644 backend/dbhelper/menu.py delete mode 100644 backend/dbhelper/role.py delete mode 100644 backend/dbhelper/user.py delete mode 100644 backend/mini.db delete mode 100644 backend/mini.db-shm delete mode 100644 backend/mini.db-wal rename backend/{dbhelper => router}/__init__.py (100%) create mode 100644 backend/router/auth.py create mode 100644 backend/router/menu.py create mode 100644 backend/router/role.py create mode 100644 backend/router/user.py create mode 100644 backend/service/__init__.py create mode 100644 backend/service/auth.py create mode 100644 backend/service/menu.py create mode 100644 backend/service/role.py create mode 100644 backend/service/user.py diff --git a/backend/controller/__init__.py b/backend/controller/__init__.py deleted file mode 100644 index 8b13789..0000000 --- a/backend/controller/__init__.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/backend/controller/common.py b/backend/controller/common.py deleted file mode 100644 index f58f775..0000000 --- a/backend/controller/common.py +++ /dev/null @@ -1,36 +0,0 @@ -import asyncio - -from fastapi import APIRouter -from starlette.websockets import WebSocket -from websockets.exceptions import WebSocketException - -from core.security import generate_token, verify_password -from core.utils import get_system_info -from dbhelper.user import get_user -from schemas import LoginForm, LoginResult, Response - -router = APIRouter(tags=["公共"]) - - -@router.post("/login", summary="登录", response_model=Response[LoginResult]) -async def login(auth_data: LoginForm): - user_obj = await get_user({"username": auth_data.username, "status__not": 9}) - if user_obj: - if verify_password(auth_data.password, user_obj.password): - return Response( - data=LoginResult( - id=user_obj.id, token=generate_token(auth_data.username) - ) - ) - return Response(code=400, msg="账号或密码错误") - - -@router.websocket("/ws", name="系统信息") -async def websocket(ws: WebSocket): - await ws.accept() - try: - while True: - await asyncio.sleep(1) - await ws.send_json(get_system_info()) - except WebSocketException: - await ws.close() diff --git a/backend/controller/menu.py b/backend/controller/menu.py deleted file mode 100644 index 714cbe9..0000000 --- a/backend/controller/menu.py +++ /dev/null @@ -1,40 +0,0 @@ -# router service db router+service db -from fastapi import APIRouter - -from core.utils import list_to_tree -from dbhelper.menu import del_menu, get_menu, get_tree_menu, insert_menu, put_menu -from schemas import MenuIn, MenuRead, Response - -router = APIRouter(prefix="/menu", tags=["菜单管理"]) - - -@router.post("", summary="菜单新增", response_model=Response[MenuRead]) -async def menu_add(data: MenuIn): - return Response(data=await insert_menu(data)) - - -@router.get("", summary="菜单列表", response_model=Response) -async def menu_arr(): - menus = await get_tree_menu() - try: - data = list_to_tree(menus) - except KeyError: - return Response(code=400, msg="菜单根节点丢失") - return Response(data=data) - - -@router.delete("/{pk}", summary="菜单删除", response_model=Response) -async def menu_del(pk: int): - if await get_menu({"pid": pk}) is not None: - return Response(code=400, msg="请先删除子节点") - if await del_menu(pk) == 0: - return Response(code=400, msg="菜单不存在") - return Response() - - -@router.put("/{pk}", summary="菜单更新", response_model=Response) -async def menu_put(pk: int, data: MenuIn): - """更新菜单""" - if await put_menu(pk, data) == 0: - return Response(code=400, msg="菜单不存在") - return Response() diff --git a/backend/controller/role.py b/backend/controller/role.py deleted file mode 100644 index ec25f84..0000000 --- a/backend/controller/role.py +++ /dev/null @@ -1,78 +0,0 @@ -from fastapi import APIRouter, Query - -from core.utils import list_to_tree -from dbhelper.menu import get_menu -from dbhelper.role import ( - del_role, - get_role, - get_role_menus, - get_roles, - new_role, - put_role, -) -from schemas import ListAll, Response, RoleIn, RoleInfo, RoleQuery, RoleRead - -router = APIRouter(prefix="/role", tags=["角色管理"]) - - -@router.post("", summary="角色新增", response_model=Response[RoleInfo]) -async def role_add(data: RoleIn): - if result := await new_role(data): - return Response(data=result) - return Response(code=400, msg="菜单不存在") - - -@router.get("/{rid}/menu", summary="查询角色拥有权限", response_model=Response) -async def role_has_menu(rid: int): - """ - rid: 角色ID - """ - menus = await get_role_menus(rid) - - try: - result = list_to_tree(menus) - except KeyError: - return Response(code=400, msg="菜单缺少根节点.") - return Response(data=result) - - -@router.get("", summary="角色列表", response_model=Response[ListAll[list[RoleRead]]]) -async def role_arr( - offset: int = Query(default=1, description="偏移量-页码"), - limit: int = Query(default=10, description="数据量"), -): - skip = (offset - 1) * limit - roles, count = await get_roles(skip, limit) - return Response(data=ListAll(total=count, items=roles)) - - -@router.delete("/{pk}", summary="角色删除", response_model=Response) -async def role_del(pk: int): - if await del_role(pk) == 0: - return Response(code=400, msg="角色不存在") - return Response() - - -@router.put("/{pk}", summary="角色更新", response_model=Response) -async def role_put(pk: int, data: RoleIn): - """更新角色""" - if await get_role({"id": pk}) is None: - - return Response(code=400, msg="角色不存在") - # 如果不为ture -> 有菜单id不存在 - if not all([await get_menu({"id": mid}) for mid in data.menus]): - return Response(code=400, msg="菜单不存在") - - if await put_role(pk, data) == 0: - return Response(code=400, msg="角色不存在") - return Response() - - -@router.post("/query", summary="角色查询", response_model=Response[ListAll[list[RoleRead]]]) -async def role_query(query: RoleQuery): - """post条件查询角色表""" - size = query.limit - skip = (query.offset - 1) * size - del query.offset, query.limit - users, count = await get_roles(skip, size, query.dict()) - return Response(data=ListAll(total=count, items=users)) diff --git a/backend/controller/user.py b/backend/controller/user.py deleted file mode 100644 index f425f4e..0000000 --- a/backend/controller/user.py +++ /dev/null @@ -1,89 +0,0 @@ -from fastapi import APIRouter, Depends, Query - -from core.security import check_permissions, get_password_hash -from dbhelper.user import ( - del_user, - get_user, - get_user_info, - get_users, - insert_user, - put_user, - select_role, -) -from schemas import Response, UserAdd, UserInfo, UserPut, UserQuery, UserRead -from schemas.common import ListAll - -router = APIRouter(prefix="/user", tags=["用户管理"]) - - -@router.post("", summary="用户新增", response_model=Response[UserRead]) -async def user_add(data: UserAdd): - """新增用户并分配角色 一步到位""" - if await get_user({"username": data.username}) is not None: - return Response(code=400, msg="用户名已存在") - rids = data.roles - del data.roles - data.password = get_password_hash(data.password) - result = await insert_user(data, rids) - if isinstance(result, int): - return Response(code=400, msg=f"角色{result}不存在") - return Response(data=result) - - -@router.get("/{pk}", summary="用户信息", response_model=Response[UserInfo]) -async def user_info(pk: int): - """获取用户信息""" - obj = await get_user({"id": pk}) - if obj is None: - return Response(code=400, msg="用户不存在") - return Response(data=await get_user_info(obj)) - - -@router.get("", summary="用户列表", response_model=Response[ListAll[list[UserRead]]]) -async def user_arr( - offset: int = Query(default=1, description="偏移量-页码"), - limit: int = Query(default=10, description="数据量"), -): - """分页列表数据""" - skip = (offset - 1) * limit - users, count = await get_users(skip, limit) - return Response(data=ListAll(total=count, items=users)) - - -@router.post("/query", summary="用户查询", response_model=Response[ListAll[list[UserRead]]]) -async def user_list(query: UserQuery): - """post查询用户列表""" - size = query.limit - skip = (query.offset - 1) * size - del query.offset, query.limit - users, count = await get_users(skip, size, query.dict()) - return Response(data=ListAll(total=count, items=users)) - - -@router.delete("/{pk}", summary="用户删除", response_model=Response) -async def user_del(pk: int): - """删除用户""" - if await del_user(pk) == 0: - return Response(code=400, msg="用户不存在") - return Response() - - -@router.put("/{pk}", summary="用户更新", response_model=Response) -async def user_put(pk: int, data: UserPut): - """更新用户""" - if await get_user({"id": pk}) is None: - return Response(code=400, msg="用户不存在") - - result = await put_user(pk, data) - if isinstance(result, int): - return Response(code=400, msg=f"角色不存在{result}") - return Response() - - -@router.put("/role/{rid}", summary="用户切换角色", response_model=Response) -async def user_select_role(rid: int, user=Depends(check_permissions)): - """用户切换角色""" - res = await select_role(user.id, rid) - if res == 0: - return Response(code=400, msg=f"角色不存在{res}") - return Response() diff --git a/backend/core/dbhelper.py b/backend/core/dbhelper.py index cd768f1..dfcb26f 100644 --- a/backend/core/dbhelper.py +++ b/backend/core/dbhelper.py @@ -1,14 +1,14 @@ """数据库通用查询方法""" -from typing import Optional +from tortoise import connections -from tortoise import connections, models +from models import MenuModel, RoleMenuModel, RoleModel, UserModel, UserRoleModel class DbHelper: - def __init__(self, model: models.Model): + def __init__(self, model): """ 初始化 - :param model: 模型类 + :param model: 模型类 orm model """ self.model = model @@ -20,7 +20,7 @@ class DbHelper: """ return self.model.filter(**kwargs) - async def select(self, kwargs: dict = None) -> Optional[models.Model]: + async def select(self, kwargs: dict = None): """ 查询符合条件的第一个对象, 查无结果时返回None :param kwargs: kwargs: {"name:"7y", "id": 1} @@ -58,7 +58,7 @@ class DbHelper: return await self.model.create(**data) async def selects( - self, offset: int, limit: int, kwargs: dict = None, order_by: str = None + self, offset: int, limit: int, kwargs: dict = None, order_by: str = "-created" ) -> dict: """ 条件分页查询数据列表, 支持排序 @@ -81,13 +81,13 @@ class DbHelper: items=await objs.offset(offset).limit(limit), total=await objs.count() ) - async def inserts(self, objs: list[models.Model]): + async def inserts(self, objs: list): """ 批量新增数据 :param objs: 模型列表 :return: """ - await self.model.bulk_create(objs) + await self.model.bulk_create([self.model(**obj) for obj in objs]) @classmethod async def raw_sql(cls, sql: str, args: list = None): @@ -101,3 +101,48 @@ class DbHelper: if args is None: args = [] return await db.execute_query_dict(sql, args) + + +UserDao = DbHelper(UserModel) +RoleDao = DbHelper(RoleModel) +UserRoleDao = DbHelper(UserRoleModel) +MenuDao = DbHelper(MenuModel) +RoleMenuDao = DbHelper(RoleMenuModel) + + +async def has_roles(uid): + """ + 获取用户角色信息,激活的角色升序 + :param uid: 用户id + :return: + """ + sql = """select r.id, r.name, ur.status from sys_role as r , sys_user_role as ur where r.id = ur.rid and + ur.uid = (?) and r.status = 1 and ur.status !=9 order by ur.status desc + """ + return await UserRoleDao.raw_sql(sql, [uid]) + + +async def has_user(username): + """ + 通过用户名检索数据是否存在 + :param username: + :return: + """ + return await UserDao.select({"username": username, "status__not": 9}) + + +async def has_permissions(rid, is_menu=False): + """ + 根据角色ID查到当前拥有的接口权限 + :param rid: 角色ID + :param is_menu: 是否是菜单,默认不是 -》接口 + :return: + """ + filters = "m.api, m.method" + if is_menu: + filters = "m.id, m.name, m.icon, m.path, m.type, m.component, m.pid, m.identifier, m.api, m.method" + sql = f""" + select {filters} + FROM sys_menu as m, sys_role_menu as srm WHERE m.id = srm.mid + AND srm.rid = (?) and m.status != 9 order by m.id asc""" + return await RoleMenuDao.raw_sql(sql, [rid]) diff --git a/backend/core/log.py b/backend/core/log.py index e46aa57..fcc3e27 100644 --- a/backend/core/log.py +++ b/backend/core/log.py @@ -1,15 +1 @@ -import logging -import sys - -fmt = logging.Formatter( - fmt="%(asctime)s - %(name)s:%(lineno)d - %(levelname)s - %(message)s", - datefmt="%Y-%m-%d %H:%M:%S", -) -sh = logging.StreamHandler(sys.stdout) -sh.setLevel(logging.DEBUG) -sh.setFormatter(fmt) - -# will print debug sql -logger = logging.getLogger("mini-rbac") -logger.setLevel(logging.DEBUG) -logger.addHandler(sh) +from loguru import logger diff --git a/backend/core/middleware.py b/backend/core/middleware.py index 446232a..9687341 100644 --- a/backend/core/middleware.py +++ b/backend/core/middleware.py @@ -1,12 +1,28 @@ from fastapi.middleware import Middleware from fastapi.middleware.cors import CORSMiddleware +from starlette.middleware.base import BaseHTTPMiddleware + +from core.log import logger + + +class CustomRequestLogMiddleware(BaseHTTPMiddleware): + async def dispatch(self, request, call_next): + logger.info( + f"Client: {request.client} Method: {request.method} " + f"Path: {request.url} Headers: {request.headers}" + ) + # python-multipart == await request.form() + response = await call_next(request) + return response + middlewares = [ + Middleware(CustomRequestLogMiddleware), Middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], - ) + ), ] diff --git a/backend/core/security.py b/backend/core/security.py index f6f0d1b..ef748a3 100644 --- a/backend/core/security.py +++ b/backend/core/security.py @@ -6,10 +6,8 @@ from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer from jose import JWTError, jwt from passlib.context import CryptContext +from core.dbhelper import has_permissions, has_roles, has_user from core.exceptions import PermissionsError, TokenAuthFailure -from dbhelper.menu import get_apis -from dbhelper.user import get_user, get_user_info -from models import UserModel # JWT SECRET_KEY = "lLNiBWPGiEmCLLR9kRGidgLY7Ac1rpSWwfGzTJpTmCU" @@ -59,20 +57,20 @@ async def check_token(security: HTTPAuthorizationCredentials = Depends(bearer)): try: payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) username: str = payload.get("sub") - return await get_user({"username": username}) + return await has_user(username) except JWTError: raise TokenAuthFailure(403, "认证失败") -async def check_permissions(request: Request, user: UserModel = Depends(check_token)): +async def check_permissions(request: Request, user=Depends(check_token)): """检查接口权限""" # 查询当前激活角色 - result = await get_user_info(user) - active_rid = result["roles"][0]["id"] + roles = await has_roles(user.id) + active_rid = roles[0]["id"] # 白名单 登录用户信息, 登录用户菜单信息 whitelist = [(f"/user/{user.id}", "GET"), (f"/role/{active_rid}/menu", "GET")] + [ - (f"/user/role/{rid['id']}", "PUT") for rid in result["roles"] + (f"/user/role/{rid['id']}", "PUT") for rid in roles ] if (request.url.path, request.method) in whitelist: @@ -82,11 +80,11 @@ async def check_permissions(request: Request, user: UserModel = Depends(check_to for k, v in request.path_params.items(): api = api.replace(v, "{%s}" % k) - # 2. 登录之后查一次 后面去结果查 todo 更新权限时需要更新 , 最好结果放redis + # todo 结果放redis cache_key = f"{user.username}_{active_rid}" # 缓存到fastapi 应用实例中 if not hasattr(request.app.state, cache_key): - setattr(request.app.state, cache_key, await get_apis(active_rid)) + setattr(request.app.state, cache_key, await has_permissions(active_rid)) if {"api": api, "method": request.method} not in getattr( request.app.state, cache_key ): diff --git a/backend/core/service.py b/backend/core/service.py new file mode 100644 index 0000000..ef02b88 --- /dev/null +++ b/backend/core/service.py @@ -0,0 +1,63 @@ +from core.dbhelper import DbHelper + + +class Service: + + filter_del = {"status__not": 9} + + def __init__(self, dao: DbHelper): + self.dao = dao + + async def get_items(self, offset, limit): + """ + 分页获取数据, 过滤掉删除 + :param offset: 起始值 + :param limit: 偏移量 + :return: + """ + skip = (offset - 1) * limit + return dict(data=await self.dao.selects(skip, limit, Service.filter_del)) + + async def query_items(self, query): + """ + 根据条件查询结果 + :param query: + :return: + """ + size = query.limit + skip = (query.offset - 1) * size + del query.offset, query.limit + filters = {f"{k}__contains": v for k, v in query.dict().items()} + filters.update(Service.filter_del) + return dict(data=await self.dao.selects(skip, size, filters)) + + async def delete_item(self, pk): + """ + 逻辑删除数据 + :param pk:主键 + :return: + """ + filters = {"id": pk} + filters.update(Service.filter_del) + if await self.dao.update(filters, {"status": 9}) == 0: + return dict(code=400, msg="数据不存在") + return dict() + + async def update_item(self, pk, data): + """ + 更新数据,不通用,可重写 + :param pk: 主键 + :param data: pydantic model + :return: + """ + if await self.dao.update({"id": pk}, data.dict()) == 0: + return dict(code=400, msg="数据不存在") + return dict() + + async def create_item(self, data): + """ + 创建数据,不通用可重写 + :param data: pydantic model + :return: + """ + return await self.dao.insert(data.dict()) diff --git a/backend/dbhelper/menu.py b/backend/dbhelper/menu.py deleted file mode 100644 index aae779d..0000000 --- a/backend/dbhelper/menu.py +++ /dev/null @@ -1,66 +0,0 @@ -from tortoise import connections - -from models import MenuModel -from schemas.menu import MenuIn - - -async def insert_menu(menu: MenuIn): - """新增菜单""" - return await MenuModel.create(**menu.dict()) - - -async def get_menus(skip: int, limit: int, kwargs: dict = None): - """ - 分页获取用户并且支持字段模糊查询 - Args: - skip: 偏移量 - limit: 数量 - kwargs: 查询字典 - - Returns: - - """ - if kwargs is not None: - kwargs = {f"{k}__contains": v for k, v in kwargs.items()} - else: - kwargs = {} - result = MenuModel.filter(status__not=9, **kwargs).all().order_by("-created") - return await result.offset(skip).limit(limit) - - -async def get_tree_menu(): - return await MenuModel.filter(status__not=9).all().values() - - -async def get_menu(kwargs): - """ - 根据条件查询到第一条符合结果的数据 - Args: - kwargs: - - Returns: - - """ - return await MenuModel.filter(**kwargs).first() - - -async def del_menu(mid: int): - """删除菜单""" - return await MenuModel.filter(id=mid).update(status=9) - - -async def get_apis(pk: int): - """返回当前角色拥有的接口权限列表""" - db = connections.get("default") - return await db.execute_query_dict( - """ - select m.api, m.method - FROM sys_menu as m, sys_role_menu as srm WHERE m.id = srm.mid - AND srm.rid = (?) and m.status != 9""", - [pk], - ) - - -async def put_menu(pk: int, data): - """更新菜单""" - return await MenuModel.filter(id=pk).update(**data.dict()) diff --git a/backend/dbhelper/role.py b/backend/dbhelper/role.py deleted file mode 100644 index 1124346..0000000 --- a/backend/dbhelper/role.py +++ /dev/null @@ -1,79 +0,0 @@ -from tortoise import connections - -from models import MenuModel, RoleMenuModel, RoleModel -from schemas.role import RoleIn - - -async def get_role_menus(rid: int): - """ - 根据角色id 获取菜单 - """ - db = connections.get("default") - # asc 降序 - return await db.execute_query_dict( - """ - select m.id, m.name, m.icon, m.path, m.type, m.component, m.pid, m.identifier, m.api, m.method - FROM sys_menu as m, sys_role_menu WHERE m.id = sys_role_menu.mid - AND sys_role_menu.rid = (?) AND sys_role_menu.`status` = 1 order by m.id asc""", - [rid], - ) - - -async def new_role(role: RoleIn): - """新增角色""" - # 校验菜单是否存在 - if not all([await MenuModel.filter(id=mid).first() for mid in role.menus]): - return False - - obj = await RoleModel.create(name=role.name, remark=role.remark) - # 写入菜单 - await RoleMenuModel.bulk_create( - [RoleMenuModel(rid=obj.id, mid=mid) for mid in role.menus] - ) - return obj - - -async def get_roles(skip: int, limit: int, kwargs: dict = None): - """ - 分页获取用户并且支持字段模糊查询 - Args: - skip: 偏移量 - limit: 数量 - kwargs: 查询字典 - - Returns: - - """ - if kwargs is not None: - kwargs = {f"{k}__contains": v for k, v in kwargs.items()} - else: - kwargs = {} - result = RoleModel.filter(**kwargs).all().order_by("-created") - return await result.offset(skip).limit(limit), await result.count() - - -async def get_role(kwargs): - """ - 根据条件查询到第一条符合结果的数据 - Args: - kwargs: - - Returns: - - """ - return await RoleModel.filter(**kwargs).first() - - -async def del_role(rid: int): - """删除用户""" - return await RoleModel.filter(id=rid).update(status=9) - - -async def put_role(pk, data): - """更新角色 菜单""" - await RoleModel.filter(id=pk).update(name=data.name, remark=data.remark) - await RoleMenuModel.filter(rid=pk).update(status=9) - - await RoleMenuModel.bulk_create( - [RoleMenuModel(rid=pk, mid=mid) for mid in data.menus] - ) diff --git a/backend/dbhelper/user.py b/backend/dbhelper/user.py deleted file mode 100644 index e979ec8..0000000 --- a/backend/dbhelper/user.py +++ /dev/null @@ -1,121 +0,0 @@ -from fastapi.encoders import jsonable_encoder -from tortoise import connections - -from dbhelper.role import get_role -from models import UserModel, UserRoleModel -from schemas import UserPut - - -async def get_user(kwargs): - """ - 根据条件查询到第一条符合结果的数据 - Args: - kwargs: - - Returns: - - """ - return await UserModel.filter(**kwargs).first() - - -async def get_user_info(user: UserModel): - """ - 根据id查用户角色列表 按激活角色倒序显示 - """ - db = connections.get("default") - # 查角色表 用户角色表中 角色状态 = 1, 关联表中 状态 != 9 为有效角色 - sql_result = await db.execute_query_dict( - """ - select r.id, r.name, ur.status from sys_role as r , sys_user_role as ur where r.id = ur.rid and - ur.uid = (?) and r.status = 1 and ur.status !=9 order by ur.status desc - """, - [user.id], - ) - return { - **jsonable_encoder(user), - "roles": sql_result, - } - - -async def get_users(skip: int, limit: int, kwargs: dict = None): - """ - 分页获取用户并且支持字段模糊查询 - Args: - skip: 偏移量 - limit: 数量 - kwargs: 查询字典 - - Returns: - - """ - if kwargs is not None: - kwargs = {f"{k}__contains": v for k, v in kwargs.items()} - else: - kwargs = {} - result = UserModel.filter(**kwargs).all().order_by("-created") - return await result.offset(skip).limit(limit), await result.count() - - -async def insert_user(user, roles): - """新增用户,选择角色""" - for role in roles: - if await get_role({"id": role.rid, "status__not": 9}) is None: - return role.rid - - # 创建用户 - obj = await UserModel.create(**user.dict()) - # 已有角色 关联 角色id 和是否选中状态 - await UserRoleModel.bulk_create( - [UserRoleModel(rid=role.rid, uid=obj.id, status=role.status) for role in roles] - ) - return obj - - -async def del_user(uid: int): - """删除用户""" - return await UserModel.filter(id=uid).update(status=9) - - -async def put_user(uid: int, data: UserPut): - """更新用户""" - from core.security import get_password_hash - - rids = data.roles - del data.roles - for role in rids: - if await get_role({"id": role.rid, "status__not": 9}) is None: - return role.rid - # 更新用户 - if data.password != "加密之后的密码": - data.password = get_password_hash(data.password) - else: - del data.password - await UserModel.filter(id=uid).update(**data.dict()) - - # todo 1. 先前有的角色,这次更新成没有 2. 先前没有的角色 这次更新成有, 3. 只更新了状态 - - db = connections.get("default") - # 1. 先把用户有的角色做删除 - has_roles = await db.execute_query_dict( - """ - select r.id from sys_role as r , sys_user_role as ur where r.id = ur.rid and - ur.uid = (?) and r.status = 1 and ur.status !=9 - """, - [uid], - ) - - # 2. 将先有的数据标记 删除 - [await UserRoleModel.filter(rid=role["id"]).update(status=9) for role in has_roles] - - # 2. 新增次此更新的数据 - await UserRoleModel.bulk_create( - [UserRoleModel(uid=uid, **role.dict()) for role in rids] - ) - - -async def select_role(uid: int, rid: int): - """用户切换角色""" - # 1.将用户id 未删除角色状态置为正常 1 ( 除切换角色id ) - await UserRoleModel.filter(uid=uid, rid__not=rid, status__not=9).update(status=1) - # 2.将用户id 角色id 和当前角色匹配的数据置为选中 - return await UserRoleModel.filter(uid=uid, rid=rid, status__not=9).update(status=5) diff --git a/backend/main.py b/backend/main.py index 5d1cb97..31e93dc 100644 --- a/backend/main.py +++ b/backend/main.py @@ -13,9 +13,7 @@ app = FastAPI( exception_handlers=exception_handlers, ) -load_routers( - app, "controller", no_depends="common", depends=[Depends(check_permissions)] -) +load_routers(app, "router", no_depends="auth", depends=[Depends(check_permissions)]) if __name__ == "__main__": import uvicorn diff --git a/backend/mini.db b/backend/mini.db deleted file mode 100644 index e2a2455a104f1cf6a5d6b143f3ac8831aaaf3ab5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4096 zcmWFz^vNtqRY=P(%1ta$FlG>7U}9o$P*7lCU|@t|AVoG{WYF`k83r diff --git a/backend/mini.db-shm b/backend/mini.db-shm deleted file mode 100644 index d7e2633ffe1d14b88aa8bc247eacbaad3d94d24f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32768 zcmeI)H*XX{6u|NSVH?}n29r(B83QKgoJajiIpbA<8u{WzxUHJ*!>dnurR0tzUgfC36Apnw7jD4>7>3Mim}0tzUgfC36A zpnw7jD4>7>3Mim}0tzUgfC36Apnw7jD4>7>3Mim}0tzUgfC36Apnw7jD4;;r3M9yn zzKl>5eNCX0PITr*)^=)R3S?TK9bM@`FZwco!3<+0V;DyjlbOa$<}jZ{ETx)NtYt%{ zH@BVwttya5H+s^Wehg#?!x_a`#xs#AOlKBzS-@hJv4YjCVl;bpV zmP=gcCii*Bi&j00n=8;VfdaZyK_B`vh@p&NG?h$X5>uJMZ050$B`jwpYgkWB%SyXi zfh-p&WDEN_$E_^y)Akg|yg)HqIly^t^CUM>xSLPIG~aT;?j*c*0Yj@tl{u<_&KN z-)r|$Aa?}1u$x1i7>3Mim}0tzUg WfC36Apnw7jD4>7>3MimJ8iBv$;#*q) diff --git a/backend/mini.db-wal b/backend/mini.db-wal deleted file mode 100644 index 7f21fe6eccc4c05a899c05678f447780013d6711..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 622152 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 Ig!2ggKTHEVDF6Tf diff --git a/backend/requirements.txt b/backend/requirements.txt index 8e3034a..6c09b2b 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -7,3 +7,4 @@ requests==2.28.1 tortoise-orm==0.19.2 uvicorn==0.18.3 websockets==10.3 +loguru==0.6.0 diff --git a/backend/dbhelper/__init__.py b/backend/router/__init__.py similarity index 100% rename from backend/dbhelper/__init__.py rename to backend/router/__init__.py diff --git a/backend/router/auth.py b/backend/router/auth.py new file mode 100644 index 0000000..694e3c0 --- /dev/null +++ b/backend/router/auth.py @@ -0,0 +1,19 @@ +from fastapi import APIRouter, WebSocket + +from schemas import common as BaseSchema +from service import auth as AuthService + +router = APIRouter(tags=["公共"]) + + +LoginResult = BaseSchema.Response[BaseSchema.LoginResult] + + +@router.post("/login", summary="登录", response_model=LoginResult) +async def login(data: BaseSchema.LoginForm): + return await AuthService.user_login(data) + + +@router.websocket("/ws", name="系统信息") +async def get_system_info(ws: WebSocket): + await AuthService.system_info(ws) diff --git a/backend/router/menu.py b/backend/router/menu.py new file mode 100644 index 0000000..e2381f3 --- /dev/null +++ b/backend/router/menu.py @@ -0,0 +1,30 @@ +from fastapi import APIRouter + +from schemas import common as BaseSchema +from schemas import menu as MenuSchema +from service.menu import service as MenuService + +router = APIRouter(prefix="/menu", tags=["菜单管理"]) + +Response = BaseSchema.Response + + +@router.post("", summary="菜单新增", response_model=Response[MenuSchema.MenuRead]) +async def menu_add(data: MenuSchema.MenuIn): + return await MenuService.create_item(data) + + +@router.get("", summary="菜单列表", response_model=Response) +async def menu_arr(): + return await MenuService.get_items() + + +@router.delete("/{pk}", summary="菜单删除", response_model=Response) +async def menu_del(pk: int): + return await MenuService.delete_item(pk) + + +@router.put("/{pk}", summary="菜单更新", response_model=Response) +async def menu_put(pk: int, data: MenuSchema.MenuIn): + """更新菜单""" + return await MenuService.update_item(pk, data) diff --git a/backend/router/role.py b/backend/router/role.py new file mode 100644 index 0000000..ff643fe --- /dev/null +++ b/backend/router/role.py @@ -0,0 +1,46 @@ +from fastapi import APIRouter, Query + +from schemas import common as BaseSchema +from schemas import role as RoleSchema +from service.role import service as RoleService + +router = APIRouter(prefix="/role", tags=["角色管理"]) + +Response = BaseSchema.Response +ListAll = BaseSchema.ListAll + +role_list_schema = ListAll[list[RoleSchema.RoleRead]] + + +@router.get("", summary="角色列表", response_model=Response[role_list_schema]) +async def role_list( + offset: int = Query(default=1, description="偏移量-页码"), + limit: int = Query(default=10, description="数据量"), +): + return await RoleService.get_items(offset, limit) + + +@router.post("/query", summary="角色查询", response_model=Response[role_list_schema]) +async def role_query(query: RoleSchema.RoleQuery): + return await RoleService.query_items(query) + + +@router.post("", summary="角色新增", response_model=Response[RoleSchema.RoleInfo]) +async def role_create(data: RoleSchema.RoleIn): + return await RoleService.create_item(data) + + +@router.get("/{rid}/menu", summary="查询角色拥有权限", response_model=Response) +async def role_has_menu(rid: int): + return await RoleService.has_tree_menus(rid) + + +@router.delete("/{pk}", summary="角色删除", response_model=Response) +async def role_del(pk: int): + return await RoleService.delete_item(pk) + + +@router.put("/{pk}", summary="角色更新", response_model=Response) +async def role_put(pk: int, data: RoleSchema.RoleIn): + """更新角色""" + return await RoleService.update_item(pk, data) diff --git a/backend/router/user.py b/backend/router/user.py new file mode 100644 index 0000000..896c28d --- /dev/null +++ b/backend/router/user.py @@ -0,0 +1,53 @@ +from fastapi import APIRouter, Depends, Query + +from core.security import check_permissions +from schemas import common as BaseSchema +from schemas import user as UserSchema +from service.user import service as UserService + +router = APIRouter(prefix="/user", tags=["用户管理"]) + +Response = BaseSchema.Response +ListAll = BaseSchema.ListAll + +user_list_schema = ListAll[list[UserSchema.UserRead]] + + +@router.get("", summary="用户列表", response_model=Response[user_list_schema]) +async def user_list( + offset: int = Query(default=1, description="偏移量-页码"), + limit: int = Query(default=10, description="数据量"), +): + return await UserService.get_items(offset, limit) + + +@router.post("/query", summary="用户查询", response_model=Response[user_list_schema]) +async def user_query(query: UserSchema.UserQuery): + return await UserService.query_items(query) + + +@router.post("", summary="用户新增", response_model=Response[UserSchema.UserRead]) +async def user_create(data: UserSchema.UserAdd): + return await UserService.create_item(data) + + +@router.delete("/{pk}", summary="用户删除", response_model=Response) +async def user_delete(pk: int): + return await UserService.delete_item(pk) + + +@router.get("/{pk}", summary="用户信息", response_model=Response[UserSchema.UserInfo]) +async def user_info(pk: int): + return await UserService.get_item(pk) + + +@router.put("/{pk}", summary="用户更新", response_model=Response) +async def user_update(pk: int, data: UserSchema.UserPut): + return await UserService.update_item(pk, data) + + +@router.put("/role/{rid}", summary="用户切换角色", response_model=Response) +async def user_change_role( + rid: int, user: UserSchema.UserRead = Depends(check_permissions) +): + return await UserService.change_current_role(user.id, rid) diff --git a/backend/schemas/__init__.py b/backend/schemas/__init__.py index e20bfde..8b13789 100644 --- a/backend/schemas/__init__.py +++ b/backend/schemas/__init__.py @@ -1,4 +1 @@ -from schemas.common import * -from schemas.menu import * -from schemas.role import * -from schemas.user import * + diff --git a/backend/service/__init__.py b/backend/service/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/service/auth.py b/backend/service/auth.py new file mode 100644 index 0000000..bbc52c3 --- /dev/null +++ b/backend/service/auth.py @@ -0,0 +1,26 @@ +import asyncio + +from websockets.exceptions import WebSocketException + +from core.dbhelper import has_user +from core.security import generate_token, verify_password +from core.utils import get_system_info + + +async def user_login(data): + """用户登录""" + user_obj = await has_user(data.username) + if user_obj: + if verify_password(data.password, user_obj.password): + return dict(data=dict(id=user_obj.id, token=generate_token(data.username))) + return dict(code=400, msg="账号或密码错误") + + +async def system_info(ws): + await ws.accept() + try: + while True: + await asyncio.sleep(1) + await ws.send_json(get_system_info()) + except WebSocketException: + await ws.close() diff --git a/backend/service/menu.py b/backend/service/menu.py new file mode 100644 index 0000000..b00fd05 --- /dev/null +++ b/backend/service/menu.py @@ -0,0 +1,26 @@ +from core.dbhelper import MenuDao +from core.service import Service +from core.utils import list_to_tree + + +class MenuService(Service): + def __init__(self): + super(MenuService, self).__init__(MenuDao) + + async def get_items(self): + sql = "select * from sys_menu where status != 9 ;" + menus = await self.dao.raw_sql(sql) + try: + return dict(data=list_to_tree(menus)) + except KeyError: + return dict(code=400, msg="菜单根节点丢失") + + async def delete_item(self, pk): + if await MenuDao.select({"pid": pk, "status__not": 9}) is not None: + return dict(code=400, msg="请先删除子节点") + if await MenuDao.delete(pk) == 0: + return dict(code=400, msg="菜单不存在") + return dict() + + +service = MenuService() diff --git a/backend/service/role.py b/backend/service/role.py new file mode 100644 index 0000000..c0ca35d --- /dev/null +++ b/backend/service/role.py @@ -0,0 +1,61 @@ +from core.dbhelper import MenuDao, RoleDao, RoleMenuDao, has_permissions +from core.service import Service +from core.utils import list_to_tree + + +class RoleService(Service): + def __init__(self): + super(RoleService, self).__init__(RoleDao) + + async def create_item(self, role): + """ + 创建角色 + :param role: pydantic model + :return: + """ + if not all( + [await MenuDao.select({"id": mid, "status__not": 9}) for mid in role.menus] + ): + return dict(code=400, msg="菜单不存在") + + obj = await RoleDao.insert(dict(name=role.name, remark=role.remark)) + # 写入菜单 + await RoleMenuDao.inserts([dict(rid=obj.id, mid=mid) for mid in role.menus]) + return dict(data=obj) + + async def update_item(self, pk, data): + """ + 更新角色 + :param pk: + :param data: + :return: + """ + if await RoleDao.select({"id": pk}) is None: + return dict(code=400, msg="角色不存在") + # 如果不为ture -> 有菜单id不存在 + if not all([await MenuDao.select({"id": mid}) for mid in data.menus]): + return dict(code=400, msg="菜单不存在") + + await RoleDao.update(dict(id=pk), dict(name=data.name, remark=data.remark)) + await RoleMenuDao.update(dict(rid=pk), dict(status=9)) + + await RoleMenuDao.inserts([dict(rid=pk, mid=mid) for mid in data.menus]) + + return dict() + + @staticmethod + async def has_tree_menus(pk): + """ + 查询角色拥有菜单 + :param pk: + :return: + """ + menus = await has_permissions(pk, is_menu=True) + + try: + return dict(data=list_to_tree(menus)) + except KeyError: + return dict(code=400, msg="菜单缺少根节点.") + + +service = RoleService() diff --git a/backend/service/user.py b/backend/service/user.py new file mode 100644 index 0000000..17cf53e --- /dev/null +++ b/backend/service/user.py @@ -0,0 +1,90 @@ +from fastapi.encoders import jsonable_encoder + +from core.dbhelper import RoleDao, UserDao, UserRoleDao, has_roles +from core.security import get_password_hash +from core.service import Service + + +class UserService(Service): + def __init__(self): + super(UserService, self).__init__(UserDao) + + async def create_item(self, data): + """创建用户""" + # 检查用户是否存在 + if await self.dao.select({"username": data.username}) is not None: + return dict(code=400, msg="用户名已存在") + rids = data.roles + del data.roles + data.password = get_password_hash(data.password) + # 检查选中的角色是否存在 + for role in rids: + if await RoleDao.select(dict(id=role.rid, status__not=9)) is None: + return dict(code=400, msg=f"角色{role.rid}不存在") + + # 创建用户- 用户表写入数据 + user_obj = await UserDao.insert(data.dict()) + # 关联表写入数据 + await UserRoleDao.inserts( + [dict(rid=role.rid, uid=user_obj.id, status=role.status) for role in rids] + ) + return dict(data=user_obj) + + async def get_item(self, pk): + """获取用户信息""" + user_obj = await self.dao.select({"id": pk}) + if user_obj is None: + return dict(code=400, msg="用户不存在") + roles = await has_roles(user_obj.id) + return dict(data=dict(**jsonable_encoder(user_obj), roles=roles)) + + async def update_item(self, pk, data): + """用户编辑修改""" + if await self.dao.select({"id": pk}) is None: + return dict(code=400, msg="用户不存在") + + rids = data.roles + del data.roles + for role in rids: + if await RoleDao.select({"id": role.rid, "status__not": 9}) is None: + return role.rid + # 更新用户 + if data.password != "加密之后的密码": + data.password = get_password_hash(data.password) + else: + del data.password + await UserDao.update(dict(id=pk), data.dict()) + + # todo 1. 先前有的角色,这次更新成没有 2. 先前没有的角色 这次更新成有, 3. 只更新了状态 + + roles = await has_roles(pk) + + # 2. 将先有的数据标记 删除 + [ + await UserRoleDao.update(dict(rid=role["id"], uid=pk), dict(status=9)) + for role in roles + ] + + # 2. 新增次此更新的数据 + await UserRoleDao.inserts( + [dict(role.dict(), uid=pk, status=role.status) for role in rids] + ) + return dict() + + @staticmethod + async def change_current_role(uid, rid): + """用户切换角色""" + # 1.将用户id 未删除角色状态置为正常 1 ( 除切换角色id ) + await UserRoleDao.update( + dict(uid=uid, rid__not=rid, status__not=9), dict(status=1) + ) + # 2.将用户id 角色id 和当前角色匹配的数据置为选中 + res = await UserRoleDao.update( + dict(uid=uid, rid=rid, status__not=9), dict(status=5) + ) + if res == 0: + return dict(code=400, msg=f"角色不存在{res}") + return dict() + + +service = UserService()