feat: 添加 G5 独立写入功能

- 新增 G5 数据库连接配置与可关闭的写入开关
- 在现有 legacy/G4 写入成功路径后,追加独立的 G5 写入流程
- G5 使用与 G4 相同的数据结构映射,但不写入 guid,由数据库自生成 int4 guid
- room_status 新增 G5 独立 upsert 写入路径,并保留旧表与 G5 表的独立开关
- 新增 G5 写入统计与启动摘要输出
- 更新 StatsCounters 和 StatsReporter 以支持 G5 统计
- 增加测试覆盖,确保 G5 写入逻辑与 room_status 的独立执行
- 新增 G5 相关数据库表结构 SQL 文件
This commit is contained in:
2026-03-10 16:29:24 +08:00
parent fe76884b27
commit 2f8857f98e
14 changed files with 924 additions and 130 deletions

View File

@@ -0,0 +1,15 @@
# Change: add g5 independent write
## Why
当前服务已经支持 legacy 与 G4 热表双写,但需要在不影响现有主链路的前提下,再额外向一个临时 G5 库独立写入同构数据,便于过渡期联调与验证。
## What Changes
- 新增 G5 独立数据库连接配置与可关闭的写入开关
- 在现有 legacy/G4 写入成功路径后,追加独立的 G5 写入流程
- G5 使用与 G4 相同的数据结构映射,但不写入 guid由数据库自生成 int4 guid
- room_status 新增 G5 独立 upsert 写入路径,并保留旧表与 G5 表的独立开关
- 新增 G5 写入统计与启动摘要输出
## Impact
- Affected specs: db, processor
- Affected code: src/config/config.js, src/index.js, src/db/databaseManager.js, src/processor/heartbeatProcessor.js, src/stats/statsManager.js, test/*.test.js

View File

@@ -0,0 +1,63 @@
## MODIFIED Requirements
### Requirement: 双明细独立编排
系统 MUST 提供多目标明细写入编排能力按启动配置分别控制旧表、G4 热表和临时 G5 热表的写入,各路写入结果相互独立。
#### Scenario: 旧表、G4 与 G5 可独立组合
- **WHEN** `legacyHeartbeatEnabled``g4HotHeartbeatEnabled``g5HeartbeatEnabled` 任意组合启停
- **THEN** 系统应只对开启的目标执行写入
- **AND** 任一路写入失败都不应阻塞其他已开启目标的写入
#### Scenario: G5 写入不影响主链路暂停判定
- **WHEN** G5 临时库写入失败或连接异常
- **THEN** 系统不应因此暂停 legacy/G4 主链路消费
- **AND** 应记录日志与统计以便排查
### Requirement: 写入目标启动配置
系统 MUST 通过启动配置环境变量分别控制旧表、G4 热表、临时 G5 热表和 room_status 的写入开关与目标表名。
#### Scenario: 读取 G5 独立连接与表配置
- **WHEN** 系统启动时
- **THEN** 应读取 `DB_G5_HEARTBEAT_ENABLED` 作为 G5 写入开关
- **AND** 应读取 `DB_G5_TABLE` 作为 G5 目标表名,默认 `heartbeat.heartbeat_events_g5`
- **AND** 应读取 `POSTGRES_HOST_G5``POSTGRES_PORT_G5``POSTGRES_DATABASE_G5``POSTGRES_USER_G5``POSTGRES_PASSWORD_G5``POSTGRES_IDLE_TIMEOUT_MS_G5` 作为 G5 独立连接参数
### Requirement: G5 热表独立写入能力
系统 MUST 支持向 `heartbeat.heartbeat_events_g5` 执行批量 COPY 写入,并与 G4 热表共享同一套字段展开逻辑。
#### Scenario: G5 写入复用 G4 字段映射
- **WHEN** `g5HeartbeatEnabled=true` 且有一批心跳数据待写入
- **THEN** 系统应使用与 G4 热表一致的字段展开规则写入 `heartbeat.heartbeat_events_g5`
- **AND** `service_mask` 应展开为 `svc_01``svc_64`
- **AND** 电力与空调数组应展开为 `_1``_2``_residual`
#### Scenario: G5 guid 由数据库生成
- **WHEN** 系统写入 `heartbeat.heartbeat_events_g5`
- **THEN** 系统不应为 `guid` 字段赋值
- **AND** 应依赖数据库默认值生成 `int4` 类型 guid
#### Scenario: G5 原始数组列固定写空
- **WHEN** 系统写入 `heartbeat.heartbeat_events_g5`
- **THEN** `service_mask``elec_address``air_address``voltage``ampere``power``phase``energy``sum_energy``state``model``speed``set_temp``now_temp``solenoid_valve``extra` 应统一写入 `null`
- **AND** `_1``_2``_residual` 展开列仍应按来源数据正常写入
### Requirement: room_status 新旧双表独立 upsert
系统 MUST 支持将 room_status 同时写入旧表与 G5 表,并允许分别开关。
#### Scenario: 旧表与 G5 表可独立开关
- **WHEN** `DB_ROOM_STATUS_ENABLED``DB_G5_ROOM_STATUS_ENABLED` 任意组合启停
- **THEN** 系统应仅对开启的 room_status 目标表执行 upsert
- **AND** 任一 room_status 目标失败不应阻塞另一目标
#### Scenario: room_status 全盘使用 ON CONFLICT DO UPDATE
- **WHEN** 系统写入旧 room_status 表或 G5 room_status 表
- **THEN** 系统应统一使用 `INSERT ... ON CONFLICT DO UPDATE`
- **AND** 不应退回为单独的 `INSERT``UPDATE` 路径
#### Scenario: room_status SQL 不更新 ts_ms
- **WHEN** 系统执行旧 room_status 或 G5 room_status 的 upsert SQL
- **THEN** `DO UPDATE SET` 子句中不应包含 `ts_ms = EXCLUDED.ts_ms`
#### Scenario: G5 room_status 使用 hotel_id 与 room_id 冲突键
- **WHEN** 系统写入 `room_status.room_status_moment_g5`
- **THEN** 应使用 `(hotel_id, room_id)` 作为 `ON CONFLICT` 的冲突键
- **AND** 应将 `device_id` 作为普通可更新列写入

View File

@@ -0,0 +1,19 @@
## MODIFIED Requirements
### Requirement: 按 sink 维度的统计与监控
系统 MUST 按写入目标维度分别统计成功数、失败数与降级事件。
#### Scenario: legacy、g4Hot 与 g5 分别统计
- **WHEN** 系统完成一批次的多目标写入编排
- **THEN** 应分别统计 legacy 写入成功数、失败数
- **AND** 应分别统计 g4Hot 写入成功数、失败数
- **AND** 应分别统计 g5 写入成功数、失败数
- **AND** 统计项应在 Redis 控制台输出或 stats 汇总中可见
### Requirement: room_status 新旧双表独立执行
系统 MUST 在批次处理中独立执行旧 room_status 与 G5 room_status 的 upsert不依赖任意明细写入结果。
#### Scenario: 两个 room_status 目标独立执行
- **WHEN** 一批心跳数据完成验证与转换
- **THEN** 只要 `DB_ROOM_STATUS_ENABLED=true` 就应执行旧 room_status upsert
- **AND** 只要 `DB_G5_ROOM_STATUS_ENABLED=true` 就应执行 G5 room_status upsert
- **AND** 任一路 room_status 写入失败都不应阻塞另一条 room_status 路径或主处理流程

View File

@@ -0,0 +1,6 @@
## 1. Implementation
- [x] 1.1 增加 G5 独立连接与开关配置读取
- [x] 1.2 增加 G5 独立写库实现,复用 G4 热表映射逻辑并省略 guid 写入
- [x] 1.3 将 G5 写入接入现有批处理流程,保持与 legacy/G4/room_status 相互独立
- [x] 1.4 增加 G5 统计、启动摘要与测试覆盖
- [x] 1.5 增加旧 room_status 与 G5 room_status 的独立 ON CONFLICT DO UPDATE 写入路径