5.7 KiB
5.7 KiB
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. 架构变更
src/config/config.js: 添加roomStatusDb配置项。src/db/roomStatusManager.js: 新增单例类,用于管理log_platform的数据库连接池。src/db/statusBatchProcessor.js: 针对room_status_moment的专用批量处理器。- 原因: Upsert 逻辑复杂,且与
rcu_action_events的追加写日志模式不同。 - 它需要在批处理内聚合每个设备的更新,以减少数据库负载(去重)。
- 原因: Upsert 逻辑复杂,且与
src/processor/statusExtractor.js: 辅助工具,用于将KafkaPayload转换为StatusRow数据结构。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. 执行计划
- 添加配置 (Config) 和数据库管理器 (DB Manager)。
- 实现
StatusExtractor(将 Kafka 载荷转换为快照数据)。 - 实现
StatusBatchProcessor(包含内存合并逻辑)。 - 更新
processKafkaMessage,使其同时返回LogRows(现有) 和StatusUpdate(新增)。 - 在主循环中处理分发。