From 160ad85051681af77ee9b315dd5810bad8754bbf Mon Sep 17 00:00:00 2001 From: wangkai <10903508@qq.com> Date: Sat, 24 Oct 2026 13:50:32 +0800 Subject: [PATCH] =?UTF-8?q?feat(line):=20=E6=99=BA=E7=94=B5=E6=94=B9?= =?UTF-8?q?=E9=80=A0=E7=BA=BF=E8=B7=AF=E5=8F=B0=E8=B4=A6=E6=8E=A5=E5=85=A5?= =?UTF-8?q?=20ICC=20=E6=B6=88=E9=98=B2=E6=8E=A2=E6=B5=8B=E5=99=A8=E7=9C=9F?= =?UTF-8?q?=E5=AE=9E=E5=90=8C=E6=AD=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 重建 electric_line 表结构(迁移20261024_icc_line_rebuild.sql并同步db_init/db_seed):删building_id/floor_no/location/icc_device_code占位字段,equip_id改可空,新增imei/gateway_id/type_code/type_name/fire_system_code/fire_system_name/part_unit_loop_code/channel_no/channel_sn/produce_date,gateway_id外键→electric_gateway - 同步服务IccLineSyncService复用OAuth2真实鉴权分页拉取探测器(POST /detector/subsystem/page,pageSize=50短页停止),按line_code(detectorCode)幂等upsert写icc_sync_log;customer_id由orgCode→customer.icc_code解析、gateway_id由parentCode→electric_gateway.device_code匹配、equip_id同步不填充 - 列表接口返回gateway/type/imei等新字段,keyword检索含imei;前端台账页更新为新列(IMEI/所属网关/探测器类型/回路通道/消防系统/生产日期),保留手动同步按钮与筛选 - 更新系统设计文档electric_line表定义与spec(add-icc-line-sync) --- .trae/specs/add-icc-line-sync/checklist.md | 12 ++ .trae/specs/add-icc-line-sync/spec.md | 71 +++++++ .trae/specs/add-icc-line-sync/tasks.md | 25 +++ client/src/api/device.ts | 17 +- client/src/views/device/line/index.vue | 40 ++-- database/db_init.sql | 64 ++++--- database/db_seed.sql | 8 + .../migrations/20261024_icc_line_rebuild.sql | 48 +++++ server/src/entity/electric_line.entity.ts | 67 ++++--- server/src/service/electric_line.service.ts | 33 ++-- server/src/service/icc_line_sync.service.ts | 178 +++++++++--------- server/src/task/icc_line_mapper.ts | 124 ++++++++---- 系统设计/表结构新增及变更.md | 23 +++ 13 files changed, 510 insertions(+), 200 deletions(-) create mode 100644 .trae/specs/add-icc-line-sync/checklist.md create mode 100644 .trae/specs/add-icc-line-sync/spec.md create mode 100644 .trae/specs/add-icc-line-sync/tasks.md create mode 100644 database/migrations/20261024_icc_line_rebuild.sql diff --git a/.trae/specs/add-icc-line-sync/checklist.md b/.trae/specs/add-icc-line-sync/checklist.md new file mode 100644 index 0000000..95457fb --- /dev/null +++ b/.trae/specs/add-icc-line-sync/checklist.md @@ -0,0 +1,12 @@ +# Checklist + +- [x] electric_line 表结构按 spec 重建:删 building_id/floor_no/location/icc_device_code;equip_id 可空;新增 imei/gateway_id/type_code/type_name/fire_system_code/fire_system_name/part_unit_loop_code/channel_no/channel_sn/produce_date;迁移已执行到生产库且 db_seed.sql/db_init.sql 一致 +- [x] 设计文档「表结构新增及变更.md」已补充 electric_line 新结构定义 +- [x] ElectricLine 实体与迁移一致(gatewayId FK→electric_gateway,删除旧占位字段) +- [x] icc_line_mapper 按 DetectorVO 映射:lineCode=detectorCode、lineName=detectorName、status 01→1/02→0、orgCode/parentCode 透出作匹配键;parseLinePage 支持 PageVO 分页结构 +- [x] icc_line_sync.service 复用 getIccAccessToken/iccFetch 真实鉴权,POST 分页 pageSize=50 短页停止全量拉取;按 line_code 幂等 upsert(equip_id 不填);customer_id 由 orgCode 匹配、gateway_id 由 parentCode 匹配,失败跳过;写 icc_sync_log(sync_type=electric_line);拉取失败仅记日志不抛业务错 +- [x] 定时任务 icc_line_sync(jobKey/cron */10)注册链路可用;POST /api/electric-lines/sync 返回 {pulled,inserted,updated,skipped,synced} +- [x] 列表接口 GET /api/electric-lines 返回新字段(含 gateway/type/imei),非 ADMIN 限定 customer_id,keyword 检索含 imei +- [x] 前端 /device/line:删除建筑/楼层/位置列,新增 IMEI/探测器类型/回路/通道/消防系统/生产日期/所属网关列;无 v-html;同步按钮调用真实接口 +- [x] 后端 tsc --noEmit、前端 vue-tsc -b 均通过 +- [x] 真实同步端到端:admin 登录 → 手动同步 → 探测器数据落库且 customer/gateway 关联正确 → 重跑幂等 updated;浏览器页面新列渲染正常 diff --git a/.trae/specs/add-icc-line-sync/spec.md b/.trae/specs/add-icc-line-sync/spec.md new file mode 100644 index 0000000..43048ef --- /dev/null +++ b/.trae/specs/add-icc-line-sync/spec.md @@ -0,0 +1,71 @@ +# 智电线路真实 ICC 同步 Spec(add-icc-line-sync) + +## Why +现有 electric_line(智电改造线路台账)为占位实现:表结构是手工设计的占位字段(building/floor/location 等),同步链路 `icc_line_sync.service.ts` 走假的 `ICC_TOKEN` 配置、无真实鉴权。本次依据 ICC 平台接口文档第三节「消防探测器分页查询」(`POST /evo-apigw/evo-fdbu/{version}/detector/subsystem/page`)将线路台账升级为**真实数据同步**:重建表结构(按 DetectorVO 落库字段 + 与 customer/customer_equipment/electric_gateway 三张表的关联),后端替换为真实 OAuth2 拉取(复用 icc_customer_auth),前端台账页补充新字段展示并保留手动同步按钮。 + +## What Changes +- **表结构变更(BREAKING)**:`electric_line` 按 DetectorVO 重建字段集: + - 删除:`building_id`、`floor_no`、`location`、`icc_device_code`(占位字段无真实数据源) + - `equip_id` 由 `NOT NULL` 改为**可空**(→ customer_equipment,同步不填充,留待手动关联) + - 新增:`imei`、`gateway_id`(→ electric_gateway)、`type_code`、`type_name`、`fire_system_code`、`fire_system_name`、`part_unit_loop_code`、`channel_no`、`channel_sn`、`produce_date` + - 保留:`line_code`(= detectorCode,唯一幂等键)、`line_name`(= detectorName)、`customer_id`(NOT NULL)、`status`(= detectorStatus:01→1 在线 / 02→0 离线)、`synced_at`、`created_at` + - 不落库(仅作关联匹配键):`orgCode`→customer.icc_code 反查 customer_id;`parentCode`→electric_gateway.device_code 反查 gateway_id +- **后端同步真实化**:重写 `icc_line_mapper.ts`(DetectorVO 映射 + 分页解析)与 `icc_line_sync.service.ts`(复用 `getIccAccessToken`/`iccFetch`,POST 分页 pageSize=50 短页停止、全量拉取、按 line_code 幂等 upsert、写 icc_sync_log sync_type=electric_line、失败仅记日志不抛) +- **关联规则**:customer_id 由 orgCode 匹配;gateway_id 由 parentCode 匹配 device_code;equip_id 同步不填充 +- **前端台账页更新**:`views/device/line/index.vue` 移除楼层/建筑/位置列,新增探测器类型、IMEI、回路/通道、消防系统、生产日期、网关等列;保留「同步线路」手动按钮与筛选 +- **设计文档同步**:更新 `系统设计/表结构新增及变更.md` 补充 electric_line 新结构 +- 定时任务 `icc_line_sync`(jobKey 不变,cron */10)、菜单 61、RBAC `/api/electric-lines → device:list`、前端路由 `/device/line` 均沿用既有,无需新增 + +## Impact +- Affected specs:add-icc-line-ledger(本 spec 升级其占位实现)、add-icc-gateway-sync(复用其真实同步模式与关联表) +- Affected code: + - 后端:`server/src/entity/electric_line.entity.ts`、`server/src/task/icc_line_mapper.ts`、`server/src/service/icc_line_sync.service.ts`、`server/src/service/electric_line.service.ts`(decorate 去掉 building、新增 gateway/类型展示)、`server/src/controller/electric_line.controller.ts`(不动) + - 迁移:`database/migrations/20261011_icc_line_ledger.sql`(重建 electric_line 结构)或新增增量迁移;`database/db_seed.sql` 同步 + - 前端:`client/src/views/device/line/index.vue`、`client/src/api/device.ts`(ElectricLine 类型) + - 设计文档:`系统设计/表结构新增及变更.md` + +## ADDED Requirements + +### Requirement: electric_line 表结构按 DetectorVO 重建 +系统 SHALL 将 `electric_line` 重建为与 DetectorVO 对应的字段集,且: +- `line_code` 存 detectorCode(唯一,幂等键) +- `customer_id` NOT NULL,由 orgCode→customer.icc_code 反查 +- `gateway_id` 可空,由 parentCode→electric_gateway.device_code 反查 +- `equip_id` 可空,同步不填充 + +#### Scenario: 迁移执行成功 +- **WHEN** 执行迁移到生产库 +- **THEN** electric_line 包含新字段集,旧字段(building_id/floor_no/location/icc_device_code)被移除,equip_id 可空,索引重建;db_seed.sql 与生产库一致 + +### Requirement: 真实 ICC 探测器拉取同步 +系统 SHALL 复用 `getIccAccessToken`/`iccFetch` 真实鉴权,POST `{baseUrl}/evo-apigw/evo-fdbu/{version}/detector/subsystem/page` 分页全量拉取(pageNum 递增、pageSize=50、短页停止、MAX_PAGES 保护),按 line_code 幂等 upsert,写 icc_sync_log(sync_type=electric_line)。 + +#### Scenario: 同步成功 +- **WHEN** 手动点击「同步线路」或定时任务触发 +- **THEN** 返回 {pulled, inserted, updated, skipped, synced};新探测器落库、重复 detectorCode 更新;customer_id 解析失败的行跳过;写成功日志 + +#### Scenario: 同步失败 +- **WHEN** ICC 拉取异常(超时/HTTP 错误/鉴权失败) +- **THEN** 仅写 icc_sync_log 失败日志,不抛业务错误,接口返回 code 200 与当前计数 + +### Requirement: 前端台账页按新字段展示 +系统 SHALL 更新 `views/device/line/index.vue`:删除建筑/楼层/位置列,新增探测器类型、IMEI、回路/通道、消防系统、生产日期、所属网关列;保留筛选(客户/状态/关键词)与「同步线路」手动按钮。 + +#### Scenario: 页面渲染 +- **WHEN** 登录 admin 打开 /device/line +- **THEN** 表格展示新字段列,无 v-html,同步按钮触发 POST /api/electric-lines/sync + +## MODIFIED Requirements + +### Requirement: 线路台账列表接口(原有) +`GET /api/electric-lines` 保留分页/筛选/RBAC(非 ADMIN 限定 customer_id),decorate 从 building 改为 gateway/类型/IMEI 展示字段,删除 customer_building 关联。 + +#### Scenario: 列表返回 +- **WHEN** 带权限请求 GET /api/electric-lines +- **THEN** 返回行包含 lineCode/lineName/imei/customer/gateway/type/loop/channel/fireSystem/produceDate/status/syncedAt + +## REMOVED Requirements + +### Requirement: 占位 ICC 同步实现(原 icc_line_sync.service / icc_line_mapper 假配置映射) +**Reason**:无真实鉴权与接口,与 ICC 文档字段不对应。 +**Migration**:删除 `ICC_TOKEN` 占位用法,改为复用 icc_customer_auth 真实鉴权;映射按 DetectorVO 重写。 diff --git a/.trae/specs/add-icc-line-sync/tasks.md b/.trae/specs/add-icc-line-sync/tasks.md new file mode 100644 index 0000000..cb4a859 --- /dev/null +++ b/.trae/specs/add-icc-line-sync/tasks.md @@ -0,0 +1,25 @@ +# Tasks + +- [x] Task 1: 数据库迁移:重建 `electric_line` 结构(删 building_id/floor_no/location/icc_device_code;equip_id 改可空;新增 imei/gateway_id/type_code/type_name/fire_system_code/fire_system_name/part_unit_loop_code/channel_no/channel_sn/produce_date),迁移脚本 `database/migrations/20261024_icc_line_rebuild.sql`,同步更新 `database/db_seed.sql`(fk_line_gateway 幂等补充)与 `database/db_init.sql`(基线建表);`gateway_id` 建 `REFERENCES electric_gateway(id)` 与索引 + - [x] SubTask 1.1: 核对生产库 electric_line 现状(旧结构迁移而来,无占位数据阻碍),确认 ALTER 方案幂等 + - [x] SubTask 1.2: 执行迁移到远程生产库并验证字段/索引/外键(columns 18 个、idx_line_gateway/imei、fk_line_gateway 均就绪) + - [x] SubTask 1.3: 同步 `系统设计/表结构新增及变更.md`(补充 electric_line 新表结构定义) +- [x] Task 2: 后端实体:按新字段重写 `server/src/entity/electric_line.entity.ts`(gatewayId FK→ElectricGateway,删除 building/floor/location/iccDeviceCode) +- [x] Task 3: 映射层:重写 `server/src/task/icc_line_mapper.ts` + - [x] SubTask 3.1: 定义 `ICC_LINE_ENDPOINT = /evo-apigw/evo-fdbu/{version}/detector/subsystem/page` 与 DetectorVO 字段映射(lineCode=detectorCode、lineName=detectorName、imei、typeCode/typeName、fireSystemCode/Name、partUnitLoopCode、channelNo、channelSn、produceDate、status 归一 01→1/02→0、orgCode、parentCode 作为匹配键透出) + - [x] SubTask 3.2: `parseLinePage(payload)` 解析 PageVO{nextPage,totalCount,pageData}(兼容数组形态) +- [x] Task 4: 同步服务:重写 `server/src/service/icc_line_sync.service.ts`,复用 `getIccAccessToken`/`iccFetch`,POST 分页 pageSize=50 短页停止全量拉取;预取 orgCode→customer.id(customer.icc_code)与 parentCode→gateway.id(electric_gateway.device_code)映射避免 N+1;按 line_code 幂等 upsert(equip_id 不填);写 icc_sync_log(sync_type=electric_line);拉取失败仅记日志不抛 + - [x] SubTask 4.1: 定时任务 `icc_line_sync.task.ts`(jobKey/cron 不变)与 job_registry/JobRuntime(job_scheduler.service.ts syncIccLines)链路可用 + - [x] SubTask 4.2: 控制器 `POST /api/electric-lines/sync` 复用返回结构(pulled/inserted/updated/skipped/synced) +- [x] Task 5: 列表服务:`server/src/service/electric_line.service.ts` 更新 decorate(删除 building 反查,新增 gateway/type/imei 展示字段);keyword 检索范围补充 imei +- [x] Task 6: 前端:`client/src/api/device.ts` 更新 `ElectricLine` 类型(新字段);`client/src/views/device/line/index.vue` 删除建筑/楼层/位置列,新增 IMEI/探测器类型/回路/通道/消防系统/生产日期/所属网关列,筛选/分页/同步按钮保留 +- [x] Task 7: 验证: + - [x] SubTask 7.1: 后端 `tsc --noEmit`、前端 `vue-tsc -b` 均 0 错误 + - [x] SubTask 7.2: 真实拉取 ICC 探测器(admin token,拉取 4 条插入 4 条;重跑幂等 updated=4;icc_sync_log 3 条 status=1;列表接口返回新字段) + - [x] SubTask 7.3: 浏览器 admin 登录验证 /device/line 页面新列渲染与手动同步按钮(11 列齐全、同步提示「拉取 4 条,新增 0,更新 4」) + +# Task Dependencies +- Task 2/3/6 依赖 Task 1(表结构先就绪) +- Task 4 依赖 Task 2/3 +- Task 5 依赖 Task 2 +- Task 7 依赖全部 diff --git a/client/src/api/device.ts b/client/src/api/device.ts index 356ceb8..21a2c0f 100644 --- a/client/src/api/device.ts +++ b/client/src/api/device.ts @@ -154,9 +154,6 @@ export interface ElectricLine { iccDeviceCode?: string; lineName?: string; customerId: number; - buildingId?: number; - floorNo?: string; - location?: string; status: number; syncedAt?: string; createdAt: string; @@ -165,7 +162,19 @@ export interface ElectricLine { customerName?: string; equipCode?: string; equipName?: string; - buildingName?: string; + // 探测器 / 网关相关字段 + imei?: string; + gatewayId?: number; + gatewayCode?: string; + gatewayName?: string; + typeCode?: string; + typeName?: string; + fireSystemCode?: string; + fireSystemName?: string; + partUnitLoopCode?: string; + channelNo?: string; + channelSn?: string; + produceDate?: string; } export interface ElectricLineQuery { diff --git a/client/src/views/device/line/index.vue b/client/src/views/device/line/index.vue index 25b285c..4ed5b50 100644 --- a/client/src/views/device/line/index.vue +++ b/client/src/views/device/line/index.vue @@ -16,7 +16,7 @@ 智电改造线路台账 ELECTRIC LINE LEDGER -

由 ICC 平台同步的智电改造线路台账,按客户、设备、建筑查看线路及在线状态。

+

由 ICC 平台同步的智电改造线路台账,按客户、网关查看线路、探测器及在线状态。

@@ -47,7 +47,7 @@ @@ -103,16 +103,32 @@ {{ record.lineName || '-' }} + + -