feat: 升级流程增强 + 网关化配置 + 重连循环化
- 配置化:gateway.url/socket.path/download.prefix(网关基础地址统一推导 ws 与下载地址)、 main.home 主目录(jar/pid 相对它推导)、order.basic;properties 中文 \uXXXX 转义自动还原 - 升级流程:下载 PK 魔数检查与诊断日志;SHA 校验;替换改「改名腾位」绕开 Windows 文件锁延迟; nssm stop/start 驱动停止与拉起(pid 强杀回退);升级中标志暂停看门狗拉起避免抢占 jar 锁 - ws_client:重连改循环实现修复递归栈溢出(长时间断线后 maximum recursion depth); 退避封顶延长至 5 分钟;心跳线程独立修复「暂无心跳数据」 - 新增命令:GET_APIS/HTTP_PROXY(接口测试代理,纯透传鉴权)、GET_LOG_DOWNLOAD(日志下载备用) - 诊断增强:taskkill/进程退出/替换重试逐次日志 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
+62
-1
@@ -12,6 +12,11 @@ import threading
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
# 模块级「升级中」标志:升级流程开始时置 True,结束后置 False。
|
||||
# 看门狗探测到主程序不健康时若此标志为 True,说明是升级主动停的,跳过拉起,
|
||||
# 避免升级替换 jar 时被看门狗拉起的新进程抢占文件锁。
|
||||
_upgrading = threading.Event()
|
||||
|
||||
|
||||
class ProcessGuard:
|
||||
"""看门狗:周期健康探测 + 宕机拉起 + 告警上报。"""
|
||||
@@ -52,12 +57,18 @@ class ProcessGuard:
|
||||
log.warning("守护探测异常:%s", e)
|
||||
|
||||
def _check(self):
|
||||
"""单次探测:健康则清零计数;连续 3 次失败则拉起。"""
|
||||
"""单次探测:健康则清零计数;连续 3 次失败则拉起(升级期间跳过)。"""
|
||||
health = self.main_app_client.health()
|
||||
if health.get("ok") is True:
|
||||
self._fail_count = 0
|
||||
return
|
||||
|
||||
# 升级流程主动停的主程序,看门狗不接管,避免抢占 jar 锁
|
||||
if _upgrading.is_set():
|
||||
log.info("主程序不可达,但升级流程进行中,看门狗跳过拉起")
|
||||
self._fail_count = 0 # 不累计,升级结束后自然恢复
|
||||
return
|
||||
|
||||
self._fail_count += 1
|
||||
log.warning("主程序健康探测失败(%d/3):%s", self._fail_count, health.get("error"))
|
||||
if self._fail_count < 3:
|
||||
@@ -129,6 +140,56 @@ def restart_main_service(nssm_path: str, service_name: str) -> tuple[bool, str]:
|
||||
return False, str(e)
|
||||
|
||||
|
||||
def stop_main_service(nssm_path: str, service_name: str) -> tuple[bool, str]:
|
||||
"""
|
||||
通过 NSSM 停止主程序服务(nssm stop 会等待服务进程退出后返回)。
|
||||
|
||||
用于升级流程:停止成功即文件锁释放,替换 jar 不再被 WinError 32 挡住。
|
||||
nssm_path 为 nssm.exe 完整路径;service_name 为注册的 Windows 服务名。
|
||||
返回 (是否成功, 输出/原因)。
|
||||
"""
|
||||
try:
|
||||
proc = subprocess.run(
|
||||
[nssm_path, "stop", service_name],
|
||||
capture_output=True,
|
||||
timeout=120,
|
||||
)
|
||||
out = ((proc.stdout or b"") + (proc.stderr or b"")).decode("utf-16", errors="replace").strip()
|
||||
ok = proc.returncode == 0
|
||||
if ok:
|
||||
log.info("nssm stop %s 成功:%s", service_name, out or "(无输出)")
|
||||
else:
|
||||
log.warning("nssm stop %s 失败(exit=%s):%s", service_name, proc.returncode, out)
|
||||
return ok, out
|
||||
except Exception as e:
|
||||
log.warning("nssm stop %s 执行异常:%s", service_name, e)
|
||||
return False, str(e)
|
||||
|
||||
|
||||
def start_main_service(nssm_path: str, service_name: str) -> tuple[bool, str]:
|
||||
"""
|
||||
通过 NSSM 启动主程序服务(nssm start 触发服务进入运行态,应用初始化由健康检查轮询确认)。
|
||||
|
||||
返回 (是否成功, 输出/原因)。
|
||||
"""
|
||||
try:
|
||||
proc = subprocess.run(
|
||||
[nssm_path, "start", service_name],
|
||||
capture_output=True,
|
||||
timeout=60,
|
||||
)
|
||||
out = ((proc.stdout or b"") + (proc.stderr or b"")).decode("utf-16", errors="replace").strip()
|
||||
ok = proc.returncode == 0
|
||||
if ok:
|
||||
log.info("nssm start %s 成功:%s", service_name, out or "(无输出)")
|
||||
else:
|
||||
log.warning("nssm start %s 失败(exit=%s):%s", service_name, proc.returncode, out)
|
||||
return ok, out
|
||||
except Exception as e:
|
||||
log.warning("nssm start %s 执行异常:%s", service_name, e)
|
||||
return False, str(e)
|
||||
|
||||
|
||||
def start_main_app(start_cmd: str) -> subprocess.Popen | None:
|
||||
"""
|
||||
拉起主程序。
|
||||
|
||||
Reference in New Issue
Block a user