diff --git a/backend/.gitignore b/backend/.gitignore index f05c89f..adfd4f8 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -139,4 +139,6 @@ dmypy.json cython_debug/ .idea .pdm.toml -__pypackages__/ \ No newline at end of file +__pypackages__/ +.pytest_cache +.db-* \ No newline at end of file diff --git a/backend/controller/common.py b/backend/controller/common.py index 0831659..b40b2ac 100644 --- a/backend/controller/common.py +++ b/backend/controller/common.py @@ -1,16 +1,16 @@ -from core.resp import Response -from core.router import Router +from core import Response from core.security import generate_token, verify_password from dbhelper.user import get_user from schemas.common import LoginForm, LoginResult -common = Router(tags=["公共接口"]) - -@common.post("/login", summary="登录") async def login(auth_data: LoginForm) -> Response[LoginResult]: user_obj = await get_user({"username": auth_data.username}) 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( + data=LoginResult( + id=user_obj.id, token=generate_token(auth_data.username) + ) + ) return Response(msg="账号或密码错误") diff --git a/backend/controller/menu.py b/backend/controller/menu.py index 6de874b..16ba0cb 100644 --- a/backend/controller/menu.py +++ b/backend/controller/menu.py @@ -1,31 +1,7 @@ -from core.resp import Response -from core.router import Router -from schemas.common import QueryData +from core import Response +from dbhelper.menu import insert_menu from schemas.menu import MenuIn, MenuRead -menu = Router(prefix="/menu", tags=["菜单管理"]) - -@menu.post("", summary="菜单添加") async def menu_add(data: MenuIn) -> Response[MenuRead]: - pass - - -@menu.get("/{pk}", summary="菜单详情") -async def menu_info(pk: int) -> Response[MenuRead]: - pass - - -@menu.delete("/{pk}", summary="删除菜单") -async def menu_del(pk: int) -> Response: - pass - - -@menu.put("/{pk}", summary="编辑菜单") -async def menu_put(pk: int, data: MenuIn) -> Response[MenuRead]: - pass - - -@menu.post("/list", summary="查询菜单列表") -async def menu_list(data: QueryData) -> Response[list[MenuRead]]: - pass + return Response(data=await insert_menu(data)) diff --git a/backend/controller/role.py b/backend/controller/role.py index 64be397..d918d9f 100644 --- a/backend/controller/role.py +++ b/backend/controller/role.py @@ -1,53 +1,20 @@ import json -from core.resp import Response -from core.router import Router +from core import Response from core.utils import list_to_tree -from dbhelper.role import get_role_menus -from schemas.common import QueryData -from schemas.role import RoleAdd, RoleInfo - -role = Router(prefix="/role", tags=["角色管理"]) +from dbhelper.role import get_role_menus, new_role +from schemas.role import RoleIn, RoleInfo -@role.post("", summary="角色添加") -async def role_add(data: RoleAdd) -> Response[RoleInfo]: - pass +async def role_add(data: RoleIn) -> Response[RoleInfo]: + return Response(data=await new_role(data)) -@role.get("/{pk}", summary="角色详情") -async def role_info(pk: int) -> Response[RoleInfo]: - pass - - -@role.delete("/{pk}", summary="删除角色") -async def role_del(pk: int) -> Response: - pass - - -@role.put("/{pk}", summary="编辑角色") -async def role_put(pk: int, data: RoleAdd) -> Response[RoleInfo]: - pass - - -@role.post("/list", summary="查询角色列表") -async def role_list(data: QueryData) -> Response[list[RoleInfo]]: - pass - - -@role.get("/{pk}/menu", summary="查询角色菜单权限") -async def role_menu(pk: int): - menus = await get_role_menus(pk) +async def role_has_menu(rid: int): + """ + rid: 角色ID + """ + menus = await get_role_menus(rid) for obj in menus: obj["meta"] = json.loads(obj["meta"]) if obj["meta"] is not None else None return Response(data=list_to_tree(menus)) - - -@role.get("/{pk}/menuIds", summary="查询角色菜单ids") -async def role_menus_id(): - pass - - -@role.get("/assign", summary="分配权限") -async def role_assign(): - pass diff --git a/backend/controller/user.py b/backend/controller/user.py index dada934..9ce6cae 100644 --- a/backend/controller/user.py +++ b/backend/controller/user.py @@ -1,22 +1,29 @@ -from core.resp import Response -from core.router import Router +from fastapi import Query + +from core import Response from core.security import get_password_hash -from dbhelper.user import get_user_info, get_users, insert_user +from dbhelper.user import get_user, get_user_info, get_users, insert_user, new_user from schemas.common import ListAll -from schemas.user import UserAdd, UserInfo, UserList, UserQuery - -user = Router(prefix="/user", tags=["用户管理"]) +from schemas.user import UserAdd, UserIn, UserInfo, UserList, UserQuery, UserRead -@user.post("", summary="用户添加") async def user_add(data: UserAdd) -> Response[UserInfo]: + """新增用户并分配角色 一步到位""" roles = data.rids del data.rids - user.password = get_password_hash(user.password) + data.password = get_password_hash(data.password) return await insert_user(data, roles) -@user.get("/{pk}", summary="用户详情") +async def create_user(data: UserIn) -> Response[UserRead]: + """新增用户""" + result = await get_user({"username": data.username}) + if result is None: + data.password = get_password_hash(data.password) + return Response(data=await new_user(data)) + return Response(msg="用户名已存在") + + async def user_info(pk: int) -> Response[UserInfo]: try: return Response(data=await get_user_info(pk)) @@ -24,18 +31,17 @@ async def user_info(pk: int) -> Response[UserInfo]: return Response(msg=f"用户不存在 {e}") -@user.delete("/{pk}", summary="删除用户") -async def user_del(pk: int) -> Response: - pass +async def user_arr( + offset: int = Query(default=1, description="偏移量-页码"), + limit: int = Query(default=10, description="数据量"), +) -> Response[ListAll[UserList]]: + skip = (offset - 1) * limit + users, count = await get_users(skip, limit) + return Response(data=ListAll(total=count, items=users)) -@user.put("/{pk}", summary="编辑用户") -async def user_put(pk: int, data: UserAdd) -> Response[UserInfo]: - pass - - -@user.post("/list", summary="查询用户列表") async def user_list(query: UserQuery) -> Response[ListAll[UserList]]: + """post查询用户列表""" limit = query.size skip = (query.offset - 1) * limit del query.offset, query.size diff --git a/backend/core/__init__.py b/backend/core/__init__.py index e69de29..3d14a10 100644 --- a/backend/core/__init__.py +++ b/backend/core/__init__.py @@ -0,0 +1,125 @@ +from typing import Generic, Optional, TypeVar + +from pydantic import BaseModel, Field +from pydantic.generics import GenericModel + +T = TypeVar("T") + + +class Response(GenericModel, Generic[T]): + code: int = 200 + data: Optional[T] + msg: str = "请求成功" + + +from datetime import datetime + + +class ReadBase(BaseModel): + """数据读取的基类""" + + id: int + status: int = Field(default=1, description="数据状态 1正常默认值 9 删除 5使用中 ") + created: datetime + modified: datetime + + +from typing import Any, Callable, get_type_hints + +from fastapi import routing + + +class Route(routing.APIRoute): + """ + https://github.com/tiangolo/fastapi/issues/620 + Django挂载视图方法 + def index() -> User: + pass + Route("/", endpoint=index) + """ + + def __init__( + self, + path: str, + endpoint: Callable[..., Any], + tags: list[str], + summary: str, + **kwargs: Any + ): + if kwargs.get("response_model") is None: + kwargs["response_model"] = get_type_hints(endpoint).get("return") + super(Route, self).__init__( + path=path, endpoint=endpoint, tags=tags, summary=summary, **kwargs + ) + + @classmethod + def post( + cls, + path: str, + endpoint: Callable[..., Any], + tags: list[str], + summary: str, + **kwargs: Any + ): + return Route( + path=path, + endpoint=endpoint, + methods=["POST"], + tags=tags, + summary=summary, + **kwargs + ) + + @classmethod + def get( + cls, + path: str, + endpoint: Callable[..., Any], + tags: list[str], + summary: str, + **kwargs: Any + ): + return Route( + path=path, + endpoint=endpoint, + methods=["GET"], + tags=tags, + summary=summary, + **kwargs + ) + + @classmethod + def delete( + cls, + path: str, + endpoint: Callable[..., Any], + tags: list[str], + summary: str, + **kwargs: Any + ): + return Route( + path=path, + endpoint=endpoint, + methods=["DELETE"], + tags=tags, + summary=summary, + **kwargs + ) + + @classmethod + def put( + cls, + path: str, + endpoint: Callable[..., Any], + tags: list[str], + summary: str, + **kwargs: Any + ): + return Route( + path=path, + endpoint=endpoint, + methods=["PUT"], + tags=tags, + summary=summary, + **kwargs + ) diff --git a/backend/core/enums.py b/backend/core/enums.py deleted file mode 100644 index 27114a3..0000000 --- a/backend/core/enums.py +++ /dev/null @@ -1,35 +0,0 @@ -import enum - - -class Status(enum.IntEnum): - """ - 数据库状态枚举值 - 9 删除 5 无效 1 有效 3 使用 - """ - - DELETED: int = 9 - INACTIVE: int = 5 - ACTIVE: int = 1 - SELECTED: int = 3 - - -class UserType(enum.IntEnum): - """ - 数据库超级管理员枚举 - 0 超级管理员 1用户 - """ - - ADMIN: int = 0 - USER: int = 1 - - -class MenuType(enum.IntEnum): - """ - 菜单类型枚举 - 目录 0 - 组件 1 按钮 2 - """ - - DIR = 0 - CPN = 1 - BTN = 2 diff --git a/backend/core/events.py b/backend/core/events.py index ef1194a..26d4feb 100644 --- a/backend/core/events.py +++ b/backend/core/events.py @@ -3,9 +3,7 @@ from tortoise import Tortoise async def init_orm(): """初始化orm""" - await Tortoise.init( - db_url="sqlite://mini.db", modules={"models": ["models"]}, - ) + await Tortoise.init(db_url="sqlite://mini.db", modules={"models": ["models"]}) await Tortoise.generate_schemas() diff --git a/backend/core/resp.py b/backend/core/resp.py deleted file mode 100644 index bf79afd..0000000 --- a/backend/core/resp.py +++ /dev/null @@ -1,32 +0,0 @@ -import enum -from typing import Generic, Optional, TypeVar, Union - -from pydantic.generics import GenericModel - -T = TypeVar("T") - - -class Status(enum.IntEnum): - OK = 200 - CREATED = 201 - ACCEPTED = 202 - NO_CONTENT = 204 - BAD_REQUEST = 400 - UNAUTHORIZED = 401 - FORBIDDEN = 403 - NOT_FOUND = 404 - INTERNAL_SERVER_ERROR = 500 - NOT_IMPLEMENTED = 501 - BAD_GATEWAY = 502 - SERVICE_UNAVAILABLE = 503 - - -class Msg(enum.Enum): - OK = "OK" - FAIL = "FAIL" - - -class Response(GenericModel, Generic[T]): - code: Status = Status.OK - data: Optional[T] - msg: Union[Msg, str] = Msg.OK diff --git a/backend/core/router.py b/backend/core/router.py deleted file mode 100644 index b04d253..0000000 --- a/backend/core/router.py +++ /dev/null @@ -1,48 +0,0 @@ -from typing import TYPE_CHECKING, Any, Callable, get_type_hints - -from fastapi import APIRouter -from fastapi.routing import APIRoute - -""" -根据类型标注自动返回模型 -https://github.com/tiangolo/fastapi/issues/620 -""" - - -class Router(APIRouter): - """ - 装饰器用法 - @app.get("/") - def index() -> User: - """ - - if not TYPE_CHECKING: - - def add_api_route( - self, path: str, endpoint: Callable[..., Any], **kwargs: Any - ) -> None: - if kwargs.get("response_model") is None: - kwargs["response_model"] = get_type_hints(endpoint).get("return") - return super().add_api_route(path, endpoint, **kwargs) - - else: # pragma: no cover - pass - - -class Route(APIRoute): - """ - Django挂载视图方法 - def index() -> User: - pass - Route("/", endpoint=index) - """ - - if not TYPE_CHECKING: - - def __init__(self, path: str, endpoint: Callable[..., Any], **kwargs: Any): - if kwargs.get("response_model") is None: - kwargs["response_model"] = get_type_hints(endpoint).get("return") - super(Route, self).__init__(path=path, endpoint=endpoint, **kwargs) - - else: # pragma: no cover - pass diff --git a/backend/core/security.py b/backend/core/security.py index 793c7aa..c56d9f2 100644 --- a/backend/core/security.py +++ b/backend/core/security.py @@ -14,7 +14,7 @@ SECRET_KEY = "lLNiBWPGiEmCLLR9kRGidgLY7Ac1rpSWwfGzTJpTmCU" ALGORITHM = "HS256" -ACCESS_TOKEN_EXPIRE_MINUTES = 60 *24 * 7 +ACCESS_TOKEN_EXPIRE_MINUTES = 60 * 24 * 7 pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") bearer = HTTPBearer() @@ -45,13 +45,9 @@ def generate_token(username: str, expires_delta: Optional[timedelta] = None): if expires_delta: expire = datetime.utcnow() + expires_delta else: - expire = datetime.utcnow() + timedelta( - minutes=ACCESS_TOKEN_EXPIRE_MINUTES - ) + expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) to_encode.update(dict(exp=expire)) - encoded_jwt = jwt.encode( - to_encode, SECRET_KEY, algorithm=ALGORITHM - ) + encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) return encoded_jwt @@ -59,9 +55,7 @@ async def check_token(security: HTTPAuthorizationCredentials = Depends(bearer)): """检查用户token""" token = security.credentials try: - payload = jwt.decode( - token, SECRET_KEY, algorithms=[ALGORITHM] - ) + payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) username: str = payload.get("sub") return await get_user({"username": username}) except JWTError: diff --git a/backend/core/table.py b/backend/core/table.py index d9be1f4..24fa5cd 100644 --- a/backend/core/table.py +++ b/backend/core/table.py @@ -1,7 +1,5 @@ from tortoise import fields, models -from core.enums import Status - class Table(models.Model): """ @@ -9,7 +7,7 @@ class Table(models.Model): """ id = fields.IntField(pk=True, description="主键") - status = fields.IntEnumField(Status, description="状态", default=Status.ACTIVE) + status = fields.SmallIntField(default=1, description="状态 1有效 9 删除 5选中") created = fields.DatetimeField(auto_now_add=True, description="创建时间", null=True) modified = fields.DatetimeField(auto_now=True, description="更新时间", null=True) diff --git a/backend/core/utils.py b/backend/core/utils.py index 3385fc1..697c282 100644 --- a/backend/core/utils.py +++ b/backend/core/utils.py @@ -24,28 +24,3 @@ def list_to_tree( else: arr.append(menu) return arr - - -def menu_table(): - """生成菜单表数据""" - from models import MenuModel - MenuModel.bulk_create([ - MenuModel(name="系统管理", - meta={"icon": "Grid"}, - path="/system", - type=0), - MenuModel(name="系统设置", - meta={"icon": "Setting"}, - path="/setting", - type=0), - MenuModel(name="菜单管理", - meta={"icon": "Menu"}, - path="/system/menu", - type=1, - component="/system/menu", - pid=1, - api="/menu", - method="{'GET}", - regx="^/menu$" - ) - ]) \ No newline at end of file diff --git a/backend/dbhelper/menu.py b/backend/dbhelper/menu.py new file mode 100644 index 0000000..dd90c43 --- /dev/null +++ b/backend/dbhelper/menu.py @@ -0,0 +1,7 @@ +from models import MenuModel +from schemas.menu import MenuIn + + +async def insert_menu(menu: MenuIn): + """新增菜单""" + return await MenuModel.create(**menu.dict()) diff --git a/backend/dbhelper/role.py b/backend/dbhelper/role.py index c7ab763..9e2d8a0 100644 --- a/backend/dbhelper/role.py +++ b/backend/dbhelper/role.py @@ -1,5 +1,8 @@ from tortoise import connections +from models import RoleModel +from schemas.role import RoleIn + async def get_role_menus(rid: int): """ @@ -13,3 +16,8 @@ async def get_role_menus(rid: int): AND sys_role_menu.rid = (%s) AND m.`status` = 1 ORDER BY m.sort""", [rid], ) + + +async def new_role(role: RoleIn): + """新增角色""" + return await RoleModel.create(**role.dict()) diff --git a/backend/dbhelper/user.py b/backend/dbhelper/user.py index 72f307d..b397a24 100644 --- a/backend/dbhelper/user.py +++ b/backend/dbhelper/user.py @@ -1,8 +1,7 @@ from tortoise.transactions import atomic -from core.enums import Status from models import RoleModel, UserModel, UserRoleModel -from schemas.user import UserRole +from schemas.user import UserIn, UserRole async def get_user(kwargs): @@ -32,13 +31,13 @@ async def get_user_info(pk: int): active_rid = role[0].get("rid") rids = [] for obj in role: - if obj.get("status") == Status.SELECTED: + if obj.get("status") == 5: active_rid = obj.get("rid") rids.append(obj.get("rid")) return {**user, "active_rid": active_rid, "rids": rids} -async def get_users(skip: int, limit: int, kwargs: dict): +async def get_users(skip: int, limit: int, kwargs: dict = None): """ 分页获取用户并且支持字段模糊查询 Args: @@ -49,7 +48,10 @@ async def get_users(skip: int, limit: int, kwargs: dict): Returns: """ - kwargs = {f"{k}__contains": v for k, v in kwargs.items()} + if kwargs is not None: + kwargs = {f"{k}__contains": v for k, v in kwargs.items()} + else: + kwargs = {} result = ( UserModel.filter(status__not_in=[9, 5], **kwargs).all().order_by("-created") ) @@ -66,7 +68,12 @@ async def insert_user(user, roles): user_role = UserRole(rid=rid, uid=obj.id) if index == 0: - user_role.status = Status.SELECTED + user_role.status = 5 # 第一个角色默认, 添加到关系表 await UserRoleModel.create(**user_role.dict()) return user + + +async def new_user(user: UserIn): + """新增用户""" + return await UserModel.create(**user.dict()) diff --git a/backend/main.py b/backend/main.py index b9c525e..21ad460 100644 --- a/backend/main.py +++ b/backend/main.py @@ -1,41 +1,23 @@ from fastapi import FastAPI -from fastapi.openapi.docs import get_swagger_ui_html -from controller import register_routers from core.events import close_orm, init_orm -from core.log import logger_db_client -from core.utils import menu_table +from core.log import logger_db_client as logger +from core.middleware import middlewares +from router.url import routes app = FastAPI( - on_startup=[init_orm, menu_table], + on_startup=[init_orm], on_shutdown=[close_orm], - docs_url=None, - redoc_url=None, + routes=routes, + middleware=middlewares, ) -register_routers(app) - - -@app.get("/docs", include_in_schema=False) -async def custom_swagger_ui_html(): - return get_swagger_ui_html( - openapi_url=app.openapi_url, - title=app.title + " - Swagger UI", - oauth2_redirect_url=app.swagger_ui_oauth2_redirect_url, - swagger_js_url="https://cdn.bootcdn.net/ajax/libs/swagger-ui/4.10.3/swagger-ui-bundle.js", - swagger_css_url="https://cdn.bootcdn.net/ajax/libs/swagger-ui/4.10.3/swagger-ui.css", - ) - - -for i in app.routes: - logger_db_client.debug(i.__dict__) - logger_db_client.info(f"{i.path}, {i.methods}, {i.path_regex}") - """ - 'path_regex': re.compile('^/role/(?P< -pk>[^/]+)/menu$'), 'path_format': '/role/{pk}/menu', - """ - - -if __name__ == '__main__': +if __name__ == "__main__": import uvicorn - uvicorn.run("main:app", reload=True) \ No newline at end of file + + for i in app.routes: + logger.info( + f"{i.path}, {i.methods}, {i.path_regex}, {i.__dict__.get('summary')}, {i.endpoint}" + ) + + uvicorn.run("main:app", reload=True) diff --git a/backend/mini.db b/backend/mini.db index 1975609..e2a2455 100644 Binary files a/backend/mini.db and b/backend/mini.db differ diff --git a/backend/mini.db-shm b/backend/mini.db-shm index fe9ac28..089db77 100644 Binary files a/backend/mini.db-shm and b/backend/mini.db-shm differ diff --git a/backend/mini.db-wal b/backend/mini.db-wal index e69de29..9f19091 100644 Binary files a/backend/mini.db-wal and b/backend/mini.db-wal differ diff --git a/backend/models/menu.py b/backend/models/menu.py index a761c2b..5c914dd 100644 --- a/backend/models/menu.py +++ b/backend/models/menu.py @@ -1,4 +1,3 @@ -from core.enums import MenuType from core.table import Table, fields @@ -10,7 +9,7 @@ class MenuModel(Table): name = fields.CharField(max_length=20, description="名称", null=True) meta = fields.JSONField(description="元数据信息", null=True) path = fields.CharField(max_length=128, description="菜单url", null=True) - type = fields.IntEnumField(MenuType, description="菜单类型") + type = fields.SmallIntField(description="菜单类型 0目录 1组件 2按钮") component = fields.CharField(max_length=128, description="组件地址", null=True) pid = fields.IntField(description="父id", null=True) identifier = fields.CharField(max_length=30, description="权限标识 user:add", null=True) diff --git a/backend/models/user.py b/backend/models/user.py index db7723f..b18d171 100644 --- a/backend/models/user.py +++ b/backend/models/user.py @@ -1,4 +1,3 @@ -from core.enums import UserType from core.table import Table, fields @@ -16,4 +15,3 @@ class UserModel(Table): table_description = "用户表" # 索引 unique_together = ("username",) - diff --git a/backend/requirements.txt b/backend/requirements.txt index 36c823d..11cac64 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -1,23 +1,35 @@ aiosqlite==0.17.0 anyio==3.6.1 +attrs==22.1.0 bcrypt==4.0.0 +certifi==2022.6.15.1 +charset-normalizer==2.1.1 click==8.1.3 colorama==0.4.5 ecdsa==0.18.0 fastapi==0.82.0 h11==0.13.0 idna==3.3 +iniconfig==1.1.1 iso8601==1.0.2 +packaging==21.3 passlib==1.7.4 +pluggy==1.0.0 +py==1.11.0 pyasn1==0.4.8 pydantic==1.10.2 +pyparsing==3.0.9 pypika-tortoise==0.1.6 +pytest==7.1.3 python-jose==3.3.0 pytz==2022.2.1 +requests==2.28.1 rsa==4.9 six==1.16.0 sniffio==1.3.0 starlette==0.19.1 +tomli==2.0.1 tortoise-orm==0.19.2 typing-extensions==4.3.0 -uvicorn==0.18.3 +urllib3==1.26.12 +uvicorn==0.18.3 \ No newline at end of file diff --git a/backend/router/__init__.py b/backend/router/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/router/url.py b/backend/router/url.py new file mode 100644 index 0000000..a0131e3 --- /dev/null +++ b/backend/router/url.py @@ -0,0 +1,23 @@ +from controller.common import login +from controller.menu import menu_add +from controller.role import role_add, role_has_menu +from controller.user import create_user, user_arr, user_info, user_list +from core import Route + +routes = [ + Route.post("/login", endpoint=login, tags=["公共"], summary="登录"), + # 用户管理 + Route.post("/user", endpoint=create_user, tags=["用户管理"], summary="用户新增"), + Route.get("/user/{pk}", endpoint=user_info, tags=["用户管理"], summary="用户信息"), + Route.get("/user", endpoint=user_arr, tags=["用户管理"], summary="用户列表"), + Route.post("/user/query", endpoint=user_list, tags=["用户管理"], summary="用户列表查询"), + # 角色管理 + Route.post("/role", endpoint=role_add, tags=["角色管理"], summary="角色新增"), + Route.get( + "role/{rid}/menu", endpoint=role_has_menu, tags=["角色管理"], summary="查询角色拥有权限" + ), + # 菜单新增 + Route.post("/menu", endpoint=menu_add, tags=["菜单管理"], summary="菜单新增"), +] + +__all__ = [routes] diff --git a/backend/schemas/menu.py b/backend/schemas/menu.py index d44cec8..0d6b58d 100644 --- a/backend/schemas/menu.py +++ b/backend/schemas/menu.py @@ -4,3 +4,29 @@ from models import MenuModel MenuRead = pydantic_model_creator(MenuModel, name="MenuOut") MenuIn = pydantic_model_creator(MenuModel, name="MenuIn", exclude_readonly=True) + + +# from pydantic import BaseModel, Field +# from typing import Optional +# from core import ReadBase +# +# +# class MenuBasic(BaseModel): +# name: str +# meta: Optional[str] = Field(default=None, description="元信息") +# path: Optional[str] = Field(default=None, description="前端路由地址") +# type: int = Field(description="0 目录 1 组件 2 按钮") +# component: Optional[str] = Field(default=None, description="前端组件地址") +# pid: int = Field(default=0, description="0 表示没有根节点") +# identifier: Optional[str] = Field(default=None, description="权限标识符 -> 按钮显示") +# api: Optional[str] = Field(default=None, description="后端接口地址") +# method: Optional[str] = Field(default=None, description="接口请求方法") +# regx: Optional[str] = Field(default=None, description="正则匹配") +# +# +# class MenuIn(MenuBasic): +# pass +# +# +# class MenuRead(MenuBasic, ReadBase): +# pass diff --git a/backend/schemas/role.py b/backend/schemas/role.py index 1bb5325..0b0cb86 100644 --- a/backend/schemas/role.py +++ b/backend/schemas/role.py @@ -1,15 +1,23 @@ - -from pydantic import Field +from pydantic import BaseModel, Field from tortoise.contrib.pydantic import pydantic_model_creator +from core import ReadBase from models import RoleModel RoleRed = pydantic_model_creator(RoleModel, name="RoleOut") -RoleIn = pydantic_model_creator(RoleModel, name="RoleIn", exclude_readonly=True) -class RoleAdd(RoleIn): - menus: list[int] = Field(..., description="菜单列表") +class RoleBasic(BaseModel): + name: str = Field(None, description="角色名称") + remark: str = Field(None, description="备注信息") + + +class RoleIn(RoleBasic): + pass + + +class RoleRed(RoleBasic, ReadBase): + pass class RoleInfo(RoleRed): diff --git a/backend/schemas/user.py b/backend/schemas/user.py index af14397..d64406b 100644 --- a/backend/schemas/user.py +++ b/backend/schemas/user.py @@ -1,16 +1,28 @@ from typing import List, Optional -from pydantic import Field +from pydantic import BaseModel, Field from tortoise.contrib.pydantic import pydantic_model_creator -from models import UserModel, UserRoleModel +from models import UserRoleModel from schemas.common import QueryData -UserRead = pydantic_model_creator(UserModel, name="UserOut", exclude=("password",)) -UserIn = pydantic_model_creator(UserModel, name="UserIn", exclude_readonly=True, exclude=("status",)) - UserRole = pydantic_model_creator(UserRoleModel, name="UserRole", exclude_readonly=True) +from core import ReadBase + + +class UserBasic(BaseModel): + username: str + nickname: str + + +class UserIn(UserBasic): + password: str + + +class UserRead(UserBasic, ReadBase): + pass + class UserInfo(UserRead): active_rid: int = Field(..., description="用户当前激活角色") diff --git a/backend/tests/__init__.py b/backend/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/tests/test_case.py b/backend/tests/test_case.py new file mode 100644 index 0000000..7edda83 --- /dev/null +++ b/backend/tests/test_case.py @@ -0,0 +1,201 @@ +import json + +import requests as client + +from schemas.menu import MenuIn +from schemas.role import RoleIn +from schemas.user import UserIn + +base = "http://localhost:8000" + + +def test_user_add(): + url = base + "/user" + res = client.request( + method="post", + url=url, + json=UserIn(username="admin", nickname="超级管理员", password="123456").dict(), + ) + assert res.status_code == 200 + res = client.request( + method="post", + url=url, + json=UserIn(username="tester", nickname="测试员", password="123456").dict(), + ) + assert res.status_code == 200 + + +def test_role_add(): + url = base + "/role" + res = client.request( + method="post", url=url, json=RoleIn(name="super", remark="全部权限").dict() + ) + assert res.status_code == 200 + res = client.request( + method="post", url=url, json=RoleIn(name="user", remark="用户权限").dict() + ) + assert res.status_code == 200 + + +def test_menu_add(): + url = base + "/menu" + # id 1 + res = client.request( + method="post", + url=url, + json=MenuIn( + name="系统管理", + meta=json.dumps({"icon": "Group"}), + path="/system", + type=0, + component=None, + pid=0, + identifier=None, + api=None, + method=None, + regx=None, + ).dict(), + ) + + assert res.status_code == 200 + # id 2 + res = client.request( + method="post", + url=url, + json=MenuIn( + name="用户管理", + meta=json.dumps({"icon": "User"}), + path="/system/user", + type=1, + component="/system/user.vue", + pid=1, + identifier=None, + api="/user", + method="{'GET'}", + regx="^/user$", + ).dict(), + ) + assert res.status_code == 200 + # id 3 + res = client.request( + method="post", + url=url, + json=MenuIn( + name="角色管理", + meta=json.dumps({"icon": "User"}), + path="/system/role", + type=1, + component="/system/role.vue", + pid=1, + identifier=None, + api="/role", + method="{'GET'}", + regx="^/role$", + ).dict(), + ) + + # id 4 + res = client.request( + method="post", + url=url, + json=MenuIn( + name="菜单管理", + meta=json.dumps({"icon": "Menu"}), + path="/system/menu", + type=1, + component="/system/menu.vue", + pid=1, + identifier=None, + api="/menu", + method="{'GET'}", + regx="^/menu$", + ).dict(), + ) + + # id 5 + res = client.request( + method="post", + url=url, + json=MenuIn( + name="系统设置", + meta=json.dumps({"icon": "Setting"}), + path="/setting", + type=0, + component=None, + pid=0, + identifier=None, + api=None, + method=None, + regx=None, + ).dict(), + ) + + res = client.request( + method="post", + url=url, + json=MenuIn( + name="系统监控", + meta=json.dumps({"icon": "minitor"}), + path="/setting/minitor", + type=0, + component="/setting/minitor.vue", + pid=5, + identifier=None, + api=None, + method=None, + regx=None, + ).dict(), + ) + + res = client.request( + method="post", + url=url, + json=MenuIn( + name="新增用户", + meta=json.dumps({"icon": "Add"}), + path=None, + type=2, + component=None, + pid=2, + identifier="user:add", + api="/user", + method="{'POST'}", + regx="^/user$", + ).dict(), + ) + assert res.status_code == 200 + + res = client.request( + method="post", + url=url, + json=MenuIn( + name="查询用户", + meta=json.dumps({"icon": "Select"}), + path=None, + type=2, + component=None, + pid=2, + identifier="user:query", + api="/user/query", + method="{'POST'}", + regx="^/user/query$", + ).dict(), + ) + + res = client.request( + method="post", + url=url, + json=MenuIn( + name="角色管理", + meta=json.dumps({"icon": "User"}), + path="/system/role", + type=1, + component="/system/role.vue", + pid=1, + identifier=None, + api="/role", + method="{'GET'}", + regx="^/role", + ).dict(), + ) + assert res.status_code == 200