功能(启动): 添加应用启动脚本

- start_app.py: 生产模式启动(自动打开浏览器)
- start_dev.py: 开发模式启动(热重载)
- start_desktop.py: 桌面客户端启动(pywebview)
- scripts/init_database.py: 数据库初始化脚本
This commit is contained in:
w
2026-02-21 09:48:01 +08:00
parent 587c8728de
commit f0354eecbc
4 changed files with 331 additions and 0 deletions
+40
View File
@@ -0,0 +1,40 @@
#!/usr/bin/env python3
"""数据库初始化脚本"""
import asyncio
import sys
from pathlib import Path
# 添加项目根目录到 Python 路径
project_root = Path(__file__).parent.parent
sys.path.insert(0, str(project_root))
async def init_database():
"""初始化数据库表"""
print("正在初始化数据库...")
try:
from backend.database import init_db
# 调用异步初始化函数
await init_db()
print("✓ 数据库初始化成功")
return True
except Exception as e:
print(f"✗ 数据库初始化失败: {e}")
import traceback
traceback.print_exc()
return False
def main():
"""主函数"""
success = asyncio.run(init_database())
sys.exit(0 if success else 1)
if __name__ == "__main__":
main()
+95
View File
@@ -0,0 +1,95 @@
#!/usr/bin/env python3
"""
CountBot 应用启动脚本
生产模式启动,自动打开浏览器
"""
import os
import sys
import webbrowser
import threading
from pathlib import Path
# 添加项目根目录到 Python 路径
project_root = Path(__file__).parent
sys.path.insert(0, str(project_root))
# 跨平台 SSL 兼容性处理macOS 需要额外配置证书)
from backend.utils.ssl_compat import ensure_ssl_certificates
ensure_ssl_certificates()
def open_browser_delayed(url: str, delay: float = 2.0) -> None:
"""
延迟打开浏览器
Args:
url: 要打开的 URL
delay: 延迟时间(秒)
"""
def _open():
import time
time.sleep(delay)
try:
webbrowser.open(url)
except Exception:
pass # 静默失败,不影响服务器启动
threading.Thread(target=_open, daemon=True).start()
def main() -> None:
"""启动应用"""
import uvicorn
from backend.utils.logger import setup_logger
from backend.utils.process_manager import setup_graceful_shutdown
from loguru import logger
# 初始化日志系统
setup_logger()
# 设置优雅关闭机制(包括清理孤儿进程、写入 PID、注册信号处理器
process_manager = setup_graceful_shutdown(logger=logger)
# 配置
host = os.getenv("HOST", "127.0.0.1")
port = int(os.getenv("PORT", "8000"))
# 确保环境变量与实际绑定地址一致(供 app.py 读取)
os.environ["HOST"] = host
# 打印启动信息
logger.info("=" * 60)
logger.info("CountBot 启动中...")
if host == "127.0.0.1":
logger.info("远程访问已开启 — 监听所有网络接口")
logger.info(f"本地访问: http://localhost:{port}")
logger.info(f"远程访问: http://<your-ip>:{port}")
else:
logger.info(f"访问地址: http://localhost:{port}")
logger.info("如需远程访问,请设置 HOST=0.0.0.0")
logger.info("=" * 60)
try:
# 启动服务器
uvicorn.run(
"backend.app:app",
host=host,
port=port,
reload=False, # 生产模式不启用热重载
log_level="info"
)
except KeyboardInterrupt:
logger.info("Received keyboard interrupt")
except Exception as e:
logger.error(f"Server error: {e}")
raise
finally:
# 确保清理 PID 文件
process_manager.remove_pid_file()
logger.info("Application shutdown complete")
if __name__ == "__main__":
main()
+151
View File
@@ -0,0 +1,151 @@
#!/usr/bin/env python3
"""CountBot Desktop — pywebview 桌面启动入口"""
import os
import sys
import platform
import threading
from pathlib import Path
# 项目根目录(兼容 PyInstaller 打包)
if getattr(sys, "frozen", False):
PROJECT_ROOT = Path(sys._MEIPASS)
else:
PROJECT_ROOT = Path(__file__).parent
sys.path.insert(0, str(PROJECT_ROOT))
from backend.utils.ssl_compat import ensure_ssl_certificates
ensure_ssl_certificates()
_server = None
RESOURCES_DIR = PROJECT_ROOT / "resources"
# ── 图标 ──────────────────────────────────────────────
def get_icon_path() -> str | None:
"""按平台返回图标路径: .ico(Win) / .icns(Mac) / .png(Linux)"""
name_map = {"Windows": "countbot.ico", "Darwin": "countbot.icns"}
icon = RESOURCES_DIR / name_map.get(platform.system(), "countbot.png")
return str(icon) if icon.exists() else None
def _set_macos_dock_icon(path: str) -> None:
"""通过 PyObjC 设置 macOS Dock 图标"""
try:
from AppKit import NSApplication, NSImage
img = NSImage.alloc().initWithContentsOfFile_(path)
if img:
NSApplication.sharedApplication().setApplicationIconImage_(img)
except Exception:
pass
def _set_windows_app_id() -> None:
"""设置 Windows AppUserModelID使任务栏显示自定义图标"""
try:
import ctypes
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(
"countbot.desktop.app"
)
except Exception:
pass
# ── 后端服务 ──────────────────────────────────────────
def _start_backend(host: str, port: int) -> None:
"""后台线程启动 FastAPI/Uvicorn"""
global _server
import uvicorn
from loguru import logger
try:
cfg = uvicorn.Config("backend.app:app", host=host, port=port,
reload=False, log_level="info")
_server = uvicorn.Server(cfg)
_server.run()
except Exception as e:
logger.error(f"后端启动失败: {e}")
sys.exit(1)
def _shutdown() -> None:
global _server
if _server:
_server.should_exit = True
def _wait_for_server(host: str, port: int, timeout: float = 15.0) -> bool:
"""轮询 /api/health 直到后端就绪"""
import time, urllib.request
url = f"http://{host}:{port}/api/health"
deadline = time.time() + timeout
while time.time() < deadline:
try:
if urllib.request.urlopen(url, timeout=2).status == 200:
return True
except Exception:
pass
time.sleep(0.3)
return False
def _check_frontend() -> bool:
index = PROJECT_ROOT / "frontend" / "dist" / "index.html"
return index.exists()
# ── 主入口 ────────────────────────────────────────────
def main():
import webview
from loguru import logger
host = os.getenv("HOST", "127.0.0.1")
port = int(os.getenv("PORT", "8000"))
os.environ["HOST"] = host
logger.info(f"CountBot Desktop 启动中… http://{host}:{port}")
if not _check_frontend():
logger.error("frontend/dist/index.html 不存在,无法启动")
sys.exit(1)
# 启动后端
threading.Thread(target=_start_backend, args=(host, port), daemon=True).start()
if not _wait_for_server(host, port):
logger.error("后端启动超时")
sys.exit(1)
# 设置平台图标
icon_path = get_icon_path()
if icon_path:
logger.info(f"图标: {icon_path}")
if platform.system() == "Darwin":
_set_macos_dock_icon(icon_path)
elif platform.system() == "Windows":
_set_windows_app_id()
# 创建窗口
window = webview.create_window(
title="CountBot Desktop",
url=f"http://{host}:{port}",
width=960, height=680,
min_size=(720, 480),
resizable=True, text_select=True,
)
window.events.closing += lambda: _shutdown()
start_kwargs = {"debug": os.getenv("DEBUG", "").lower() in ("1", "true")}
if icon_path:
start_kwargs["icon"] = icon_path
webview.start(**start_kwargs)
logger.info("CountBot Desktop 已退出")
os._exit(0)
if __name__ == "__main__":
main()
+45
View File
@@ -0,0 +1,45 @@
#!/usr/bin/env python3
"""
CountBot 应用启动脚本
开发模式启动,支持热重载
"""
import os
import sys
from pathlib import Path
# 添加项目根目录到 Python 路径
project_root = Path(__file__).parent
sys.path.insert(0, str(project_root))
def main():
"""启动应用(开发模式)"""
import uvicorn
from loguru import logger
# 配置
host = os.getenv("HOST", "127.0.0.1")
port = int(os.getenv("PORT", "8000"))
logger.info("=" * 60)
logger.info("CountBot 开发模式启动中...")
logger.info(f"访问地址: http://localhost:{port}")
logger.info("热重载已启用")
logger.info("=" * 60)
# 不自动打开浏览器,用户可以手动访问
# 启动服务器(开发模式)
uvicorn.run(
"backend.app:app",
host=host,
port=port,
reload=True, # 开发模式启用热重载
reload_dirs=["backend"], # 监控 backend 目录
log_level="debug"
)
if __name__ == "__main__":
main()