login page

This commit is contained in:
zy7y
2022-09-11 18:34:18 +08:00
commit a1c23c8cf8
52 changed files with 4149 additions and 0 deletions

0
backend/core/__init__.py Normal file
View File

35
backend/core/enums.py Normal file
View 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
View 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()

View File

@@ -0,0 +1,6 @@
from fastapi.exceptions import HTTPException
class TokenAuthFailure(HTTPException):
status_code = 401
detail = "认证失败"

15
backend/core/log.py Normal file
View 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)

View 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
View 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
View 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
View 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
View 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
View 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$"
)
])