This commit is contained in:
薇薇安 2026-01-18 18:00:19 +08:00
parent 38ebbebd95
commit c92ce63a3a

View File

@ -1,6 +1,7 @@
import os import os
import re import re
import subprocess import subprocess
from pathlib import Path
from typing import Any, Dict, Optional, Tuple from typing import Any, Dict, Optional, Tuple
from fastapi import APIRouter, HTTPException, Header from fastapi import APIRouter, HTTPException, Header
@ -28,6 +29,22 @@ def _build_supervisorctl_cmd(args: list[str]) -> list[str]:
supervisor_conf = os.getenv("SUPERVISOR_CONF", "").strip() supervisor_conf = os.getenv("SUPERVISOR_CONF", "").strip()
use_sudo = os.getenv("SUPERVISOR_USE_SUDO", "false").lower() == "true" use_sudo = os.getenv("SUPERVISOR_USE_SUDO", "false").lower() == "true"
# 如果没显式配置 SUPERVISOR_CONF,就尝试自动探测常见路径(宝塔/系统)
if not supervisor_conf:
candidates = [
"/www/server/panel/plugin/supervisor/supervisord.conf",
"/www/server/panel/plugin/supervisor/supervisor.conf",
"/etc/supervisor/supervisord.conf",
"/etc/supervisord.conf",
]
for p in candidates:
try:
if Path(p).exists():
supervisor_conf = p
break
except Exception:
continue
cmd: list[str] = [] cmd: list[str] = []
if use_sudo: if use_sudo:
# 需要你在 sudoers 配置 NOPASSWD(sudo -n 才不会卡住) # 需要你在 sudoers 配置 NOPASSWD(sudo -n 才不会卡住)
@ -75,6 +92,89 @@ def _get_program_name() -> str:
return os.getenv("SUPERVISOR_TRADING_PROGRAM", "auto_sys").strip() or "auto_sys" return os.getenv("SUPERVISOR_TRADING_PROGRAM", "auto_sys").strip() or "auto_sys"
def _select_best_process_name(program: str, status_all_raw: str) -> Optional[str]:
"""
从 `supervisorctl status` 全量输出中,找到最匹配的真实进程名。
兼容 supervisor 的 group:process 格式,例如:auto_sys:auto_sys_00
"""
if not status_all_raw:
return None
lines = [ln.strip() for ln in status_all_raw.splitlines() if ln.strip()]
names: list[str] = []
for ln in lines:
name = ln.split(None, 1)[0].strip()
if name:
names.append(name)
# 精确优先:program / program_00 / program:program_00
preferred = [program, f"{program}_00", f"{program}:{program}_00"]
for cand in preferred:
if cand in names:
return cand
# 次优:任意以 program_ 开头
for name in names:
if name.startswith(program + "_"):
return name
# 次优:任意以 program: 开头
for name in names:
if name.startswith(program + ":"):
return name
return None
def _status_with_fallback(program: str) -> Tuple[str, Optional[str], Optional[str]]:
"""
- 优先 `status <program>`
- 若 no such process:返回全量 status,并尝试解析真实 name(例如 auto_sys:auto_sys_00)
返回:(raw, resolved_name, status_all)
"""
try:
raw = _run_supervisorctl(["status", program])
return raw, program, None
except Exception as e:
msg = str(e).lower()
if "no such process" not in msg:
raise
status_all = _run_supervisorctl(["status"])
resolved = _select_best_process_name(program, status_all)
if resolved:
try:
raw = _run_supervisorctl(["status", resolved])
return raw, resolved, status_all
except Exception:
# 兜底:至少把全量输出返回,方便你确认真实进程名
return status_all, None, status_all
return status_all, None, status_all
def _action_with_fallback(action: str, program: str) -> Tuple[str, Optional[str], Optional[str]]:
"""
对 start/stop/restart 做兜底:如果 program 不存在,尝试解析真实 name 再执行。
返回:(output, resolved_name, status_all)
"""
try:
out = _run_supervisorctl([action, program])
return out, program, None
except Exception as e:
msg = str(e).lower()
if "no such process" not in msg:
raise
status_all = _run_supervisorctl(["status"])
resolved = _select_best_process_name(program, status_all)
if not resolved:
# 没找到就把全量输出带上,方便定位
raise RuntimeError(f"no such process: {program}. 当前 supervisor 进程列表:\n{status_all}")
out = _run_supervisorctl([action, resolved])
return out, resolved, status_all
@router.post("/clear-cache") @router.post("/clear-cache")
async def clear_cache(x_admin_token: Optional[str] = Header(default=None, alias="X-Admin-Token")) -> Dict[str, Any]: async def clear_cache(x_admin_token: Optional[str] = Header(default=None, alias="X-Admin-Token")) -> Dict[str, Any]:
""" """
@ -147,15 +247,17 @@ async def trading_status(x_admin_token: Optional[str] = Header(default=None, ali
program = _get_program_name() program = _get_program_name()
try: try:
raw = _run_supervisorctl(["status", program]) raw, resolved_name, status_all = _status_with_fallback(program)
running, pid, state = _parse_supervisor_status(raw) running, pid, state = _parse_supervisor_status(raw)
return { return {
"mode": "supervisor", "mode": "supervisor",
"program": program, "program": program,
"resolved_name": resolved_name,
"running": running, "running": running,
"pid": pid, "pid": pid,
"state": state, "state": state,
"raw": raw, "raw": raw,
"status_all": status_all,
} }
except Exception as e: except Exception as e:
raise HTTPException( raise HTTPException(
@ -170,8 +272,8 @@ async def trading_start(x_admin_token: Optional[str] = Header(default=None, alia
program = _get_program_name() program = _get_program_name()
try: try:
out = _run_supervisorctl(["start", program]) out, resolved_name, status_all = _action_with_fallback("start", program)
raw = _run_supervisorctl(["status", program]) raw, resolved_name2, status_all2 = _status_with_fallback(resolved_name or program)
running, pid, state = _parse_supervisor_status(raw) running, pid, state = _parse_supervisor_status(raw)
return { return {
"message": "交易系统已启动(supervisor)", "message": "交易系统已启动(supervisor)",
@ -179,10 +281,12 @@ async def trading_start(x_admin_token: Optional[str] = Header(default=None, alia
"status": { "status": {
"mode": "supervisor", "mode": "supervisor",
"program": program, "program": program,
"resolved_name": resolved_name2 or resolved_name,
"running": running, "running": running,
"pid": pid, "pid": pid,
"state": state, "state": state,
"raw": raw, "raw": raw,
"status_all": status_all2 or status_all,
}, },
} }
except Exception as e: except Exception as e:
@ -195,8 +299,8 @@ async def trading_stop(x_admin_token: Optional[str] = Header(default=None, alias
program = _get_program_name() program = _get_program_name()
try: try:
out = _run_supervisorctl(["stop", program]) out, resolved_name, status_all = _action_with_fallback("stop", program)
raw = _run_supervisorctl(["status", program]) raw, resolved_name2, status_all2 = _status_with_fallback(resolved_name or program)
running, pid, state = _parse_supervisor_status(raw) running, pid, state = _parse_supervisor_status(raw)
return { return {
"message": "交易系统已停止(supervisor)", "message": "交易系统已停止(supervisor)",
@ -204,10 +308,12 @@ async def trading_stop(x_admin_token: Optional[str] = Header(default=None, alias
"status": { "status": {
"mode": "supervisor", "mode": "supervisor",
"program": program, "program": program,
"resolved_name": resolved_name2 or resolved_name,
"running": running, "running": running,
"pid": pid, "pid": pid,
"state": state, "state": state,
"raw": raw, "raw": raw,
"status_all": status_all2 or status_all,
}, },
} }
except Exception as e: except Exception as e:
@ -220,8 +326,8 @@ async def trading_restart(x_admin_token: Optional[str] = Header(default=None, al
program = _get_program_name() program = _get_program_name()
try: try:
out = _run_supervisorctl(["restart", program]) out, resolved_name, status_all = _action_with_fallback("restart", program)
raw = _run_supervisorctl(["status", program]) raw, resolved_name2, status_all2 = _status_with_fallback(resolved_name or program)
running, pid, state = _parse_supervisor_status(raw) running, pid, state = _parse_supervisor_status(raw)
return { return {
"message": "交易系统已重启(supervisor)", "message": "交易系统已重启(supervisor)",
@ -229,10 +335,12 @@ async def trading_restart(x_admin_token: Optional[str] = Header(default=None, al
"status": { "status": {
"mode": "supervisor", "mode": "supervisor",
"program": program, "program": program,
"resolved_name": resolved_name2 or resolved_name,
"running": running, "running": running,
"pid": pid, "pid": pid,
"state": state, "state": state,
"raw": raw, "raw": raw,
"status_all": status_all2 or status_all,
}, },
} }
except Exception as e: except Exception as e: