feat: 扩展心跳消息支持电力与空调设备数组字段
新增 Kafka 消息中 electricity[] 和 air_conditioner[] 数组字段支持,用于存储电力与空调设备明细数据。数据库表新增对应数组列并创建 GIN 索引优化查询性能,processor 实现数组字段校验与聚合转换逻辑。 主要变更: - Kafka 消息规范新增 electricity 和 air_conditioner 数组字段定义 - 数据库 heartbeat_events 表新增 14 个数组列并创建 4 个 GIN 索引 - processor 实现数组字段解析、校验及聚合转换逻辑 - 更新相关文档与测试用例,确保端到端功能完整
This commit is contained in:
117
test/arrays.test.js
Normal file
117
test/arrays.test.js
Normal file
@@ -0,0 +1,117 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { HeartbeatProcessor } from '../src/processor/heartbeatProcessor.js';
|
||||
|
||||
function buildBasePayload() {
|
||||
return {
|
||||
ts_ms: 1700000000123,
|
||||
hotel_id: 1,
|
||||
room_id: '101',
|
||||
device_id: 'dev-1',
|
||||
ip: '127.0.0.1:1234',
|
||||
power_state: 1,
|
||||
guest_type: 0,
|
||||
cardless_state: 0,
|
||||
service_mask: 5,
|
||||
pms_state: 1,
|
||||
carbon_state: 0,
|
||||
device_count: 1,
|
||||
comm_seq: 7,
|
||||
};
|
||||
}
|
||||
|
||||
describe('HeartbeatProcessor arrays', () => {
|
||||
it('aggregates electricity[] into aligned column arrays (keeps order)', () => {
|
||||
const processor = new HeartbeatProcessor(
|
||||
{ batchSize: 100, batchTimeout: 1000 },
|
||||
{ insertHeartbeatEvents: async () => ({ insertedCount: 0 }) }
|
||||
);
|
||||
|
||||
const payload = {
|
||||
...buildBasePayload(),
|
||||
electricity: [
|
||||
{ address: 'add11', ampere: '1.1', power: 704.3, voltage: 3.2, phase: 'A', energy: '10', sum_energy: 100 },
|
||||
{ address: 'add12', ampere: 2.2, power: '705.3', voltage: '3.3', phase: 'B', energy: 11, sum_energy: '101' },
|
||||
{ address: 'add13', ampere: 'N/A', power: 706.3, voltage: 3.4, phase: '', energy: null, sum_energy: undefined },
|
||||
],
|
||||
};
|
||||
|
||||
assert.equal(processor.validateData(payload), true);
|
||||
|
||||
const transformed = processor.transformData(payload);
|
||||
assert.deepEqual(transformed.elec_address, ['add11', 'add12', 'add13']);
|
||||
assert.deepEqual(transformed.ampere, [1.1, 2.2, null]);
|
||||
assert.deepEqual(transformed.power, [704.3, 705.3, 706.3]);
|
||||
assert.deepEqual(transformed.voltage, [3.2, 3.3, 3.4]);
|
||||
assert.deepEqual(transformed.phase, ['A', 'B', null]);
|
||||
assert.deepEqual(transformed.energy, [10, 11, null]);
|
||||
assert.deepEqual(transformed.sum_energy, [100, 101, null]);
|
||||
});
|
||||
|
||||
it('aggregates air_conditioner[] into aligned column arrays (keeps order)', () => {
|
||||
const processor = new HeartbeatProcessor(
|
||||
{ batchSize: 100, batchTimeout: 1000 },
|
||||
{ insertHeartbeatEvents: async () => ({ insertedCount: 0 }) }
|
||||
);
|
||||
|
||||
const payload = {
|
||||
...buildBasePayload(),
|
||||
air_conditioner: [
|
||||
{ address: 'ac1', state: 1, model: 2, speed: 3, set_temp: 26, now_temp: 25, solenoid_valve: 1 },
|
||||
{ address: 'ac2', state: '0', model: '1', speed: '2', set_temp: '24', now_temp: '23', solenoid_valve: '0' },
|
||||
{ address: 'ac3', state: 'bad', model: null, speed: undefined, set_temp: '', now_temp: 22, solenoid_valve: 1.2 },
|
||||
],
|
||||
};
|
||||
|
||||
assert.equal(processor.validateData(payload), true);
|
||||
|
||||
const transformed = processor.transformData(payload);
|
||||
assert.deepEqual(transformed.air_address, ['ac1', 'ac2', 'ac3']);
|
||||
assert.deepEqual(transformed.state, [1, 0, null]);
|
||||
assert.deepEqual(transformed.model, [2, 1, null]);
|
||||
assert.deepEqual(transformed.speed, [3, 2, null]);
|
||||
assert.deepEqual(transformed.set_temp, [26, 24, null]);
|
||||
assert.deepEqual(transformed.now_temp, [25, 23, 22]);
|
||||
assert.deepEqual(transformed.solenoid_valve, [1, 0, 1]);
|
||||
});
|
||||
|
||||
it('rejects non-array electricity/air_conditioner', () => {
|
||||
const processor = new HeartbeatProcessor(
|
||||
{ batchSize: 100, batchTimeout: 1000 },
|
||||
{ insertHeartbeatEvents: async () => ({ insertedCount: 0 }) }
|
||||
);
|
||||
|
||||
const bad1 = { ...buildBasePayload(), electricity: { address: 'x' } };
|
||||
const bad2 = { ...buildBasePayload(), air_conditioner: { address: 'y' } };
|
||||
|
||||
assert.equal(processor.validateData(bad1), false);
|
||||
assert.equal(processor.validateData(bad2), false);
|
||||
});
|
||||
|
||||
it('end-to-end: message buffer -> processMessage -> insertHeartbeatEvents payload includes arrays', async () => {
|
||||
let captured = null;
|
||||
const db = {
|
||||
insertHeartbeatEvents: async (events) => {
|
||||
captured = events;
|
||||
return { insertedCount: events.length };
|
||||
},
|
||||
};
|
||||
|
||||
const processor = new HeartbeatProcessor({ batchSize: 1, batchTimeout: 1000 }, db);
|
||||
|
||||
const payload = {
|
||||
...buildBasePayload(),
|
||||
electricity: [{ address: 'add11', voltage: 3.2, ampere: 1.1, power: 704.3, phase: 'A', energy: 10, sum_energy: 100 }],
|
||||
air_conditioner: [{ address: 'ac1', state: 1, model: 2, speed: 3, set_temp: 26, now_temp: 25, solenoid_valve: 1 }],
|
||||
};
|
||||
|
||||
const message = { value: Buffer.from(JSON.stringify(payload), 'utf8') };
|
||||
const res = await processor.processMessage(message);
|
||||
assert.deepEqual(res, { insertedCount: 1 });
|
||||
assert.ok(Array.isArray(captured));
|
||||
assert.equal(captured.length, 1);
|
||||
assert.deepEqual(captured[0].elec_address, ['add11']);
|
||||
assert.deepEqual(captured[0].air_address, ['ac1']);
|
||||
assert.deepEqual(captured[0].state, [1]);
|
||||
assert.deepEqual(captured[0].model, [2]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user