14 KiB
NextTJWater Server
next-tjwater-server 是 NextTJWater 供水系统的 FastAPI 后端,提供认证、项目、管网、模拟、爆管、漏损、SCADA、地图服务、工况任务和受控 MCP 能力。业务数据和时序数据按已认证项目路由,后台工作进程也必须使用明确的项目范围。
技术栈
- Python 3.12
- FastAPI / Uvicorn
- Pydantic / SQLAlchemy / psycopg
- PostgreSQL、PostGIS、TimescaleDB
- WNTR、EPANET、Cython、科学计算与空间分析依赖
- pytest
目录结构
app/main.py FastAPI 入口
app/api/ HTTP API 路由
app/auth/ 认证和权限上下文
app/core/ 配置、日志和基础设施初始化
app/domain/ 领域模型和 Pydantic schema
app/infra/ 数据库、EPANET 和外部集成
app/services/ 业务服务编排
app/algorithms/ 管网算法、模拟、爆管、漏损、清洗和健康分析
app/native/ 本地管网数据读写与转换
tests/ 后端测试
resources/ SQL、模板和示例资源
infra/docker/ Docker Compose 编排
本地开发
先从样板创建本地配置,再按启用的功能填写:
cp .env.example .env
.env 只用于本地或部署主机运行,不得提交。生产配置由部署主机的受控环境文件注入,不将完整应用 .env 保存为 Gitea Actions 密钥。
推荐使用已有 conda 环境:
conda run -n server python -m pytest tests/unit tests/auth -q
conda run -n server uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload
如需要进入环境:
conda activate server
GET /health 是无需鉴权的 HTTP 服务存活检查,返回 200 和 {"status":"ok"},不查询数据库。
GET /ready 检查应用启动状态、元数据库 SELECT 1 及已启用的 MCP 传输生命周期;
全部就绪返回 200,否则返回 503,元数据库探测预算为 2 秒。MCP 关闭时标记为
disabled,不阻止就绪。返回结果不包含数据库地址、凭据或原始异常,禁止缓存。
就绪状态不等同于 OAuth、用户权限或真实工具调用成功;指定项目的业务库和时序库仍通过
带身份与 X-Project-Id 的 GET /api/v1/projects/current/database-health 检查。
环境配置
.env.example 是唯一应提交的环境配置样板,主要分为以下几组:
- 元数据库、项目业务库、TimescaleDB 及项目数据库连接池。
- GeoServer 项目供应、Keycloak JWT 校验、CORS 和工作台事件。
- 天地图地理编码和地名搜索。
- 调查任务 worker、外围工单同步、数据与动作适配器。
- 可选的 MCP resource、OAuth issuer、宿主和超时预算。
基础数据库变量用于启动和项目供应。运行时的项目请求仍以元数据库中的 biz_data 和 iot_data 路由为准,不会因为存在默认数据库变量而回退到固定项目。
CORS 通过后端 .env 或部署进程环境变量中的 CORS_ALLOWED_ORIGINS 配置,进程环境变量优先。
必须使用英文逗号分隔的明确页面来源(协议、域名或 IP、端口,不带路径),不能填写 *。
未设置时默认只允许本机 5173、15173 开发来源。正式部署按实际浏览器地址填写,例如:
CORS_ALLOWED_ORIGINS=https://next-demo.waternetwork.cn,http://192.168.1.133,https://192.168.1.133,http://192.168.1.110,https://192.168.1.110
CORS 匹配浏览器页面来源,不是 Nginx 连接 IP 或域名解析结果;非默认端口须逐项列出。
保留跨域凭据支持及 Authorization、X-Project-Id 请求头,接口认证和权限校验继续生效。
修改后需重启进程;使用 Compose env_file 注入时需重新创建容器才能读取新值。
业务 Skill 与闭环
爆管处置工程图覆盖上报、候选定位、隔离阀门、影响分析、地图与数据展示、审批处置和反馈闭环。Server 通过任务 MCP 按 workflow 分发以下业务 Skill,当前版本均为 2.0.0:
| workflow | Skill | 调查重点 |
|---|---|---|
| burst_report | 爆管处置 | 候选定位、隔离阀门、影响分析、抢修及恢复闭环 |
| water_quality_complaint | 水质投诉 | 投诉关联、监测与采样、候选影响、处置及复测回访 |
| general | 预约跟进 | 审批与回执、现场履约、反馈复核及与原事件的闭环区别 |
JSONL 整理遵守会话文件权限,后台图表和表格通过既有报告 ui_blocks 展示;Skill 不增加审批、执行或原工单关单权限。预约投影不自动启动 Agent 调查,普通协作与后台执行分别遵守其原有工具范围。
常用命令
外围接入样板作为 /peripheral 子应用随主 API 进程启动,复用 PostgreSQL 且不自动建表;授权边界和契约见 外围样板说明。
外围样板提供 synthetic 测试工单和测试数据的版本化 CRUD;工单经同步器投影为 workbench.operational_tasks 中的外围来源任务。固定演示时窗和造数方式见 合成样本说明,运行态读取边界见 工作台运行态说明。这些数据不代表现场事实,也不会触发设备控制。
conda run -n server python -m pytest tests -q
conda run -n server python scripts/run_server.py
docker build -t tjwater-server:local .
docker compose -f infra/docker/docker-compose.yml config
pytest:运行自动化测试。scripts/run_server.py:使用项目脚本启动服务。docker build:构建后端镜像。docker compose config:检查 compose 配置和变量展开。
开发规范
- Python 文件、函数、变量、Pydantic 字段、JSON body 字段和 query 参数使用
snake_case。 - Python 类和 Pydantic 模型使用
PascalCase。 - 新 HTTP 路径使用
kebab-case,例如/api/v1/pressure-status/analyze。 - 优先复用现有 FastAPI/service/repository 边界。
- 不要把临时数据、数据库 dump、日志或本地运行产物纳入提交。
项目数据库路由
项目级 REST 请求通过 X-Project-Id 解析元数据中的数据库配置:
biz_dataDSN 用于管网业务数据。每个物理业务库使用同名_template数据库,通过逻辑订阅只同步networkschema;模拟临时库从该项目模板克隆。WNDB_SCHEMA_TEMPLATE_DB_NAME(当前为tjwater_v2_schema_template)仅用于创建空业务库和 INP 导入暂存库。iot_dataDSN 用于 TimescaleDB,始终使用元数据配置的完整 DSN,不再从项目代码推导数据库名。- 元数据、业务库和 TimescaleDB 可以部署在同一主机,也可以分别部署。
在线重算会原子替换一个时刻的节点、管段基线。历史分块已经压缩时,写入事务使用
TIMESCALEDB_REALTIME_MAX_DECOMPRESSED_TUPLES 设置解压预算,默认 100 万条;配置必须
为正整数,不允许 0。该设置仅在实时快照写入事务内生效,不改变数据库全局设置或普通读取。
预算按需根据受影响压缩批次的规模调整,超过预算仍会失败并回滚当前快照;此前完成的其他
时刻不会一起回滚。同步、异步及单表批量快照入口使用同一预算。
压缩写入回归位于 tests/integration/test_realtime_compression.py;仅在设置
REALTIME_COMPRESSION_TEST_DSN 时执行,并且只接受库名以 tjw_test_realtime_ 开头的
独立可丢弃数据库。测试会创建和删除该库内的 realtime schema,禁止指向项目数据库。
完整新建供水项目使用 POST /api/v1/admin/project-provisions,以
multipart/form-data 同时提交 name、小写 code、可选的
description、gs_workspace、map_zoom 和 INP file。工作流会按顺序完成:
- EPANET 校验 INP,并从空结构模板创建业务库、导入模型;
- 创建同名
_template,复制 32 张network表并建立逻辑订阅; - 从
TIMESCALEDB_SCHEMA_TEMPLATE_DB_NAME创建空时序库; - 创建 GeoServer 工作空间、PostGIS 数据存储和 7 个 GIS 图层,将客户端缓存设为
GEOSERVER_CLIENT_CACHE_SECONDS; - 最后在一个元数据事务中写入项目、两条加密数据库路由和创建者成员关系,并将项目设为
active。
基础设施任一步失败时按 GeoServer、时序库、管网模板、业务库的逆序清理;元数据提交失败也执行同样清理。旧 POST /admin/projects 仅保留给已经由外部流程创建好的资源登记使用,并已标记为 deprecated。
使用模板复制或临时方案库的模拟功能时,biz_data 账号必须具备数据库创建和删除权限;只有显式删除项目时才会终止该项目的现有数据库会话,普通复制不会主动中断复制源会话。
奉贤项目的原始 GIS 与水质监测点使用独立脚本维护。导入脚本读取两份交付目录并写入项目 BizDB;水质脚本调用现有灵敏度分析服务生成 50 个监测点;发布脚本创建 10 个项目图层并刷新 GeoServer 缓存:
conda run -n server python scripts/import_fengxian_gis.py --database tjwater_v2
conda run -n server python scripts/generate_fengxian_water_quality_points.py --project tjwater_v2
conda run -n server python scripts/publish_fengxian_map_layers.py --database tjwater_v2 --workspace tjwater_next
发布命令需要配置 GEOSERVER_URL、GEOSERVER_USERNAME 和 GEOSERVER_PASSWORD。其中 7 个基础管网图层仍由项目供应流程创建,奉贤发布脚本追加 9 个原始 GIS 图层和 1 个水质监测点图层。
工况任务工作进程
调查任务 worker 和外围工单同步器是独立进程,不随 API 进程自动启动:
conda run -n server python scripts/run_operational_worker.py
conda run -n server python scripts/sync_peripheral_work_orders.py
OPERATIONAL_WORKER_PROJECT_IDS 是逗号分隔的显式项目 UUID 列表,非空时优先于单项目兼容变量 OPERATIONAL_WORKER_PROJECT_ID。如需覆盖所有已启用项目,应将这些项目的 UUID 全部明确写入列表。进程不会自动枚举项目,配置列表也不会自动授权;服务身份必须是每个目标项目的有效成员。
每个项目保持独立的事件游标、会话、消息游标和 OAuth 缓存。OPERATIONAL_WORKER_MAX_CONCURRENCY 是每个项目的上限,而不是全局上限。更改项目列表后,需要同时重启 worker 和外围同步器。完整配置、授权和重试边界见 工况工作进程说明。
测试与发布
提交前根据改动范围运行最小有效测试:
conda run -n server python -m pytest tests/unit tests/auth -q
发布镜像前建议运行:
docker build -t tjwater-server:local .
Dockerfile 的 test 阶段会执行 Python 编译检查、应用导入、OpenAPI 检查和完整 pytest。
Gitea Actions 工作流位于 .gitea/workflows/package.yml,可手动触发,也会在推送 v* tag 时运行。工作流构建 test 阶段,推送不可变的 sha-* 镜像,然后通过受限 SSH 账号对 next-backend 执行候选验证。只有 tag 工作流会在验证通过后提升正式版本。仓库需要以下 Actions secrets:
REGISTRY_USERNAMEREGISTRY_PASSWORDDEV_DEPLOY_SSH_KEY
这些密钥只用于镜像仓库和受限开发机部署,不替代后端应用的运行环境配置。
安全规则
不要提交 .env、客户数据、数据库 dump、日志、生成缓存、db_inp/、temp/、data/ 或本地密钥。CI/CD 凭据应放在 Gitea secrets 和仓库变量中。
产品 MCP 查询
Server 可选启用 MCP 2026-07-28,默认关闭。当前目录包含 67 项类型化工具,其中 42 项查询、12 项计算、13 项预览式写入。MCP 使用专用 OAuth resource Bearer、宿主绑定上下文和现有项目或任务权限,不提供自由命令执行。
写入工具默认只执行 dry_run。真实写入必须使用同一 request_id,带上预览返回的 expected_digest,并通过宿主原生许可和项目权限检查。超时或结果不明时先查询操作状态,不盲目重试。实施、启用和授权边界见 MCP 实施与启用 和 协议与旧 CLI 能力对照。
后台计算队列
长计算通过项目数据库队列和独立计算 Worker 执行,API 保持4个进程,HTTP超时不延长。接入需要新增数据库迁移和独立服务,详见计算队列部署与MCP契约。
后台调查通过 POST /api/v1/operational-tasks/{task_id}/questions 保存结构化问题,并沿现有任务通知展示。当前 run 可在没有最终报告时正常结束,任务进入等待人工;具备 operational_task.write 权限的用户通过 questions/{question_id}/answer 回答,原任务排队新一轮。回答保存为交互记录,不自动转为现场证据或审批。问题支持最多三个单选、多选或自定义回答,版本校验和请求幂等保护重复提交。任务详情返回最近 100 组提问,完整历史由 MCP 的 get_task_context(section="questions") 分页读取。
部署任务提问功能前,在各项目业务库执行 resources/sql/028_task_questions.sql,再同步 Server、Agent 与前端版本。迁移不修改既有调查记录;更新代码不会自动迁移数据库或重启服务。
外围工单来源统一为系统数据。部署对应版本前,在各项目业务库执行 resources/sql/029_peripheral_work_orders_system_data.sql,再同步 Server、同步器、Agent 与前端版本。迁移会修正既有外围源工单、修订记录、任务快照和外围反馈证据,不修改测试数据或模拟监测数据。
多 Agent 后台调查按实例归属运行。部署对应版本前,在每个启用调查 Worker 的项目 BizDB 执行 resources/sql/030_operational_run_agent_ownership.sql,再同步 Server、Agent 和 Worker,并为各 Agent 配置唯一且重启稳定的 AGENT_INSTANCE_ID。迁移只增加运行归属列和活跃运行索引;旧运行不猜测归属,待其结束或租约恢复后,新一代运行开始记录实例。