feat(projects): automate project infrastructure provisioning
Generic Container CI/CD / test-build-publish (push) Successful in 1m13s
Server CI/CD v2 / build-test-publish-and-deploy (push) Successful in 1m13s

This commit is contained in:
2026-09-11 10:57:51 +08:00
parent 10a7a66a41
commit 90b02057bc
38 changed files with 2345 additions and 189 deletions
+38 -13
View File
@@ -1,12 +1,12 @@
# TJWater 数据库改造说明与当前结构
> 本文记录截至 2026-08-27 的数据库实际状态。结构、约束、行数、TimescaleDB chunk 和策略均直接读取数据库,不以仓库中的 SQL 脚本为依据。文中不包含主机、端口、账号、密码或 DSN。
> 本文记录截至 2026-09-10 的数据库实际状态。结构、约束、行数、TimescaleDB chunk 和策略均直接读取数据库,不以仓库中的 SQL 脚本为依据。文中不包含主机、端口、账号、密码或 DSN。
## 改造范围与当前状态
本次改造保留原 `tjwater` 业务库和时序库,新建的隔离库完成验证后已正式重命名为 `tjwater_v2`。元数据库仍为 `system_hub`,逻辑项目 `tjwater_next` 通过 `biz_data``iot_data` 两条路由分别关联 `tjwater_v2` 业务库与时序库。项目已切换为 `active`,原数据库没有被覆盖,仍可用于对照和回退。
本次改造保留原 `tjwater` 业务库和时序库,新建的隔离库完成验证后已正式重命名为 `tjwater_v2`。元数据库仍为 `system_hub`,逻辑项目 `tjwater_v2` 通过 `biz_data``iot_data` 两条路由分别关联 `tjwater_v2` 业务库与时序库。项目已切换为 `active`,原数据库没有被覆盖,仍可用于对照和回退。
命名约定:正式物理库名为 `tjwater_v2`WNDB 版本模板固定`tjwater_v2_template`。元数据库中的逻辑项目代码和 GeoServer 工作空间仍 `tjwater_next`;这些是路由与图层限定名,不是物理库名。模板已创建并清空项目模型、SCADA、分析运行和物化视图数据,仅保留数据库结构、PostGIS 对象和必要配置键种子,普通连接已关闭
命名约定:正式物理库名和元数据项目代码均`tjwater_v2`其管网镜像模板`tjwater_v2_template`;其他物理业务库同样使用同名 `_template`。项目模板通过逻辑订阅只同步 `network` schema。数据为空且禁止普通连接的 `tjwater_v2_schema_template` 仅用于创建业务库和 INP 暂存库。GeoServer 工作空间仍使用 `tjwater_next`,它是图层限定名,不是项目代码或物理库名。
已经完成的数据库修改包括:
@@ -15,7 +15,7 @@
- `realtime` 采用冷热数据策略,72 小时后的 chunk 自动转为有序列存。
- `analysis``stored_at` 分区,入库满 24 小时的 chunk 自动转为有序列存。
- GIS 查询层改用物化视图,当前 7 张物化视图均已填充。
- GeoServer 已建立 `tjwater_next` 工作空间和同名数据存储,数据存储连接 `tjwater_v2` 业务库的 `gis` schema 并发布 7 个图层。GeoWebCache 的服务端与客户端缓存有效期为 300 秒
- GeoServer 已建立 `tjwater_next` 工作空间和同名数据存储,数据存储连接 `tjwater_v2` 业务库的 `gis` schema 并发布 7 个图层。全部 TJWater 业务工作空间的 GeoWebCache 客户端缓存有效期统一为 30 天(`2,592,000` 秒);服务端瓦片在 GIS 物化视图刷新或坐标修正后显式清理
- 旧库中的 `operation``current_operation``batch_operation``operation_table``restore_operation``snapshot_operation` 没有进入新业务库。
- `system_hub.public` 补充了项目数据库外键、数据库路由约束、连接池约束、必要的非空约束,以及 5 张表和 44 个字段的中文数据库注释。
- 用户角色和项目角色仍是可扩展字符串,没有增加枚举检查约束。
@@ -25,14 +25,14 @@
- 后端批量元素查询读取 GIS 物化视图,模型增删改和 INP 导入提交后执行并发刷新;批量事务只刷新一次。
- `pattern_values``pattern_flow_samples``curve_points``demands``link_vertices` 的顺序号按所属父对象编号,主键已改为父对象 ID 与 `sequence_no` 的复合键。
逻辑项目 `tjwater_next` 当前为 `active`排水项目 `lingang` 已迁入 `system_hub.public`,供水和排水后端共用同一套项目、成员、数据库路由和审计表。
逻辑项目 `tjwater_v2` 当前为 `active`
## 数据库总体关系
```mermaid
flowchart LR
subgraph META["system_hub 元数据库"]
MP["public<br/>统一元数据<br/>5 个项目"]
MP["public<br/>统一元数据<br/>8 个项目"]
end
subgraph OLD["原数据库,保持不变"]
@@ -50,9 +50,8 @@ flowchart LR
MP -->|"tjwater 的 biz_data"| OB
MP -->|"tjwater 的 iot_data"| OT
MP -->|"项目 tjwater_next 的 biz_data"| NB
MP -->|"项目 tjwater_next 的 iot_data"| NT
MP -->|"lingang 的两条数据库路由"| DRAIN["排水项目数据库"]
MP -->|"项目 tjwater_v2 的 biz_data"| NB
MP -->|"项目 tjwater_v2 的 iot_data"| NT
NB -->|"gis 物化视图"| GS -->|"WFS / WMTS"| WEB
```
@@ -62,7 +61,7 @@ flowchart LR
### public:当前主元数据
`public` 当前有 5 个项目、1 个用户、5 条成员关系和 10 条数据库路由。5 个项目均配置了一条 `biz_data` 和一条 `iot_data` 路由。审计日志数量会随接口请求持续增加,不在文档中固化行数。
`public` 当前有 8 个项目5 个 `active`、3 个 `inactive`、1 个用户、5 条成员关系和 16 条数据库路由。8 个项目均配置了一条 `biz_data` 和一条 `iot_data` 路由。审计日志数量会随接口请求持续增加,不在文档中固化行数。
| 表 | 用途 | 主要字段 |
| --- | --- | --- |
@@ -136,7 +135,7 @@ erDiagram
### 排水元数据合并
排水后端原先使用独立的 `hub` schema,其中有 1 个 `lingang` 项目、2 条数据库路由和 203 条审计记录,没有用户或成员关系。项目 UUID 保持不变,两条路由转换为 `public.project_databases` 使用的 Fernet 加密格式,连接池参数由 `pool_size + max_overflow` 映射为 `pool_min_size + pool_max_size`。原审计记录已迁入 `public.audit_logs`
排水后端原先使用独立的 `hub` schema。迁移校验后确认 `lingang` 项目属于误入数据,其项目和数据库路由已从当前 `public` 元数据中移除
排水后端已改用 `public.projects``public.project_databases``public.user_project_membership``public.users``public.audit_logs`。供水和排水服务读取同一份项目状态、Keycloak 身份、项目权限及数据库路由。原 `hub` schema 已在迁移校验和连接测试通过后删除。
@@ -318,7 +317,33 @@ flowchart LR
### GeoServer 与前端图层
`system_hub.public.projects` 中的 `tjwater_next` 项目已配置 `gs_workspace=tjwater_next`,当前状态为 `active`。GeoServer 的 `tjwater_next` 数据存储连接 `tjwater_v2` 业务库并限定到 `gis` schema,图层名称直接采用物化视图名称。7 个图层使用相同的项目管网发布边界,空图层和视口内没有要素的瓦片会返回空 MVT,不会产生越界错误。
`system_hub.public.projects` 中的 `tjwater_v2` 项目已配置 `gs_workspace=tjwater_next`,当前状态为 `active`。GeoServer 的 `tjwater_next` 数据存储连接 `tjwater_v2` 业务库并限定到 `gis` schema,图层名称直接采用物化视图名称。7 个图层使用相同的项目管网发布边界,空图层和视口内没有要素的瓦片会返回空 MVT,不会产生越界错误。
GeoWebCache 对 TJWater 的 11 个业务工作空间、80 个已发布图层统一返回 `Cache-Control: max-age=2592000, must-revalidate`。GIS 数据更新后必须先刷新物化视图、重算 GeoServer 的 native/latLon 边界,再显式清理对应图层的服务端瓦片;30 天客户端缓存不会替代这套更新流程。
### 高铁湛江北站项目
`zjb` 是高铁湛江北站供水项目的逻辑项目代码、业务库、时序库、GeoServer 工作空间和数据存储名称。业务库已使用纠正版 `zhanjiangbei_water_network_pump_station_corrected.inp` 重新导入,当前有 272 个节点和 285 条连接,其中包括 6 台泵和 3 个阀门;`zjb_template` 通过逻辑订阅同步 32 张 `network` 表。时序库使用与 v2 项目一致的 `analysis``realtime``scada` 结构,目前数据为空。元数据库中项目状态为 `active``biz_data``iot_data` 均使用 14 条项目连接池。
源文件使用尚未取得测量定义的 DXF 工程坐标。`zjb.public.spatial_ref_sys` 将其登记为 `TJWater:990001`,原始几何数值保存在该 SRID 下;`gis.to_web_mercator` 根据四个 OSM 控制点执行二维四参数拟合,7 张 GeoServer 物化视图输出 `EPSG:3857`。控制点均方根残差约 4.62 米,适合在线地图展示,不可用于施工或测量放样,取得正式控制点后应重新拟合并刷新物化视图与瓦片。当前发布范围约为 `110.3513°E110.3573°E、21.2185°N21.2225°N`
### 新项目供应工作流
后端通过 `POST /api/v1/admin/project-provisions` 接收项目元数据和 INP 文件。项目在全部外部资源通过校验后才写入元数据库并变为 `active`,因此普通项目列表不会看到创建中的半成品。
```mermaid
flowchart LR
INP["INP 校验"] --> BIZ["创建 BizDB<br/>导入 network / gis"]
BIZ --> TMPL["创建 code_template<br/>复制 network 并订阅"]
TMPL --> TS["从空模板创建 TimescaleDB"]
TS --> GS["创建 GeoServer<br/>7 图层 + 30 天客户端缓存"]
GS --> META["元数据单事务<br/>项目 + 2 路由 + 创建者成员"]
META --> ACTIVE["active"]
```
任一步失败都只清理由本次请求新建的资源,并按 GeoServer、TimescaleDB、项目模板、业务库逆序回滚。业务 PostgreSQL 必须为每个活动模板提供一个逻辑复制 worker;工作流在创建前保留一个 worker 余量,容量不足会在资源创建前失败。容器部署基线为 `max_worker_processes=32``max_logical_replication_workers=24``max_replication_slots=24``max_wal_senders=24`。旧版非活动项目的三个模板订阅已停用,不再占用 worker。
TimescaleDB 的空结构模板为 `tjwater_v2_timescale_template`,普通连接关闭;当前结构包含空的 `analysis``realtime``scada` 表、hypertable 与压缩策略。新时序库直接从该模板克隆,不从任一业务项目复制数据。
| 前端数据源 | GeoServer 图层 | 几何 | 当前要素数 |
| --- | --- | --- | ---: |
@@ -426,7 +451,7 @@ erDiagram
元数据库通过 SQLAlchemy 异步连接池访问;项目请求按 `system_hub.public.project_databases` 路由到业务库和时序库。异步业务查询和异步时序查询由项目级动态池管理,池条目记录借用数;配置更新建立新一代池,旧池只在已有借用归还后关闭。原生 WNDB 同步访问使用按数据库缓存且同样带借用计数的 `psycopg_pool.ConnectionPool`,数据库创建、复制和删除使用独立的 PostgreSQL 管理池及数据库级 advisory lock,同步 TimescaleDB 访问也使用按数据库缓存的连接池。应用目录中已没有直接调用 `psycopg.connect` 的业务代码。
WNDB 批量修改和模拟参数准备在同一条池连接和同一事务中执行,提交后只刷新一次 GIS 物化视图。普通写入和 INP 整体替换使用同一项目级事务锁;INP 先在唯一暂存库校验,再从一致性快照事务替换当前库的 `network/gis` 模型表。临时分析库由固定模板建结构后复制当前项目模型、SCADA 映射并刷新视图。实时节点和连接结果在一个事务中执行整体先删后写,同一结果时间使用事务级锁;分析结果按 `run_id` 加事务级锁。Timescale 复合查询按节点/管段批量读取,SCADA 清洗使用单条集合更新,不再逐点往返。
WNDB 批量修改和模拟参数准备在同一条池连接和同一事务中执行,提交后只刷新一次 GIS 物化视图。普通写入和 INP 整体替换使用同一项目级事务锁;INP 先在唯一暂存库校验,再从一致性快照事务替换当前库的 `network/gis` 模型表。临时分析库从当前项目的 `_template` 管网镜像克隆,只使用 `network` 数据修改参数、导出 INP 并运行 EPANET;GIS 和 SCADA 都不进入临时库。实时节点和连接结果在一个事务中执行整体先删后写,同一结果时间使用事务级锁;分析结果按 `run_id` 加事务级锁。Timescale 复合查询按节点/管段批量读取,SCADA 清洗使用单条集合更新,不再逐点往返。
自动化真实数据库测试分别执行 64 次业务库和 64 次时序库并发借用,查询结果一致,连接均能归还池中。嵌套 WNDB 写入和分析运行生命周期测试会在外层强制回滚,数据库没有残留记录。`DatabaseCommand` 的 pattern 新增、修改、删除也在同一池化事务中完成,并验证了五张明细表的复合主键、级联解除需求模式关联、结果变更和整体回滚。实时覆盖测试确认第二批数据替换第一批数据,外层回滚后测试记录为 0。
+14 -8
View File
@@ -8,7 +8,9 @@
本次调整了代码文件、导入关系、命令分派、项目生命周期接口和 WNDB 内部命令对象。无状态服务不再发布“打开、关闭、是否打开项目”三个旧 HTTP 操作,数据库连接在请求中按需从池借用。历史撤销日志已从数据库中移除,内部接口不再保留无效的兼容字段。真实库回归时发现五张明细表错误地把局部顺序号设成全局主键,已在 `tjwater_v2` 中改为父对象 ID 与 `sequence_no` 的复合主键。
`tjwater_v2` 是 v2 业务库和时序库的正式物理库名元数据库中的逻辑项目代码`tjwater_next`,由项目路由指向 `tjwater_v2`;两者不必同名。版本模板固定为 `tjwater_v2_template`,不按项目代码动态派生。目前该模板已从实际 v2 结构创建、清空项目数据、刷新空物化视图并封存,压缩后约 19 MB
`tjwater_v2` 是 v2 业务库和时序库的正式物理库名元数据库中的逻辑项目代码`tjwater_v2`GeoServer 工作空间仍为 `tjwater_next`。每个物理业务库动态派生同名 `_template` 管网镜像,逻辑订阅只同步 `network` schema。独立的 `tjwater_v2_schema_template` 保持空数据并禁止普通连接,只提供统一 v2 结构
五个源业务库(包括 `zjb`)都在各自的数据库命名空间内使用 publication `wndb_network_pub`,五个模板库都使用 subscription `wndb_network_sub`。PostgreSQL 实例级的 replication slot 按物理库唯一命名,例如 `tjwater_v2_network_slot``md_v2_network_slot``zjb_network_slot`,因此同名发布/订阅不会串库。逻辑复制只跟踪 32 张 `network` 表的 DML;GIS、SCADA、分析和其他业务数据不进入发布,DDL 与序列状态也需显式维护。
## 当前目录
@@ -62,15 +64,17 @@ app/native/wndb/
`database.py` 提供 `ChangeSet``DatabaseCommand`、参数化查询和物化视图刷新。`DatabaseCommand` 只保存待执行的 SQL 和执行成功后返回给调用方的变更列表,不再生成或保存撤销 SQL。模型直接修改时按需刷新视图;批量命令在外层事务提交后只刷新一次。物化视图保留模型坐标 `x``y`,同时将供 GeoServer 使用的 `geom` 转换为 `EPSG:3857`,WNDB 查询不会把发布坐标误当成模型坐标。
`projects.py` 只负责项目数据库的创建、复制、删除和异常安全的临时库上下文,不再保存“项目已打开”状态,也不混入模型查询。`postgres`模板库、旧 WNDB 模板库 `project` 和元数据库均属于保护对象;模板复制源只能精确匹配 `WNDB_TEMPLATE_DB_NAME`,不能重新引入每项目 `_template`。批量清理不再扫描并删除服务器上的未知数据库,调用方必须显式提供每一个目标库名。数据库级 advisory lock 与 `datallowconn` 共同串行化多 worker 下的复制和删除。普通项目复制若复制源仍有其他会话会直接失败,不再主动终止正常请求
`projects.py` 只负责项目数据库的创建、复制、删除和异常安全的临时库上下文,不再保存“项目已打开”状态,也不混入模型查询。`postgres`空结构模板、所有 `_template` 项目模板和元数据库属于保护对象。旧 WNDB 模板库 `project` 已退出架构并从业务 PostgreSQL 与 TimescaleDB 实例删除。每个物理业务库对应一个同名 `_template` 管网模板,逻辑订阅只同步 `network` schema;空结构模板由 `WNDB_SCHEMA_TEMPLATE_DB_NAME` 配置,用于创建业务库和 INP 暂存库。批量清理不再扫描并删除服务器上的未知数据库,调用方必须显式提供目标。数据库级 advisory lock 与 `datallowconn` 串行化生命周期操作;克隆项目模板时仅临时禁止连接,完成后恢复,以便订阅工作进程继续同步
`model_replace.py` 在源库可重复读快照中读取 `network``gis` 基表,并按外键拓扑顺序复制到目标业务库。替换在单一事务内完成,不再删除并重建整个业务库;普通模型修改和整体替换共用同一项目级事务锁。INP 替换时,`analysis.results` 保留历史记录,只有新模型中不存在的元素引用会置空,`asset.scada_devices` 保留仍能匹配新节点或管段的设备;临时分析库则从当前项目复制模型和有效 SCADA 映射
`project_templates.py` 管理正式项目与其 `_template` 的一对一关系:先为源库 32 张 `network` 表创建 publication 和唯一 replication slot,再从空结构模板建立目标库、做一次一致性网络数据复制,最后以 `copy_data=false` 接续逻辑订阅。建库前检查复制 worker 余量,订阅的 worker、32 张关系状态均 ready 后才允许工作流继续。删除或失败回滚时先移除 subscription 和 slot,再删除模板库及 publication,不遗留 WAL slot
`model_replace.py` 在源库可重复读快照中读取 `network``gis` 基表,并按外键拓扑顺序复制到目标业务库。替换在单一事务内完成,不再删除并重建整个业务库;普通模型修改和整体替换共用同一项目级事务锁。INP 暂存库与目标项目使用不同原始坐标 SRID 时,复制阶段保留坐标数值并将几何重新标记为目标列的 SRID,支持 `zjb` 等使用项目自定义工程坐标系的业务库。INP 替换时,`analysis.results` 保留历史记录,只有新模型中不存在的元素引用会置空,`asset.scada_devices` 保留仍能匹配新节点或管段的设备。临时分析库只需要订阅模板中的 `network` 数据;GIS 只是 INP 的可选展示章节,SCADA 也不是 EPANET 求解器的输入表。
### model:管网模型和仿真配置
`model` 按业务实体命名,不再使用 `s2_junctions.py` 这类 INP 章节编号。节点、连接、模式、曲线、需求、规则和仿真设置都能从文件名直接定位。
`options_v2.py``options_v3.py` 分别负责 EPANET V2、V3 的 `[OPTIONS]` 章节导入导出;数据库中的 `engine_version = 'legacy'` 仍表示 V2 配置,仅作为现有存储标识保留。
`options_v2.py``options_v3.py` 分别负责 EPANET V2、V3 的 `[OPTIONS]` 章节导入导出;数据库中的 `engine_version = 'legacy'` 仍表示 V2 配置,仅作为现有存储标识保留。通过 V3 入口导入标准 EPANET INP 时会同时保存 legacy 原值和映射后的 V3 值,保证数据库再次导出的 V2 INP 不会引用模板遗留的 pattern。
每个实体模块保留三类紧密相关的函数:读取实体、生成并执行实体变更、转换该实体对应的一行或一段 INP 内容。完整文件的读取顺序、事务和项目生命周期由 `inp` 目录负责。因此,实体级编解码仍靠近实体定义,跨章节编排已经集中。
@@ -99,7 +103,7 @@ app/native/wndb/
`sections.py` 只保存 INP 章节名称和输出顺序。旧文件中混放的 `s1_title``s2_junction` 等命令类型常量已经移除。
`importer.py` 负责文件分段、导入顺序、项目事务、版本转换和导入后的物化视图刷新。INP 更新先从 `tjwater_v2_template` 创建唯一暂存库并完成解析,再在当前业务库中事务替换模型表。模型提交后即使暂存库清理失败也仍会刷新物化视图;清理失败会记录日志,不再遮蔽主操作。ChangeSet 导入使用每请求唯一临时文件并在 `finally` 删除,避免同项目并发导入互相覆盖。`exporter.py` 负责按 EPANET 版本组织各章节并写出文件或 `ChangeSet`
`importer.py` 负责文件分段、导入顺序、项目事务、版本转换和导入后的物化视图刷新。INP 更新先从 `tjwater_v2_schema_template` 创建唯一暂存库并完成解析,再在当前业务库中事务替换模型表。模型提交后即使暂存库清理失败也仍会刷新物化视图;清理失败会记录日志,不再遮蔽主操作。ChangeSet 导入使用每请求唯一临时文件并在 `finally` 删除,避免同项目并发导入互相覆盖。`exporter.py` 负责按 EPANET 版本组织各章节并写出文件或 `ChangeSet`
### commands:批量修改和级联关系
@@ -146,7 +150,9 @@ flowchart LR
WNDB 当前使用同步 `psycopg` 连接池。`network/``components/` 和同步 EPANET 仿真接口统一声明为同步处理函数;公开 REST 路由的异步适配器把这些函数送入线程池,并把项目路由上下文传入工作线程,不会在事件循环线程上阻塞数据库或求解器。异步业务库和时序库访问使用带借用计数和代际切换的项目池:活跃旧池不会被 LRU 淘汰或强制关闭,配置变化后新请求立即使用新池,旧池在已有借用归还后关闭。元数据库保持独立 SQLAlchemy 异步池。
临时分析库先由固定 `tjwater_v2_template` 提供结构,再从当前项目的一致性快照复制 `network/gis` 模型与 SCADA 映射并刷新物化视图。V3→V2 格式转换不需要项目模型,单独使用空模板临时库。旧 `online_Analysis.py`、restore 和 open/close 项目脚本已经删除,不再保留每项目模板与 operation 恢复入口。
临时分析库直接从当前物理业务库的 `_template` 管网镜像创建,模板中的 `network` 数据由逻辑订阅维护。扩展仿真只在临时库修改管网参数、导出 INP 并调用 EPANET;GIS 和 SCADA 都不复制到临时库。当前 v2 项目的 SCADA 设备均为 `non_realtime`,扩展仿真使用接口显式传入的参数。V3→V2 格式转换不需要项目模型,单独使用空结构模板临时库。旧 `online_Analysis.py`、restore 和 open/close 项目脚本已经删除,不再保留 operation 恢复入口。
监测点选址、爆管定位和 DMA 漏损识别统一通过应用服务按请求导出唯一的临时 INP。算法运行期间文件有效,成功或异常退出时都会删除;不再复用按项目命名的固定 INP 缓存,避免模型更新后算法继续读取旧文件,也避免并发请求覆盖彼此的输入。
## 依赖方向
@@ -196,7 +202,7 @@ WNDB 根包不再作为依赖汇聚点。上层若只需要管道查询,应直
## 验证结果
- 本地 conda 环境单元、鉴权和 API 测试:313 项通过,2 项按条件跳过。
- 一次性实库从 `tjwater_v2_template` 创建后,通过 INP 暂存解析和事务替换得到 11 个节点、13 条连接、11 条坐标及 9 条 junction 物化视图记录,验证后已完整删除。
- 一次性实库从空结构模板创建后,通过 INP 暂存解析和事务替换得到 11 个节点、13 条连接、11 条坐标及 9 条 junction 物化视图记录,验证后已完整删除。
- `tjwater_v2` 统一视图覆盖 87,907 个节点和 91,054 条链路,与六个来源物化视图的合计数量一致。实测完整节点读取约 0.17 秒、完整链路读取约 0.10 秒、完整拓扑两次批量查询约 1.12 秒;耗时仅作为当前环境基线,不作为固定性能承诺。
- `tjwater_v2` 真实数据库测试11 项通过,覆盖业务库和时序库并发借用、失效连接自动重建、临时库模型/SCADA/视图完整克隆与清理、嵌套事务回滚、分析运行生命周期、恶意标识符转义、明细表复合主键、统一 GIS 查询视图,以及 WNDB pattern 增删改、级联解除需求关联和整体回滚。
- `tjwater_v2` 真实数据库测试覆盖业务库和时序库并发借用、失效连接自动重建、只含 `network` 数据的临时库仿真与清理、嵌套事务回滚、分析运行生命周期、恶意标识符转义、明细表复合主键、统一 GIS 查询视图,以及 WNDB pattern 增删改、级联解除需求关联和整体回滚。
- Python 编译、未使用导入扫描、撤销字段残留扫描和 `git diff --check` 均通过。