# 观澜 · 后端(`app_backEnd/`) **职责**:组件服务(分析/振动/仿真/三维)、统一网关、运维控制台、重算编排与作业状态、CLI 与进程管理 **允许依赖**:app_common, app_ETL, app_algorithmModel, app_ontology **对外公开面**:`common/app_backEnd_guanlan/api.py` —— 模块间调用**只许**走这里(`scripts/module_boundary_audit.py` 机器检查)。 **当前实现落点(P4 已实体迁入)**:guanlan.py、scripts/{windscada_serve,guanlan_gateway,guanlan_ops,_ops_*}.py、src/windscada/{config,terms,i18n,lang,report_export}.py **迁移状态**:**P4 已完成(2026-09-22)** —— 21 文件 / 10,482 行迁入本模块:`cli.py`(原根 `guanlan.py`)、`serve.py`(分析组件)、`gateway.py`、`ops.py`、`service.py`、`start_hidden.py`、`ops_{launch,run,start_and_open,stop_keep_gateway}.py`、`windcms/{serve,orchestrator,llm,agent}.py` 与`config/terms/i18n/lang/report_export/deid/deid_public.py`。入口路径(根 `guanlan.py`、`scripts/<9 件>`)与旧库路径全部留兼容壳;网关与 Nginx 的接入点契约改名为 `gateway_contract.py`(避免与真实现 `gateway.py` 撞名)。实逮并修掉三处:① 壳里 sys.path 插错层(`scripts/` 的 parent 不是安装根);② `serve`/`gateway` 没有 `main()`(入口在 `__main__` 守卫里直接起服务)⇒ 壳改为"有 main 调 main、没有就 `runpy.run_path(run_name='__main__')`";③ P0 的 `gateway.py` 接口契约与 P4 真实现撞名(真实现被挡在门外)。验收:服务起停 + **六条路由全 200**(`/`、`/detail/v2`、`/detail/api/fleet`、`/ops`、`/ops/api/state`、`/cms/`、`/sim/`)。 > 依据:`docs/重构方案_模块化_v0.1.md`(用户令 2026-09-22:只重构目录与接口、零行为变化、逐版本可回滚)。 **P4 后续消缺(2.11.5 / 2026-09-28)**:`service.py` 在 Windows 上的 `restart` 原先直接调 `sc.exe restart <名>` —— 而 **sc.exe 根本没有 restart 子命令**(这是与 `systemctl restart` 的差异),所以该命令一直是"打印用法帮助 + 非零退出",服务从未真正重启过(`stop`/`start` 是有效的,所以此前靠手动两步绕过)。现改为 **停 → 轮询 SCM 直到 STOPPED(最长 90 s)→ 起**:`sc.exe stop` 是异步的,不等停稳就 `start` 会吃 1056/1058 错,中间必须等。`--dry-run` 会把这两条命令与"等待"这一步一并打印出来。 **同一条命令的第二次修正(2.11.6 / 2026-09-28,远端实测)**:2.11.5 的等待判据是"状态不是 RUNNING 就算停稳",而 `sc.exe stop` 之后 SCM 先回 **STOP_PENDING** ⇒ 判据立刻为真、`start` 抢跑,远端服务实测报 1056("服务的一个实例已在运行"),网关/仿真/三维端口被停掉约一分钟。现改为 `win_state()` + `wait_state("STOPPED", 120s)`(必须**等于** STOPPED),start 再给三次机会(间隔 5 s)。教训:**异步动作的"完成"判据要认目标态,不能认"不是起始态"**。 **P5 连带调整(2026-09-28)**:① `lang.py`(双语文案语言包)与 `terms.py`(显示层术语)划归前端 —— 消费者只有前端出页器与本模块 `cli.py` 的"语言包闸",留在这里会造成"前端取后端"的反向依赖;`cli.py` 改为经 `app_frontEnd...api` 取用,`configs/modules.yaml` 的 `allow` 相应加上 `app_frontEnd`。② 经典页模板(`CHART_JS` / `PAGE_FLEET` / `PAGE_PROBLEM` / `PAGE_TURBINE`,约 246 KB,其中图表 JS 176 KB)从 `serve.py` 抽出到前端 `assets/classic_chart.js` + `pages/classic_*.html` + `pages/classic.py`,`serve.py` 经 `app_frontEnd...api` 取用(值逐字节一致,`serve.py` 5549 → 2707 行)。 **stop 消缺(2.11.8 / 2026-09-28)**:`cli.py` 的 `cmd_stop()` 原先直接 `PIDS.unlink()` —— 在**服务宿主与启动器并存**的机器上,`run/pids.json` 可能已被清掉、被杀软占着句柄、或读到一半(坏 JSON)⇒ `guanlan.py stop` 会以 `FileNotFoundError` / `JSONDecodeError` 中断退出,而组件其实已经停掉了。现改为:读记录失败按空记录处理并如实报一句;删记录失败也只报一句、**不影响停机结果与退出码**。回归:pids.json 不存在 / 是目录(读+删都失败)/ 内容损坏 三种情形下均不抛异常且 rc=0。 ## 安装部署与运行 本系统是**单包离线交付**:解压到无空格无中文路径 → 安装 → 自检 → 启动(一套安装,七个模块共用一份 `.venv`)。 本模块目录里的脚本只是**入口包装**,内部调安装根的统一入口,保证"进模块目录也能装/跑/卸": | 动作 | Windows | Linux/macOS | 内部实际调用 | |---|---|---|---| | 安装 | `install.bat` | `sh install.sh` | 安装根 `install.bat` / `install.sh`(建 `.venv`、装依赖、自检、注册服务) | | 运行 | `run.bat` | `sh run.sh` | 安装根 `start.bat`(Windows)/`python guanlan.py serve`(跨平台) | | 卸载 | `uninstall.bat` | `sh uninstall.sh` | 安装根 `uninstall.bat` / `uninstall.sh` | 目录约定(P9,2026-09-28 用户令): ``` app_backEnd/ common/ 模块内通用插件/组件(旧导入前缀的兼容别名在此保留一个版本) configs/ 本模块专属配置域(共用单件在安装根 configs/) data/ 模块相关数据件(运行期数据在安装根 data/、outputs/) app_backEnd_guanlan/ 观澜在本模块的实现包(模块间调用只许经其中的 api.py) README.MD 本文件(职责 / 公开面 / 安装部署与运行) install.bat|sh uninstall.bat|sh run.bat|sh ``` 自检与打包:安装根 `check.bat`(=`python guanlan.py check`)、`pack.bat` / `sh pack.sh` (打包 + 开箱验证:页面 5/5 + 卸载核验)。