diff --git a/backend/api/routes/system.py b/backend/api/routes/system.py index 48ff2e5..1bc4c37 100644 --- a/backend/api/routes/system.py +++ b/backend/api/routes/system.py @@ -1151,3 +1151,37 @@ async def backend_restart(_admin: Dict[str, Any] = Depends(require_system_admin) "note": "重启期间接口可能短暂不可用,页面可等待 3-5 秒后刷新状态。", } + +@router.post("/backend/stop") +async def backend_stop(_admin: Dict[str, Any] = Depends(require_system_admin)) -> Dict[str, Any]: + """ + 停止后端服务(uvicorn)。 + 警告:停止后 API 将不可用,必须手动登录服务器启动! + """ + backend_dir = Path(__file__).parent.parent.parent # backend/ + stop_script = backend_dir / "stop.sh" + if not stop_script.exists(): + raise HTTPException(status_code=500, detail=f"找不到停止脚本: {stop_script}") + + cur_pid = os.getpid() + + # 后台执行:sleep 1 后再停止,保证当前请求可以返回 + cmd = ["bash", "-lc", f"sleep 1; '{stop_script}'"] + try: + subprocess.Popen( + cmd, + cwd=str(backend_dir), + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + start_new_session=True, + ) + except Exception as e: + raise HTTPException(status_code=500, detail=f"启动停止脚本失败: {e}") + + return { + "message": "已发起后端停止(1s 后执行)", + "pid_before": cur_pid, + "script": str(stop_script), + "warning": "后端服务停止后,Web 界面将无法访问,请手动在服务器启动!", + } + diff --git a/frontend/src/components/GlobalConfig.jsx b/frontend/src/components/GlobalConfig.jsx index 7ad4d22..3b81e3b 100644 --- a/frontend/src/components/GlobalConfig.jsx +++ b/frontend/src/components/GlobalConfig.jsx @@ -610,43 +610,16 @@ const GlobalConfig = () => { } } - const handleStartTrading = async () => { + const handleStopBackend = async () => { + if (!window.confirm('警告:确定要停止后端服务吗?\n\n停止后 Web 界面将立即无法访问!\n必须手动登录服务器执行 ./start.sh 才能恢复。')) return setSystemBusy(true) setMessage('') try { - const res = await api.startTradingSystem() - setMessage(res.message || '交易系统已启动') - await loadSystemStatus() + const res = await api.stopBackend() + setMessage(res.message || '后端服务已停止') + alert('后端服务已停止,页面将不再响应。请手动去服务器启动。') } catch (error) { - setMessage('启动失败: ' + (error.message || '未知错误')) - } finally { - setSystemBusy(false) - } - } - - const handleStopTrading = async () => { - setSystemBusy(true) - setMessage('') - try { - const res = await api.stopTradingSystem() - setMessage(res.message || '交易系统已停止') - await loadSystemStatus() - } catch (error) { - setMessage('停止失败: ' + (error.message || '未知错误')) - } finally { - setSystemBusy(false) - } - } - - const handleRestartTrading = async () => { - setSystemBusy(true) - setMessage('') - try { - const res = await api.restartTradingSystem() - setMessage(res.message || '交易系统已重启') - await loadSystemStatus() - } catch (error) { - setMessage('重启失败: ' + (error.message || '未知错误')) + setMessage('停止后端失败: ' + (error.message || '未知错误')) } finally { setSystemBusy(false) } @@ -1203,6 +1176,15 @@ const GlobalConfig = () => { > 重启后端服务 + + 停止后端服务 + { 交易服务管理 - - 启动 - - - 停止 - { + const response = await fetch(buildUrl('/api/system/backend/stop'), { + 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(); + }, + // 日志监控(Redis List) getSystemLogs: async (params = {}) => { const query = new URLSearchParams(params).toString();