Files
hospital-front-agent/agent/main.py
T
lianlonggangandClaude f07bbbc49d feat: RESTART 走 NSSM 服务重启 + 失败原因可观测化
- RESTART 命令优先通过 nssm restart 重启主程序服务(假死场景可用),未配置服务名回退环回 HTTP
- nssm 输出按 UTF-16 解码并记录日志,权限不足时提示需以服务方式运行
- 环回管理接口 code=3001 时明确报鉴权失败,不再误报不可达
- 新增 nssm.path / main.service.name / log.dir 配置项
- 增加文件日志(RotatingFileHandler 5MB×5),服务方式运行可查日志
- HTTP 调用失败与状态码异常统一 WARNING 记录(原 DEBUG 不可见)
- 断线日志区分「网络不通」与「被服务器拒绝(未注册/token 错误)」
- 连接 URL 自动拼接 device.id,消除双处配置不一致

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-27 17:24:17 +08:00

105 lines
3.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- 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 _dispatcher is not None:
_dispatcher.shutdown_log_stream()
if _guard is not None:
_guard.stop()
if _client is not None:
_client.stop()
sys.exit(0)
if __name__ == "__main__":
run()