Files
Web_BLS_RCUAction_Server/openspec/changes/2026-02-06-room-status-moment/spec.md

5.7 KiB
Raw Blame History

Room Status Moment 集成方案

1. 背景

我们需要将一个新的数据库表 room_status.room_status_moment(快照表)集成到现有的 Kafka 处理流程中。 该表用于存储每个房间/设备的最新状态。 现有的逻辑(批量插入到 rcu_action_events)必须保持不变。

2. 数据库配置

新表位于一个独立的数据库中(可能是 log_platform,或者现有数据库中的新模式 room_status)。 我们将添加对 ROOM_STATUS 独立连接池的支持,以确保灵活性。

环境变量配置:

# 现有数据库配置
DB_HOST=...
...

# 新 Room Status 数据库配置 (如果未提供,默认使用现有数据库,但使用独立的连接池)
ROOM_STATUS_DB_HOST=...
ROOM_STATUS_DB_PORT=...
ROOM_STATUS_DB_USER=...
ROOM_STATUS_DB_PASSWORD=...
ROOM_STATUS_DB_DATABASE=log_platform  <-- SQL 脚本中的目标数据库名
ROOM_STATUS_DB_SCHEMA=room_status

3. 字段映射策略

目标表:room_status.room_status_moment 唯一键:(hotel_id, room_id, device_id)

源字段 (Kafka) 目标字段 更新逻辑
hotel_id hotel_id 主键/索引键
room_id room_id 主键/索引键
device_id device_id 主键/索引键
ts_ms ts_ms 始终更新为最新值
sys_lock_status sys_lock_status 直接映射 (如果存在)
device_list (0x36)
control_list (0x0F)
dev_loops (JSONB) 合并策略 (Merge):
Key: 001002003 (Type(3)+Addr(3)+Loop(3))
Value: dev_data (int)
操作: `old_json
fault_list (0x36) faulty_device_count (JSONB) 替换策略 (Replace):
由于 0x36 上报的是完整故障列表,我们直接覆盖该字段。
内容: {dev_type, dev_addr, dev_loop, error_type, error_data} 的列表
fault_list -> item error_type=1 online_status 如果 error_data=1 -> 离线 (0)
如果 error_data=0 -> 在线 (1)
需要验证具体的映射约定

关于在线状态 (Online Status) 的说明: 文档描述: "0x01: 0:在线 1:离线"。 表字段 online_status 类型为 INT2。 约定:通常 1=在线, 0=离线。 逻辑:

  • 如果故障类型 0x01, 数据 0 (在线) -> 设置 online_status = 1
  • 如果故障类型 0x01, 数据 1 (离线) -> 设置 online_status = 0
  • 否则 -> 不更新 online_status

4. Upsert 逻辑 (PostgreSQL)

我们将使用 INSERT ... ON CONFLICT DO UPDATE 语法。

INSERT INTO room_status.room_status_moment (
  guid, ts_ms, hotel_id, room_id, device_id,
  sys_lock_status, online_status,
  dev_loops, faulty_device_count
) VALUES (
  $guid, $ts_ms, $hotel_id, $room_id, $device_id,
  $sys_lock_status, $online_status,
  $dev_loops::jsonb, $faulty_device_count::jsonb
)
ON CONFLICT (hotel_id, room_id, device_id)
DO UPDATE SET
  ts_ms = EXCLUDED.ts_ms,
  -- 仅在新数据不为空时更新 sys_lock_status
  sys_lock_status = COALESCE(EXCLUDED.sys_lock_status, room_status.room_status_moment.sys_lock_status),
  -- 仅在新数据不为空时更新 online_status
  online_status = COALESCE(EXCLUDED.online_status, room_status.room_status_moment.online_status),
  -- 合并 dev_loops
  dev_loops = CASE
    WHEN EXCLUDED.dev_loops IS NULL THEN room_status.room_status_moment.dev_loops
    ELSE COALESCE(room_status.room_status_moment.dev_loops, '{}'::jsonb) || EXCLUDED.dev_loops
  END,
  -- 如果存在则替换 faulty_device_count
  faulty_device_count = COALESCE(EXCLUDED.faulty_device_count, room_status.room_status_moment.faulty_device_count)
WHERE
  -- 可选优化:仅在时间戳更新时写入
  -- 注意:对于 JSON 合并,很难在不计算的情况下检测是否相等。
  -- 我们依赖 ts_ms 的变化来表示数据的“新鲜度”。
  EXCLUDED.ts_ms >= room_status.room_status_moment.ts_ms
;

5. 架构变更

  1. src/config/config.js: 添加 roomStatusDb 配置项。
  2. src/db/roomStatusManager.js: 新增单例类,用于管理 log_platform 的数据库连接池。
  3. src/db/statusBatchProcessor.js: 针对 room_status_moment 的专用批量处理器。
    • 原因: Upsert 逻辑复杂,且与 rcu_action_events 的追加写日志模式不同。
    • 它需要在批处理内聚合每个设备的更新,以减少数据库负载(去重)。
  4. src/processor/statusExtractor.js: 辅助工具,用于将 KafkaPayload 转换为 StatusRow 数据结构。
  5. src/index.js: 挂载新的处理器逻辑。

6. 去重策略 (内存批量聚合)

由于 room_status_moment 是快照表,如果我们在 1 秒内收到同一设备的 10 次更新:

  • 我们只需要写入 最后一次 的状态(或合并后的状态)。
  • StatusBatchProcessor 应该维护一个映射: Map<Key(hotel,room,device), LatestData>
  • 在 Flush 时,将 Map 的值转换为批量 Upsert 操作。
  • 约束: dev_loops 的更新如果是针对不同回路的,可能需要累积合并。
  • 优化策略:
    • 如果 dev_loops 是部分更新,我们不能简单地取最后一条消息。
    • 但是,在短时间的批处理窗口(例如 500ms我们可以在内存中将它们合并后再发送给数据库。
    • 结构: Map<Key, MergedState>
    • 逻辑: MergedState.dev_loops = Object.assign({}, old.dev_loops, new.dev_loops)

7. 执行计划

  1. 添加配置 (Config) 和数据库管理器 (DB Manager)。
  2. 实现 StatusExtractor (将 Kafka 载荷转换为快照数据)。
  3. 实现 StatusBatchProcessor (包含内存合并逻辑)。
  4. 更新 processKafkaMessage,使其同时返回 LogRows (现有) 和 StatusUpdate (新增)。
  5. 在主循环中处理分发。