Files
AquaControlAI/开发文档/三文档一致性与冲突审查报告.md
T
2026-07-11 18:04:51 +08:00

982 lines
30 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 三文档一致性与冲突审查报告
## 1. 审查对象
- `spec-数据管理.md`v1.1,最后更新 2026-07-09
- `spec-历史数据.md`v1.0,最后更新 2026-07-09
- `code-standards.md`v1.1,最后更新 2026-07-11
本报告按本地文件实际行号定位。严重级别定义:
- **阻断**:按现有文档无法得到唯一、可运行或安全的实现,应在开发前解决。
- **高**:会造成接口不兼容、数据错误、安全问题或大范围返工。
- **中**:实现可以继续,但不同团队很可能产生不同解释。
- **低**:主要影响命名、示例准确性或长期维护。
## 2. 总体结论
共发现 **38 项**需要协调的问题,其中:
- 阻断:4 项
- 高:12 项
- 中:18 项
- 低:4 项
最需要优先解决的主题是:
1. 服务进程模型与运行时内存状态的访问方式不一致。
2. 历史模块直接读取数据管理模块数据库,违反模块自治要求。
3. TDengine 中 UUID 字段长度与 API/PG UUID 表达不兼容。
4. 表格查询间隔规则自相矛盾,且缺少实现所需的 `history_interval`
5. 写入接口允许客户端自报操作者,和鉴权规范冲突。
6. CSV、统一响应包、错误码、质量戳存在多套互不兼容的约定。
7. 历史数据对禁用/删除点位不可访问,与“展示所有已存储历史数据”冲突。
---
## 3. 冲突明细
### A. 架构与模块边界
#### C-01|阻断|历史模块直接访问数据管理内部数据,违反模块自治
**位置**
- `spec-历史数据.md` L30-L37:历史模块直接查询 PostgreSQL 设备、采集点配置。
- `spec-历史数据.md` L133-L139、L221-L223:直接读取 `collection_points` 构建树。
- `code-standards.md` L29-L33:模块间通过 API 通信,不直接访问其他模块内部数据。
- `code-standards.md` L614-L620:代码审查要求检查跨模块 Repo 调用。
**冲突内容**
历史规格把 `collection_points``devices` 当作历史模块可直接查询的数据源;代码规范则要求模块之间只能通过 API 通信。
**影响**
- 历史模块会与数据管理表结构强耦合。
- 数据管理字段或逻辑删除规则变化会直接破坏历史模块。
- 无法明确历史 Service 应调用数据管理 API、共享领域服务,还是共享 Repository。
**建议**
二选一并写入架构决策:
1. **模块化单体方案**:允许 Service 通过共享只读领域接口访问配置数据,并删除“模块间只能通过 API”的绝对要求。
2. **服务化方案**:历史模块通过数据管理内部 API/客户端获取点位元数据,禁止直接读表。
---
#### C-02|阻断|`server`/`collector` 分进程结构与“直接共享内存单例”冲突
**位置**
- `code-standards.md` L71-L76:存在独立的 `cmd/server``cmd/collector` 可执行程序。
- `spec-数据管理.md` L439-L443:最新值维护在采集引擎内存。
- `spec-数据管理.md` L1024-L1051:采集引擎单例运行在服务端进程中,Handler 直接获得实例引用。
- `spec-数据管理.md` L325-L329:设备 API 从采集引擎运行时状态组装 `connection_status`
**冲突内容**
目录规范表明 Web 服务和采集器是两个独立进程;数据管理规格却要求 API Handler 直接引用采集引擎内存对象。独立进程不能直接共享 Go 内存。
**影响**
`latest_value``connection_status` 等接口无法按文档实现,除非实际部署结构与目录规范不同。
**建议**
明确唯一运行模型:
- 若采集器嵌入 Web 服务:删除独立 `cmd/collector`,或说明它仅用于离线/独立部署。
- 若保持双进程:引入 Redis、MQTT、NATS、数据库状态表或内部 RPC;Web 服务不得直接引用 CollectorManager。
---
#### C-03|高|Handler 直接访问采集引擎,违反三层架构
**位置**
- `spec-数据管理.md` L325-L329、L1024-L1051API Handler 直接查询引擎实例并组装状态。
- `code-standards.md` L165-L180Handler → Service → Repository 单向调用。
- `code-standards.md` L184-L203Handler 仅做参数校验和响应转换。
**冲突内容**
运行时状态查询属于业务聚合逻辑,应由 Service 完成;规格明确要求 Handler 直接访问 Engine。
**影响**
Handler 与运行时实现耦合,单元测试和未来进程拆分困难。
**建议**
定义 `RuntimeStatusProvider` 接口,由 Service 注入;Handler 只调用 Service。
---
#### C-04|高|历史“最新值”的数据源和语义未统一
**位置**
- `spec-数据管理.md` L439-L454`latest_value` 明确定义为采集引擎内存中的实时值。
- `spec-历史数据.md` L141-L159、L221-L273:历史树也返回同名 `latest_value`
- `spec-历史数据.md` L32-L37:历史模块声明的数据源只有 PostgreSQL 和 TDengine。
**冲突内容**
历史模块没有说明如何取得实时内存值。若从 TDengine 读取,它受 `history_interval` 影响,不再是数据管理接口定义的“实时最新值”;若从采集引擎读取,又违反当前模块边界和分进程结构。
**影响**
同名字段在两个接口中可能代表不同时间新鲜度。
**建议**
明确命名和来源:
- 实时缓存值:`realtime_value`,由运行时状态服务提供。
- TDengine 最新存储值:`latest_stored_value`,并返回 `stored_at`/延迟说明。
---
### B. 历史数据可见性与业务规则
#### C-05|高|“展示所有已存储历史数据”与禁用/删除过滤冲突
**位置**
- `spec-历史数据.md` L23-L28:模块负责展示所有已存储历史时序数据。
- `spec-历史数据.md` L99-L109:树仅显示点位和设备均启用、未删除的点。
- `spec-数据管理.md` L142-L150:逻辑删除时数据保留。
- `spec-数据管理.md` L243-L247:删除设备会逻辑删除其点位,但 TDengine 历史数据未删除。
**冲突内容**
禁用或逻辑删除后,历史数据仍保留,但点位从树中消失,用户无法再查询这些已存储数据。
**影响**
历史追溯、事故审计和删除前数据查看不可实现。
**建议**
历史树应区分:
- 活跃点位;
- 已禁用点位;
- 已删除/归档点位。
至少提供“包含归档点位”筛选,并禁止采集但允许只读历史查询。
---
#### C-06|中|树声明“可用历史数据点”,实际只检查配置,不检查是否存在历史数据
**位置**
- `spec-历史数据.md` L133-L139:只显示包含“可用历史数据点”的分组。
- `spec-历史数据.md` L99-L109:判定条件仅为 enabled/deleted/store_history。
- `spec-数据管理.md` L1017-L1022:子表和数据只有实际写入时才产生。
**冲突内容**
刚启用 `store_history`、尚未首次写入的点位也会进入树,但并不存在历史数据。
**影响**
“可用”含义不一致,用户勾选后可能得到空结果。
**建议**
将术语改为“已配置历史存储的点位”,或额外检查 TDengine 子表/首条数据是否存在,并返回 `has_history_data`
---
#### C-07|中|树过滤规则与查询接口校验规则不一致
**位置**
- `spec-历史数据.md` L99-L109:树过滤 enabled/deleted/store_history。
- `spec-历史数据.md` L302-L311、L371-L383:查询仅根据 `point_ids` 映射子表,没有说明重复校验业务状态。
**冲突内容**
客户端可绕过树,直接传入禁用、删除或 `store_history=false` 的旧点位 ID。
**影响**
UI 和 API 的业务边界不一致;可能访问本应隐藏的数据。
**建议**
明确查询权限策略:是否允许归档历史查询。Service 必须根据该策略重新校验点位,不信任前端树。
---
### C. 数据模型与数据格式
#### C-08|阻断|TDengine UUID 字段长度与 PostgreSQL/API UUID 不兼容
**位置**
- `spec-数据管理.md` L58-L63、L337-L346:主键为 PostgreSQL `UUID`
- `spec-历史数据.md` L64-L74`point_id``device_id``VARCHAR(32)`
- `spec-数据管理.md` L989-L1001:同样使用 `VARCHAR(32)`
- `spec-数据管理.md` L1006-L1014:仅“子表名”明确去除 UUID 连字符。
- API 示例普遍使用带连字符的 UUID/UUID 语义。
**冲突内容**
标准 UUID 字符串带连字符时长度为 36;`VARCHAR(32)` 只能保存去连字符格式。文档只规定子表名去连字符,没有规定普通字段和 TAG 也去连字符。
**影响**
插入失败、截断、查询无法匹配 PostgreSQL ID,属于数据完整性阻断问题。
**建议**
统一一种方案:
- 推荐 TDengine `point_id``device_id` 改为 `VARCHAR(36)`API/PG/TD 全部使用标准 UUID 字符串;
- 子表名使用固定安全前缀加 32 位无连字符形式,例如 `p_<uuid32>`
---
#### C-09|高|可变名称/设备/数据类型与 TDengine 冗余字段、TAGS 更新规则缺失
**位置**
- `spec-数据管理.md` L235-L241、L504-L506:允许修改设备和采集点配置。
- `spec-数据管理.md` L991-L1001TDengine 保存 `point_name``device_name``data_type`
- `spec-数据管理.md` L1004-L1014:子表按点位 ID 创建,TAGS 在创建时写入。
- `spec-历史数据.md` L141-L159、L314-L344:历史接口返回名称和数据类型。
**冲突内容**
点位改名、换设备、改数据类型后,PostgreSQL 当前配置与 TDengine 历史行/TAGS 可能不同;文档没有规定更新 TAG、保留历史名称还是使用当前名称。
**影响**
同一条曲线可能显示错误设备名或数据类型;历史审计失去“当时配置”。
**建议**
明确元数据版本策略:
- 历史响应默认使用当前配置,但另存 `recorded_point_name`
- 或配置变更生成新点位 ID/新子表;
- 若允许更新 TAG,写明 TDengine 更新流程及失败补偿。
---
#### C-10|高|表格要求显示“单位”,但数据模型和 API 没有单位字段
**位置**
- `spec-历史数据.md` L665-L674:列标题为 `点位名称(单位)`
- `spec-数据管理.md` L337-L363`collection_points``unit`
- `spec-历史数据.md` L141-L159、L314-L439:树、曲线和表格响应均无 `unit`
**冲突内容**
前端要求无法由任何已定义数据源实现。
**影响**
不同前端可能硬编码单位、忽略单位或自行推断,产生错误展示。
**建议**
`collection_points` 增加 `unit VARCHAR(...)`,并在树、曲线、表格 API 元数据中返回;或删除单位要求。
---
#### C-11|中|树节点属性定义与实际 API 响应不一致
**位置**
- `spec-历史数据.md` L141-L159:点位节点要求包含 `group_name`
- `spec-历史数据.md` L225-L273`GET /history/tree` 的点位对象没有 `group_name`
**冲突内容**
同一文档对同一对象给出两种结构。
**影响**
前后端类型定义不一致。
**建议**
补齐 `group_name`,或从节点属性要求中删除并说明可由父节点推导。
---
#### C-12|中|质量缺失状态 `none` 与历史 API 的 `null` 表达冲突
**位置**
- `spec-数据管理.md` L943-L952:无记录时 API 质量为 `none`,前端始终处理字符串质量戳。
- `code-standards.md` L466-L477:重复同一约定。
- `spec-历史数据.md` L378-L383、L394-L439:未匹配数据直接返回数组元素 `null`
- `spec-历史数据.md` L467-L471CSV 中用 `—`
**冲突内容**
缺失数据到底是:
- `{ "value": null, "quality": "none" }`
- 整个元素为 `null`
- 或完全没有点;
文档没有统一。
**影响**
前端类型、图表 gap 处理、CSV 转换会出现分支差异。
**建议**
统一为显式对象,例如:
```json
{ "value": null, "quality": "none", "matched_ts": null }
```
曲线原始序列可继续用“无记录即无点”,但表格对齐结果应保持固定对象结构。
---
#### C-13|中|坏质量数据是否必须有数值未定义
**位置**
- `spec-数据管理.md` L917-L935、L943-L949:断线、读取失败、null、解析异常均为 bad。
- `spec-历史数据.md` L327-L331bad 示例仍有数值。
- `spec-历史数据.md` L590-L597:连续 bad 数据要求画虚线。
**冲突内容**
读取失败或 null 时通常没有可绘制数值;历史曲线规则却假设 bad 点有数值并可连接。
**影响**
实现者可能使用上次值、0、null 或丢点,曲线结果完全不同。
**建议**
定义 bad 子类型或值策略:
- `bad_with_value`:有可疑数值,可虚线绘制;
- `bad_no_value`:值为 null,形成断线;
- 禁止用 0 或上次值隐式补齐,除非响应显式标注。
---
### D. 历史查询接口与算法
#### C-14|阻断|`interval_minutes` 约束自相矛盾
**位置**
- `spec-历史数据.md` L362-L367:范围 1~1440,“必须能被60整除”,同时建议 1、2、5、10、15、20、30、60。
- `spec-历史数据.md` L658-L663:前端选项仅上述 8 个值。
**冲突内容**
中文“必须能被 60 整除”通常表示 `interval % 60 == 0`,则 1、2、5、10 等均不满足。文档实际可能想表达“必须是 60 的约数”。
**影响**
后端校验会产生两种完全不同的实现。
**建议**
直接定义枚举,避免自然语言:
```text
interval_minutes ∈ {1, 2, 5, 10, 15, 20, 30, 60}
```
若确需支持到 1440,应给出完整规则和前端选项。
---
#### C-15|高|前端需要 `history_interval`,树接口却不返回
**位置**
- `spec-历史数据.md` L369:前端应根据已选点位最大的 `history_interval` 限制或提示。
- `spec-历史数据.md` L141-L159、L225-L273:树节点不含 `history_interval`
- `spec-数据管理.md` L355-L357:该字段只存在于采集点配置。
**冲突内容**
历史页面没有已定义接口可获得每个勾选点位的 `history_interval`
**影响**
前端只能忽略要求,或逐点调用数据管理详情接口,造成跨模块依赖和 N+1 请求。
**建议**
在历史树节点中增加 `history_interval`,或增加批量元数据接口。
---
#### C-16|高|`history_interval` 无上限,与历史表格可选范围不兼容
**位置**
- `spec-数据管理.md` L355-L357、L491-L499:仅规定最小值 1 分钟,没有上限。
- `spec-历史数据.md` L367:表格 API 最大 1440 分钟。
- `spec-历史数据.md` L662:前端最大选项仅 60 分钟。
- `spec-历史数据.md` L369:建议表格间隔不小于最大 `history_interval`
**冲突内容**
若点位 `history_interval=120`,前端没有可选值满足建议;若大于 1440,连 API 都无法满足。
**影响**
合法的数据管理配置会产生无法正确展示的历史点位。
**建议**
统一约束。推荐:
- `history_interval` 枚举与历史表格可选间隔共享;
- 或前端动态生成可选值并允许到 1440;
- 数据管理 API 增加最大值校验。
---
#### C-17|高|代码规范要求最大 31 天,历史接口未定义该限制
**位置**
- `code-standards.md` L571-L579:时间范围最大跨度不超过 31 天。
- `spec-历史数据.md` L284-L300、L349-L367:只要求起止时间,未规定顺序和最大跨度。
- `spec-历史数据.md` L498-L508:自定义时间范围未规定上限。
**冲突内容**
规格可被理解为允许任意时间跨度,代码规范要求拒绝超过 31 天。
**影响**
前后端校验不一致;大查询可能导致内存和网络压力。
**建议**
在三个历史接口中明确:
- `end_time > start_time`
- 最大 31 天;
- 超限错误码;
- 导出是否允许更长范围以及异步策略。
---
#### C-18|高|动态子表 SQL 与“禁止字符串拼接 SQL”冲突
**位置**
- `spec-历史数据.md` L302-L311`FROM {subtable_name}` 动态替换表名。
- `code-standards.md` L222-L226、L562-L569:必须参数化,禁止字符串拼接 SQL。
**冲突内容**
表名通常不能作为普通参数绑定;历史规格又要求动态表名。
**影响**
开发者可能直接拼接用户可影响的标识,形成 SQL 注入风险,或违反代码审查规则。
**建议**
为动态标识符增加明确例外和安全实现:
1. 只接受数据库查询得到的 UUID
2. 服务端派生固定格式 `p_<uuid32>`
3. 使用严格正则白名单;
4. 值条件仍使用参数绑定;
5. 禁止直接使用请求中的表名。
---
#### C-19|中|最近邻窗口与原始查询边界不一致
**位置**
- `spec-历史数据.md` L375:只查询 `start_time ~ end_time`
- `spec-历史数据.md` L378-L382:每个目标点在 `± interval/2` 窗口内寻找最近值。
**冲突内容**
第一个目标点可能需要 `start_time` 之前的数据,最后一个目标点可能需要 `end_time` 之后的数据;当前查询范围把这些候选值排除。
**影响**
边界行会出现不必要的 `null`,与“最近邻窗口”定义不符。
**建议**
原始查询范围扩展为:
```text
[start_time - window, end_time + window]
```
最终输出仍只保留目标时间序列。
---
#### C-20|中|最近邻算法缺少并列、复用和非整除终点规则
**位置**
- `spec-历史数据.md` L373-L383。
**缺失内容**
- 前后两个点距离相同,选前还是选后;
- 同一原始点能否匹配两个目标时间;
- `end_time-start_time` 不是 interval 整数倍时是否追加 `end_time`
- bad 与 good 距离相同时是否优先 good。
**影响**
不同后端实现会返回不同表格和 CSV。
**建议**
补充确定性规则,并建立算法测试样例。
---
#### C-21|高|曲线原始查询缺少降采样/结果上限,难以满足性能规范
**位置**
- `spec-历史数据.md` L298-L311:最多 20 点,但返回范围内全部原始数据。
- `spec-数据管理.md` L355:采集周期最小 1 秒。
- `code-standards.md` L631-L638:要求审查分页、索引和连接池等性能问题。
- `code-standards.md` L577:最大跨度仍可达 31 天。
**问题**
单个 1 秒点位 31 天约 267 万条;20 点可能超过 5300 万条。API 没有 `max_points`、降采样或服务端聚合。
**影响**
内存、TDengine 查询、JSON 序列化、浏览器 ECharts 都可能失控。
**建议**
根据像素宽度/最大点数服务端降采样,或要求客户端传 `max_samples`;原始明细导出采用流式响应。
---
#### C-22|中|Y 轴“后端转换”方案与 API 响应结构不匹配
**位置**
- `spec-历史数据.md` L553-L577:推荐后端把原始值转换为显示坐标。
- `spec-历史数据.md` L314-L344:曲线 API 只返回原始 `value`,没有 `display_value` 或映射元数据。
**冲突内容**
推荐方案没有数据合同支持,前端无法同时绘制映射值和显示原始 tooltip。
**建议**
确定唯一职责:
- 推荐纯前端映射;或
- API 返回 `display_value`、原始 `value` 和分段映射定义。
---
### E. REST API、错误和安全
#### C-23|高|所有 API 统一 JSON 包装与 CSV 文件响应冲突
**位置**
- `code-standards.md` L360-L376:“所有 API 响应”统一为 `{code,message,data}`
- `spec-数据管理.md` L249-L273、L591-L605:导出直接返回 CSV。
- `spec-历史数据.md` L442-L471:导出直接返回 CSV。
**冲突内容**
文件下载不可能同时是原始 CSV 和 JSON 包装。
**建议**
在代码规范中增加明确例外:文件流、SSE、WebSocket 不使用统一 JSON 包装;错误响应仍返回统一 JSON。
---
#### C-24|高|写入失败/超时仍返回 `code=0`、`message=success`
**位置**
- `spec-数据管理.md` L805-L836`result=failed/timeout`,但 envelope 仍为成功。
- `code-standards.md` L372-L376、L393-L403`code=0` 表示成功,非 0 表示业务错误。
**冲突内容**
同一响应同时表示“成功”和“写入失败”。
**影响**
通用请求层可能把失败当作成功;告警和重试逻辑不可靠。
**建议**
区分两类情况:
- 请求成功且设备写入成功:`code=0`
- 请求已处理但设备写入失败:使用明确业务错误码,HTTP 409/422/502/504 按原因选择,同时 `data` 可附审计记录 ID。
---
#### C-25|高|写入请求允许客户端指定 operator,与鉴权规范冲突
**位置**
- `spec-数据管理.md` L736-L767:客户端提交 `source``operator`
- `spec-数据管理.md` L1092-L1098:写入需要登录权限。
- `code-standards.md` L581-L586:人工写入需登录,自动写入需 API Token。
**冲突内容**
已鉴权身份应由服务端从登录会话或 Token 推导;允许请求体任意填写 `operator` 会导致身份伪造。`source` 同样不应完全信任客户端声明。
**影响**
写入审计日志不可可信,存在严重审计与安全风险。
**建议**
- 从认证上下文生成 `operator` 和调用方类型;
- 请求体删除 `operator`,必要时仅保留 `operator_display_name` 作为非权威备注;
- manual/auto 使用不同凭证或不同内部路由。
---
#### C-26|中|写入日志错误字段命名不一致
**位置**
- `spec-数据管理.md` L719-L723:日志列表返回 `error_message`
- `spec-数据管理.md` L805-L836:写入结果返回 `error`
- `spec-数据管理.md` L851-L855:数据库字段为 `error_message`
- `code-standards.md` L29-L30:同一概念跨层命名一致。
**影响**
前端需要两套字段,Go DTO 和模型映射易混乱。
**建议**
统一为 `error_message`,或明确 `error` 是标准错误对象而不是字符串。
---
#### C-27|中|写入值在不同 API 中类型不一致
**位置**
- `spec-数据管理.md` L719-L720:日志列表的 `target_value``readback_value` 是字符串。
- `spec-数据管理.md` L790-L800:写入响应 `value``readback_value` 是数字。
- `spec-数据管理.md` L851-L853:数据库统一存 TEXT。
**影响**
同一业务值在接口间不能复用类型,BOOL/INT/REAL 转换规则不清楚。
**建议**
日志 API 返回:
```json
{
"data_type": "REAL",
"target_value": 20.5,
"readback_value": 20.5,
"raw_target_value": "20.5"
}
```
或统一使用可辨别联合类型。
---
#### C-28|中|日志查询筛选值遗漏 `timeout`
**位置**
- `spec-数据管理.md` L693-L695`result` 仅说明 success/failed。
- `spec-数据管理.md` L822-L835:存在 timeout。
- `spec-数据管理.md` L854:数据库枚举包含 timeout。
**建议**
筛选枚举统一为 `success | failed | timeout`
---
#### C-29|高|错误码分类和模块分段无法同时成立
**位置**
- `code-standards.md` L395-L403400xx=参数、401xx=鉴权、403xx=权限、404xx=未找到、409xx=冲突。
- `code-standards.md` L405-L414:采集点=41001~41999,写入点=42001~42999,历史=43001~43999。
**冲突内容**
例如历史数据参数错误按类型应是 400xx,但按模块必须是 430xx;历史数据未找到按类型应是 404xx,但又必须是 430xx。
**影响**
无法设计一致的错误码,前端不能按前缀分类。
**建议**
采用一种二维编码方式,例如:
- HTTP 状态表达错误类别;
- 业务码按模块分段;
- 或业务码格式 `模块两位 + 类别两位 + 序号`,并给出算法。
---
#### C-30|中|代码规范示例直接返回 `err.Error()`,与安全审查项冲突
**位置**
- `code-standards.md` L184-L199:绑定失败时把 `err.Error()` 返回前端。
- `code-standards.md` L640-L647:禁止直接向前端泄露错误信息。
**影响**
开发者会复制示例,可能暴露内部字段、解析器或库错误。
**建议**
对外仅返回稳定校验消息;原始错误写结构化日志。
---
### F. CSV 与文件规范
#### C-31|高|历史 CSV 表头不符合统一 `snake_case`
**位置**
- `spec-历史数据.md` L457-L470:表头为中文“时间”、动态点位名和“_质量”。
- `code-standards.md` L504-L515CSV 标题字段名统一使用 `snake_case`
- `spec-数据管理.md` L263-L267、L595-L600:配置 CSV 使用 snake_case。
**冲突内容**
历史展示型 CSV 和配置导入型 CSV 实际需求不同,但代码规范没有区分。
**建议**
在规范中分为:
1. **机器可导入配置 CSV**:固定 snake_case。
2. **用户展示/报表 CSV**:允许本地化和动态列名,但需定义转义和重复点名处理。
---
#### C-32|高|历史 CSV 文件名使用用户输入,违反路径安全规范
**位置**
- `spec-历史数据.md` L676-L682:文件名包含 `{start_time}``{end_time}``{interval}`
- `code-standards.md` L571-L579:导出文件名不得包含用户输入。
**影响**
除路径穿越外,ISO 时间中的冒号、加号在部分系统不适合作为文件名。
**建议**
服务端解析并重新格式化为安全值,例如:
```text
history_20260709T000000_20260709T010000_10m.csv
```
只使用数字、ASCII 字母、下划线和短横线。
---
### G. 前端交互和接口契约
#### C-33|中|模式切换“不重复请求”与“自动重新查询”冲突
**位置**
- `spec-历史数据.md` L715-L720:切换模式“不重复请求数据,按需加载”。
- `spec-历史数据.md` L740-L741:切换模式自动重新查询。
**冲突内容**
无法判断是每次切换都请求、首次进入某模式请求一次,还是使用缓存。
**建议**
明确缓存键:`mode + point_ids + start_time + end_time + interval`。只有缓存不存在或参数变化时请求。
---
#### C-34|中|20 点上限同时被描述为建议和强制
**位置**
- `spec-历史数据.md` L164-L171:建议最多 20。
- `spec-历史数据.md` L296-L300、L362-L367、L748-L753API/附录要求最多 20。
**建议**
统一为强制规则;前端在第 21 个点时阻止并提示,后端仍校验。
---
#### C-35|中|设备 PUT 声称与新增同结构,但新增结构没有 `enabled`
**位置**
- `spec-数据管理.md` L182-L198:新增请求体无 `enabled`
- `spec-数据管理.md` L235-L241:PUT 与新增相同,同时说明可修改 `enabled`
**影响**
无法按请求合同执行启用/禁用。
**建议**
使用独立 `UpdateDeviceRequest`,明确字段可选性;或增加专用动作接口 `/devices/{id}/enable``/disable`
---
#### C-36|中|“立即生效”与 5 秒轮询实现不一致
**位置**
- `spec-数据管理.md` L243-L247、L504-L511、L970-L981:删除/变更后立即停止或动态更新。
- `spec-数据管理.md` L983:可每 5 秒轮询数据库。
**冲突内容**
轮询方案最多延迟约 5 秒,不是“立即”。
**建议**
定义 SLA,例如 5 秒内生效;若必须立即,使用事务后事件、LISTEN/NOTIFY 或消息总线。
---
### H. 命名、代码风格和内部标准
#### C-37|中|索引命名不符合 `idx_{表名}_{字段名}`
**位置**
- `code-standards.md` L432-L442:索引命名要求包含实际字段名。
- `spec-数据管理.md` L368-L372`idx_collection_points_device/group`,实际字段为 `device_id/group_name`
- `spec-数据管理.md` L643-L645`idx_write_points_device/group`
- `spec-数据管理.md` L863-L866`idx_write_logs_point/device/time`,实际字段为 `point_id/device_id/created_at`
**建议**
改为:
- `idx_collection_points_device_id`
- `idx_collection_points_group_name`
- `idx_write_logs_created_at`
- 其他同理
或放宽规范,明确允许语义化简称。
---
#### C-38|低|核心类型命名 `CollectPoint` 与 `CollectionPoint` 不一致
**位置**
- `spec-数据管理.md` L39-L46:概念图使用 `CollectPoint`
- `code-standards.md` L664-L669Go 模型使用 `CollectionPoint`
- API/table/package 也使用 `collection`
**建议**
统一为 `CollectionPoint`,避免出现第三种命名。
---
## 4. 其他需要补充但尚不足以判定为直接冲突的事项
以下问题属于规格缺失或高风险歧义,建议一并处理:
1. `spec-数据管理.md` L926-L929 使用“值在合理范围内”判定 good,但数据模型没有合理范围、量程或校验表达式字段。
2. Modbus `byte_order``word_order` 的职责存在重叠;`CDAB` 已涉及字交换,同时又提供 `word_order=BA`,组合语义需要重新定义。
3. 历史查询同一原始点是否可匹配多个表格时间点未规定。
4. 历史树、分组、点位的排序规则未规定。
5. 最新值不存在、采集器未运行或缓存过期时,`latest_value``null`、省略还是返回 `quality=none` 未规定。
6. 导出大文件是否流式传输、是否设置 Content-Disposition、超时和最大行数未规定。
7. `history_retention_days` 只有参数说明,没有系统配置存储模型、API、权限和变更失败补偿。
8. `CREATE/ALTER DATABASE aquacontrolai` 使用固定数据库名,而代码规范要求连接参数环境化;需明确数据库名是否也是环境配置。
9. 数据管理导出请求示例含 `// 可选` 注释,不是合法 JSON。
10. 代码规范日志示例用 INFO 记录“连接成功”,但日志埋点表把“设备连接/断开”统一定为 WARN;应拆分成功 INFO、断开/失败 WARN。
---
## 5. 建议的协调优先级
### 第一批:开发前必须定稿
1. C-02 服务进程模型和运行时状态传输。
2. C-01 模块边界和历史元数据访问方式。
3. C-08 UUID 存储格式。
4. C-14/C-15/C-16 表格间隔与 `history_interval` 合同。
5. C-25 写入身份来源与鉴权。
6. C-24/C-29 错误语义和错误码体系。
7. C-05 归档历史数据可见性。
### 第二批:接口冻结前完成
1. C-10 单位字段。
2. C-12 质量缺失表示。
3. C-17/C-21 时间范围、降采样、结果上限。
4. C-23 文件响应例外。
5. C-31/C-32 CSV 两类规范和安全文件名。
6. C-09 TDengine 元数据变更策略。
### 第三批:编码规范和文档清理
1. C-33 至 C-38。
2. 补齐排序、空值、超时、流式导出和配置变更 SLA。
3. 为关键算法增加契约测试样例。
---
## 6. 推荐的统一决策摘要
建议最终统一为以下基线:
- Web API 与 Collector 保持双进程,通过内部状态服务或消息/缓存同步状态。
- 历史模块通过只读领域接口取得点位元数据,不直接依赖数据管理 Repo。
- UUID 在 API、PG、TDengine 字段中统一使用 36 字符标准形式;仅表名使用 `p_<uuid32>`
- 历史树返回 `history_interval``unit``archived``has_history_data`
- 表格间隔采用明确枚举;缺失值统一 `{value:null, quality:"none"}`
- 历史查询最大 31 天并强制降采样/最大点数。
- 写入操作者从认证上下文生成,失败使用非零业务码。
- JSON API 使用统一 envelopeCSV 文件流为明确例外。
- 配置 CSV 使用固定 snake_case;历史报表 CSV 允许本地化动态表头。