feat: user api

This commit is contained in:
zy7y 2022-09-12 15:11:12 +08:00
parent 28013b0e8e
commit 7db1277dd9
30 changed files with 513 additions and 304 deletions

2
backend/.gitignore vendored
View File

@ -140,3 +140,5 @@ cython_debug/
.idea .idea
.pdm.toml .pdm.toml
__pypackages__/ __pypackages__/
.pytest_cache
.db-*

View File

@ -1,16 +1,16 @@
from core.resp import Response from core import Response
from core.router import Router
from core.security import generate_token, verify_password from core.security import generate_token, verify_password
from dbhelper.user import get_user from dbhelper.user import get_user
from schemas.common import LoginForm, LoginResult from schemas.common import LoginForm, LoginResult
common = Router(tags=["公共接口"])
@common.post("/login", summary="登录")
async def login(auth_data: LoginForm) -> Response[LoginResult]: async def login(auth_data: LoginForm) -> Response[LoginResult]:
user_obj = await get_user({"username": auth_data.username}) user_obj = await get_user({"username": auth_data.username})
if user_obj: if user_obj:
if verify_password(auth_data.password, user_obj.password): 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="账号或密码错误") return Response(msg="账号或密码错误")

View File

@ -1,31 +1,7 @@
from core.resp import Response from core import Response
from core.router import Router from dbhelper.menu import insert_menu
from schemas.common import QueryData
from schemas.menu import MenuIn, MenuRead from schemas.menu import MenuIn, MenuRead
menu = Router(prefix="/menu", tags=["菜单管理"])
@menu.post("", summary="菜单添加")
async def menu_add(data: MenuIn) -> Response[MenuRead]: async def menu_add(data: MenuIn) -> Response[MenuRead]:
pass return Response(data=await insert_menu(data))
@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

View File

@ -1,53 +1,20 @@
import json import json
from core.resp import Response from core import Response
from core.router import Router
from core.utils import list_to_tree from core.utils import list_to_tree
from dbhelper.role import get_role_menus from dbhelper.role import get_role_menus, new_role
from schemas.common import QueryData from schemas.role import RoleIn, RoleInfo
from schemas.role import RoleAdd, RoleInfo
role = Router(prefix="/role", tags=["角色管理"])
@role.post("", summary="角色添加") async def role_add(data: RoleIn) -> Response[RoleInfo]:
async def role_add(data: RoleAdd) -> Response[RoleInfo]: return Response(data=await new_role(data))
pass
@role.get("/{pk}", summary="角色详情") async def role_has_menu(rid: int):
async def role_info(pk: int) -> Response[RoleInfo]: """
pass rid: 角色ID
"""
menus = await get_role_menus(rid)
@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)
for obj in menus: for obj in menus:
obj["meta"] = json.loads(obj["meta"]) if obj["meta"] is not None else None obj["meta"] = json.loads(obj["meta"]) if obj["meta"] is not None else None
return Response(data=list_to_tree(menus)) 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

View File

@ -1,22 +1,29 @@
from core.resp import Response from fastapi import Query
from core.router import Router
from core import Response
from core.security import get_password_hash 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.common import ListAll
from schemas.user import UserAdd, UserInfo, UserList, UserQuery from schemas.user import UserAdd, UserIn, UserInfo, UserList, UserQuery, UserRead
user = Router(prefix="/user", tags=["用户管理"])
@user.post("", summary="用户添加")
async def user_add(data: UserAdd) -> Response[UserInfo]: async def user_add(data: UserAdd) -> Response[UserInfo]:
"""新增用户并分配角色 一步到位"""
roles = data.rids roles = data.rids
del 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) 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]: async def user_info(pk: int) -> Response[UserInfo]:
try: try:
return Response(data=await get_user_info(pk)) 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}") return Response(msg=f"用户不存在 {e}")
@user.delete("/{pk}", summary="删除用户") async def user_arr(
async def user_del(pk: int) -> Response: offset: int = Query(default=1, description="偏移量-页码"),
pass 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]]: async def user_list(query: UserQuery) -> Response[ListAll[UserList]]:
"""post查询用户列表"""
limit = query.size limit = query.size
skip = (query.offset - 1) * limit skip = (query.offset - 1) * limit
del query.offset, query.size del query.offset, query.size

View File

@ -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
)

View File

@ -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

View File

@ -3,9 +3,7 @@ from tortoise import Tortoise
async def init_orm(): async def init_orm():
"""初始化orm""" """初始化orm"""
await Tortoise.init( await Tortoise.init(db_url="sqlite://mini.db", modules={"models": ["models"]})
db_url="sqlite://mini.db", modules={"models": ["models"]},
)
await Tortoise.generate_schemas() await Tortoise.generate_schemas()

View File

@ -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

View File

@ -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

View File

@ -14,7 +14,7 @@ SECRET_KEY = "lLNiBWPGiEmCLLR9kRGidgLY7Ac1rpSWwfGzTJpTmCU"
ALGORITHM = "HS256" ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 60 *24 * 7 ACCESS_TOKEN_EXPIRE_MINUTES = 60 * 24 * 7
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
bearer = HTTPBearer() bearer = HTTPBearer()
@ -45,13 +45,9 @@ def generate_token(username: str, expires_delta: Optional[timedelta] = None):
if expires_delta: if expires_delta:
expire = datetime.utcnow() + expires_delta expire = datetime.utcnow() + expires_delta
else: else:
expire = datetime.utcnow() + timedelta( expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
minutes=ACCESS_TOKEN_EXPIRE_MINUTES
)
to_encode.update(dict(exp=expire)) to_encode.update(dict(exp=expire))
encoded_jwt = jwt.encode( encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
to_encode, SECRET_KEY, algorithm=ALGORITHM
)
return encoded_jwt return encoded_jwt
@ -59,9 +55,7 @@ async def check_token(security: HTTPAuthorizationCredentials = Depends(bearer)):
"""检查用户token""" """检查用户token"""
token = security.credentials token = security.credentials
try: try:
payload = jwt.decode( payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
token, SECRET_KEY, algorithms=[ALGORITHM]
)
username: str = payload.get("sub") username: str = payload.get("sub")
return await get_user({"username": username}) return await get_user({"username": username})
except JWTError: except JWTError:

View File

@ -1,7 +1,5 @@
from tortoise import fields, models from tortoise import fields, models
from core.enums import Status
class Table(models.Model): class Table(models.Model):
""" """
@ -9,7 +7,7 @@ class Table(models.Model):
""" """
id = fields.IntField(pk=True, description="主键") 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) created = fields.DatetimeField(auto_now_add=True, description="创建时间", null=True)
modified = fields.DatetimeField(auto_now=True, description="更新时间", null=True) modified = fields.DatetimeField(auto_now=True, description="更新时间", null=True)

View File

@ -24,28 +24,3 @@ def list_to_tree(
else: else:
arr.append(menu) arr.append(menu)
return arr 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$"
)
])

7
backend/dbhelper/menu.py Normal file
View File

@ -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())

View File

@ -1,5 +1,8 @@
from tortoise import connections from tortoise import connections
from models import RoleModel
from schemas.role import RoleIn
async def get_role_menus(rid: int): 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""", AND sys_role_menu.rid = (%s) AND m.`status` = 1 ORDER BY m.sort""",
[rid], [rid],
) )
async def new_role(role: RoleIn):
"""新增角色"""
return await RoleModel.create(**role.dict())

View File

@ -1,8 +1,7 @@
from tortoise.transactions import atomic from tortoise.transactions import atomic
from core.enums import Status
from models import RoleModel, UserModel, UserRoleModel from models import RoleModel, UserModel, UserRoleModel
from schemas.user import UserRole from schemas.user import UserIn, UserRole
async def get_user(kwargs): async def get_user(kwargs):
@ -32,13 +31,13 @@ async def get_user_info(pk: int):
active_rid = role[0].get("rid") active_rid = role[0].get("rid")
rids = [] rids = []
for obj in role: for obj in role:
if obj.get("status") == Status.SELECTED: if obj.get("status") == 5:
active_rid = obj.get("rid") active_rid = obj.get("rid")
rids.append(obj.get("rid")) rids.append(obj.get("rid"))
return {**user, "active_rid": active_rid, "rids": rids} 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: Args:
@ -49,7 +48,10 @@ async def get_users(skip: int, limit: int, kwargs: dict):
Returns: 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 = ( result = (
UserModel.filter(status__not_in=[9, 5], **kwargs).all().order_by("-created") 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) user_role = UserRole(rid=rid, uid=obj.id)
if index == 0: if index == 0:
user_role.status = Status.SELECTED user_role.status = 5
# 第一个角色默认, 添加到关系表 # 第一个角色默认, 添加到关系表
await UserRoleModel.create(**user_role.dict()) await UserRoleModel.create(**user_role.dict())
return user return user
async def new_user(user: UserIn):
"""新增用户"""
return await UserModel.create(**user.dict())

View File

@ -1,41 +1,23 @@
from fastapi import FastAPI 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.events import close_orm, init_orm
from core.log import logger_db_client from core.log import logger_db_client as logger
from core.utils import menu_table from core.middleware import middlewares
from router.url import routes
app = FastAPI( app = FastAPI(
on_startup=[init_orm, menu_table], on_startup=[init_orm],
on_shutdown=[close_orm], on_shutdown=[close_orm],
docs_url=None, routes=routes,
redoc_url=None, middleware=middlewares,
) )
register_routers(app) if __name__ == "__main__":
@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__':
import uvicorn import uvicorn
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) uvicorn.run("main:app", reload=True)

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,4 +1,3 @@
from core.enums import MenuType
from core.table import Table, fields from core.table import Table, fields
@ -10,7 +9,7 @@ class MenuModel(Table):
name = fields.CharField(max_length=20, description="名称", null=True) name = fields.CharField(max_length=20, description="名称", null=True)
meta = fields.JSONField(description="元数据信息", null=True) meta = fields.JSONField(description="元数据信息", null=True)
path = fields.CharField(max_length=128, description="菜单url", 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) component = fields.CharField(max_length=128, description="组件地址", null=True)
pid = fields.IntField(description="父id", null=True) pid = fields.IntField(description="父id", null=True)
identifier = fields.CharField(max_length=30, description="权限标识 user:add", null=True) identifier = fields.CharField(max_length=30, description="权限标识 user:add", null=True)

View File

@ -1,4 +1,3 @@
from core.enums import UserType
from core.table import Table, fields from core.table import Table, fields
@ -16,4 +15,3 @@ class UserModel(Table):
table_description = "用户表" table_description = "用户表"
# 索引 # 索引
unique_together = ("username",) unique_together = ("username",)

View File

@ -1,23 +1,35 @@
aiosqlite==0.17.0 aiosqlite==0.17.0
anyio==3.6.1 anyio==3.6.1
attrs==22.1.0
bcrypt==4.0.0 bcrypt==4.0.0
certifi==2022.6.15.1
charset-normalizer==2.1.1
click==8.1.3 click==8.1.3
colorama==0.4.5 colorama==0.4.5
ecdsa==0.18.0 ecdsa==0.18.0
fastapi==0.82.0 fastapi==0.82.0
h11==0.13.0 h11==0.13.0
idna==3.3 idna==3.3
iniconfig==1.1.1
iso8601==1.0.2 iso8601==1.0.2
packaging==21.3
passlib==1.7.4 passlib==1.7.4
pluggy==1.0.0
py==1.11.0
pyasn1==0.4.8 pyasn1==0.4.8
pydantic==1.10.2 pydantic==1.10.2
pyparsing==3.0.9
pypika-tortoise==0.1.6 pypika-tortoise==0.1.6
pytest==7.1.3
python-jose==3.3.0 python-jose==3.3.0
pytz==2022.2.1 pytz==2022.2.1
requests==2.28.1
rsa==4.9 rsa==4.9
six==1.16.0 six==1.16.0
sniffio==1.3.0 sniffio==1.3.0
starlette==0.19.1 starlette==0.19.1
tomli==2.0.1
tortoise-orm==0.19.2 tortoise-orm==0.19.2
typing-extensions==4.3.0 typing-extensions==4.3.0
urllib3==1.26.12
uvicorn==0.18.3 uvicorn==0.18.3

View File

23
backend/router/url.py Normal file
View File

@ -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]

View File

@ -4,3 +4,29 @@ from models import MenuModel
MenuRead = pydantic_model_creator(MenuModel, name="MenuOut") MenuRead = pydantic_model_creator(MenuModel, name="MenuOut")
MenuIn = pydantic_model_creator(MenuModel, name="MenuIn", exclude_readonly=True) 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

View File

@ -1,15 +1,23 @@
from pydantic import BaseModel, Field
from pydantic import Field
from tortoise.contrib.pydantic import pydantic_model_creator from tortoise.contrib.pydantic import pydantic_model_creator
from core import ReadBase
from models import RoleModel from models import RoleModel
RoleRed = pydantic_model_creator(RoleModel, name="RoleOut") RoleRed = pydantic_model_creator(RoleModel, name="RoleOut")
RoleIn = pydantic_model_creator(RoleModel, name="RoleIn", exclude_readonly=True)
class RoleAdd(RoleIn): class RoleBasic(BaseModel):
menus: list[int] = Field(..., description="菜单列表") name: str = Field(None, description="角色名称")
remark: str = Field(None, description="备注信息")
class RoleIn(RoleBasic):
pass
class RoleRed(RoleBasic, ReadBase):
pass
class RoleInfo(RoleRed): class RoleInfo(RoleRed):

View File

@ -1,16 +1,28 @@
from typing import List, Optional from typing import List, Optional
from pydantic import Field from pydantic import BaseModel, Field
from tortoise.contrib.pydantic import pydantic_model_creator from tortoise.contrib.pydantic import pydantic_model_creator
from models import UserModel, UserRoleModel from models import UserRoleModel
from schemas.common import QueryData 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) 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): class UserInfo(UserRead):
active_rid: int = Field(..., description="用户当前激活角色") active_rid: int = Field(..., description="用户当前激活角色")

View File

201
backend/tests/test_case.py Normal file
View File

@ -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