release: Elysia ToDo v1.0.0
鍏ㄦ爤涓汉淇℃伅绠$悊搴旂敤锛岄泦鎴愬緟鍔炰换鍔°€佷範鎯墦鍗°€佺邯蹇垫棩鎻愰啋銆佽祫浜ф€昏鍔熻兘銆 Made-with: Cursor
This commit is contained in:
229
main.py
Normal file
229
main.py
Normal file
@@ -0,0 +1,229 @@
|
||||
"""
|
||||
爱莉希雅待办事项 - 项目启动入口
|
||||
|
||||
功能:
|
||||
1. 编译前端项目
|
||||
2. 启动 FastAPI 后端服务
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import shutil
|
||||
import subprocess
|
||||
import time
|
||||
|
||||
|
||||
# 路径配置
|
||||
PROJECT_ROOT = os.path.dirname(os.path.abspath(__file__))
|
||||
WEBUI_DIR = os.path.join(PROJECT_ROOT, "WebUI")
|
||||
DIST_DIR = os.path.join(WEBUI_DIR, "dist")
|
||||
WEBUI_TARGET = os.path.join(PROJECT_ROOT, "api", "webui")
|
||||
API_MAIN = os.path.join(PROJECT_ROOT, "api", "app", "main.py")
|
||||
|
||||
|
||||
def log_info(msg):
|
||||
"""打印信息"""
|
||||
print(f"[启动] {msg}")
|
||||
|
||||
|
||||
def log_error(msg):
|
||||
"""打印错误"""
|
||||
print(f"[错误] {msg}", file=sys.stderr)
|
||||
|
||||
|
||||
def needs_rebuild() -> bool:
|
||||
"""判断是否需要重新编译前端(基于文件修改时间)"""
|
||||
if not os.path.exists(DIST_DIR):
|
||||
return True
|
||||
src_dir = os.path.join(WEBUI_DIR, "src")
|
||||
if not os.path.exists(src_dir):
|
||||
return True
|
||||
# 获取 dist 目录最新修改时间
|
||||
dist_mtime = max(
|
||||
os.path.getmtime(os.path.join(dp, f))
|
||||
for dp, dn, filenames in os.walk(DIST_DIR)
|
||||
for f in filenames
|
||||
)
|
||||
# 检查 src 目录中是否有比 dist 更新的文件
|
||||
for dp, dn, filenames in os.walk(src_dir):
|
||||
for f in filenames:
|
||||
if f.endswith(('.vue', '.ts', '.js', '.scss', '.css')):
|
||||
filepath = os.path.join(dp, f)
|
||||
if os.path.getmtime(filepath) > dist_mtime:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def build_frontend():
|
||||
"""编译前端项目"""
|
||||
# 智能判断是否需要重新编译
|
||||
if not needs_rebuild():
|
||||
log_info("前端产物已是最新,跳过编译")
|
||||
return True
|
||||
|
||||
log_info("开始编译前端项目...")
|
||||
|
||||
# 检查 WebUI 目录是否存在
|
||||
if not os.path.exists(WEBUI_DIR):
|
||||
log_error(f"WebUI 目录不存在: {WEBUI_DIR}")
|
||||
return False
|
||||
|
||||
# 检查 node_modules 是否存在
|
||||
node_modules = os.path.join(WEBUI_DIR, "node_modules")
|
||||
if not os.path.exists(node_modules):
|
||||
log_info("安装前端依赖...")
|
||||
# Windows 上使用 npm.cmd
|
||||
npm_cmd = "npm.cmd" if os.name == "nt" else "npm"
|
||||
result = subprocess.run(
|
||||
[npm_cmd, "install"],
|
||||
cwd=WEBUI_DIR,
|
||||
encoding="utf-8",
|
||||
errors="ignore"
|
||||
)
|
||||
if result.returncode != 0:
|
||||
log_error(f"安装依赖失败")
|
||||
return False
|
||||
|
||||
# 执行编译
|
||||
log_info("执行 npm run build...")
|
||||
npm_cmd = "npm.cmd" if os.name == "nt" else "npm"
|
||||
result = subprocess.run(
|
||||
[npm_cmd, "run", "build"],
|
||||
cwd=WEBUI_DIR,
|
||||
encoding="utf-8",
|
||||
errors="ignore"
|
||||
)
|
||||
if result.returncode != 0:
|
||||
log_error(f"编译失败")
|
||||
return False
|
||||
|
||||
log_info("前端编译完成!")
|
||||
return True
|
||||
|
||||
|
||||
def copy_dist_to_webui():
|
||||
"""复制编译产物到 webui 目录"""
|
||||
log_info("复制编译产物到 webui 目录...")
|
||||
|
||||
# 检查 dist 目录是否存在
|
||||
if not os.path.exists(DIST_DIR):
|
||||
log_error(f"编译产物目录不存在: {DIST_DIR}")
|
||||
return False
|
||||
|
||||
# 删除旧的 webui 目录(如果存在)
|
||||
if os.path.exists(WEBUI_TARGET):
|
||||
shutil.rmtree(WEBUI_TARGET)
|
||||
|
||||
# 复制 dist 到 webui
|
||||
shutil.copytree(DIST_DIR, WEBUI_TARGET)
|
||||
log_info(f"编译产物已复制到: {WEBUI_TARGET}")
|
||||
return True
|
||||
|
||||
|
||||
def find_pid_on_port(port: int) -> int | None:
|
||||
"""查找占用指定端口的进程 PID"""
|
||||
if os.name == "nt":
|
||||
result = subprocess.run(
|
||||
["netstat", "-ano", "-p", "TCP"],
|
||||
capture_output=True, text=True, encoding="gbk", errors="ignore"
|
||||
)
|
||||
for line in result.stdout.splitlines():
|
||||
if f":{port}" in line and "LISTENING" in line:
|
||||
parts = line.split()
|
||||
return int(parts[-1])
|
||||
else:
|
||||
result = subprocess.run(
|
||||
["lsof", "-t", "-i", f":{port}", "-sTCP:LISTEN"],
|
||||
capture_output=True, text=True, errors="ignore"
|
||||
)
|
||||
output = result.stdout.strip()
|
||||
if output:
|
||||
return int(output.splitlines()[0])
|
||||
return None
|
||||
|
||||
|
||||
def kill_process(pid: int) -> bool:
|
||||
"""终止指定 PID 的进程"""
|
||||
log_info(f"正在终止占用端口的进程 (PID: {pid})...")
|
||||
try:
|
||||
if os.name == "nt":
|
||||
subprocess.run(["taskkill", "/PID", str(pid), "/F"],
|
||||
capture_output=True, timeout=10)
|
||||
else:
|
||||
os.kill(pid, 9)
|
||||
time.sleep(1)
|
||||
log_info(f"进程 {pid} 已终止")
|
||||
return True
|
||||
except Exception as e:
|
||||
log_error(f"终止进程 {pid} 失败: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def check_and_free_port(port: int) -> bool:
|
||||
"""检测端口是否被占用,如果被占用则尝试释放"""
|
||||
pid = find_pid_on_port(port)
|
||||
if pid is None:
|
||||
return True
|
||||
|
||||
log_info(f"端口 {port} 已被进程 {pid} 占用")
|
||||
if kill_process(pid):
|
||||
# 验证端口是否已释放
|
||||
if find_pid_on_port(port) is None:
|
||||
log_info(f"端口 {port} 已释放")
|
||||
return True
|
||||
log_error(f"端口 {port} 仍被占用,尝试再次终止...")
|
||||
time.sleep(2)
|
||||
pid2 = find_pid_on_port(port)
|
||||
if pid2 is not None:
|
||||
kill_process(pid2)
|
||||
if find_pid_on_port(port) is None:
|
||||
return True
|
||||
|
||||
log_error(f"无法释放端口 {port},请手动处理")
|
||||
return False
|
||||
|
||||
|
||||
def start_backend():
|
||||
"""启动后端服务"""
|
||||
log_info("启动后端服务...")
|
||||
|
||||
# 添加 api 目录到 Python 路径(不使用 os.chdir,避免全局副作用)
|
||||
api_dir = os.path.join(PROJECT_ROOT, "api")
|
||||
if api_dir not in sys.path:
|
||||
sys.path.insert(0, api_dir)
|
||||
|
||||
from app.config import HOST, PORT
|
||||
|
||||
# 检查端口是否被占用,自动释放
|
||||
if not check_and_free_port(PORT):
|
||||
log_error(f"端口 {PORT} 无法释放,启动失败")
|
||||
sys.exit(1)
|
||||
|
||||
import uvicorn
|
||||
|
||||
log_info(f"服务启动成功: http://{HOST}:{PORT}")
|
||||
log_info(f"API 文档: http://{HOST}:{PORT}/docs")
|
||||
log_info(f"前端页面: http://{HOST}:{PORT}/")
|
||||
|
||||
uvicorn.run("app.main:app", host=HOST, port=PORT, reload=False)
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
print("=" * 50)
|
||||
print(" 爱莉希雅待办事项 - 项目启动")
|
||||
print("=" * 50)
|
||||
|
||||
# 1. 编译前端
|
||||
if not build_frontend():
|
||||
sys.exit(1)
|
||||
|
||||
# 2. 复制编译产物
|
||||
if not copy_dist_to_webui():
|
||||
sys.exit(1)
|
||||
|
||||
# 3. 启动后端
|
||||
start_backend()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user