Refactor backend MVC (#2)
* docs(requirements.txt):升级fastapi、uvicorn版本 * refactor(user):重构用户router、service * ref: role list api * doc: 1 * refactor(backend): mvc ref
This commit is contained in:
@@ -1,14 +1,14 @@
|
||||
"""数据库通用查询方法"""
|
||||
from typing import Optional
|
||||
from tortoise import connections
|
||||
|
||||
from tortoise import connections, models
|
||||
from models import MenuModel, RoleMenuModel, RoleModel, UserModel, UserRoleModel
|
||||
|
||||
|
||||
class DbHelper:
|
||||
def __init__(self, model: models.Model):
|
||||
def __init__(self, model):
|
||||
"""
|
||||
初始化
|
||||
:param model: 模型类
|
||||
:param model: 模型类 orm model
|
||||
"""
|
||||
self.model = model
|
||||
|
||||
@@ -20,7 +20,7 @@ class DbHelper:
|
||||
"""
|
||||
return self.model.filter(**kwargs)
|
||||
|
||||
async def select(self, kwargs: dict = None) -> Optional[models.Model]:
|
||||
async def select(self, kwargs: dict = None):
|
||||
"""
|
||||
查询符合条件的第一个对象, 查无结果时返回None
|
||||
:param kwargs: kwargs: {"name:"7y", "id": 1}
|
||||
@@ -58,7 +58,7 @@ class DbHelper:
|
||||
return await self.model.create(**data)
|
||||
|
||||
async def selects(
|
||||
self, offset: int, limit: int, kwargs: dict = None, order_by: str = None
|
||||
self, offset: int, limit: int, kwargs: dict = None, order_by: str = "-created"
|
||||
) -> dict:
|
||||
"""
|
||||
条件分页查询数据列表, 支持排序
|
||||
@@ -81,13 +81,13 @@ class DbHelper:
|
||||
items=await objs.offset(offset).limit(limit), total=await objs.count()
|
||||
)
|
||||
|
||||
async def inserts(self, objs: list[models.Model]):
|
||||
async def inserts(self, objs: list):
|
||||
"""
|
||||
批量新增数据
|
||||
:param objs: 模型列表
|
||||
:return:
|
||||
"""
|
||||
await self.model.bulk_create(objs)
|
||||
await self.model.bulk_create([self.model(**obj) for obj in objs])
|
||||
|
||||
@classmethod
|
||||
async def raw_sql(cls, sql: str, args: list = None):
|
||||
@@ -101,3 +101,48 @@ class DbHelper:
|
||||
if args is None:
|
||||
args = []
|
||||
return await db.execute_query_dict(sql, args)
|
||||
|
||||
|
||||
UserDao = DbHelper(UserModel)
|
||||
RoleDao = DbHelper(RoleModel)
|
||||
UserRoleDao = DbHelper(UserRoleModel)
|
||||
MenuDao = DbHelper(MenuModel)
|
||||
RoleMenuDao = DbHelper(RoleMenuModel)
|
||||
|
||||
|
||||
async def has_roles(uid):
|
||||
"""
|
||||
获取用户角色信息,激活的角色升序
|
||||
:param uid: 用户id
|
||||
:return:
|
||||
"""
|
||||
sql = """select r.id, r.name, ur.status from sys_role as r , sys_user_role as ur where r.id = ur.rid and
|
||||
ur.uid = (?) and r.status = 1 and ur.status !=9 order by ur.status desc
|
||||
"""
|
||||
return await UserRoleDao.raw_sql(sql, [uid])
|
||||
|
||||
|
||||
async def has_user(username):
|
||||
"""
|
||||
通过用户名检索数据是否存在
|
||||
:param username:
|
||||
:return:
|
||||
"""
|
||||
return await UserDao.select({"username": username, "status__not": 9})
|
||||
|
||||
|
||||
async def has_permissions(rid, is_menu=False):
|
||||
"""
|
||||
根据角色ID查到当前拥有的接口权限
|
||||
:param rid: 角色ID
|
||||
:param is_menu: 是否是菜单,默认不是 -》接口
|
||||
:return:
|
||||
"""
|
||||
filters = "m.api, m.method"
|
||||
if is_menu:
|
||||
filters = "m.id, m.name, m.icon, m.path, m.type, m.component, m.pid, m.identifier, m.api, m.method"
|
||||
sql = f"""
|
||||
select {filters}
|
||||
FROM sys_menu as m, sys_role_menu as srm WHERE m.id = srm.mid
|
||||
AND srm.rid = (?) and m.status != 9 order by m.id asc"""
|
||||
return await RoleMenuDao.raw_sql(sql, [rid])
|
||||
|
@@ -1,15 +1 @@
|
||||
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 = logging.getLogger("mini-rbac")
|
||||
logger.setLevel(logging.DEBUG)
|
||||
logger.addHandler(sh)
|
||||
from loguru import logger
|
||||
|
@@ -1,12 +1,28 @@
|
||||
from fastapi.middleware import Middleware
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from starlette.middleware.base import BaseHTTPMiddleware
|
||||
|
||||
from core.log import logger
|
||||
|
||||
|
||||
class CustomRequestLogMiddleware(BaseHTTPMiddleware):
|
||||
async def dispatch(self, request, call_next):
|
||||
logger.info(
|
||||
f"Client: {request.client} Method: {request.method} "
|
||||
f"Path: {request.url} Headers: {request.headers}"
|
||||
)
|
||||
# python-multipart == await request.form()
|
||||
response = await call_next(request)
|
||||
return response
|
||||
|
||||
|
||||
middlewares = [
|
||||
Middleware(CustomRequestLogMiddleware),
|
||||
Middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
),
|
||||
]
|
||||
|
@@ -6,10 +6,8 @@ from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
||||
from jose import JWTError, jwt
|
||||
from passlib.context import CryptContext
|
||||
|
||||
from core.dbhelper import has_permissions, has_roles, has_user
|
||||
from core.exceptions import PermissionsError, TokenAuthFailure
|
||||
from dbhelper.menu import get_apis
|
||||
from dbhelper.user import get_user, get_user_info
|
||||
from models import UserModel
|
||||
|
||||
# JWT
|
||||
SECRET_KEY = "lLNiBWPGiEmCLLR9kRGidgLY7Ac1rpSWwfGzTJpTmCU"
|
||||
@@ -59,20 +57,20 @@ async def check_token(security: HTTPAuthorizationCredentials = Depends(bearer)):
|
||||
try:
|
||||
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
|
||||
username: str = payload.get("sub")
|
||||
return await get_user({"username": username})
|
||||
return await has_user(username)
|
||||
except JWTError:
|
||||
raise TokenAuthFailure(403, "认证失败")
|
||||
|
||||
|
||||
async def check_permissions(request: Request, user: UserModel = Depends(check_token)):
|
||||
async def check_permissions(request: Request, user=Depends(check_token)):
|
||||
"""检查接口权限"""
|
||||
# 查询当前激活角色
|
||||
result = await get_user_info(user)
|
||||
active_rid = result["roles"][0]["id"]
|
||||
roles = await has_roles(user.id)
|
||||
active_rid = roles[0]["id"]
|
||||
|
||||
# 白名单 登录用户信息, 登录用户菜单信息
|
||||
whitelist = [(f"/user/{user.id}", "GET"), (f"/role/{active_rid}/menu", "GET")] + [
|
||||
(f"/user/role/{rid['id']}", "PUT") for rid in result["roles"]
|
||||
(f"/user/role/{rid['id']}", "PUT") for rid in roles
|
||||
]
|
||||
|
||||
if (request.url.path, request.method) in whitelist:
|
||||
@@ -82,11 +80,11 @@ async def check_permissions(request: Request, user: UserModel = Depends(check_to
|
||||
for k, v in request.path_params.items():
|
||||
api = api.replace(v, "{%s}" % k)
|
||||
|
||||
# 2. 登录之后查一次 后面去结果查 todo 更新权限时需要更新 , 最好结果放redis
|
||||
# todo 结果放redis
|
||||
cache_key = f"{user.username}_{active_rid}"
|
||||
# 缓存到fastapi 应用实例中
|
||||
if not hasattr(request.app.state, cache_key):
|
||||
setattr(request.app.state, cache_key, await get_apis(active_rid))
|
||||
setattr(request.app.state, cache_key, await has_permissions(active_rid))
|
||||
if {"api": api, "method": request.method} not in getattr(
|
||||
request.app.state, cache_key
|
||||
):
|
||||
|
63
backend/core/service.py
Normal file
63
backend/core/service.py
Normal file
@@ -0,0 +1,63 @@
|
||||
from core.dbhelper import DbHelper
|
||||
|
||||
|
||||
class Service:
|
||||
|
||||
filter_del = {"status__not": 9}
|
||||
|
||||
def __init__(self, dao: DbHelper):
|
||||
self.dao = dao
|
||||
|
||||
async def get_items(self, offset, limit):
|
||||
"""
|
||||
分页获取数据, 过滤掉删除
|
||||
:param offset: 起始值
|
||||
:param limit: 偏移量
|
||||
:return:
|
||||
"""
|
||||
skip = (offset - 1) * limit
|
||||
return dict(data=await self.dao.selects(skip, limit, Service.filter_del))
|
||||
|
||||
async def query_items(self, query):
|
||||
"""
|
||||
根据条件查询结果
|
||||
:param query:
|
||||
:return:
|
||||
"""
|
||||
size = query.limit
|
||||
skip = (query.offset - 1) * size
|
||||
del query.offset, query.limit
|
||||
filters = {f"{k}__contains": v for k, v in query.dict().items()}
|
||||
filters.update(Service.filter_del)
|
||||
return dict(data=await self.dao.selects(skip, size, filters))
|
||||
|
||||
async def delete_item(self, pk):
|
||||
"""
|
||||
逻辑删除数据
|
||||
:param pk:主键
|
||||
:return:
|
||||
"""
|
||||
filters = {"id": pk}
|
||||
filters.update(Service.filter_del)
|
||||
if await self.dao.update(filters, {"status": 9}) == 0:
|
||||
return dict(code=400, msg="数据不存在")
|
||||
return dict()
|
||||
|
||||
async def update_item(self, pk, data):
|
||||
"""
|
||||
更新数据,不通用,可重写
|
||||
:param pk: 主键
|
||||
:param data: pydantic model
|
||||
:return:
|
||||
"""
|
||||
if await self.dao.update({"id": pk}, data.dict()) == 0:
|
||||
return dict(code=400, msg="数据不存在")
|
||||
return dict()
|
||||
|
||||
async def create_item(self, data):
|
||||
"""
|
||||
创建数据,不通用可重写
|
||||
:param data: pydantic model
|
||||
:return:
|
||||
"""
|
||||
return await self.dao.insert(data.dict())
|
Reference in New Issue
Block a user