This commit is contained in:
薇薇安 2026-01-21 19:29:10 +08:00
parent 18cfeaf5db
commit fe855df566
3 changed files with 149 additions and 0 deletions

View File

@ -592,6 +592,20 @@ def _parse_supervisor_status(raw: str) -> Tuple[bool, Optional[int], str]:
return False, None, state
return False, None, "UNKNOWN"
def _list_supervisor_process_names(status_all_raw: str) -> list[str]:
names: list[str] = []
if not status_all_raw:
return names
for ln in status_all_raw.splitlines():
s = (ln or "").strip()
if not s:
continue
# 每行格式:<name> <STATE> ...
name = s.split(None, 1)[0].strip()
if name:
names.append(name)
return names
def _get_program_name() -> str:
# 你给的宝塔配置是 [program:auto_sys]
@ -880,6 +894,99 @@ async def trading_restart(_admin: Dict[str, Any] = Depends(require_system_admin)
raise HTTPException(status_code=500, detail=f"supervisorctl restart 失败: {e}")
@router.post("/trading/restart-all")
async def trading_restart_all(
_admin: Dict[str, Any] = Depends(require_system_admin),
prefix: str = "auto_sys_acc",
include_default: bool = False,
do_update: bool = True,
) -> Dict[str, Any]:
"""
一键重启所有账号交易进程supervisor
- 默认重启所有以 auto_sys_acc 开头的 program例如 auto_sys_acc1/2/3...
- 可选 include_default=true同时包含 SUPERVISOR_TRADING_PROGRAM默认 auto_sys
- 可选 do_update=true先执行 supervisorctl reread/update 再重启确保新 ini 生效
"""
try:
prefix = (prefix or "auto_sys_acc").strip()
if not prefix:
prefix = "auto_sys_acc"
# 先读取全量 status拿到有哪些进程
status_all = _run_supervisorctl(["status"])
names = _list_supervisor_process_names(status_all)
targets: list[str] = []
for n in names:
if n.startswith(prefix):
targets.append(n)
if include_default:
default_prog = _get_program_name()
if default_prog and default_prog not in targets and default_prog in names:
targets.append(default_prog)
if not targets:
return {
"message": "未找到可重启的交易进程",
"prefix": prefix,
"include_default": include_default,
"count": 0,
"targets": [],
"status_all": status_all,
}
reread_out = ""
update_out = ""
if do_update:
try:
reread_out = _run_supervisorctl(["reread"])
except Exception as e:
reread_out = f"failed: {e}"
try:
update_out = _run_supervisorctl(["update"])
except Exception as e:
update_out = f"failed: {e}"
results: list[Dict[str, Any]] = []
ok = 0
failed = 0
for prog in targets:
try:
out = _run_supervisorctl(["restart", prog])
raw = _run_supervisorctl(["status", prog])
running, pid, state = _parse_supervisor_status(raw)
results.append(
{
"program": prog,
"ok": True,
"output": out,
"status": {"running": running, "pid": pid, "state": state, "raw": raw},
}
)
ok += 1
except Exception as e:
failed += 1
results.append({"program": prog, "ok": False, "error": str(e)})
return {
"message": "已发起批量重启",
"prefix": prefix,
"include_default": include_default,
"do_update": do_update,
"count": len(targets),
"ok": ok,
"failed": failed,
"reread": reread_out,
"update": update_out,
"targets": targets,
"results": results,
}
except Exception as e:
raise HTTPException(status_code=500, detail=f"批量重启失败: {e}")
@router.get("/backend/status")
async def backend_status(_admin: Dict[str, Any] = Depends(require_system_admin)) -> Dict[str, Any]:
"""

View File

@ -379,6 +379,20 @@ const ConfigPanel = ({ currentUser }) => {
}
}
const handleRestartAllTrading = async () => {
if (!window.confirm('确定要重启【所有账号】的交易进程吗?这会让所有用户的交易服务短暂中断(约 3-10 秒),用于升级代码后统一生效。')) return
setSystemBusy(true)
setMessage('')
try {
const res = await api.restartAllTradingSystems({ prefix: 'auto_sys_acc', do_update: true })
setMessage(`已发起批量重启:共 ${res.count} 个,成功 ${res.ok},失败 ${res.failed}`)
} catch (e) {
setMessage('批量重启失败: ' + (e?.message || '未知错误'))
} finally {
setSystemBusy(false)
}
}
useEffect(() => {
loadConfigs()
checkFeasibility()
@ -972,6 +986,15 @@ const ConfigPanel = ({ currentUser }) => {
>
重启交易系统
</button>
<button
type="button"
className="system-btn primary"
onClick={handleRestartAllTrading}
disabled={systemBusy}
title="批量重启所有账号交易进程auto_sys_acc*),用于代码升级后统一生效"
>
重启所有账号交易
</button>
<button
type="button"
className="system-btn primary"

View File

@ -508,6 +508,25 @@ export const api = {
return response.json();
},
restartAllTradingSystems: async (opts = {}) => {
const params = new URLSearchParams()
if (opts?.prefix) params.set('prefix', String(opts.prefix))
if (opts?.include_default) params.set('include_default', 'true')
if (opts?.do_update === false) params.set('do_update', 'false')
const url = params.toString()
? `${buildUrl('/api/system/trading/restart-all')}?${params.toString()}`
: buildUrl('/api/system/trading/restart-all')
const response = await fetch(url, {
method: 'POST',
headers: withAccountHeaders({ 'Content-Type': 'application/json' }),
})
if (!response.ok) {
const error = await response.json().catch(() => ({ detail: '批量重启失败' }))
throw new Error(error.detail || '批量重启失败')
}
return response.json()
},
// 后端控制uvicorn
getBackendStatus: async () => {
const response = await fetch(buildUrl('/api/system/backend/status'), { headers: withAccountHeaders() });