# 数据库规范 (Database Specification) ## 1. PostgreSQL G5 连接配置 ### 1.1 连接信息 | 配置项 | 值 | 说明 | |--------|-----|------| | 主机 | 10.8.8.80 | G5 数据库服务器 | | 端口 | 5434 | 非标准端口 | | 数据库 | dbv6 | 目标数据库 | | 用户 | (从环境变量) | POSTGRES_USER_G5 | | 密码 | (从环密变量) | POSTGRES_PASSWORD_G5 | ### 1.2 表结构定义 ```sql CREATE TABLE IF NOT EXISTS public.room_status_moment_g5 ( hotel_id SMALLINT NOT NULL, room_id TEXT NOT NULL, device_id VARCHAR(255), ts_ms BIGINT NOT NULL, status SMALLINT DEFAULT 1, -- 主键:确保同一房间只有一行 PRIMARY KEY (hotel_id, room_id) ); ``` ## 2. Upsert 操作(批量插入/更新) ### 2.1 SQL 语句结构 ```sql INSERT INTO room_status_moment_g5 (hotel_id, room_id, device_id, ts_ms, status) VALUES ($1::smallint, $2, $3, $4, 1), ($5::smallint, $6, $7, $8, 1), ... ON CONFLICT (hotel_id, room_id) DO UPDATE SET ts_ms = EXCLUDED.ts_ms, status = 1 WHERE EXCLUDED.ts_ms >= room_status_moment_g5.ts_ms; ``` ### 2.2 类型转换设计决策 | 转换 | 理由 | |------|------| | hotel_id: string → ::smallint | G5 表使用 smallint;Kafka 送字符串避免精度问题 | | room_id: string → text | 支持中文、特殊字符 | | device_id: string → varchar | 与 G5 schema 兼容 | | ts_ms: number → bigint | JavaScript number 足以覆盖 64-bit 整数范围 | ## 3. 批量处理实现 ### 3.1 HeartbeatDbManager 类 位置: `src/db/heartbeatDbManager.js` ```javascript export class HeartbeatDbManager { constructor(pool) { this.pool = pool; } async upsertBatch(records) { if (!records || records.length === 0) { return; // 无需写入 } // 构建参数化查询 const valueClauses = []; const params = []; records.forEach((record, idx) => { const baseParamIdx = idx * 4; valueClauses.push( `($${baseParamIdx + 1}::smallint, $${baseParamIdx + 2}, $${baseParamIdx + 3}, $${baseParamIdx + 4}, 1)` ); params.push( record.hotel_id, // 字符串,::smallint 转换 record.room_id, record.device_id, record.ts_ms ); }); const query = ` INSERT INTO room_status_moment_g5 (hotel_id, room_id, device_id, ts_ms, status) VALUES ${valueClauses.join(',')} ON CONFLICT (hotel_id, room_id) DO UPDATE SET ts_ms = EXCLUDED.ts_ms, status = 1 WHERE EXCLUDED.ts_ms >= room_status_moment_g5.ts_ms `; try { const result = await this.pool.query(query, params); logger.info(`Batch upsert: ${records.length} records, ${result.rowCount} rows affected`); return result; } catch (err) { logger.error(`Batch upsert failed: ${err.message}`, err); throw err; } } } ``` --- **上次修订**: 2026-03-11