feat(analog): 新增ICC模拟量数据同步 - 重建electricity_data表(迁移20261024_icc_analog_data.sql:删除equip_id与electricity占位列、icc_device_code更名imei、新增analog_type/analog_name/analog_unit/analog_value、collect_time以同步时间填充、按月分区2026_10~2027_03、唯一键(line_code,analog_type,collect_time));同步服务IccAnalogSyncService复用OAuth2鉴权,按已同步线路line_code分批(≤50)拉取最新模拟量(POST /analog/subsystem/page,pageSize=500短页停止)幂等upsert并写icc_sync_log(sync_type=electricity_data);新增POST /api/electricity-data/sync手动同步(RBAC device:list);注册icc_analog_sync每5分钟定时任务与sys_job种子;同步db_init/db_seed与设计文档;无前端改动
This commit is contained in:
1 parent
160ad85051
commit
c92fc7f298
15 files changed
+701
-27
No files matched your search
@@ -0,0 +1,11 @@
|
||||
# Checklist
|
||||
|
||||
- [x] 生产库 electricity_data 现状已核对(有无表/字段/分区/数据),迁移方案据此确定
|
||||
- [x] 迁移 `20261024_icc_analog_data.sql` 已执行到生产库:字段集(id/line_id/line_code/imei/line_name/customer_id/analog_type/analog_name/analog_unit/analog_value/collect_time,**无 equip_id**)正确,唯一键 (line_code, analog_type, collect_time)、按月分区(2026_10~2027_03)、索引(customer/line/time)就绪
|
||||
- [x] db_init.sql 中 electricity_data 基线定义与迁移一致
|
||||
- [x] 后端实体 `electricity_data.entity.ts` 字段与迁移一致,映射 `icc_analog_mapper.ts` 按实际返回字段正确映射(line_code/imei/analog_type/analog_name/analog_unit/analog_value)
|
||||
- [x] 同步服务 `icc_analog_sync.service.ts` 复用 OAuth2 鉴权,先读已同步线路 line_code,分批 deviceCodes(≤50)POST 模拟量接口 pageSize=500 短页停止,collect_time 用同步时间,按 (line_code, analog_type, collect_time) 幂等 upsert,写 icc_sync_log(sync_type=electricity_data),失败仅记日志不抛
|
||||
- [x] `POST /api/electricity-data/sync` 接口可调用并返回 {pulled, inserted, updated, skipped, synced},RBAC 路由 `/api/electricity-data` → device:list 生效
|
||||
- [x] 定时任务 `icc_analog_sync`(每 5 分钟 `*/5 * * * *`)已注册:job_registry BUILTIN_JOBS + JobRuntime.syncIccAnalogs + db_seed.sql sys_job 种子幂等
|
||||
- [x] 设计文档「表结构新增及变更.md」已补充 electricity_data 新表结构及同步服务/定时任务说明
|
||||
- [x] 验证通过:tsc 编译无错误;真实同步拉取到模拟量数据入库(74 行/快照,analog_name/analog_unit 填充正确);幂等重跑正常(二次快照 148 行);无前端改动
|
||||
@@ -0,0 +1,63 @@
|
||||
# ICC 模拟量数据同步 Spec(add-icc-analog-data)
|
||||
|
||||
## Why
|
||||
现有 `electricity_data`(线路用电信息表)仅有一版占位定义(单列 `electricity`),与 ICC 平台「消防最新模拟量信息分页查询」接口(`POST /evo-apigw/evo-fdbu/{version}/analog/subsystem/page`,实测返回 `{deviceCode, uniqueCode, analogType, analogName, analogUnit, analogValue}`,无时间字段)不对应。本次按接口返回数据重建表结构,并实现真实同步服务与定时任务,将已同步线路对应探测器的模拟量数据写入 electricity_data。
|
||||
|
||||
## What Changes
|
||||
- **表结构重建(BREAKING)**:
|
||||
- 删除占位列 `electricity` → 通用模拟量行:`analog_type`(analogType)、`analog_value` NUMERIC(14,4)(analogValue)、`analog_name`(analogName,如:剩余电流)、`analog_unit`(analogUnit,如:mA)
|
||||
- `icc_device_code` 改为 `imei`(映射 uniqueCode)
|
||||
- **删除 `equip_id`**(用户确认不需要客户设备关联)
|
||||
- 保留:`line_id`(NOT NULL → electric_line)、`line_code`(NOT NULL,= deviceCode 冗余 + 幂等键)、`line_name`(冗余展示)、`customer_id`(NOT NULL,逻辑隔离键)、`collect_time`(NOT NULL,分区键)
|
||||
- **collect_time 语义(实测确认)**:ICC「最新模拟量分页查询」接口实际不返回 reportTime 时间字段,经用户确认以**同步时间**作为采集时间(分区键)
|
||||
- 唯一幂等键:`UNIQUE (line_code, analog_type, collect_time)`(同一次同步内重复上报覆盖更新)
|
||||
- 按月 RANGE 分区(分区键 collect_time),迁移建当月及后续月份分区
|
||||
- **同步服务**(复用 icc_customer_auth 真实 OAuth2 鉴权):
|
||||
- 先读取已同步的 electric_line(line_code = detectorCode),按 deviceCodes 分批(每批 ≤50)POST 模拟量接口,pageSize=500 短页停止全量拉取
|
||||
- 预取 deviceCode→(line_id, customer_id) 映射避免 N+1;imei=uniqueCode、analog_type=analogType、analog_name=analogName、analog_unit=analogUnit、analog_value=analogValue 数值解析、collect_time=同步时间
|
||||
- 按唯一键 (line_code, analog_type, collect_time) 幂等 upsert;写 icc_sync_log(sync_type=electricity_data);失败仅记日志不抛业务错
|
||||
- **定时任务**:jobKey=`icc_analog_sync`(默认每 5 分钟,cron `*/5 * * * *`,sys_job 种子幂等),注册进 job_registry/JobRuntime(syncIccAnalogs)
|
||||
- **同步接口**:`POST /api/electricity-data/sync`(JWT+RBAC device:list,返回 pulled/inserted/updated/skipped/synced);不做前端页面/菜单
|
||||
- **迁移与文档**:新增 `database/migrations/20261024_icc_analog_data.sql`(建表/分区/索引/注释 + sys_job 种子),同步 `database/db_init.sql` 与 `系统设计/表结构新增及变更.md`
|
||||
|
||||
## Impact
|
||||
- Affected specs:add-icc-line-sync(electric_line 为模拟量关联目标,先同步线路)
|
||||
- Affected code:
|
||||
- 后端新增:`server/src/entity/electricity_data.entity.ts`、`server/src/task/icc_analog_mapper.ts`、`server/src/service/icc_analog_sync.service.ts`、`server/src/task/icc_analog_sync.task.ts`、`server/src/controller/electricity_data.controller.ts`
|
||||
- 后端修改:`server/src/task/job_registry.ts`、`server/src/service/job_scheduler.service.ts`、`server/src/middleware/rbac.middleware.ts`
|
||||
- 数据库:新增 `database/migrations/20261024_icc_analog_data.sql`;修改 `database/db_init.sql`、`database/db_seed.sql`(sys_job 种子)
|
||||
- 设计文档:`系统设计/表结构新增及变更.md`
|
||||
- 前端:无改动
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: electricity_data 表结构按 AnalogInfoVO 重建
|
||||
系统 SHALL 将 `electricity_data` 重建为按月分区时序表:id(复合主键含 collect_time)、line_id(NOT NULL FK→electric_line)、line_code(NOT NULL)、imei、line_name、customer_id(NOT NULL)、analog_type、analog_name、analog_unit、analog_value NUMERIC(14,4)、collect_time TIMESTAMPTZ(NOT NULL 分区键,同步时间),唯一键 `UNIQUE (line_code, analog_type, collect_time)`,索引 customer_id/line_id/collect_time。
|
||||
|
||||
#### Scenario: 迁移执行成功
|
||||
- **WHEN** 执行迁移到生产库
|
||||
- **THEN** electricity_data 含上述字段/分区/唯一键/索引;db_init.sql 与迁移一致
|
||||
|
||||
### Requirement: 真实 ICC 模拟量拉取同步
|
||||
系统 SHALL 复用 `getIccAccessToken`/`iccFetch`,以已同步线路的 line_code(detectorCode)分批作为 deviceCodes,POST 模拟量接口分页拉取(pageSize=500 短页停止),以同步时间作为 collect_time,按 (line_code, analog_type, collect_time) 幂等 upsert,写 icc_sync_log(sync_type=electricity_data),失败仅记日志不抛。
|
||||
|
||||
#### Scenario: 同步成功
|
||||
- **WHEN** 定时任务或 POST /api/electricity-data/sync 触发
|
||||
- **THEN** 返回 {pulled, inserted, updated, skipped, synced};同键重复上报覆盖更新;无线路匹配的行跳过
|
||||
|
||||
#### Scenario: 同步失败
|
||||
- **WHEN** ICC 拉取异常
|
||||
- **THEN** 仅写 icc_sync_log 失败日志,接口返回 code 200 与当前计数
|
||||
|
||||
### Requirement: 定时任务注册
|
||||
系统 SHALL 注册 `icc_analog_sync` 定时任务(默认每 5 分钟,cron `*/5 * * * *`),注册进 job_registry BUILTIN_JOBS 与 JobRuntime.syncIccAnalogs,sys_job 种子幂等。
|
||||
|
||||
#### Scenario: 任务可执行
|
||||
- **WHEN** 任务调度触发
|
||||
- **THEN** 调用同步服务并将结果写入执行记录
|
||||
|
||||
## REMOVED Requirements
|
||||
|
||||
### Requirement: 占位 electricity 单列结构(旧 electricity_data)
|
||||
**Reason**:与模拟量接口返回结构不对应。
|
||||
**Migration**:electricity → analog_type/analog_value;icc_device_code → imei;equip_id 删除;collect_time 改由同步时间填充(接口实测无 reportTime)。
|
||||
@@ -0,0 +1,32 @@
|
||||
# Tasks
|
||||
|
||||
- [x] Task 1: 核对生产库 electricity_data 现状(表是否存在、旧表字段/分区/有无数据),确定迁移采用 CREATE IF NOT EXISTS 还是增量变更
|
||||
- [x] SubTask 1.1: 查询生产库 information_schema 确认 electricity_data 是否存在及其字段/分区/数据量
|
||||
- [x] Task 2: 新建迁移 `database/migrations/20261024_icc_analog_data.sql`(无 equip_id):
|
||||
- 无旧表 → `CREATE TABLE IF NOT EXISTS electricity_data`:id BIGSERIAL、line_id BIGINT NOT NULL(FK→electric_line)、line_code VARCHAR(64) NOT NULL、imei VARCHAR(64)、line_name VARCHAR(128)、customer_id BIGINT NOT NULL、analog_type VARCHAR(64)、analog_name VARCHAR(128)、analog_unit VARCHAR(32)、analog_value NUMERIC(14,4)、collect_time TIMESTAMPTZ NOT NULL;主键 (id, collect_time);唯一键 UNIQUE (line_code, analog_type, collect_time);PARTITION BY RANGE (collect_time)
|
||||
- 有旧表 → 幂等变更:删除 equip_id/electricity、icc_device_code 更名 imei、新增 analog_type/analog_name/analog_unit/analog_value(数据量小可 DROP 重建)
|
||||
- 建分区 2026_10 ~ 2027_03(IF NOT EXISTS)、索引 idx_ed_customer/idx_ed_line/idx_ed_time、表与列 COMMENT
|
||||
- 执行到远程生产库并验证(列/分区/唯一键/索引),同步更新 `database/db_init.sql` 基线定义
|
||||
- [x] Task 3: 后端实体与映射:新建 `server/src/entity/electricity_data.entity.ts`(字段 analog_name/analog_unit,FK→electric_line)、`server/src/task/icc_analog_mapper.ts`(AnalogInfoVO 映射:deviceCode→line_code、uniqueCode→imei、analogType→analog_type、analogName→analog_name、analogUnit→analog_unit、analogValue 数值解析→analog_value;分页解析兼容 data.pageData;接口实测无 reportTime,collect_time 由同步时间填充)
|
||||
- [x] Task 4: 同步服务:新建 `server/src/service/icc_analog_sync.service.ts`
|
||||
- [x] 复用 `getIccAccessToken`/`iccFetch`;先读已同步 electric_line 取 line_code(detectorCode)与 (line_id, customer_id)
|
||||
- [x] deviceCodes 分批(每批 ≤50)POST `/analog/subsystem/page`,pageSize=500 短页停止全量拉取
|
||||
- [x] 按 (line_code, analog_type, collect_time) 幂等 upsert(collect_time 用同步时间,同一次同步内重复上报覆盖更新);写 icc_sync_log(sync_type=electricity_data);失败仅记日志不抛业务错
|
||||
- [x] Task 5: 同步接口 + RBAC + 定时任务:
|
||||
- [x] 新建 `server/src/controller/electricity_data.controller.ts`:`POST /api/electricity-data/sync` 返回 {pulled, inserted, updated, skipped, synced}
|
||||
- [x] `server/src/middleware/rbac.middleware.ts` 增加 `/api/electricity-data` → `device:list`
|
||||
- [x] 新建 `server/src/task/icc_analog_sync.task.ts`(jobKey=icc_analog_sync,默认每 5 分钟 `*/5 * * * *`);`job_registry.ts` BUILTIN_JOBS 注册 + `job_scheduler.service.ts` JobRuntime.syncIccAnalogs 注入实现
|
||||
- [x] `database/db_seed.sql` 增加 sys_job 种子(job_key=icc_analog_sync,幂等)
|
||||
- [x] Task 6: 同步更新 `系统设计/表结构新增及变更.md`:追加 electricity_data 新表结构(字段/类型/键/注释 + 与 customer/customer_equipment/electric_line 关联说明 + 同步服务与定时任务说明)
|
||||
- [x] Task 7: 验证:
|
||||
- [x] SubTask 7.1: `tsc` 编译通过(server)
|
||||
- [x] SubTask 7.2: 启动后端,调用 `POST /api/electricity-data/sync`(先确认线路已同步),确认拉到真实模拟量数据入库、幂等重跑 updated/skipped 正常
|
||||
- [x] SubTask 7.3: 验证 icc_sync_log 落日志、定时任务注册存在(无前端改动,无需浏览器验证)
|
||||
|
||||
# Task Dependencies
|
||||
- Task 2 依赖 Task 1(确认生产库现状后写迁移)
|
||||
- Task 3 依赖 Task 2(表结构定稿后建实体)
|
||||
- Task 4 依赖 Task 3(实体与映射就绪后实现同步服务)
|
||||
- Task 5 依赖 Task 4(同步服务实现后可接线 controller/定时任务)
|
||||
- Task 6 依赖 Task 2(表结构定稿后写文档)
|
||||
- Task 7 依赖 Task 4、Task 5
|
||||
Reference in new issue
Block a user