login page
This commit is contained in:
0
backend/core/__init__.py
Normal file
0
backend/core/__init__.py
Normal file
35
backend/core/enums.py
Normal file
35
backend/core/enums.py
Normal file
@@ -0,0 +1,35 @@
|
||||
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
|
13
backend/core/events.py
Normal file
13
backend/core/events.py
Normal file
@@ -0,0 +1,13 @@
|
||||
from tortoise import Tortoise
|
||||
|
||||
|
||||
async def init_orm():
|
||||
"""初始化orm"""
|
||||
await Tortoise.init(
|
||||
db_url="sqlite://mini.db", modules={"models": ["models"]}
|
||||
)
|
||||
|
||||
|
||||
async def close_orm():
|
||||
"""关闭orm"""
|
||||
await Tortoise.close_connections()
|
6
backend/core/exceptions.py
Normal file
6
backend/core/exceptions.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from fastapi.exceptions import HTTPException
|
||||
|
||||
|
||||
class TokenAuthFailure(HTTPException):
|
||||
status_code = 401
|
||||
detail = "认证失败"
|
15
backend/core/log.py
Normal file
15
backend/core/log.py
Normal file
@@ -0,0 +1,15 @@
|
||||
import logging
|
||||
import sys
|
||||
|
||||
fmt = logging.Formatter(
|
||||
fmt="%(asctime)s - %(name)s:%(lineno)d - %(levelname)s - %(message)s",
|
||||
datefmt="%Y-%m-%d %H:%M:%S",
|
||||
)
|
||||
sh = logging.StreamHandler(sys.stdout)
|
||||
sh.setLevel(logging.DEBUG)
|
||||
sh.setFormatter(fmt)
|
||||
|
||||
# will print debug sql
|
||||
logger_db_client = logging.getLogger("mini-rbac")
|
||||
logger_db_client.setLevel(logging.DEBUG)
|
||||
logger_db_client.addHandler(sh)
|
12
backend/core/middleware.py
Normal file
12
backend/core/middleware.py
Normal file
@@ -0,0 +1,12 @@
|
||||
from fastapi.middleware import Middleware
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
|
||||
middlewares = [
|
||||
Middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
]
|
32
backend/core/resp.py
Normal file
32
backend/core/resp.py
Normal file
@@ -0,0 +1,32 @@
|
||||
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
|
||||
msg: Union[Msg, str] = Msg.OK
|
||||
data: Optional[T]
|
48
backend/core/router.py
Normal file
48
backend/core/router.py
Normal file
@@ -0,0 +1,48 @@
|
||||
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
|
68
backend/core/security.py
Normal file
68
backend/core/security.py
Normal file
@@ -0,0 +1,68 @@
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import Depends
|
||||
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
||||
from jose import JWTError, jwt
|
||||
from passlib.context import CryptContext
|
||||
|
||||
from core.exceptions import TokenAuthFailure
|
||||
from dbhelper.user import get_user
|
||||
|
||||
# JWT
|
||||
SECRET_KEY = "lLNiBWPGiEmCLLR9kRGidgLY7Ac1rpSWwfGzTJpTmCU"
|
||||
|
||||
ALGORITHM = "HS256"
|
||||
|
||||
ACCESS_TOKEN_EXPIRE_MINUTES = 60 *24 * 7
|
||||
|
||||
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
||||
bearer = HTTPBearer()
|
||||
|
||||
|
||||
def verify_password(plain_password: str, hashed_password: str) -> bool:
|
||||
"""
|
||||
验证明文密码 vs hash密码
|
||||
:param plain_password: 明文密码
|
||||
:param hashed_password: hash密码
|
||||
:return:
|
||||
"""
|
||||
return pwd_context.verify(plain_password, hashed_password)
|
||||
|
||||
|
||||
def get_password_hash(password: str) -> str:
|
||||
"""
|
||||
加密明文
|
||||
:param password: 明文密码
|
||||
:return:
|
||||
"""
|
||||
return pwd_context.hash(password)
|
||||
|
||||
|
||||
def generate_token(username: str, expires_delta: Optional[timedelta] = None):
|
||||
"""生成token"""
|
||||
to_encode = {"sub": username}.copy()
|
||||
if expires_delta:
|
||||
expire = datetime.utcnow() + expires_delta
|
||||
else:
|
||||
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
|
||||
)
|
||||
return encoded_jwt
|
||||
|
||||
|
||||
async def check_token(security: HTTPAuthorizationCredentials = Depends(bearer)):
|
||||
"""检查用户token"""
|
||||
token = security.credentials
|
||||
try:
|
||||
payload = jwt.decode(
|
||||
token, SECRET_KEY, algorithms=[ALGORITHM]
|
||||
)
|
||||
username: str = payload.get("sub")
|
||||
return await get_user({"username": username})
|
||||
except JWTError:
|
||||
raise TokenAuthFailure
|
19
backend/core/table.py
Normal file
19
backend/core/table.py
Normal file
@@ -0,0 +1,19 @@
|
||||
from tortoise import fields, models
|
||||
|
||||
from core.enums import Status
|
||||
|
||||
|
||||
class Table(models.Model):
|
||||
"""
|
||||
抽象模型
|
||||
"""
|
||||
|
||||
id = fields.IntField(pk=True, description="主键")
|
||||
status = fields.IntEnumField(Status, description="状态", default=Status.ACTIVE)
|
||||
created = fields.DatetimeField(auto_now_add=True, description="创建时间", null=True)
|
||||
modified = fields.DatetimeField(auto_now=True, description="更新时间", null=True)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
ordering = ["-created"]
|
||||
indexes = ("status",)
|
51
backend/core/utils.py
Normal file
51
backend/core/utils.py
Normal file
@@ -0,0 +1,51 @@
|
||||
def list_to_tree(
|
||||
menus, parent_flag: str = "pid", children_key: str = "children"
|
||||
) -> list:
|
||||
"""
|
||||
list 结构转 树结构
|
||||
:param menus: [{id:1, pid: 3}]
|
||||
:param parent_flag: 节点关系字段
|
||||
:param children_key: 生成树结构的子节点字段
|
||||
:return: list 类型的 树嵌套数据
|
||||
""" ""
|
||||
# 先转成字典 id作为key, 数据作为value
|
||||
menu_map = {menu["id"]: menu for menu in menus}
|
||||
arr = []
|
||||
for menu in menus:
|
||||
|
||||
# 有父级
|
||||
if mid := menu.get(parent_flag):
|
||||
# 有 子项的情况
|
||||
if result := menu_map[mid].get(children_key):
|
||||
result.append(menu)
|
||||
else:
|
||||
# 无子项的情况
|
||||
menu_map[mid][children_key] = [menu]
|
||||
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$"
|
||||
)
|
||||
])
|
Reference in New Issue
Block a user