feat: 初始化仓库

This commit is contained in:
2025-02-12 02:38:29 +08:00
commit 46e9e79670
67 changed files with 8960 additions and 0 deletions

7
controller/__init__.py Normal file
View File

@@ -0,0 +1,7 @@
# _*_ coding : UTF-8 _*_
# @Time : 2025/01/19 00:57
# @UpdateTime : 2025/01/19 00:57
# @Author : sonder
# @File : __init__.py.py
# @Software : PyCharm
# @Comment : 本程序

258
controller/login.py Normal file
View File

@@ -0,0 +1,258 @@
# _*_ coding : UTF-8 _*_
# @Time : 2025/01/19 01:42
# @UpdateTime : 2025/01/19 01:42
# @Author : sonder
# @File : login.py
# @Software : PyCharm
# @Comment : 本程序
import uuid
from datetime import timedelta, datetime
from typing import Optional, Union
from fastapi import Form, Depends, Request
from fastapi.encoders import jsonable_encoder
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from jose import jwt
from jose.exceptions import JWEInvalidAuth, ExpiredSignatureError, JWEError
from config.constant import RedisKeyConfig
from config.env import JwtConfig
from controller.query import QueryController
from exceptions.exception import AuthException
from models import LoginLog
from schemas.login import LoginParams
from utils.log import logger
from utils.password import Password
oauth2_scheme = OAuth2PasswordBearer(tokenUrl='login')
class CustomOAuth2PasswordRequestForm(OAuth2PasswordRequestForm):
"""
自定义OAuth2PasswordRequestForm类增加验证码及会话编号参数
"""
def __init__(
self,
grant_type: str = Form(default=None, regex='password'),
username: str = Form(..., description="用户账号"),
password: str = Form(..., description="用户密码"),
scope: str = Form(default=''),
client_id: Optional[str] = Form(default=None),
client_secret: Optional[str] = Form(default=None),
loginDays: Optional[int] = Form(default=1),
code: Optional[str] = Form(default=''),
uuid: Optional[str] = Form(default=''),
):
super().__init__(
grant_type=grant_type,
username=username,
password=password,
scope=scope,
client_id=client_id,
client_secret=client_secret,
)
self.code = code
self.uuid = uuid
self.loginDays = loginDays
class LoginController:
"""
登录控制器
"""
@classmethod
async def login(cls, params: LoginParams):
"""
登录
:param params:
:return:
"""
result = await QueryController.get_user_by_username(params.username)
if result and await Password.verify_password(params.password, result.password):
userInfo = await QueryController.get_user_info(user_id=result.id.__str__())
logger.success(f"用户 {params.username} 登录成功")
session_id = uuid.uuid4().__str__()
accessToken = await cls.create_token(
data={"user": jsonable_encoder(userInfo), "id": result.id.__str__(), "session_id": session_id},
expires_delta=timedelta(minutes=params.loginDays * 24 * 60))
expiresTime = (datetime.now() + timedelta(minutes=params.loginDays * 24 * 60)).timestamp()
refreshToken = await cls.create_token(
data={"user": jsonable_encoder(userInfo), "id": result.id.__str__(), "session_id": session_id},
expires_delta=timedelta(minutes=(params.loginDays * 24 + 2) * 60))
return {"status": True, "accessToken": accessToken, "refreshToken": refreshToken,
"userInfo": userInfo,
"expiresTime": expiresTime, "session_id": session_id, "expiresIn": params.loginDays * 24 * 60}
logger.error(f"用户 {params.username} 登录失败")
return {"status": False}
@classmethod
async def create_token(cls, data: dict, expires_delta: Union[timedelta, None] = None) -> str:
"""
创建token
:param data: 存储数据
:param expires_delta: 过期时间
:return: token
"""
to_copy = data.copy()
if expires_delta:
expire = datetime.now() + expires_delta
else:
expire = datetime.now() + timedelta(minutes=JwtConfig.jwt_expire_minutes)
to_copy.update({"exp": expire})
return jwt.encode(claims=to_copy, key=JwtConfig.jwt_secret_key, algorithm=JwtConfig.jwt_algorithm)
@classmethod
async def get_current_user(cls, request: Request = Request, token: str = Depends(oauth2_scheme)):
"""
获取当前用户
:param request:
:param token:
:return:
"""
try:
if token.startswith('Bearer'):
token = token.split(' ')[1]
payload = jwt.decode(token=token, key=JwtConfig.jwt_secret_key, algorithms=[JwtConfig.jwt_algorithm])
user_id: str = payload.get("id", "")
session_id: str = payload.get('session_id', "")
if not user_id:
logger.warning('用户token不合法')
raise AuthException(data='', message='用户token不合法')
except (JWEInvalidAuth, ExpiredSignatureError, JWEError):
logger.warning('用户token已失效请重新登录')
raise AuthException(data='', message='用户token已失效请重新登录')
userInfo = await request.app.state.redis.get(f'{RedisKeyConfig.USER_INFO.key}:{user_id}')
if userInfo:
userInfo = eval(userInfo)
if not userInfo:
userInfo = await QueryController.get_user_info(user_id=user_id)
await request.app.state.redis.set(f'{RedisKeyConfig.USER_INFO.key}:{user_id}',
str(jsonable_encoder(userInfo)),
ex=timedelta(minutes=5))
if not userInfo:
logger.warning('用户token不合法')
raise AuthException(data='', message='用户token不合法')
redis_token = await request.app.state.redis.get(f'{RedisKeyConfig.ACCESS_TOKEN.key}:{session_id}')
if not redis_token:
logger.warning('用户token已失效请重新登录')
raise AuthException(data='', message='用户token已失效请重新登录')
return userInfo
@classmethod
async def logout(cls, request: Request = Request, token: str = Depends(oauth2_scheme)) -> bool:
"""
登出
"""
try:
if token.startswith('Bearer'):
token = token.split(' ')[1]
payload = jwt.decode(token=token, key=JwtConfig.jwt_secret_key, algorithms=[JwtConfig.jwt_algorithm])
session_id: str = payload.get('session_id', "")
except (JWEInvalidAuth, ExpiredSignatureError, JWEError):
logger.warning('用户token已失效请重新登录')
raise AuthException(data='', message='用户token已失效请重新登录')
redis_token = await request.app.state.redis.get(f'{RedisKeyConfig.ACCESS_TOKEN.key}:{session_id}')
if redis_token == token:
await request.app.state.redis.delete(f'{RedisKeyConfig.ACCESS_TOKEN.key}:{session_id}')
return True
return False
@classmethod
async def get_user_routes(cls, user_id: str) -> Union[list, None]:
"""
获取用户路由
"""
permissions = await QueryController.get_user_permissions(user_id=user_id)
for permission in permissions:
permission["id"] = str(permission["id"])
permission["parentId"] = str(permission["parentId"]) if permission.get("parentId") else ""
permissions = await cls.find_complete_data(permissions)
return permissions
@classmethod
async def find_node_recursive(cls, node_id: str, data: list) -> dict:
"""
递归查找节点
:param node_id: 节点ID
:param data: 数据
"""
result = {}
data = list(filter(lambda x: x.get('type') == 0, data))
for item in data:
if item["id"] == node_id:
children = []
for child_item in data:
if child_item["parentId"] == node_id:
child_node = await cls.find_node_recursive(child_item["id"], data)
if child_node:
children.append(child_node)
result = {
"name": item["name"],
"path": item["path"],
"meta": {
"title": item["title"],
"rank": item["rank"],
"icon": item["icon"],
"permissions": [item["auths"]],
},
"children": children
}
if item["component"]:
result["component"] = item["component"].replace(".vue", "").replace(".ts", "").replace(".tsx",
"").replace(
".js", "").replace(".jsx", "").strip()
if item["redirect"]:
result["redirect"] = item["redirect"]
if result["name"] == "":
result.pop("name")
if result["children"] == []:
result.pop("children")
break
return result
@classmethod
async def find_complete_data(cls, data: list) -> list:
"""
查找完整数据
:param data: 数据
"""
complete_data = []
root_ids = [item["id"] for item in data if not item["parentId"]]
for root_id in root_ids:
complete_data.append(await cls.find_node_recursive(root_id, data))
return complete_data
@classmethod
async def get_online_user(cls, request: Request) -> list:
"""
获取在线用户
"""
access_token_keys = await request.app.state.redis.keys(f'{RedisKeyConfig.ACCESS_TOKEN.key}*')
if not access_token_keys:
access_token_keys = []
access_token_values_list = [await request.app.state.redis.get(key) for key in access_token_keys]
online_info_list = []
for item in access_token_values_list:
payload = jwt.decode(item, JwtConfig.jwt_secret_key, algorithms=[JwtConfig.jwt_algorithm])
session_id = payload.get("session_id")
result = await LoginLog.get_or_none(session_id=session_id).values(
id="id",
user_id="user__id",
username="user__username",
user_nickname="user__nickname",
department_id="user__department__id",
department_name="user__department__name",
login_ip="login_ip",
login_location="login_location",
browser="browser",
os="os",
status="status",
login_time="login_time",
session_id="session_id",
create_time="create_time",
update_time="update_time"
)
online_info_list.append(result)
return online_info_list

132
controller/query.py Normal file
View File

@@ -0,0 +1,132 @@
# _*_ coding : UTF-8 _*_
# @Time : 2025/01/23 21:22
# @UpdateTime : 2025/01/23 21:22
# @Author : sonder
# @File : query.py
# @Software : PyCharm
# @Comment : 本程序
from typing import Union
from tortoise.expressions import Q
from models import User, UserRole, RolePermission
from utils.common import filterKeyValues
class QueryController:
"""
数据库查询控制器
"""
@classmethod
async def get_user_by_username(cls, username: str) -> Union[User, None]:
"""
根据用户名获取用户信息
:param username:用户名|手机号|邮箱
:return:
"""
return await User.get_or_none(Q(username=username) | Q(email=username) | Q(phone=username), del_flag=1)
@classmethod
async def get_user_by_id(cls, user_id: str) -> Union[User, None]:
"""
:param user_id:
:return:
"""
return await User.get_or_none(id=user_id, del_flag=1)
@classmethod
async def get_user_info(cls, user_id: str) -> Union[dict, None]:
"""
:param user_id:用户ID
"""
# 获取用户信息
userInfo = await User.get_or_none(id=user_id, del_flag=1).values(
id="id",
username="username",
phone="phone",
email="email",
avatar="avatar",
nickname="nickname",
gender="gender",
status="status",
create_time="create_time",
update_time="update_time",
department_id="department__id",
department_name="department__name"
)
# 获取用户角色
userRoles = await UserRole.filter(user_id=user_id, del_flag=1).values(
role_id="role__id",
role_name="role__name",
role_code="role__code"
)
# 获取用户角色标识
userRole = await filterKeyValues(userRoles, "role_code")
# 获取用户角色ID
userRoleIds = await filterKeyValues(userRoles, "role_id")
# 根据用户角色ID获取用户权限
permissions = []
for item in userRoleIds:
permission = await RolePermission.filter(role_id=item, del_flag=1).values(
permission_id="permission__id",
permission_name="permission__name",
permission_auths="permission__auths"
)
permissions.extend(permission)
permissions = await filterKeyValues(permissions, "permission_auths")
permissions = list(set(permissions))
userInfo["roles"] = userRole
userInfo["permissions"] = permissions
return userInfo
@classmethod
async def register_user_before(cls, username: str, phone: str, email: str) -> Union[User, None]:
"""
注册用户前,检查用户名、手机号、邮箱是否已存在
:param phone:
:param username:
:param email:
:return:
"""
return await User.get_or_none(Q(username=username) | Q(email=email) | Q(phone=phone), del_flag=1)
@classmethod
async def get_user_permissions(cls, user_id: str) -> Union[list, None]:
"""
获取用户权限
"""
# 获取用户角色
userRoles = await UserRole.filter(user_id=user_id, del_flag=1).values(
role_id="role__id",
role_name="role__name",
role_code="role__code"
)
# 获取用户角色ID
userRoleIds = await filterKeyValues(userRoles, "role_id")
# 根据用户角色ID获取用户权限
permissions = []
for item in userRoleIds:
permission = await RolePermission.filter(role_id=item, del_flag=1).values(
id="permission__id",
parentId="permission__parent_id",
name="permission__name",
title="permission__title",
type="permission__menu_type",
path="permission__path",
component="permission__component",
redirect="permission__redirect",
rank="permission__rank",
icon="permission__icon",
extraIcon="permission__extra_icon",
enterTransition="permission__enter_transition",
leaveTransition="permission__leave_transition",
activePath="permission__active_path",
auths="permission__auths",
keepAlive="permission__keep_alive",
hiddenTag="permission__hidden_tag",
showLink="permission__show_link",
showParent="permission__show_parent"
)
permissions.extend(permission)
return permissions