feat: 实现GUID主键与service_mask索引改造

- 将主键从自增id改为GUID格式并添加格式校验
- 为service_mask添加表达式索引优化首位查询性能
- 更新相关文档说明改造方案与验证步骤
- 添加统计模块记录数据库写入与Kafka消费量
- 重构Redis心跳协议改用LIST类型存储项目状态
- 修复部署脚本中的服务名称不一致问题
This commit is contained in:
2026-01-17 18:37:44 +08:00
parent 662eeee380
commit 41301f9ce5
21 changed files with 828 additions and 106 deletions

View File

@@ -1,5 +1,6 @@
import assert from 'node:assert/strict';
import { HeartbeatProcessor } from '../src/processor/heartbeatProcessor.js';
import { RedisIntegration } from '../src/redis/redisIntegration.js';
describe('HeartbeatProcessor smoke', () => {
it('decodes JSON buffer into object', () => {
@@ -25,3 +26,108 @@ describe('HeartbeatProcessor smoke', () => {
});
});
describe('RedisIntegration protocol', () => {
it('writes heartbeat to 项目心跳 LIST', async () => {
const redis = new RedisIntegration({
enabled: true,
projectName: 'BLS主机心跳日志',
apiBaseUrl: 'http://127.0.0.1:3000',
});
const calls = { rPush: [], lTrim: [] };
redis.client = {
isReady: true,
rPush: async (key, value) => {
calls.rPush.push({ key, value });
},
lTrim: async (key, start, stop) => {
calls.lTrim.push({ key, start, stop });
},
};
const before = Date.now();
await redis.writeHeartbeat();
const after = Date.now();
assert.equal(calls.rPush.length, 1);
assert.equal(calls.rPush[0].key, '项目心跳');
const payload = JSON.parse(calls.rPush[0].value);
assert.equal(payload.projectName, 'BLS主机心跳日志');
assert.equal(payload.apiBaseUrl, 'http://127.0.0.1:3000');
assert.equal(typeof payload.lastActiveAt, 'number');
assert.ok(payload.lastActiveAt >= before && payload.lastActiveAt <= after);
assert.equal(calls.lTrim.length, 1);
assert.deepEqual(calls.lTrim[0], { key: '项目心跳', start: -2000, stop: -1 });
});
it('caches heartbeat when redis is not ready and flushes later', async () => {
const redis = new RedisIntegration({
enabled: true,
projectName: 'BLS主机心跳日志',
apiBaseUrl: 'http://127.0.0.1:3000',
});
const calls = { rPush: [], lTrim: [] };
redis.client = {
isReady: false,
connect: async () => {},
rPush: async (key, value) => {
calls.rPush.push({ key, value });
},
lTrim: async (key, start, stop) => {
calls.lTrim.push({ key, start, stop });
},
};
await redis.writeHeartbeat();
assert.ok(redis._pendingHeartbeat);
redis.client.isReady = true;
await redis.flushPendingHeartbeat();
assert.equal(redis._pendingHeartbeat, null);
assert.equal(calls.rPush.length, 1);
assert.equal(calls.rPush[0].key, '项目心跳');
const payload = JSON.parse(calls.rPush[0].value);
assert.equal(payload.projectName, 'BLS主机心跳日志');
assert.equal(payload.apiBaseUrl, 'http://127.0.0.1:3000');
assert.equal(typeof payload.lastActiveAt, 'number');
assert.equal(calls.lTrim.length, 1);
});
it('buffers console logs when redis is not ready', async () => {
const redis = new RedisIntegration({
enabled: true,
projectName: 'BLS主机心跳日志',
apiBaseUrl: 'http://127.0.0.1:3000',
});
const calls = { rPush: [], lTrim: [] };
redis.client = {
isReady: false,
connect: async () => {},
rPush: async (key, ...values) => {
calls.rPush.push({ key, values });
},
lTrim: async (key, start, stop) => {
calls.lTrim.push({ key, start, stop });
},
};
await redis.info('hello', { module: 'test' });
assert.equal(redis._pendingConsoleLogs.length, 1);
redis.client.isReady = true;
await redis.flushPendingConsoleLogs();
assert.equal(redis._pendingConsoleLogs.length, 0);
assert.equal(calls.rPush.length, 1);
assert.equal(calls.rPush[0].key, 'BLS主机心跳日志_项目控制台');
assert.equal(calls.rPush[0].values.length, 1);
const entry = JSON.parse(calls.rPush[0].values[0]);
assert.equal(entry.level, 'info');
assert.equal(entry.message, 'hello');
assert.equal(entry.metadata.module, 'test');
});
});