"""SmartEDT 后端服务入口。 主要职责: - 加载配置与初始化日志 - 初始化数据库 schema/TimescaleDB - 构造核心服务(仿真、监控、鉴权/RBAC) - 挂载 HTTP/WebSocket 路由并启动 uvicorn """ from __future__ import annotations import argparse import asyncio import logging import multiprocessing import platform import sys from pathlib import Path # 修复 Windows 下 psycopg 异步连接的 Event Loop 问题 if platform.system() == "Windows": # 1. 强制设置 SelectorEventLoopPolicy asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) # 2. Monkeypatch 阻止 uvicorn 覆盖策略 (uvicorn 默认在 Windows 上强制使用 Proactor) # 这对 psycopg 3 (SQLAlchemy async) 是必须的,因为 Proactor 不支持 add_reader/add_writer original_set_policy = asyncio.set_event_loop_policy def patched_set_policy(policy): # 如果尝试设置 Proactor,则忽略并保持 Selector if isinstance(policy, asyncio.WindowsProactorEventLoopPolicy): return original_set_policy(policy) asyncio.set_event_loop_policy = patched_set_policy import uvicorn from dotenv import load_dotenv from fastapi import FastAPI, WebSocket from contextlib import asynccontextmanager from backend.config.settings import load_settings from backend.database.engine import create_engine, create_session_factory from backend.database.schema import init_schema, init_timescaledb from backend.services.broadcaster import Broadcaster from backend.services.simulation_manager import SimulationManager from backend.services.server_monitor import ServerMonitorService from backend.services.unity_socket_client import UnitySocketClient from backend.device.mock_vehicle import MockVehicleDevice from backend.api import auth_routes, rbac_routes, routes, unity_routes, user_routes, ws from backend.utils import configure_logging logger = logging.getLogger("backend") def _default_backend_log_file() -> Path | None: """在打包运行态下返回默认日志文件路径;开发态返回 None。""" if getattr(sys, "frozen", False): exe_dir = Path(sys.executable).resolve().parent return exe_dir / "logs" / "backend.log" return None def _force_windows_selector_event_loop_for_uvicorn() -> None: """避免 uvicorn 在 Windows 上切换到 ProactorEventLoop(与 psycopg async 不兼容)。""" if platform.system() != "Windows": return try: import uvicorn.loops.asyncio as uvicorn_asyncio_loop except Exception: return def _selector_loop_factory(use_subprocess: bool = False): return asyncio.SelectorEventLoop uvicorn_asyncio_loop.asyncio_loop_factory = _selector_loop_factory # 全局单例容器(简单实现) class Container: """简易全局容器:集中创建与持有配置、DB 引擎、session 工厂与各服务单例。""" def __init__(self): load_dotenv() self.settings = load_settings() configure_logging( "INFO" if not self.settings.server.debug else "DEBUG", log_file=_default_backend_log_file(), ) self.file_root = self.settings.files.root_path self.file_root.mkdir(parents=True, exist_ok=True) self.engine = create_engine(self.settings.database) self.session_factory = create_session_factory(self.engine) self.broadcaster = Broadcaster() self.unity_client = UnitySocketClient(self.settings.unity.host, self.settings.unity.port) # 实例化服务 self.simulation_manager = SimulationManager( self.session_factory, self.broadcaster, unity_client=self.unity_client, ) # 实例化监控服务 self.server_monitor = ServerMonitorService( self.session_factory, self.broadcaster ) container = Container() @asynccontextmanager async def lifespan(app: FastAPI): """FastAPI 生命周期:启动初始化与停机清理。""" # 启动前初始化 await init_schema(container.engine) if container.settings.database.timescaledb: try: await init_timescaledb(container.engine) except Exception as e: # 可能是已存在或权限问题,仅打印日志 logger.warning("TimescaleDB init warning: %s", e) # 注册默认设备 mock_dev = MockVehicleDevice(device_id="mock_01") await container.simulation_manager.register_device(mock_dev) # 启动监控服务 await container.server_monitor.start() yield # 停机清理 await container.server_monitor.stop() await container.simulation_manager.stop() await container.engine.dispose() app = FastAPI(title="SmartEDT Backend", version="0.1.0", lifespan=lifespan) app.include_router(routes.get_router(simulation_manager=container.simulation_manager, file_root=container.file_root)) app.include_router(auth_routes.get_router(session_factory=container.session_factory)) app.include_router(rbac_routes.get_router(session_factory=container.session_factory)) app.include_router(user_routes.get_router(session_factory=container.session_factory)) app.include_router(unity_routes.get_router(simulation_manager=container.simulation_manager, session_factory=container.session_factory)) @app.websocket("/ws") async def ws_endpoint(websocket: WebSocket): await ws.websocket_handler(websocket, broadcaster=container.broadcaster) def main() -> None: """命令行入口:解析参数并启动 uvicorn。""" parser = argparse.ArgumentParser() parser.add_argument("--host", default=None) parser.add_argument("--port", type=int, default=None) parser.add_argument("--debug", action="store_true") args = parser.parse_args() settings = load_settings() host = args.host or settings.server.host port = args.port or settings.server.port debug = args.debug or settings.server.debug _force_windows_selector_event_loop_for_uvicorn() if getattr(sys, 'frozen', False): uvicorn.run(app, host=host, port=port, log_level="debug" if debug else "info") else: uvicorn.run("backend.main:app", host=host, port=port, reload=debug, log_level="debug" if debug else "info") if __name__ == "__main__": multiprocessing.freeze_support() main()