# -*- coding: utf-8 -*- """Agent 启动入口。 启动方式: python -m agent.main (源码运行) hospital-agent.exe (PyInstaller 打包后) 职责: 1. WebSocket 主动连接 B 端管理服务器(注册/心跳/命令) 2. 守护主程序(健康探测 + 宕机拉起) 由 WinSW/NSSM Windows 服务守护本进程。 """ import logging import logging.handlers import os import signal import sys import threading from agent.config import AgentConfig from agent.main_app_client import MainAppClient log = logging.getLogger(__name__) # 组装阶段先声明,供信号处理器引用 _dispatcher = None _guard = None _client = None def run(): """主流程:加载配置 → 组装组件 → 启动 → 主线程保活。""" global _dispatcher, _guard, _client config = AgentConfig.load() _setup_logging(config.log_dir) log.info("Agent 启动:deviceId=%s, server=%s", config.device_id, config.server_url) # 源码运行时根目录即工作目录,PyInstaller 打包后同理(服务管理器负责设置工作目录) # 组装组件(延迟导入避免环) from agent.dispatcher import CommandDispatcher from agent.process_guard import ProcessGuard from agent.ws_client import AgentClient main_app_client = MainAppClient(config) dispatcher = CommandDispatcher(config, main_app_client) client = AgentClient(config, dispatcher) dispatcher.set_client(client) guard = ProcessGuard(config, main_app_client, client) _dispatcher, _guard, _client = dispatcher, guard, client # 优雅停机信号处理 signal.signal(signal.SIGINT, _shutdown) signal.signal(signal.SIGTERM, _shutdown) client.start() guard.start() # 主线程保活 threading.Event().wait() def _setup_logging(log_dir: str): """日志输出到控制台 + 文件(服务方式运行时靠文件留存,按大小轮转)。""" fmt = logging.Formatter( "{asctime} {levelname} [{name}] {message}", style="{", ) root = logging.getLogger() root.setLevel(logging.INFO) console = logging.StreamHandler() console.setFormatter(fmt) root.addHandler(console) try: os.makedirs(log_dir, exist_ok=True) file_handler = logging.handlers.RotatingFileHandler( os.path.join(log_dir, "agent.log"), maxBytes=5 * 1024 * 1024, # 单文件 5MB backupCount=5, # 保留 5 个轮转备份 encoding="utf-8", ) file_handler.setFormatter(fmt) root.addHandler(file_handler) except Exception as e: root.warning("日志文件初始化失败(仅控制台输出):%s", e) def _shutdown(signum, frame): """优雅停机:停守护 → 断连接 → 退出。""" log.info("Agent 停机中...(signal=%s)", signum) if _guard is not None: _guard.stop() if _client is not None: _client.stop() sys.exit(0) if __name__ == "__main__": run()