diff --git a/app/api/routers/service.py b/app/api/routers/service.py index 70c0a3e..350c879 100644 --- a/app/api/routers/service.py +++ b/app/api/routers/service.py @@ -1,13 +1,14 @@ import os import platform -import subprocess import time from threading import Thread from fastapi import APIRouter from app.services.runtime import ( + is_container_runtime, is_supervisor_running, + restart_method_name, restart_via_supervisor, terminate_current_process, ) @@ -18,38 +19,35 @@ router = APIRouter(tags=["service"]) @router.post("/api/service/restart") async def restart_service(): - if is_supervisor_running(): + supervisor_running = is_supervisor_running() + container_runtime = is_container_runtime() + + if supervisor_running: success, message = restart_via_supervisor() - return {"success": success, "message": message, "method": "supervisor"} - - if platform.system() != "Windows": - try: - result = subprocess.run( - ["supervisorctl", "restart", "fastapi"], - capture_output=True, - text=True, - timeout=30, - ) - if result.returncode == 0: - return { - "success": True, - "message": "服务正在通过 supervisor 重启...", - "method": "supervisor", - } - except FileNotFoundError: - pass - except subprocess.TimeoutExpired: + if success: return { - "success": False, - "message": "重启操作超时,请检查 supervisor 状态", + "success": True, + "message": message, "method": "supervisor", + "container": container_runtime, } - except Exception: - pass + method = restart_method_name(supervisor_running) thread = Thread(target=_delayed_exit, daemon=True) thread.start() - return {"success": True, "message": "服务正在重启,请稍后刷新页面...", "method": "signal"} + + message = "服务正在重启,请稍后刷新页面..." + if supervisor_running: + message = "supervisor 命令不可用,已切换为进程重启..." + elif container_runtime: + message = "服务进程正在退出,容器或进程管理器将拉起服务..." + + return { + "success": True, + "message": message, + "method": method, + "container": container_runtime, + } @router.get("/api/service/status") @@ -59,6 +57,7 @@ async def get_service_status(): "version": "2.0.2", "platform": platform.system(), "supervisor": is_supervisor_running(), + "container": is_container_runtime(), "pid": os.getpid(), "python_version": platform.python_version(), } @@ -67,4 +66,3 @@ async def get_service_status(): def _delayed_exit() -> None: time.sleep(1) terminate_current_process() - diff --git a/app/services/runtime.py b/app/services/runtime.py index 97393c5..b04bee0 100644 --- a/app/services/runtime.py +++ b/app/services/runtime.py @@ -25,6 +25,24 @@ def is_supervisor_running() -> bool: return _command_succeeds(["pgrep", "-f", "supervisord"], timeout=2) +def is_container_runtime() -> bool: + if os.environ.get("container"): + return True + if Path("/.dockerenv").exists(): + return True + + cgroup_path = Path("/proc/1/cgroup") + if not cgroup_path.exists(): + return False + + try: + cgroup = cgroup_path.read_text(encoding="utf-8", errors="ignore").lower() + except Exception: + return False + + return any(marker in cgroup for marker in ("docker", "kubepods", "containerd")) + + def restart_via_supervisor() -> tuple[bool, str]: try: result = subprocess.run( @@ -52,10 +70,19 @@ def terminate_current_process() -> None: os.kill(os.getpid(), signal.SIGTERM) +def restart_method_name(supervisor_running: bool) -> str: + if supervisor_running: + return "supervisor" + if platform.system() == "Windows": + return "windows-process" + if is_container_runtime(): + return "container-process" + return "process" + + def _command_succeeds(command: list[str], timeout: int) -> bool: try: result = subprocess.run(command, capture_output=True, text=True, timeout=timeout) return result.returncode == 0 and bool(result.stdout.strip() or command[0] == "supervisorctl") except Exception: return False - diff --git a/docs/project_context.md b/docs/project_context.md index ddfad86..aa213d9 100644 --- a/docs/project_context.md +++ b/docs/project_context.md @@ -1,5 +1,13 @@ # 项目上下文记录 +## 2026-05-18:完善服务重启交互和运行时兼容 + +- `frontend/src/AppShell.vue` 的重启按钮点击后会先弹出确认框,确认后显示全屏“正在重启服务”遮罩和旋转加载动画,避免用户重复操作。 +- 重启请求发出后前端会轮询 `/api/service/status`,服务恢复时自动刷新页面;重启过程中请求中断会被视为正常情况继续等待。 +- `app/api/routers/service.py` 和 `app/services/runtime.py` 统一重启逻辑:优先使用 supervisor 重启,失败时退回进程退出;Windows 本地依赖 `run.bat` 循环拉起,容器环境会识别 Docker/containerd/k8s 并交给 supervisor 或容器重启策略拉起。 +- `/api/service/status` 返回值新增 `container` 字段,用于前端或排障区分容器运行时。 +- 已执行 `.venv\Scripts\python.exe -m compileall app` 和 `npm run build`,均通过;未启动浏览器或 headless Chrome。 + ## 2026-05-18:修正字段映射标题换行 - `frontend/src/styles.css` 调整设置页字段映射卡片头部布局,“字段映射配置”和字段数量标签不再被搜索框挤压换行。 diff --git a/frontend/src/AppShell.vue b/frontend/src/AppShell.vue index 8e6d057..a40b57a 100644 --- a/frontend/src/AppShell.vue +++ b/frontend/src/AppShell.vue @@ -63,7 +63,13 @@ - @@ -79,12 +85,19 @@ + +
+
+
+
{{ restartOverlayText }}
+
+