import asyncio import signal import sys from pathlib import Path # src 为代码 root:项目不打包安装(tool.uv package=false), # 此处显式将 src 加入 sys.path,使 `uv run python -m src.main` 与 IDE source-root 行为一致 _SRC_DIR = str(Path(__file__).resolve().parent) if _SRC_DIR not in sys.path: sys.path.insert(0, _SRC_DIR) from loguru import logger from src.browser.manager import get_browser_manager from src.config.settings import get_settings from src.config.accounts import load_accounts_config from src.notifications.service import NotificationService from src.utils.logger import setup_logger from src.task_queue.dispatcher import TaskDispatcher from src.task_queue.delay_zset_listener import RedisZSetDelayListener from src.task_queue.listener import RedisStreamListener from src.task_queue.trade_fetch_listener import TradeFetchStreamListener from src.task_queue.redis_manager import get_redis_manager from src.api.server import start_api_server async def main() -> None: """ 应用程序主入口函数。 - 初始化定制的 Loguru 结构化日志系统。 - 校验 Pydantic 配置及多站点账号 YAML 配置文件是否正确。 - 针对不同操作系统平台注册优雅退出信号处理器。 - 启动 Redis Stream 消息队列持续消费监听循环。 """ # 1. 初始化按天分割日志系统 setup_logger() logger.info("正在初始化日本采购自动化守护进程...") # 2. 启动前快速校验:验证全局环境变量设置与账号配置文件 try: settings = get_settings() logger.info(f"应用配置加载成功: APP_ENV={settings.app_env.upper()}") NotificationService(settings).validate_configuration() # 解析并校验 accounts.yaml load_accounts_config() await get_redis_manager().initialize() except Exception as e: logger.critical(f"由于配置错误,守护进程启动失败: {e}") sys.exit(1) # 3. 预热浏览器:为所有已配置账号提前启动常驻浏览器上下文,任务到达时即开即用 await get_browser_manager().prewarm() # 4. 实例化核心任务分发器与 Stream 监听器 dispatcher = TaskDispatcher() stream_listener = RedisStreamListener(dispatcher) delay_listener = RedisZSetDelayListener(dispatcher) trade_fetch_listener = TradeFetchStreamListener(dispatcher) # 4.1 启动截图查看 API 服务 api_runner = await start_api_server() # 5. 优雅停机信号处理 (跨平台支持) loop = asyncio.get_running_loop() def shutdown_handler(sig_name: str) -> None: logger.warning(f"接收到系统退出信号 '{sig_name}'。正在启动优雅停机流程...") # 触发监听器停止的协程任务 asyncio.create_task(stream_listener.stop()) asyncio.create_task(delay_listener.stop()) asyncio.create_task(trade_fetch_listener.stop()) # Windows 默认的 Proactor 事件循环不支持 add_signal_handler。 if sys.platform != "win32": logger.debug("正在注册 Unix 系统信号监听器 (SIGINT / SIGTERM)。") for sig in (signal.SIGINT, signal.SIGTERM): try: loop.add_signal_handler(sig, lambda s=sig: shutdown_handler(s.name)) except NotImplementedError: pass else: logger.debug("运行于 Windows 平台。跳过系统信号注册,将直接捕获 KeyboardInterrupt。") # 6. 启动消息监听消费主循环 try: stream_task = asyncio.create_task(stream_listener.listen()) delay_task = asyncio.create_task(delay_listener.listen()) trade_fetch_task = asyncio.create_task(trade_fetch_listener.listen()) await asyncio.gather(stream_task, delay_task, trade_fetch_task) except asyncio.CancelledError: logger.warning("监听器轮询执行被事件循环取消。") except Exception as e: logger.exception(f"主守护进程轮询中发生致命崩溃: {e}") finally: # 保证安全回收与连接断开 await asyncio.gather( stream_listener.stop(), delay_listener.stop(), trade_fetch_listener.stop(), get_browser_manager().close_all(), get_redis_manager().close(), api_runner.cleanup(), return_exceptions=True, ) logger.success("日本采购自动化守护进程已安全优雅退出。") if __name__ == "__main__": try: asyncio.run(main()) except KeyboardInterrupt: logger.warning("捕获到键盘手动中断。立即退出程序...") sys.exit(0)