Files
AquaControlAI/docs/development-log.md
T
2026-07-13 14:31:49 +08:00

210 lines
23 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.
# 开发维护记录
## 2026-07-12|需求分析
- 研读 `spec-数据管理.md` v1.2、`spec-历史数据.md` v1.1、`code-standards.md` v1.2 和一致性决策基线 v1.0。
- 确认采用单进程 Go 服务、Handler → Service → Repository、每设备独立协议连接、同设备读写串行、PostgreSQL 当前元数据权威、TDengine 子表由 UUID 派生。
- 历史查询边界统一为最多 20 点、31 天;曲线默认 2000 样本;表格使用精确半间隔最近邻;CSV 最大 50,000 行。
## 技术与视觉决策
- 前端使用 Vue 3 + TypeScript + Pinia + Vue Router + Axios + ECharts。
- 视觉采用深石墨底、荧光绿主色、青色辅助数据序列;避免卡片堆叠,保持工业数据表和图表的高信息密度。
- 非线性分段轴算法位于 `web/src/components/charts/`API 和导出只使用原始值。
- PostgreSQL 使用 pgx 连接池;TDengine 使用官方 Go REST 驱动,连接信息全部来自环境变量。
## 已识别问题与处理
- 现场给出的读取地址 `DB2.DB2.1186.0` 与规格 `DB{db}.{byte}.{bit}` 冲突。实现严格接受 `DB2.1186.0`;现场测试前需由用户/PLC 工程师确认前一形式是否只是录入笔误。
- PLC `192.168.107.10:102` 在 2026-07-12 的 TCP 探测未在 30 秒窗口内完成;未执行任何写入。
- 用户提供的数据库凭据仅用于本次环境验证,不写入代码、示例或日志。
## 变更摘要
- 新增 PostgreSQL/TDengine 幂等迁移。
- 新增设备、采集点、写入点、历史数据四个桌面工作区。
- 新增设备 API、点位/日志列表、历史树、曲线、表格和 CSV 导出。
- 新增协议注册表、S7/Modbus 地址与配置校验骨架。
## 测试记录
- PostgreSQL `101.35.54.96:5432`:TCP 可达;目标库最初不存在,已通过受控标识符创建 `aquacontrolai` 并成功执行迁移。
- TDengine `101.35.54.96:6041`:TCP 可达;已创建/确认数据库及两个超级表,REST 驱动健康检查通过。
- PLC `192.168.107.10:102`:早期 TCP 探测超时,后续通过 S7 客户端成功连接并完成受控读写联调,详见下方补充记录。
- `go test ./...`:通过。
- `npm run build`:通过;Vite 提示 ECharts 主包超过 500 kB,后续可拆为异步图表 chunk。
- `GET /api/v1/health`:真实数据库配置下返回 `code=0`
- Playwright Chromium 1600×1000:历史页可渲染,无框架错误覆盖层;空库状态正确显示 0/20 和空图表。应用内浏览器缺少必需控制入口,按规范使用 Playwright 回退。
- 视觉比对:应用壳、色板、导航、紧凑工具栏、开放式图表/表格容器与概念一致;空库无法验证多序列曲线、点位树密度和游标数据态。
## 2026-07-12|现场 S7 联调补充
- 用户确认采集地址为 `DB2.1186.0`、类型 REAL。考虑现场工程记法,校验器仅对 DB 数值地址兼容末尾 `.0`;驱动实际按 DB2、字节偏移 1186 连续读取 4 字节。
- 写入地址 `MD540` 按 M 区字节偏移 540 连续读写 4 字节 IEEE-754 REAL。
- PLC 建立连接成功;`DB2.1186.0` 实际读值 `724.0000610351562`,质量 `good`
- 写入测试成功:`MD540=10.0`,回读 `10.0`,日志 ID `36d173ec-a496-495b-ba6a-d7048e616afa`
- 强制恢复成功:`MD540=0.0`,回读 `0.0`,日志 ID `88c0d3dc-3988-46b9-9e98-d03819f6dbbc`
- TDengine REST 驱动不支持在 `USING ... TAGS` 中绑定参数;动态表名仍只由 UUID 派生,标签/字符串采用单引号倍增转义,数值使用 `strconv.FormatFloat`,并增加表名和转义单元测试。
- 修复 TDengine REST 时间语义:API 的 ISO 8601 时间统一换算为 `Asia/Shanghai` 后生成 TDengine 查询字面量,现场最近一小时查询返回 28 条记录并成功绘制曲线。
- 修复 `vue-tsc -b``src/` 旁生成陈旧 `.js` 并覆盖 TypeScript 模块解析的问题:启用 `noEmit`、删除生成物;Playwright 验证控制台无 warning/error。
- Playwright 1600×1000 验证:历史树 1 个可选现场点位、默认勾选、ECharts canvas 正常、曲线显示真实 TDengine 数据;切换表格模式后渲染 7 行最近邻数据。
## 2026-07-12|剩余功能收尾
- 补齐设备、采集点、写入点详情 API,采集分组 API,以及三类配置 CSV 导入/导出。
- CSV 真实回灌验证:三类各 1 行,均为 `created=0, updated=1, failed=0`,名称幂等规则有效且没有产生重复记录。
- 补齐采集点和写入点新增、编辑、删除弹窗;三类页面导入文件选择与导出下载按钮已接入真实 API。
- 新增历史保留策略 GET/PUT;更新顺序为 TDengine `ALTER DATABASE KEEP` 成功后再提交 PostgreSQL 生效值。真实环境以 365 天原值回写验证成功。
- Playwright 逐页验证:设备、采集点、写入点三个新增弹窗均可打开并取消;历史保留配置可见;表格模式渲染 7 行;控制台无 warning/error。
- 补齐设备列表的协议/启用筛选,采集点和写入点的设备/分组/类型/启用/写入开关筛选,以及统一 `page/page_size`(最大 100)分页;写入日志支持点位、设备、结果和关键词筛选。
- 历史页新增精确到秒的自定义起止时间输入;快捷时间与自定义时间共用查询缓存键。
- 最终浏览器回归:三类页面导入/导出入口均可见,三个新增弹窗均可打开;历史页 2 个时间输入、保留策略控件和 7 行表格数据正常;控制台无 warning/error。
## 2026-07-12|设备状态展示调整
- 设备管理表头由“运行状态”改为“设备状态”。
- 后端状态映射为:`connected → 在线``disconnected → 离线``disabled → 禁用`
- 前端使用无圆点的文字卡片:在线绿色、离线红色、禁用灰色。
- 1600×1000 Playwright 回归:设备状态列标题与“离线”红色文字卡片正常显示,控制台无错误。
- 设备管理表格进一步调整:连接地址拆为“地址”和“端口”;移除“启用”列;新增“最近在线”和“最近离线”。时间由采集引擎连接状态事件维护,接口字段为 `last_online_at``last_offline_at`
- 真实接口回归返回在线设备时间戳,页面列顺序为:名称、协议、地址、端口、设备状态、最近在线、最近离线、操作。
## 2026-07-12|数据采集分组与 REAL 解析调整
- 新增 `collection_groups` 表及幂等迁移;启动时同步历史 `group_name` 并删除 `collection_points.valid_min/valid_max`
- 新增分组 GET/POST/PUT/DELETE API。删除分组前端二次提示,后端事务内逻辑删除该分组全部采集点;`default` 分组禁止删除。
- 数据采集页新增左侧分组栏、分组添加/改名/删除操作,新建采集点改为分组下拉选择。
- 采集列表将“地址 / 类型”拆成“地址”和“类型”;新建/编辑表单移除有效最小值和有效最大值。
- S7 REAL 解析统一四舍五入到小数点后最多 3 位;采集质量不再执行有效范围判断。
- 真实数据库验证:分组新增、改名、删除级联均通过;删除后采集点查询总数为 0;现场 REAL 值示例 `1411.719`
## 2026-07-12|设备与采集点弹窗复选框优化
- 设备新增弹窗的“启用设备”与采集点新增弹窗的“启用采集”“存储历史”统一为整行可点击的文字卡片控件。
- 复选框采用隐藏原生控件 + 自定义勾选方块,提供未选中、荧光绿色选中和键盘聚焦态;保留原有 `v-model` 数据绑定,不改变接口语义。
- 收紧通用表单输入选择器,避免复选框继承文本输入框的 `width: 100%` 导致位置异常。
- `npm run build`:通过(Vite 仍提示 ECharts 主包超过 500 kB 的既有拆包建议)。
- Playwright Chromium 回归:设备弹窗 1 个复选框、采集点弹窗 2 个复选框均正常渲染;设备默认勾选后点击可取消,采集点“存储历史”点击后可取消;两个弹窗无控制台错误。
- 回归截图:`C:\Users\Administrator\AppData\Local\Temp\aquacontrol-device-checkbox-final.png``C:\Users\Administrator\AppData\Local\Temp\aquacontrol-collection-checkbox-final.png`
## 2026-07-12|数据写入分组与回读展示调整
- 数据写入页新增与数据采集一致的左侧分组栏,分组名称、添加、改名和删除均复用 `collection_groups` 接口;后端改为同步维护采集点和写入点的分组归属,删除分组时两类点位一起逻辑删除。
- 写入点列表将“地址 / 类型”拆为独立列,移除回读容差列,改为展示该写入点最近一次写入日志中的 `readback_value`;无回读记录时显示“—”。
- 写入点数据库迁移 `000003_write_points_simplify.up.sql` 合并 `enabled``write_enabled` 为单一 `write_enabled`,移除 `readback_tolerance`;迁移具备重启幂等性,旧数据先保留原有效权限再删除冗余列。
- 写入点新增/编辑表单移除回读容差和启用字段,仅保留统一的“允许写入”自定义复选框;CSV 导入/导出同步移除冗余字段。
- REAL 回读验证保留固定协议精度 `0.0001`,不再作为点位配置或 API 字段暴露。
- 真实环境回归:`GET /api/v1/write-points` 返回不含 `enabled``readback_tolerance`,分组接口同时返回 `collection_count``write_count`;写入点 CSV 表头已更新为 `write_enabled`
- Playwright Chromium 1600×1000:写入页列表、共享分组筛选和新增弹窗均正常;弹窗无“启用/回读容差”,允许写入卡片可切换;控制台无 warning/error。未执行现场 PLC 写入,避免改变控制状态。
- 回归截图:`C:\Users\Administrator\AppData\Local\Temp\aquacontrol-write-point-table-final.png``C:\Users\Administrator\AppData\Local\Temp\aquacontrol-write-point-final.png`
## 2026-07-12|三类数据页面刷新与采集点编辑修复
- 修复采集点编辑无法更换所属设备的问题:移除 PostgreSQL 更新语句中对 `history_started_at`、旧设备和旧数据类型的限制,编辑请求现在可以正常提交新的 `device_id`;前端保存失败时显示明确错误提示。
- 数据采集列表在质量列后新增“更新时间”,取点位 `updated_at` 并按中文本地时间格式展示。
- 数据采集、设备管理、数据写入均新增“刷新数据”按钮,刷新按钮执行期间禁用并显示旋转图标。
- 数据采集刷新同时重新加载采集点分组;采集点新增/编辑/删除及 CSV 导入后刷新列表和分组;设备新增/编辑/删除及 CSV 导入后刷新列表;写入点新增/编辑/删除、执行写入及 CSV 导入后刷新列表和共享分组。
- Playwright Chromium 1600×1000 回归:三个页面刷新按钮各 1 个;采集列表包含“更新时间”;编辑弹窗包含设备选择器;三个页面刷新交互无控制台 warning/error。现场 PLC 写入未执行。
- 回归截图:`C:\Users\Administrator\AppData\Local\Temp\aquacontrol-collection-refresh-final.png`
- 更正“更新时间”字段:列表现在展示实时采集数据 `latest_value.ts`,不再展示配置的 `updated_at`
## 2026-07-12|分组删除改为点位迁移
- 删除非 `default` 分组时,后端事务先将该分组下未逻辑删除的采集点和写入点统一迁移到 `default`,再删除分组记录;不再逻辑删除任何点位。
- 事务内确保 `default` 分组存在,`default` 分组仍禁止删除。
- 数据采集和数据写入页面的删除确认提示同步改为“自动转移到 default 分组”。
- 开发验证:使用临时写入点执行“建组→建点→删组”回归,点位保留且 `group_name` 已迁移为 `default`,随后清理临时点;Go 测试和静态检查、前端构建均通过,未执行现场 PLC 写入。
# 2026-07-12 · 历史数据增强与归档清理
## 需求分析
- 历史点位树支持分组折叠/展开、分组全选/取消;点位、时间范围和表格间隔变化后自动查询,保留最多 20 个点位限制。
- 曲线横轴必须与系统时间一致并按选择范围固定起止点;无数据序列不显示;bad 质量值使用虚线;图例状态保留并控制对应曲线;游标采用非吸附、带动画的轴指示器。
- 曲线高度提升,游标明细最多显示约 5 行并在点位较多时纵向滚动;表格点位列较多时横向滚动。
- 已逻辑删除点位的历史子表提供显式清理入口,禁用但仍可恢复的点位不纳入清理。
## 技术方案与变更
- PostgreSQL 新增只读查询已删除采集点 UUID 的方法;TDengine 新增按 UUID 派生子表的安全删除方法,API 增加 `POST /api/v1/history/archive/cleanup`,前端增加清理确认和结果反馈。
- TDengine REST 驱动返回的本地墙上时间改为显式重建为 `Asia/Shanghai`,避免重复施加时区偏移;表格时间列统一输出上海时区。
- 历史页面增加自动查询防抖和请求序列保护,曲线传入选定时间范围并动态设置时间刻度;bad 数据拆分为虚线序列,且共享逻辑图例名称。
## 验证记录
- `go test ./...`:通过。
- `npm run build`:通过;仅保留既有 ECharts 体积提示。
- `GET /api/v1/health`:真实 PostgreSQL/TDengine 配置下返回 `code=0`
- Playwright Chromium 1600×1000:验证分组全选、点位变更自动曲线查询、曲线/表格切换自动查询、间隔变化自动表格查询;曲线高度 520px;控制台无错误。截图:`C:\Users\Administrator\AppData\Local\Temp\aquacontrol-history-curve-final.png``C:\Users\Administrator\AppData\Local\Temp\aquacontrol-history-final.png`
- 归档清理接口真实回归时识别并清理了 13 个已逻辑删除点位的历史子表;活跃点位和仅禁用点位未清理。清理为不可逆操作,前端已增加二次确认。
## 2026-07-12 · 自定义时间与任意时刻游标
- 修复固定快捷范围切换后编辑自定义时间可能被旧范围覆盖的问题:时间输入在 `input``change` 阶段均同步,先保存被编辑的端点,再校验起止时间,允许用户分两步完成暂时不完整的自定义范围。
- 游标改为基于鼠标在时间轴上的实际像素位置换算时间,不再使用最近存储点作为游标时间;对每条曲线按前后有效样本进行线性插值,游标表格中的所有曲线使用同一时间戳,边界使用最近有效值,质量戳为 bad 或跨越 bad 样本时保留 bad 标识。
- 插值结果在游标表格中使用 `≈` 标记,ECharts 轴指示器继续关闭吸附并保留平滑动画;图例隐藏的曲线不参与游标结果。
## 2026-07-12 · 本轮验证
- Playwright 验证固定范围后编辑起止时间保持 `custom` 状态;鼠标移动到非存储时刻时,两条曲线游标显示相同时间 `2026/7/12 23:08:03`,无控制台错误。
## 2026-07-13 · 游标视觉跟随与离开区域保持
- 移除 ECharts 默认会吸附存储点的轴指示线,改用图表内独立的垂直游标线;鼠标像素位置先换算为时间,再用 `requestAnimationFrame` 更新线位置,避免在 ECharts 事件处理期间调用 `setOption`
- 游标线仅覆盖绘图区,和插值明细使用同一时间;鼠标离开曲线区域不再清空最后一次游标时间和明细数据。
- 回归验证连续移动得到 `23:41:48``23:44:25``23:47:00``23:49:38` 四个不同时间点;离开图表后仍保留 2 条曲线明细,控制台无错误。
## 2026-07-13 · 设备状态与采集成功状态同步
- 根因:采集读失败后 `Manager` 将设备状态置为 `disconnected`;缓存连接后续读取成功时此前只更新点位最新数据,没有恢复设备状态,因此设备管理页持续显示离线。
- 修复:采集成功后调用 `Manager.MarkConnected` 恢复在线状态;`MarkConnected``MarkDisconnected` 仅在状态发生变化时更新时间戳,避免每个采集周期反复刷新最近在线/离线时间。
- 验证:真实环境中 PLC1、PLC2、PLC3 均显示 `connected`40 个采集点的 `latest_value.ts` 持续更新;新增设备状态恢复及时间戳稳定性单元测试。
## 2026-07-13 · 启停流程文档与历史曲线断档修复
- 维护约定:从本次起,后续每次代码、配置、部署流程或验证方式调整,都必须同步记录到 `docs/development-log.md`
- README 启停流程补充:明确项目按前后端分离运行;后端默认 `8080`,前端 Vite 默认 `5173`;应用不会自动读取 `.env`,启动后端前必须先在 PowerShell 中注入环境变量;补充后端、前端启动命令、健康检查命令和按端口停止服务命令。
- pnpm 构建脚本审批:将 `pnpm-workspace.yaml``allowBuilds.esbuild` 从占位值修正为 `true`,解决 `esbuild@0.25.12` postinstall 被拦截导致前端无法构建的问题。
- 启动验证:`.env` 已指向云端数据库,PostgreSQL `101.35.54.96:5432` 与 TDengine REST `101.35.54.96:6041` TCP 连通;`GET http://127.0.0.1:8080/api/v1/health``GET http://127.0.0.1:5173/api/v1/health` 均返回 `code=0`
- 服务停止验证:停止后端 Go 进程与前端 Vite 进程后,`8080``5173` 均无监听进程。
- 历史曲线断档根因:曲线查询只返回真实采样点,停机或未采集时段没有显式空值断点;ECharts 在相邻有效点之间会直接连线,因此会把 2026-07-13 02:00 到 08:00 这类无数据时段渲染成连续曲线。
- 历史曲线修复:后端 `history/query` 按点位 `history_interval` 判断断档,相邻样本间隔超过 `3 × history_interval` 时插入 `value=null, quality=none` 断点;前端游标插值遇到空值或非 good 质量断点时不再跨断档估算,避免提示表显示伪造的连续值。
- 新增测试:`internal/service/platform/history_test.go` 覆盖长时间无数据插入断点、短时间采集抖动不误判断档。
- 验证:`go test ./...` 通过;`pnpm build` 通过,仅保留既有 ECharts chunk 体积提示;`pnpm lint` 未通过,原因为项目当前缺少 ESLint 9 所需的 `eslint.config.js`,不是本次变更引入。
## 2026-07-13 · TDengine 时间戳时区修复
- 现象:TDengine 工具中最新历史数据时间显示为 `2026-07-13 17:50:53.026`,比现场当前时间快 8 小时;页面实时数据时间显示正常。
- 根因:历史写入使用 `2006-01-02 15:04:05.000` 无时区格式,TDengine REST 链路按 UTC instant 存储;数据库工具按本地时区显示后形成 `+8h` 偏移。历史查询代码此前又把读回时间按上海墙上时间重建,导致页面显示正常但库内物理时间不一致。
- 验证:临时 TDengine 探测表写入同一时间的两种格式,`plain` 无时区读回为 `2026-07-13T10:10:10.123Z``rfc3339``+08:00` 读回为 `2026-07-13T02:10:10.123Z`,证明无时区写入会偏移 8 小时。
- 修复:TDengine 写入和查询时间字面量统一改为 RFC3339 样式 `yyyy-MM-ddTHH:mm:ss.SSS+08:00`;读回时间按真实 instant 转换为 `Asia/Shanghai`,不再重建墙上时间。
- 测试:新增 `TestTDTimeLiteralUsesShanghaiOffset`,锁定 TDengine 时间字面量必须带 `+08:00`
- 验证:`go test ./...` 通过;`pnpm build` 通过,仅保留既有 ECharts chunk 体积提示。
- 运行验证:重启后端后,`8080` 后端健康检查与 `5173` 前端代理健康检查均返回 `code=0`;对点位 `1dd3c3c4-bb27-4b57-bd10-039a6e2a81d9` 查询 `2026-07-13T10:00:00+08:00``10:30:00+08:00` 区间,TDengine 返回新记录 `2026-07-13 10:05:41.002 +08:00`,证明新写入时间已按本地真实时间入库。
- 注意:修复只保证后续新写入数据时间正确;已经写入 TDengine 的旧历史行物理时间戳已偏移到未来,如需修正,需要在停采后执行一次受控数据回拨/重写操作,不能自动盲改。
## 2026-07-13 · TDengine 旧历史数据时间回拨
- 操作前先停止后端采集进程,确认 `8080` 无监听,避免迁移期间继续写入历史数据。
- 迁移范围:枚举 TDengine `collection_data` 下 40 张 `p_*` 点位子表,共 1562 行历史数据;dry-run 统计 1526 行为旧无时区写入数据,需要整体回拨 8 小时,36 行位于 `2026-07-13 10:00~10:30 +08:00` 修复后新写入窗口,保留原时间不动。
- 备份:执行前导出 JSONL 本地备份 `C:\Users\ADMINI~1\AppData\Local\Temp\aquacontrolai-tdengine-history-backup-20260713-101548.jsonl`,记录原始时间、修正后时间、点位、设备、质量和值。
- 修复方式:因时间戳为 TDengine 时序主键,不直接执行盲目 `UPDATE`;脚本逐表读取全量行、计算修正时间、检查修正后同表时间戳冲突,然后 `DROP TABLE` 子表并按原稳定表 `collection_data`、原标签和原数据列重建写回。
- 结果:实际执行后 `repair_complete rows=1562`,迁移前后总行数一致。
- 验证:全库最新时间由未来 `18:00` 左右恢复为 `2026-07-13 10:05:46.506 +08:00`;示例点位 `1dd3c3c4-bb27-4b57-bd10-039a6e2a81d9` 最新 5 条为 `10:05:41``10:00:53``09:50:53``09:48:52``09:43:55`;该点位 `2026-07-13 02:00~08:00 +08:00` 区间查询结果为 0 条。
## 2026-07-13 · 历史数据 CSV 导出列调整
- 需求:历史数据表格导出 CSV 时,每个测点只导出一列实际值,不再为每个测点额外导出“质量”列。
- 修复:`POST /api/v1/history/export` 表头从“时间 + 点位值列 + 点位质量列”改为“时间 + 每个点位一个值列”;行数据仅写入 `value`,无值时留空,不再输出 `good/bad/—` 质量文本。
- 验证:`go test ./...` 通过;重启后端后调用 `POST /api/v1/history/export` 导出点位 `1dd3c3c4-bb27-4b57-bd10-039a6e2a81d9``2026-07-13 09:40~10:10 +08:00` 数据,CSV 表头为 `时间,2区PAC投加流量[L/h]`,列数为 2,无质量列。
## 2026-07-13 · Modbus TCP 规格文档同步
- 根据当前代码和维护记录同步 `spec-数据管理.md``spec-历史数据.md` 与一致性决策基线:移除采集有效范围和写入点回读容差旧契约,补充分组表、设备最近在线/离线、写入点单一 `write_enabled`、采集调度/断线恢复当前机制、历史归档清理、断档点、TDengine `+08:00` 时间字面量和历史 CSV 无质量列规则。
- 明确 `MODBUS_TCP` 需要具备运行时连接、读写和 REAL 字节序适配能力,避免只停留在协议注册和地址校验。
## 2026-07-13 · Modbus TCP 运行时驱动
- 补齐 `internal/protocol/modbus` 运行时 TCP 客户端:实现 MBAP 报文、事务号校验、异常响应处理、FC1/2/3/4 读取、FC5/6/16 写入,以及 `ABCD/BADC/CDAB/DCBA` REAL 字节序转换。
- 新增地址校验与 `CDAB` REAL 编解码单元测试,并使用正式协议连接完成读、写和回读验证。
- 验证:`go test ./...` 通过。