From 381080fee0288e912d4fec7594e42c9f17e4d5ad Mon Sep 17 00:00:00 2001 From: XuJiacheng Date: Wed, 18 Mar 2026 09:47:33 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E5=AF=B9=20Kafka=20`?= =?UTF-8?q?CurrentStatus`=20=E7=9A=84=20`restart`=20=E5=80=BC=E6=94=AF?= =?UTF-8?q?=E6=8C=81=EF=BC=8C=E6=9B=B4=E6=96=B0=20G5=20=E5=85=A5=E5=BA=93?= =?UTF-8?q?=E9=80=BB=E8=BE=91=E5=8F=8A=E7=9B=B8=E5=85=B3=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../proposal.md | 13 +++++++++++++ .../specs/onoffline/spec.md | 14 ++++++++++++++ .../tasks.md | 10 ++++++++++ .../openspec/specs/onoffline/spec.md | 15 ++++++++++----- bls-onoffline-backend/spec/onoffline-spec.md | 4 ++-- .../src/db/g5DatabaseManager.js | 16 +++++++++++++--- bls-onoffline-backend/src/processor/index.js | 14 +++++++++++--- .../tests/g5DatabaseManager.test.js | 15 +++++++++++++++ bls-onoffline-backend/tests/processor.test.js | 12 +++++++++--- 9 files changed, 97 insertions(+), 16 deletions(-) create mode 100644 bls-onoffline-backend/openspec/changes/archive/2026-03-18-add-restart-current-status-mapping/proposal.md create mode 100644 bls-onoffline-backend/openspec/changes/archive/2026-03-18-add-restart-current-status-mapping/specs/onoffline/spec.md create mode 100644 bls-onoffline-backend/openspec/changes/archive/2026-03-18-add-restart-current-status-mapping/tasks.md create mode 100644 bls-onoffline-backend/tests/g5DatabaseManager.test.js diff --git a/bls-onoffline-backend/openspec/changes/archive/2026-03-18-add-restart-current-status-mapping/proposal.md b/bls-onoffline-backend/openspec/changes/archive/2026-03-18-add-restart-current-status-mapping/proposal.md new file mode 100644 index 0000000..9d6f34d --- /dev/null +++ b/bls-onoffline-backend/openspec/changes/archive/2026-03-18-add-restart-current-status-mapping/proposal.md @@ -0,0 +1,13 @@ +# Change: add restart current_status mapping + +## Why +上游 Kafka 的 `CurrentStatus` 新增了 `restart` 值。现有处理逻辑仍按 `on/off` 处理,导致 `restart` 在入库时无法被正确标记为 3。 + +## What Changes +- 让 Kafka 解析链路接受 `CurrentStatus=restart`。 +- 将 G5 入库链路中的 `current_status=restart` 映射为 `3`。 +- 更新相关测试与 OpenSpec 说明,确保 `restart` 是受支持状态。 + +## Impact +- Affected specs: `openspec/specs/onoffline/spec.md` +- Affected code: `src/processor/index.js`, `src/db/g5DatabaseManager.js`, `tests/processor.test.js` diff --git a/bls-onoffline-backend/openspec/changes/archive/2026-03-18-add-restart-current-status-mapping/specs/onoffline/spec.md b/bls-onoffline-backend/openspec/changes/archive/2026-03-18-add-restart-current-status-mapping/specs/onoffline/spec.md new file mode 100644 index 0000000..edd3597 --- /dev/null +++ b/bls-onoffline-backend/openspec/changes/archive/2026-03-18-add-restart-current-status-mapping/specs/onoffline/spec.md @@ -0,0 +1,14 @@ +## MODIFIED Requirements + +### Requirement: 重启数据处理 +系统 SHALL 在 `CurrentStatus` 为 `restart` 时将 `current_status` 保留为 `restart`,并在 G5 入库链路中映射为 `3`。 + +#### Scenario: restart 状态写入 +- **GIVEN** Kafka 消息中的 `CurrentStatus` 为 `restart` +- **WHEN** 消息被处理并写入数据库 +- **THEN** 普通入库链路保留 `restart`,G5 入库链路将其写入为 `3` + +#### Scenario: 其他状态保持原样 +- **GIVEN** Kafka 消息中的 `CurrentStatus` 为 `on` 或 `off` +- **WHEN** 消息被处理并写入数据库 +- **THEN** 系统按既有规则处理该状态值 diff --git a/bls-onoffline-backend/openspec/changes/archive/2026-03-18-add-restart-current-status-mapping/tasks.md b/bls-onoffline-backend/openspec/changes/archive/2026-03-18-add-restart-current-status-mapping/tasks.md new file mode 100644 index 0000000..6c3a3ac --- /dev/null +++ b/bls-onoffline-backend/openspec/changes/archive/2026-03-18-add-restart-current-status-mapping/tasks.md @@ -0,0 +1,10 @@ +## 1. Implementation +- [x] 1.1 Update Kafka row building logic to preserve `restart` as a valid `current_status` value. +- [x] 1.2 Update G5 database mapping so `restart` maps to `3`. +- [x] 1.3 Update processor tests for the `restart` case. +- [x] 1.4 Update OpenSpec requirements for supported current status values. + +## 2. Validation +- [x] 2.1 Run `npm run test`. +- [x] 2.2 Run `npm run build`. +- [x] 2.3 Run `openspec validate add-restart-current-status-mapping --strict`. diff --git a/bls-onoffline-backend/openspec/specs/onoffline/spec.md b/bls-onoffline-backend/openspec/specs/onoffline/spec.md index a3bc8b0..31c6ffd 100644 --- a/bls-onoffline-backend/openspec/specs/onoffline/spec.md +++ b/bls-onoffline-backend/openspec/specs/onoffline/spec.md @@ -12,12 +12,17 @@ - **THEN** current_status 等于 CurrentStatus (截断至 255 字符) ### Requirement: 重启数据处理 -系统 SHALL 在 RebootReason 非空时强制 current_status 为 on。 +系统 SHALL 在 `CurrentStatus` 为 `restart` 时将 `current_status` 保留为 `restart`,并在 G5 入库链路中映射为 `3`。 -#### Scenario: 重启数据写入 -- **GIVEN** RebootReason 为非空值 -- **WHEN** 消息被处理 -- **THEN** current_status 等于 on +#### Scenario: restart 状态写入 +- **GIVEN** Kafka 消息中的 `CurrentStatus` 为 `restart` +- **WHEN** 消息被处理并写入数据库 +- **THEN** 普通入库链路保留 `restart`,G5 入库链路将其写入为 `3` + +#### Scenario: 其他状态保持原样 +- **GIVEN** Kafka 消息中的 `CurrentStatus` 为 `on` 或 `off` +- **WHEN** 消息被处理并写入数据库 +- **THEN** 系统按既有规则处理该状态值 ### Requirement: 空值保留 系统 SHALL 保留上游空值,不对字段进行补 0。 diff --git a/bls-onoffline-backend/spec/onoffline-spec.md b/bls-onoffline-backend/spec/onoffline-spec.md index 8115bef..a0341e5 100644 --- a/bls-onoffline-backend/spec/onoffline-spec.md +++ b/bls-onoffline-backend/spec/onoffline-spec.md @@ -41,10 +41,10 @@ G5库结构(双写,临时接入): 差异字段: - guid 为 int4,由库自己生成。 - record_source 固定为 CRICS。 - - current_status 为 int2,on映射为1,off映射为2,其余为0。 + - current_status 为 int2,on映射为1,off映射为2,restart映射为3,其余为0。 支持通过环境变量开关双写。 4. 数据处理规则 非重启数据:reboot_reason 为空或不存在,current_status 取 CurrentStatus -重启数据:reboot_reason 不为空,current_status 固定为 on +重启数据:reboot_reason 不为空时保留 Kafka 上游 current_status 值;若上游值为 restart,则入库标记为 restart,G5 库映射为 3 其余字段直接按 Kafka 原值落库,空值不补 0 diff --git a/bls-onoffline-backend/src/db/g5DatabaseManager.js b/bls-onoffline-backend/src/db/g5DatabaseManager.js index 99c8a01..2378128 100644 --- a/bls-onoffline-backend/src/db/g5DatabaseManager.js +++ b/bls-onoffline-backend/src/db/g5DatabaseManager.js @@ -18,6 +18,18 @@ const g5Columns = [ 'record_source' ]; +export const mapCurrentStatusToG5Code = (value) => { + if (value === 1 || value === 2 || value === 3) { + return value; + } + + const normalized = typeof value === 'string' ? value.trim().toLowerCase() : ''; + if (normalized === 'on') return 1; + if (normalized === 'off') return 2; + if (normalized === 'restart') return 3; + return 0; +}; + export class G5DatabaseManager { constructor(dbConfig) { if (!dbConfig.enabled) return; @@ -64,9 +76,7 @@ export class G5DatabaseManager { } if (column === 'current_status') { // current_status in G5 is int2 - if (row.current_status === 'on') return 1; - if (row.current_status === 'off') return 2; - return 0; + return mapCurrentStatusToG5Code(row.current_status); } return row[column] ?? null; }); diff --git a/bls-onoffline-backend/src/processor/index.js b/bls-onoffline-backend/src/processor/index.js index d8d4840..1b422f4 100644 --- a/bls-onoffline-backend/src/processor/index.js +++ b/bls-onoffline-backend/src/processor/index.js @@ -20,6 +20,16 @@ const normalizeText = (value, maxLength) => { return str; }; +const normalizeCurrentStatus = (value) => { + const currentStatus = normalizeText(value, 255); + if (currentStatus === null) return null; + const normalized = currentStatus.toLowerCase(); + if (normalized === 'on' || normalized === 'off' || normalized === 'restart') { + return normalized; + } + return currentStatus; +}; + export const buildRowsFromMessageValue = (value) => { const payload = parseKafkaPayload(value); return buildRowsFromPayload(payload); @@ -30,9 +40,7 @@ export const buildRowsFromPayload = (rawPayload) => { // Database limit is VARCHAR(255) const rebootReason = normalizeText(payload.RebootReason, 255); - const currentStatusRaw = normalizeText(payload.CurrentStatus, 255); - const hasRebootReason = rebootReason !== null && rebootReason !== ''; - const currentStatus = hasRebootReason ? 'on' : currentStatusRaw; + const currentStatus = normalizeCurrentStatus(payload.CurrentStatus); // Derive timestamp: UnixTime -> CurrentTime -> Date.now() let tsMs = payload.UnixTime; diff --git a/bls-onoffline-backend/tests/g5DatabaseManager.test.js b/bls-onoffline-backend/tests/g5DatabaseManager.test.js new file mode 100644 index 0000000..6eccd22 --- /dev/null +++ b/bls-onoffline-backend/tests/g5DatabaseManager.test.js @@ -0,0 +1,15 @@ +import { describe, it, expect } from 'vitest'; +import { mapCurrentStatusToG5Code } from '../src/db/g5DatabaseManager.js'; + +describe('G5 current_status mapping', () => { + it('maps on/off/restart to numeric codes', () => { + expect(mapCurrentStatusToG5Code('on')).toBe(1); + expect(mapCurrentStatusToG5Code('off')).toBe(2); + expect(mapCurrentStatusToG5Code('restart')).toBe(3); + }); + + it('returns 0 for unknown values', () => { + expect(mapCurrentStatusToG5Code('idle')).toBe(0); + expect(mapCurrentStatusToG5Code(null)).toBe(0); + }); +}); diff --git a/bls-onoffline-backend/tests/processor.test.js b/bls-onoffline-backend/tests/processor.test.js index a19fbfa..1f03740 100644 --- a/bls-onoffline-backend/tests/processor.test.js +++ b/bls-onoffline-backend/tests/processor.test.js @@ -26,13 +26,19 @@ describe('Processor Logic', () => { expect(rows[0].reboot_reason).toBeNull(); }); - it('should override current_status to on for reboot data', () => { - const rows = buildRowsFromPayload({ ...basePayload, CurrentStatus: 'off', RebootReason: '0x01' }); + it('should preserve restart current_status for reboot data', () => { + const rows = buildRowsFromPayload({ ...basePayload, CurrentStatus: 'restart', RebootReason: '0x01' }); expect(rows).toHaveLength(1); - expect(rows[0].current_status).toBe('on'); + expect(rows[0].current_status).toBe('restart'); expect(rows[0].reboot_reason).toBe('0x01'); }); + it('should preserve restart current_status for non-reboot data', () => { + const rows = buildRowsFromPayload({ ...basePayload, CurrentStatus: 'restart', RebootReason: null }); + expect(rows).toHaveLength(1); + expect(rows[0].current_status).toBe('restart'); + }); + it('should keep empty optional fields as empty strings', () => { const rows = buildRowsFromPayload({ ...basePayload,