From 46607a68df866c1efecf9db1e3b46e23c1809687 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=A6=82=E5=A8=81?= Date: Fri, 22 Aug 2025 18:44:12 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=99=BB=E5=BD=95=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E6=8E=A7=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/config/init.json | 13 +-- backend/app/core/db.py | 23 +++++- backend/app/core/logger.py | 32 +++++++- backend/app/main.py | 75 ++++++++++++++++-- .../models/__init__.py} | 0 backend/app/models/db/user.py | 4 +- backend/app/models/schemas/system.py | 14 ++-- backend/app/models/schemas/user.py | 13 +++ backend/requirements.txt | 1 + backend/your_workflow | Bin 16384 -> 0 bytes backend/your_workflow-shm | Bin 32768 -> 0 bytes 11 files changed, 148 insertions(+), 27 deletions(-) rename backend/{your_workflow-wal => app/models/__init__.py} (100%) create mode 100644 backend/app/models/schemas/user.py delete mode 100644 backend/your_workflow delete mode 100644 backend/your_workflow-shm diff --git a/backend/app/config/init.json b/backend/app/config/init.json index 0b5a6b6..165657e 100644 --- a/backend/app/config/init.json +++ b/backend/app/config/init.json @@ -1,6 +1,6 @@ { "is_init": true, - "db_type": "sqlite", + "db_type": "mysql", "mysql": { "host": "", "port": "", @@ -8,9 +8,10 @@ "password": "", "db_name": "" }, - "db_host": "string", - "db_port": "string", - "db_user": "string", - "db_password": "string", - "db_name": "your_workflow" + "db_host": "localhost", + "db_port": 3306, + "db_user": "root", + "db_password": "12345", + "db_name": "your_workflow", + "admin_password": "admin" } \ No newline at end of file diff --git a/backend/app/core/db.py b/backend/app/core/db.py index bbf30ee..3532131 100644 --- a/backend/app/core/db.py +++ b/backend/app/core/db.py @@ -1,13 +1,23 @@ from tortoise.contrib.fastapi import Tortoise +from .logger import logger +from pathlib import Path +from models.db.user import User +import hashlib -async def init_tortoise(db_type: str, db_config: dict): + +async def init_tortoise( + db_type: str, db_config: dict, admin_password: str | None = None +): if db_type == "mysql": db_url = ( f"mysql://{db_config['db_user']}:{db_config['db_password']}" f"@{db_config['db_host']}:{db_config['db_port']}/{db_config['db_name']}" ) elif db_type == "sqlite": - db_url = f"sqlite://{db_config['db_name']}" + BASE_DIR = Path(__file__).parent.parent.resolve() / "sqlite" + BASE_DIR.mkdir(parents=True, exist_ok=True) + db_file = BASE_DIR / db_config["db_name"] + db_url = f"sqlite://{db_file}" else: raise ValueError("Unsupported db_type") try: @@ -16,9 +26,18 @@ async def init_tortoise(db_type: str, db_config: dict): modules={"models": ["models.db"]}, # 这里写你模型所在路径 ) await Tortoise.generate_schemas() + + if admin_password: + admin_user = await User.filter(username="admin").first() + if not admin_user: + await User( + password=hashlib.md5(admin_password.encode()).hexdigest(), username="admin", level=0 + ).save() return True except Exception as e: + logger.error(e) raise e + async def close_tortoise(): await Tortoise.close_connections() diff --git a/backend/app/core/logger.py b/backend/app/core/logger.py index 8581fb9..93b1d20 100644 --- a/backend/app/core/logger.py +++ b/backend/app/core/logger.py @@ -1,7 +1,33 @@ import logging -logging.basicConfig( - level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s:\n%(message)s" -) +# 定义颜色 +RESET = "\033[0m" +COLORS = { + "DEBUG": "\033[36m", # 青色 + "INFO": "\033[32m", # 绿色 + "WARNING": "\033[33m", # 黄色 + "ERROR": "\033[31m", # 红色 + "CRITICAL": "\033[41m", # 红底 +} + +class ColorFormatter(logging.Formatter): + def format(self, record): + color = COLORS.get(record.levelname, RESET) + record.levelname = f"{color}{record.levelname}{RESET}" + return super().format(record) + + +# 创建 logger logger = logging.getLogger("workflow") +logger.setLevel(logging.DEBUG) + +# 创建控制台 handler +ch = logging.StreamHandler() +ch.setLevel(logging.DEBUG) + +# 设置带颜色的 formatter +formatter = ColorFormatter("%(levelname)s: \t %(name)s - %(asctime)s - %(message)s") +ch.setFormatter(formatter) + +logger.addHandler(ch) diff --git a/backend/app/main.py b/backend/app/main.py index 80ba2b2..2e46e0e 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -1,10 +1,43 @@ import uvicorn -from fastapi import FastAPI -from models.schemas.system import InitStatusResponse, InitConfigRequest +import models.db as DB +import uuid +import time +from fastapi import FastAPI, Depends, HTTPException +from models.schemas.system import InitStatusResponse, InitConfigRequest, BaseResponse +from models.schemas.user import LoginRequest, LoginResponse from core.config import load_init_config, save_init_config from core.db import init_tortoise, close_tortoise from core.logger import logger from contextlib import asynccontextmanager +from fastapi.security import OAuth2PasswordBearer +from apscheduler.schedulers.asyncio import AsyncIOScheduler + + +# 简单的登陆状态存储 +sessions: dict[str, tuple[float, DB.User]] = {} +oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") +scheduler = AsyncIOScheduler() + + +# 定时清理过期session +def clear_session(): + TTL = 60 * 60 * 24 + now = time.time() + expired_keys = [token for token, (expires_at, _) in sessions.items() if expires_at + TTL < now] + logger.info(f"检测 session, {now} {sessions}") + for token in expired_keys: + sessions.pop(token, None) + + if expired_keys: + logger.info(f"清理过期 now: {now} session: {expired_keys}") + + +def get_current_user(token:str = Depends(oauth2_scheme)) -> DB.User | None: + if not token or token not in sessions: + raise HTTPException(status_code=401, detail="用户未登陆") + _, user = sessions[token] + sessions[token] = time.time(), user + return user async def is_init(): @@ -15,16 +48,23 @@ async def is_init(): @asynccontextmanager async def lifespan(app: FastAPI): - + # 数据库 if await is_init(): - logger.info("初始化数据库") init_config = await load_init_config() - await init_tortoise(init_config["db_type"], init_config) + await init_tortoise(init_config["db_type"], init_config, init_config.get("admin_password")) + logger.info("初始化数据库") + # 定时器 + scheduler.add_job(clear_session, "interval", seconds=5) + scheduler.start() + logger.info("初始化定时器") yield - logger.info("关闭数据库") + scheduler.shutdown() + logger.info("关闭定时器") + await close_tortoise() + logger.info("关闭数据库") def create_app(): @@ -62,9 +102,10 @@ def create_app(): init_config["db_user"] = request.db_user init_config["db_password"] = request.db_password init_config["db_name"] = request.db_name + init_config["admin_password"] = request.admin_password init_config["is_init"] = True - init_success = await init_tortoise(request.db_type, init_config) + init_success = await init_tortoise(request.db_type, init_config, request.admin_password) if init_success: await save_init_config(init_config) @@ -73,6 +114,26 @@ def create_app(): return InitStatusResponse(success=False, message="初始化数据库失败") except Exception as e: return InitStatusResponse(success=False, message=str(e)) + + @app.post("/login", tags=["User"], description="用户登录", response_model=LoginResponse) + async def login(login_info: LoginRequest): + user = await DB.User.filter(username=login_info.username, password=login_info.get_md5_password()).first() + if user: + token = uuid.uuid4().hex + sessions[token] = time.time(), user + logger.info(sessions) + return LoginResponse(data={ + "token": token, + "user": user.json(del_columns=["password"]) + }) + return LoginResponse(success=False, message="帐号或密码错误") + + @app.post("/logout", tags=["User"], description="退出登录", response_model=BaseResponse) + async def logout(token:str): + sessions.pop(token, None) + logger.info(sessions) + return BaseResponse() + return app diff --git a/backend/your_workflow-wal b/backend/app/models/__init__.py similarity index 100% rename from backend/your_workflow-wal rename to backend/app/models/__init__.py diff --git a/backend/app/models/db/user.py b/backend/app/models/db/user.py index 3e8006a..4ceee3a 100644 --- a/backend/app/models/db/user.py +++ b/backend/app/models/db/user.py @@ -2,8 +2,8 @@ from tortoise import fields from .system import BaseModel class User(BaseModel): - username = fields.CharField(max_length=50, unique=True) - password = fields.CharField(max_length=128) + username = fields.CharField(max_length=50, null=True) + password = fields.CharField(max_length=128, null=True) level = fields.IntField(default=1, null=True, description="0:超级用户 1:普通用户") class Meta: diff --git a/backend/app/models/schemas/system.py b/backend/app/models/schemas/system.py index ecd3468..7a38a23 100644 --- a/backend/app/models/schemas/system.py +++ b/backend/app/models/schemas/system.py @@ -7,13 +7,13 @@ class BaseResponse(BaseModel): class InitConfigRequest(BaseModel): - db_type: str - admin_password: str - db_host: str | None = None - db_port: str | None = None - db_user: str | None = None - db_password: str | None = None - db_name: str | None = None + db_type: str = "sqlite" + admin_password: str = "admin" + db_host: str | None = "localhost" + db_port: int | None = 3306 + db_user: str | None = "root" + db_password: str | None = "12345" + db_name: str | None = "your_workflow" class InitStatusResponse(BaseResponse): diff --git a/backend/app/models/schemas/user.py b/backend/app/models/schemas/user.py new file mode 100644 index 0000000..f70f41d --- /dev/null +++ b/backend/app/models/schemas/user.py @@ -0,0 +1,13 @@ +from pydantic import BaseModel +from .system import BaseResponse +import hashlib + +class LoginRequest(BaseModel): + username: str = "admin" + password: str = "admin" + + def get_md5_password(self): + return hashlib.md5(self.password.encode()).hexdigest() + +class LoginResponse(BaseResponse): + data: dict | None \ No newline at end of file diff --git a/backend/requirements.txt b/backend/requirements.txt index c300cb5..9f6ed57 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -2,3 +2,4 @@ fastapi==0.116.1 uvicorn==0.35.0 tortoise-orm==0.25.1 aiomysql==0.2.0 +apscheduler==3.11.0 \ No newline at end of file diff --git a/backend/your_workflow b/backend/your_workflow deleted file mode 100644 index cb386eb283f635f77c5422bc668f2cbe0aca1e4c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16384 zcmeI%&r0J!90%}8Dk_Dlx1I_=a%xqqRRr;5tg{+SVvWf{Pa)b73jL!^s$PU;LD&cI zqA#E)kGtRt?3?KB11xxS(oHH`*wexu%6DKgndJ9NGM|~tki!GB83{V+_S!*2t89*` zD%&JtjH$9t$eI>Ko{aX>dw*+Hw$ypb&Azfs?iZWcnf)#s6bL{70uX=z1Rwwb2tWV= z5cppLwajFGVL_=!!BIQ&%C*5=R4O=E# z+;+*{m(?@P#otNCJrT$ckD6_vk!x7|(ABNlFp9SM2i-GWs(Owi)BRyclHWgV{9C>p zVGN+rN8PFJ>P~TOx%B>e&o&M`p1kWc2*ZnRPYyGUt*orS2PaW2aV}Z|iQ&grs60>0 z8$Z5YzdYT)+&nyg`9_tE=iA4hpYD?Gd^uYxtCRU^L1E2KLtG}a6!fF+pzo*NekDE2 z$fY(NFSlHLn2Qf65P$##AOHafKmY;|fB*y_009V$t3Wm$8210;I=vVd1Rwwb2tWV= p5P$##AOHafKwuz%|NmGB5P$##AOHafKmY;|fB*y_0D