feat: user api
This commit is contained in:
parent
28013b0e8e
commit
7db1277dd9
2
backend/.gitignore
vendored
2
backend/.gitignore
vendored
@ -140,3 +140,5 @@ cython_debug/
|
|||||||
.idea
|
.idea
|
||||||
.pdm.toml
|
.pdm.toml
|
||||||
__pypackages__/
|
__pypackages__/
|
||||||
|
.pytest_cache
|
||||||
|
.db-*
|
@ -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="账号或密码错误")
|
||||||
|
@ -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
|
|
||||||
|
@ -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
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
)
|
@ -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
|
|
@ -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()
|
||||||
|
|
||||||
|
|
||||||
|
@ -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
|
|
@ -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
|
|
@ -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:
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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
7
backend/dbhelper/menu.py
Normal 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())
|
@ -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())
|
||||||
|
@ -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:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
if kwargs is not None:
|
||||||
kwargs = {f"{k}__contains": v for k, v in kwargs.items()}
|
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())
|
||||||
|
@ -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__":
|
||||||
|
import uvicorn
|
||||||
|
|
||||||
|
for i in app.routes:
|
||||||
@app.get("/docs", include_in_schema=False)
|
logger.info(
|
||||||
async def custom_swagger_ui_html():
|
f"{i.path}, {i.methods}, {i.path_regex}, {i.__dict__.get('summary')}, {i.endpoint}"
|
||||||
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
|
|
||||||
uvicorn.run("main:app", reload=True)
|
uvicorn.run("main:app", reload=True)
|
BIN
backend/mini.db
BIN
backend/mini.db
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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)
|
||||||
|
@ -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",)
|
||||||
|
|
||||||
|
@ -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
|
0
backend/router/__init__.py
Normal file
0
backend/router/__init__.py
Normal file
23
backend/router/url.py
Normal file
23
backend/router/url.py
Normal 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]
|
@ -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
|
||||||
|
@ -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):
|
||||||
|
@ -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="用户当前激活角色")
|
||||||
|
0
backend/tests/__init__.py
Normal file
0
backend/tests/__init__.py
Normal file
201
backend/tests/test_case.py
Normal file
201
backend/tests/test_case.py
Normal 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
|
Loading…
Reference in New Issue
Block a user