# 城市生命线驾驶舱后端契约:移交与联调手册 本手册面向 UE / 前端对接团队,记录 `/api/cockpit/v1/` 的目标契约、字典、分页、错误与实时增量口径,并给出联调检查表。**当前代码与数据库已于 2026-09-24 发生结构变化,旧冻结字典不能直接当成当前运行时验收结果;第 8 节的“当前重建基线”优先于历史描述。**对应的契约测试仍为 `CockpitContractIntegrationTest` 与 `CockpitContractDictionaryTest`,但目前工作树编译未通过,不能宣称这些测试已在新模型下通过。 ## 1. 契约总则 - 所有接口均为**只读**:仅 `GET` + `@PreAuthorize("isAuthenticated()")`,不存在任何处置、派发、确认类写入口。 - 统一响应信封:`{ "code": 200, "msg": "操作成功", "data": {...} }`;错误为 `{ "code": , "msg": "", "data": { category, message, requestId, serverTime } }`。 - JSON 字段一律 `camelCase`。数据库列名属适配器内部细节,不出现在契约中。 - 设备唯一关联键:对外使用 `deviceCode`(对应 `equipment_base.equipment_code`)。设备主键内部使用 `equipment_base.equipment_id`;`work_order` 当前通过 `device_id` 关联设备,不能读取已删除的 `work_order.device_code`。WebSocket 迁移期同时下发 `device_code` 作为兼容别名。 - 时区:业务时区固定 `Asia/Shanghai`,所有时间戳为带偏移量的 ISO-8601(如 `2026-09-02T10:00:00+08:00`)。 - 可选请求头 `X-Request-Id`;未提供时服务端生成,响应与错误体中都会回传。 ## 2. 字典冻结 | 字典 | 取值 | 说明 | | --- | --- | --- | | 等级颜色 | `1 RED` / `2 YELLOW` / `3 BLUE` / `4 GREEN` / `UNKNOWN` | 1红2黄3蓝4绿 | | 监测点状态 | `ALARM` `WARNING` `WORK_ORDER` `NORMAL` `OFFLINE` `EMPTY` | 优先级即此顺序,后端负责裁决 | | 指标状态 | `NORMAL` `WARNING` `ALARM` `EXPIRED` `OFFLINE` `UNKNOWN` | 运行监测专用,`EXPIRED` 不与 `OFFLINE` 合并 | | 工单状态 | **当前运行时**:0待派单 1待接单 2已接单 3处理中 4待验收 5已结案 6延期中 7已取消 | 以当前 `WorkOrderStatusEnum`/`work_order.order_status` 为准;终态为 5、7,延期中为处理旁路。旧 ADR-0008 的“从 1 开始七态”只保留为历史决策,不得当作当前数据库编码 | | 新鲜度 | `CURRENT` `STALE` `EXPIRED` `EMPTY` `FAILED` | 每次响应携带 `freshness` | | 错误类别 | `UNAUTHORIZED` `SERVER_ERROR` `INVALID_REQUEST` `SNAPSHOT_EXPIRED` `NOT_FOUND` | 401/500/400/400/404 | 效率口径固定为 `basis = CLOSED_WORK_ORDERS_OVER_TOTAL_EVENTS`(已结案工单数 ÷ 事件总数);系统不判断预警真伪,契约中不存在任何准确率、误报率字段。 ## 3. 端点清单 四大业务模式: | 模式 | 端点 | 主要参数 | | --- | --- | --- | | 总体概览 | `GET /api/cockpit/v1/overview/infrastructure` | — | | 总体概览 | `GET /api/cockpit/v1/overview/monitoring` | `industry` | | 总体概览 | `GET /api/cockpit/v1/overview/warning-settings` | `mode`(WARNING\|ALARM) `range` `startTime` `endTime` | | 基础设施 | `GET /api/cockpit/v1/infrastructure/base-archives` | `industry` `facilityType` `ageBucket` `region` | | 基础设施 | `GET /api/cockpit/v1/infrastructure/operating-information` | `industry` `range` `from` `to` | | 基础设施 | `GET /api/cockpit/v1/infrastructure/management-information` | `industry` `organizationCode` | | 基础设施 | `GET /api/cockpit/v1/infrastructure/emergency-support` | `category` `industry` `area` | | 运行监测 | `GET /api/cockpit/v1/monitoring/overview` | `industry` `deviceType` | | 运行监测 | `GET /api/cockpit/v1/monitoring/distribution` | `industry` `deviceType` `pointStatus` `region` `page` `pageSize` `snapshotAt` | | 运行监测 | `GET /api/cockpit/v1/monitoring/situation` | `industry` `deviceType` `startTime` `endTime` `page` `pageSize` | | 预警处置 | `GET /api/cockpit/v1/events/current` | `mode`(必填) `level` `industry` `status` `page` `pageSize` | | 预警处置 | `GET /api/cockpit/v1/events/history-analysis` | `eventFamily`(必填) `level` `industry` `startTime` `endTime` | | 预警处置 | `GET /api/cockpit/v1/events/handling-efficiency` | `eventFamily`(必填) `industry` `level` `workOrderState` `startTime` `endTime` `page` `pageSize` | | 预警处置 | `GET /api/cockpit/v1/events/multidimensional-analysis` | `eventFamily`(必填) `industry` `level` `eventType` `cause` `deviceType` `region` `startTime` `endTime` | 一张图与详情: | 用途 | 端点 | | --- | --- | | 地图分页快照(**地图取数路径**) | `GET /api/cockpit/v1/map-snapshot?page&pageSize&snapshotAt&industry®ion&deviceType` | | 监测点详情 | `GET /api/cockpit/v1/monitoring-points/{monitoringPointCode}` | | 设备档案(含最新图片) | `GET /api/cockpit/v1/equipments/{deviceCode}/archive` | | 类型指标 | `GET /api/cockpit/v1/equipments/{deviceCode}/metrics/current`、`.../metrics/history` | | 事件 | `GET /api/cockpit/v1/equipments/{deviceCode}/events/current`、`.../events/history` | | 工单 | `GET /api/cockpit/v1/equipments/{deviceCode}/work-orders`、`/events/{eventSource}/{eventCode}/work-orders` | | 工单时间线 | `GET /api/cockpit/v1/work-orders/{workOrderCode}/timeline` | | 设备详情聚合 | `GET /api/cockpit/v1/equipments/{deviceCode}/detail-aggregate` | | 新鲜度 | `GET /api/cockpit/v1/equipments/{deviceCode}/freshness` | | 契约自检 | `GET /api/cockpit/v1/foundation`、`GET /api/cockpit/v1/device-boundary` | ### 地图筛选路径(冻结结论) **全局行业/范围筛选落到地图,以 `map-snapshot` 为准**(本结论替代「以 `/monitoring/distribution` 为准」的候选路径): - 地图(跨四个模式常驻的中央一张图)只从 `map-snapshot` 取数;`/monitoring/distribution` 是运行监测模式「监测分布」面板的载荷,不作为地图取数路径。 - `map-snapshot` 的目标契约接受 `industry`、`region`、`deviceType` 三个筛选入参;分页与 `snapshotAt` 语义不因筛选改变。**2026-09-24 人工裁定**:正式行业扩为 `GAS` / `WATER_SUPPLY` / `DRAINAGE` / `MANHOLE`(井盖为独立根类型);对外 `typeCode` 使用 `equipment_type.id` 的字符串形式,`type_name` 仅用于展示。`monitoring/overview` 与 `monitoring/situation` 已按此接受 `MANHOLE`;地图、分布、事件和基础设施服务仍校验旧三行业,不能把第四行业说成全接口可用。旧 `type_id/parent_type_id` 不是当前 schema 字段。 - 服务端完成过滤,并在响应 `mapFilter{industry,region,deviceType,pointCodes,deviceCodes}` 回显**实际生效范围**(编码为过滤后全量、分页前,升序)。 - `coordinateValidation` 汇总是**当前筛选范围内的全量口径**(再切片分页);逐点/逐设备保留 `renderable=false` 的对象。 - 逐点 `industry` 由类型推导(口径同 distribution:类型可解析、`objectKind = SMART_DEVICE`、行业属正式行业);不可推导时为 `null`,**默认不排除对象**,仅在请求了 `industry`/`deviceType` 过滤时按推导结果筛选。`region` 空值在响应中归一为 `未分区`。 在线试用每个切片的样例响应:打开 `docs/cockpit/` 下的同名 `*-demo.html`(离线自包含,无需后端)。 ## 4. 分页、快照与错误口径 - `page` 默认 1;`pageSize` 默认 100(地图快照默认 500),上限 1000;越界返回 `INVALID_REQUEST`。 - `map-snapshot` 的 `page > 1` 必须携带同一 `snapshotAt`,否则 `SNAPSHOT_EXPIRED`;同一 `snapshotAt` 下分页结果稳定,监测点优先、已关联设备不重复出点。 - 无效或未验证坐标的对象保留在列表与详情中,但 `coordinateValidation.renderable = false`,禁止渲染。 - 成功但无数据返回 200(例如 `freshness.state = EMPTY`),不得用 5xx 表达空;仅上游读取失败才 `SERVER_ERROR`。 - 时间范围口径:预设 `range` 取值 `LAST_24_HOURS` / `LAST_7_DAYS` / `LAST_30_DAYS`(`infrastructure/operating-information` 与 `overview/warning-settings` 同款);显式区间用 `startTime`/`endTime`(ISO-8601 带偏移,必须成对出现且 `startTime ≤ endTime`,回显 `range.type = CUSTOM`)。`overview/warning-settings` 不声明窗口时维持 `CURRENT_BASELINE`(`start`/`end` 为 `null`,趋势桶不筛选)。 - `overview/warning-settings` 的趋势桶是**业务时区日期**(`yyyy-MM-dd`),声明窗口时按「桶日期落在窗口起止日期之间(含端点)」筛选,**桶顺序沿用适配器顺序**;桶标签无法解析时保留该桶,不静默丢数据。 - 未知类型、非智能设备、非正式行业在**按行业/类型筛选时**显式排除,不做静默归类;`map-snapshot` 不带筛选时仍返回全部对象(`industry`/`typeName` 为 `null`),保证「全城底图」不因类型数据缺失而丢对象。 - 事件行(`overview`/`warningResponse` 的 `events/current` 的 `EventItem`)携带 `coordinateValidation`,与地图/监测切片**同一校验实现**(`CockpitCoordinateValidator`);坐标缺失、越界或未验证时 `renderable = false` 且给出 `reason`。 - 适配器未给出 `coordinateVerified` 时按**未验证**处理(`state=UNVERIFIED`、`reason=UNVERIFIED_COORDINATE`、`renderable=false`)——字典要求「未验证禁止渲染」,服务端不臆造验证状态。报警族在数据可得时另带 `thresholdMinValue`/`thresholdMaxValue`(预警族为 `null`)。 - `events/history-analysis` 的 `rows[]` 是**小时桶聚合行**,不存在逐事件行,因此不携带 `coordinateValidation` 与阈值字段(逐事件坐标只在 `events/current` 提供)。 ## 5. WebSocket 实时增量 端点:`/ws/deviceStatus`。 ``` // 订阅全量驾驶舱状态 → {"type":"cockpitSubscribe","scope":"all"} ← {"type":"cockpitSubscribed","scope":"all","serverUpdatedAt":"2026-09-02T10:00:00+08:00"} // 增量推送(迁移期同时下发 device_code 别名;monitoringPointCode 关联监测点, // 未关联设备为 null 且不静默丢失) ← {"type":"cockpitDeviceStatusDelta","deviceCode":"TC-PS-WS-202609020012", "device_code":"TC-PS-WS-202609020012", "monitoringPointCode":"MP-001", "data":{"status":"ALARM","statusTime":"2026-09-02T09:59:50+08:00", "serverUpdatedAt":"2026-09-02T10:00:00+08:00"}} // 兼容旧客户端:按设备订阅 → {"type":"subscribe","deviceCodes":["TC-PS-WS-202609020012"]} ← {"type":"subscribed","deviceCodes":["TC-PS-WS-202609020012"]} ``` 客户端以 `serverUpdatedAt` 判新旧(新者胜),REST 提供首帧权威快照,WebSocket 只推变化设备;断线后重新订阅即可。设备状态校准周期默认 60 秒,可配置延长。 增量帧字段口径:`monitoringPointCode` 为设备关联监测点的编码(`equipment_base.equipment_code` → `equipment_point_rel` → `monitor_point.point_code`);未关联设备该字段显式 `null`,不静默丢失。`data.reason` 无异常时显式 `null`。 ## 6. 启动与联调 后端启动(Java 11+,Maven 3.9): ```bash cd pipe-network-service mvn -DskipTests package java -jar zksy-admin/target/zksy-admin.jar --spring.profiles.active=dev ``` 或使用自带脚本 `./ry.sh start|stop|restart|status`。`ry.sh` 已固定 `-Duser.timezone=Asia/Shanghai`。 契约回归: ```bash mvn -pl zksy-admin -am test -Dtest='Cockpit*Test' ``` 联调检查表: 1. 携带正确凭据访问任一端点,返回 200 且 `data.readOnly = true`;无凭据返回 401 `UNAUTHORIZED`。 2. 对每个端点构造一次非法参数,确认 400 `INVALID_REQUEST` 且不产生业务写入。 3. 用 `snapshotAt` 连续请求 `page=1..n`,确认 `total` 一致、监测点不重复。 4. 用同一 `deviceCode` 依次请求 archive / metrics / events / work-orders / detail-aggregate,确认关联一致。 5. `detail-aggregate` 中任一分区失败时,其余分区仍返回数据。 6. 建立 WebSocket,发送 `cockpitSubscribe(scope=all)`,确认收到 `cockpitSubscribed` 与后续增量。 7. 校验所有时间戳均为 `+08:00`,`freshness` 与实际数据新鲜度一致。 ## 7. 国产化与容器化可行性 本仓库**当前没有** Dockerfile 或容器编排文件,容器化属于后续工作,此处只记录评估结论: - 运行面:标准 JVM 应用(Java 11 字节码)+ **当前 KingbaseES** / Redis / Nacos;旧文档中的 MySQL 是历史评估假设,不是当前真实数据源。迁移到 aarch64/loongarch64 信创环境仍需对应架构的 JDK、KingbaseES 驱动与实机验证,不应仅凭代码推断无需修改。 - 银河麒麟:主要风险在中文字体、时区与 TLS 库,而非业务代码;`Asia/Shanghai` 已由 `ry.sh` 与各切片 `Clock` 统一约束。 - Docker:可行。建议以 `eclipse-temurin:11-jre`(或信创基础镜像)为基座,外置 `application-prod.yml` 与密钥,数据库走独立容器或既有实例。若目标环境禁止容器,直接以 systemd + `ry.sh` 部署同样可行。 - 内存基线:`ry.sh` 默认 `-Xms512m -Xmx1024m`,如需在驾驶舱工作站与渲染任务共存,建议按实际并发上调堆上限。 - 性能测量:本轮未做生产规模压测,属未验证项;建议在取得真实设备规模后,用同一 `snapshotAt` 对 `map-snapshot` 与 `monitoring/distribution` 做分页吞吐与前端渲染测量,再决定是否加投影或索引。 ## 8. 当前重建基线(2026-09-24) 本节记录需求重同步时读取到的当前代码与 KingbaseES 事实。它覆盖并取代 2026-09-18 以前的真实库数量与旧字段假设;不表示当前 cockpit 适配器已经可运行。 ### 当前数据库模型 - `equipment_type` 当前列为 `id`、`type_name`、`parent_id`;旧 `type_id`、`parent_type_id` 不存在。已裁定对外 `typeCode = String(equipment_type.id)`;行业沿类型树上溯到根,正式行业为 `GAS`、`WATER_SUPPLY`、`DRAINAGE`、`MANHOLE`。`monitoring/overview` 与 `monitoring/situation` 已使用该映射;其余切片尚未改完。 - `equipment_base.equipment_type_id` 指向 `equipment_type.id`;`equipment_type_name` 是查询补充字段,不是持久化列。 - `work_order` 当前以 `device_id` 关联 `equipment_base.equipment_id`,不再有 `device_code`、`alarm_id`、`case_id`、`hazard_id` 列;报警转工单使用 `alarm_data.work_order_id → work_order.order_id`,案例和隐患分别通过各自的 `work_order_id` 关系读取。 - `alarm_data` 持久化 `device_code`、`work_order_id`、`warning_code`、`alarm_level`、`alarm_status`、`create_time`、`audit_status` 等;设备名称、设备类型、坐标、工单补充字段属于查询投影,不是表列。 - 工单运行时状态为 `0..7`:0待派单、1待接单、2已接单、3处理中、4待验收、5已结案、6延期中、7已取消;终态为 5/7,6 为处理旁路。 ### 当前真实库只读观测 当前 KingbaseES `app_user` 观测值:`work_order=1`、`alarm_data=3`、`early_warning=35`、`alarm_warning_rel=3`、`equipment_base=2`、`equipment_status=2`、`monitor_point=7`、`equipment_point_rel=20`、`case_info=2`、`hazard_hidden_account=12`。 - 当前两台设备中,一台为离线报警,一台在线正常;当前样例不能代表生产规模。 - 20 条点位关系中 10 条逻辑有效,但当前两台设备均无法匹配到有效关系行,旧的“设备回联 3 条/37 台设备”数字已失效。 - 3 条报警中 1 条通过 `alarm_data.work_order_id` 关联当前唯一工单;35 条预警中仅 6 条能经设备台账和类型层级解析类型。 - 点位仍存在空坐标与 `(12,12)` 等异常坐标;坐标校核字段仍未出现在当前表模型中。按最小修改与现有校验器复用原则,缺可信校核来源一律 `coordinateVerified=false`、`renderable=false`;单纯经纬度范围校验会放过 `(12,12)`,不得用它代替业务校核。新增区域边界校核需另行确认边界与坐标系来源。 ### 当前代码可验收性 本工作树**已重新编译通过**,但仍不是 #63 的验收基线: - 旧编译阻断(`WorkOrder::getDeviceCode`、`EquipmentType.getTypeId/getParentTypeId`)在当前工作树中已不存在;`mvn -pl zksy-admin -am compile -q` 于 2026-09-24 通过。 - `monitoring/overview` 与 `monitoring/situation` 已把 `MANHOLE` 纳入正式行业,并沿类型树上溯到根。定向测试 34 例 0 失败:两个控制器测试、两个真实适配器测试。 - 地图、分布、事件、基础设施等服务仍拒绝 `MANHOLE`。工单对外字典仍是旧 1–7 模型。第 6 节检查表的真实登录态联调没有执行。 - 历史“4/22 片已接真实库”只代表旧 schema 下的尝试,不能当作当前验收。 - 人工澄清:“工单字典”指代码枚举 `WorkOrderStatusEnum`,不是独立数据库表;其当前取值为 0–7,与 ADR-0010 一致。只读查询 `app_user.sys_dict_type`/`sys_dict_data` 未发现工单状态字典项,无需再以数据库字典表为联调前置条件。 ## 9. 已知边界与未验证项 - 当前工单运行时状态以 ADR-0010 的 0–7 八值枚举为权威;旧 cockpit 七态目录仍存在于响应模型/测试中,需先确认映射方案后才可宣称真实工单状态已对齐。 - 本轮已对 KingbaseES 当前 schema 与数据做**只读探查**,但**未启动应用并完成第 6 节检查表 1–7 的真实登录态端到端联调**;生产规模性能、银河麒麟与 Docker 的实机验证仍为未执行项。 - 本仓库的工作区可能包含与本契约无关的在途改动,联调前请确认部署来源是其预期提交。