feat: 初始化后端服务基础架构与核心组件

- 添加项目基础结构,包括 .gitignore、package.json、Docker 配置和环境变量示例
- 实现核心模块:Kafka 消费者、PostgreSQL 数据库管理器、Redis 客户端与错误队列
- 添加工具类:日志记录器、指标收集器、UUID 生成器
- 实现数据处理器,支持 0x36 上报和 0x0F 命令的解析与存储
- 添加数据库初始化脚本和分区管理,支持按时间范围分区
- 引入 Zod 数据验证和 Vitest 单元测试框架
- 提供完整的项目文档,包括数据库设计、Kafka 格式规范和 Redis 集成协议
This commit is contained in:
2026-01-30 11:05:00 +08:00
parent ec2b44b165
commit 86a1e79153
51 changed files with 5921 additions and 0 deletions

153
docs/kafka_format.md Normal file
View File

@@ -0,0 +1,153 @@
# Kafka 推送格式与数据拆分规范
本文档定义了上游服务向 Kafka 推送消息的标准 JSON 格式。
**核心变更**:上游服务负责将原始报文解析为结构化的 JSON 对象。对于包含多个设备状态或故障信息的命令(如 `0x36`),上游必须将其转换为 **JSON 数组**,后端服务直接遍历这些数组进行入库,不再依赖对 `udp_raw` 的二次解析。
## 1. Kafka 基础信息
* **Topic**: `blwlog4Nodejs-rcu-action-topic`
* **分区数**: 6
* **消息格式**: JSON String
## 2. 消息结构定义 (Schema)
JSON 消息由 **Header 信息****业务列表数据** 组成。
### 2.1 顶层字段 (Header & 统计)
| 字段名 | 类型 | 必填 | 说明 |
| :--- | :--- | :--- | :--- |
| **ts_ms** | Number | **是** | 日志产生时间戳 (Key1) |
| **hotel_id** | Number | **是** | 酒店 ID |
| **room_id** | String | **是** | 房间 ID |
| **device_id** | String | **是** | 设备 ID |
| **direction** | String | **是** | "上报" 或 "下发" |
| **cmd_word** | String | **是** | 命令字 (如 "0x36", "0x0F") |
| **frame_id** | Number | **是** | 通讯帧号 |
| **udp_raw** | String | **是** | UDP 原始报文 (作为备份/校验) |
| **sys_lock_status** | Number | 否 | 系统锁状态 (0:未锁, 1:锁定) |
| **report_count** | Number | 否 | 上报设备数量 (对应 device_list 长度) |
| **fault_count** | Number | 否 | 故障设备数量 (对应 fault_list 长度) |
| **action_type** | String | 否 | 行为类型 (建议上游预填,或后端默认处理) |
| **device_list** | Array | 否 | **设备状态列表** (结构见 2.2) |
| **fault_list** | Array | 否 | **设备故障列表** (结构见 2.3) |
| **control_list** | Array | 否 | **控制参数列表** (用于 0x0F) |
### 2.2 设备状态对象 (Item in `device_list`)
对应 `0x36` 命令中的 P8~P13。
| JSON 字段名 | DB 映射字段 | 类型 | 说明 |
| :--- | :--- | :--- | :--- |
| **dev_type** | `dev_type` | Number | 设备类型 |
| **dev_addr** | `dev_addr` | Number | 设备地址 |
| **dev_loop** | `dev_loop` | Number | 设备回路 |
| **dev_data** | `dev_data` | Number | 设备状态 |
### 2.3 设备故障对象 (Item in `fault_list`)
对应 `0x36` 命令中的 P15~P20。
| JSON 字段名 | DB 映射字段 | 类型 | 说明 |
| :--- | :--- | :--- | :--- |
| **dev_type** | `dev_type` | Number | 故障设备类型 (复用 dev_type) |
| **dev_addr** | `dev_addr` | Number | 故障设备地址 (复用 dev_addr) |
| **dev_loop** | `dev_loop` | Number | 故障设备回路 (复用 dev_loop) |
| **error_type** | `error_type` | Number | 故障类型 |
| **error_data** | `error_data` | Number | 故障内容 |
### 2.4 控制参数对象 (Item in `control_list`)
对应 `0x0F` 下发命令。
| JSON 字段名 | DB 映射字段 | 类型 | 说明 |
| :--- | :--- | :--- | :--- |
| **dev_type** | `dev_type` | Number | 控制设备类型 |
| **dev_addr** | `dev_addr` | Number | 控制设备地址 |
| **loop** | `dev_loop` | Number | 控制设备的回路地址 (复用 dev_loop) |
| **type_l** | `type_l` | Number | 执行方式 |
| **type_h** | `type_h` | Number | 执行内容 |
---
## 3. 后端入库逻辑
后端服务接收到 JSON 后,逻辑简化为:
1. **遍历 `device_list`**: 为数组中每个对象生成一条 DB 记录。
* 映射:`dev_type` -> `dev_type`, `dev_addr` -> `dev_addr`, `dev_loop` -> `dev_loop`, `dev_data` -> `dev_data`
* `action_type`: "36上报"。
2. **遍历 `fault_list`**: 为数组中每个对象生成一条 DB 记录。
* 映射:`dev_type` -> `dev_type`, `dev_addr` -> `dev_addr`, `dev_loop` -> `dev_loop`, `error_type` -> `error_type`, `error_data` -> `error_data`
* `action_type`: "36上报"。
3. **遍历 `control_list`**: 为数组中每个对象生成一条 DB 记录。
* 映射:`dev_type` -> `dev_type`, `dev_addr` -> `dev_addr`, `loop` -> `dev_loop`, `type_l` -> `type_l`, `type_h` -> `type_h`
* `action_type`: "0F下发"。
---
## 4. 参考 JSON 示例
### 4.1 0x36 混合上报 (2个设备状态 + 1个故障)
```json
{
"ts_ms": 1706428800123,
"hotel_id": 1001,
"room_id": "8001",
"device_id": "dev_001",
"direction": "上报",
"cmd_word": "0x36",
"frame_id": 1001,
"udp_raw": "3601...",
"sys_lock_status": 1,
"report_count": 2,
"fault_count": 1,
"device_list": [
{
"dev_type": 1,
"dev_addr": 10,
"dev_loop": 1,
"dev_data": 100
},
{
"dev_type": 1,
"dev_addr": 11,
"dev_loop": 2,
"dev_data": 0
}
],
"fault_list": [
{
"dev_type": 1,
"dev_addr": 10,
"dev_loop": 1,
"error_type": 1,
"error_data": 1
}
]
}
```
### 4.2 0x0F 下发控制 (包含多个控制指令)
```json
{
"ts_ms": 1706428805000,
"hotel_id": 1001,
"room_id": "8001",
"direction": "下发",
"cmd_word": "0x0F",
"frame_id": 1002,
"udp_raw": "0F...",
"action_type": 2,
"control_list": [
{
"dev_type": 1,
"dev_addr": 10,
"loop": 1,
"type_l": 0,
"type_h": 1
}
],
"details": {
"full_control_data": "..."
}
}
```

78
docs/project.md Normal file
View File

@@ -0,0 +1,78 @@
1. 0x36 上报数据格式
-------------------------------------------------------------------
P0 系统锁状态
0x00未锁定
0x01锁定
P1~P6 保留
P7 上报设备数量
P8~P13 设备参数描述一个设备参数固定为6Byte,具体格式如下:
DevType设备类型 1Byte
DevAddr设备地址 1Byte
DevLoop设备回路 2Byte
DevData设备状态 2Byte
P14 上报设备故障数量
P15~P20 上报设备故障数量
DevType设备类型 1Byte
DevAddr设备地址 1Byte
DevLoop设备回路 2Byte
ErrorType故障类型 1Byte
ErrorData故障内容 1Byte
-------------------------------------------------------------------
上报设备的状态具体参数需要查看备数*6Byte如果故障设备数为0则没有设备故障参数。
故障参数解析:
故障类型 故障内容
0x01 0:在线 1:离线
0x02 0~100电量
0x03 电流(10mA)
0x04 1901故障检测次数
0x05 设备回路故障具体设备,不同类型的设备上报状态的描述是不同的。
具体有多少设备状态需要上报,设备参数所占用的字节=设备数*6Byte
同样设备故障参数所占用的字节=设
2. 0x0F 下发数据格式
-------------------------------------------------------------------
P0控制设备总数
P1 ~P495设备控制参数描述一个设备控制参数固定为6Byte具体格式如下
DevType控制设备类型 - 1Byte
DevAddr控制设备地址 - 1Byte
Loop控制设备的回路地址 - 2Byte
Type控制设备的输出类型 - 2Byte
Type_L执行方式
Type_H执行内容
-------------------------------------------------------------------
该命令一般用于服务下发控制数据
3. 0x0F 上报数据格式
ACK (待补充)
4. 数据表结构
不可为空字段:
日志产生时间ts_ms
入库时间write_ts_ms
酒店index
房间index
方向(上传/下发index
命令字index
通讯帧号
UDP消息原文
记录行为类型ACK、下发控制、主动控制、设备回路状态index通过设备类型区分
可为空字段:
系统锁状态
本次上报数量
DevType设备类型 1Byte
DevAddr设备地址 1Byte
DevLoop设备回路 2Byte
DevData设备状态 2Byte
本次故障数量
DevType设备类型 1Byte
DevAddr设备地址 1Byte
DevLoop设备回路 2Byte
ErrorType故障类型 1Byte
ErrorData故障内容 1Byte
一条命令可能会有多条状态,每个状态生成一条记录,通过命令字和帧号来做串联。
一条UDP通讯可能对照多条数据库记录
5. 队列结构
队列分区数6
Topicblwlog4Nodejs-rcu-action-topic

92
docs/readme.md Normal file
View File

@@ -0,0 +1,92 @@
# BLS RCU Action Server 开发文档
## 1. 项目概述
本项目旨在构建一个后端服务,负责从 Kafka 接收 RCU客房控制单元的通讯日志解析数据结构并将其持久化存储到 PostgreSQL 数据库中。
核心目标是将不同类型的通讯协议数据0x36 上报、0x0F 下发、ACK统一存储并针对不定长数据结构采用 JSON 格式进行灵活保存。
## 2. 系统架构
**数据流向**: `MCU/Server` (产生数据) -> `Kafka` (消息队列) -> `Action Server` (消费 & 解析) -> `PostgreSQL` (存储)
- **Kafka Topic**: `blwlog4Nodejs-rcu-action-topic` (分区数: 6)
- **数据库**: PostgreSQL
## 3. 数据库设计
数据库名:`bls_rcu_action`
模式名: `rcu_action`
### 3.1 表结构设计
表名: `rcu_action_events`
| 字段名 | 类型 | 说明 | 备注 |
| :--- | :--- | :--- | :--- |
| **guid** | VARCHAR(32) | 主键 (Key2) | 32位无符号UUID |
| **ts_ms** | BIGINT | 日志产生时间 (Key1) | **必填** (L49) |
| **write_ts_ms** | BIGINT | 入库时间 | **必填** (L50) |
| **hotel_id** | INTEGER | 酒店ID | **必填** (L51) Index |
| **room_id** | VARCHAR(32) | 房间ID | **必填** (L52) Index |
| **device_id** | VARCHAR(32) | 设备ID | **必填** (新增) Index |
| **direction** | VARCHAR(10) | 数据方向 | **必填** (L53) "上报"/"下发" Index |
| **cmd_word** | VARCHAR(10) | 命令字 | **必填** (L54) 如 "0x36", "0x0F" Index |
| **frame_id** | INTEGER | 通讯帧号 | **必填** (L55) 用于串联命令与状态 |
| **udp_raw** | TEXT | UDP消息原文 | **必填** (L56) Hex字符串 |
| **action_type** | VARCHAR(20) | 记录行为类型 | **必填** (L57) Index |
| **sys_lock_status** | SMALLINT | 系统锁状态 | (L59) 可空 |
| **report_count** | SMALLINT | 本次上报数量 | (L60) 可空 |
| **dev_type** | SMALLINT | 设备类型 | (L61) 可空 (统一字段) |
| **dev_addr** | SMALLINT | 设备地址 | (L62) 可空 (统一字段) |
| **dev_loop** | INTEGER | 设备回路 | (L63) 可空 (统一字段) |
| **dev_data** | INTEGER | 设备状态 | (L64) 可空 (0x36状态) |
| **fault_count** | SMALLINT | 本次故障数量 | (L65) 可空 |
| **error_type** | SMALLINT | 故障类型 | (L69) 可空 (0x36故障) |
| **error_data** | SMALLINT | 故障内容 | (L70) 可空 (0x36故障) |
| **type_l** | SMALLINT | 执行方式 | 可空 (0x0F下发) |
| **type_h** | SMALLINT | 执行内容 | 可空 (0x0F下发) |
| **details** | JSONB | 业务详情数据 | 存储不定长设备列表、故障信息等 |
| **extra** | JSONB | 扩展信息 | 存储通讯原文等扩展数据 |
**主键定义**: `(ts_ms, guid)`
**索引定义**: 备注带index的字段为需要索引的字段用于提高查询效率。
### 3.2 字典定义
**Action Type (记录行为类型)**:
- `"0FACK"`: ACK (应答)
- `"0F下发"`: 下发控制 (0x0F 下发)
- `"36上报"`: 设备回路状态 (0x36 上报)
**Direction (方向)**:
- `"上报"`: Upload
- `"下发"`: Download
## 4. 数据解析与存储映射
### 4.1 0x36 上报数据 (设备状态/故障)
* **命令字**: "0x36"
* **拆分逻辑**: 根据 `project.md` 说明,一条 UDP 可能包含多个设备状态,需拆分为多条记录入库,每条记录填充 `dev_type` 等字段。同时将完整的不定长列表存入 `details` 以便追溯。
* **Action Type**: 4 (设备回路状态)
**Mapping**:
- `sys_lock_status` -> P0
- `report_count` -> P7
- `dev_type`, `dev_addr`... -> 从 P8~P13 循环解析,每组生成一条 DB 记录
- `details`: `{ "all_devices": [...], "all_faults": [...] }`
- `extra`: `{ "raw_hex": "..." }`
### 4.2 0x0F 下发数据 (控制指令)
* **命令字**: "0x0F"
* **Action Type**: 2 (下发控制)
* **存储逻辑**: 主要是控制指令,通常作为单条记录存储。若包含多个设备控制,可选择存第一条到字段,或仅存入 JSON。根据 "0x0F不定长存为JSON" 的需求,主要依赖 `details` 字段。
**Mapping**:
- `details`: `{ "control_params": [ ... ] }`
- `extra`: `{ "raw_hex": "..." }`
### 4.3 0x0F 上报数据 (ACK)
* **命令字**: "0x0F"
* **Action Type**: "0FACK" (ACK)
**Mapping**:
- `details`: `{ "ack_code": "0x00" }`
- `extra`: `{ "raw_hex": "..." }`

View File

@@ -0,0 +1,273 @@
# Redis 对接协议(供外部项目 AI 生成代码使用)
本文档定义"外部项目 ↔ BLS Project Console"之间通过 Redis 交互的 **Key 命名、数据类型、写入方式、读取方式与数据格式**
注:本仓库对外暴露的 Redis 连接信息如下(供对方直接连接以写入心跳/日志):
- 地址:`10.8.8.109`
- 端口:默认 `6379`
- 密码:无(空)
- 数据库:固定 `15`
示例(环境变量):
```
REDIS_HOST=10.8.8.109
REDIS_PORT=6379
REDIS_PASSWORD=
REDIS_DB=15
```
示例redis-cli
```
redis-cli -h 10.8.8.109 -p 6379 -n 15
```
> 约束:每个需要关联本控制台的外部项目,必须在同一个 RedisDB15
> - 更新 `项目心跳`(项目列表 + 心跳信息)
> - 追加 `${projectName}_项目控制台`(日志队列)
> - 命令下发为 HTTP API 调用(不通过 Redis 下发命令)
## 1. 命名约定
令:
- `projectName`:外部项目名称(建议只用字母数字下划线 `A-Za-z0-9_`;如使用中文也可,但需保证统一且 UTF-8
固定后缀:
- 控制台:`${projectName}_项目控制台`
示例projectName = `订单系统`
- `订单系统_项目控制台`
## 2. 外部项目需要写入的 2 个 Key
说明:当前控制台左侧“项目选择列表”只读取 `项目心跳`LIST。因此外部项目必须维护该 Key否则项目不会出现在列表中。
### 2.1 `项目心跳`
- Redis 数据类型:**LIST**
- 写入方式(推荐 FIFO`RPUSH 项目心跳 <json>`
- value每个列表元素为“项目心跳记录”的 JSON 字符串
示例(与当前代码读取一致;下面示例表示“逻辑结构”):
```json
[
{
"projectName": "BLS主机心跳日志",
"apiBaseUrl": "http://127.0.0.1:3000",
"lastActiveAt": 1768566165572
}
]
```
示例Redis 写入命令):
```
RPUSH 项目心跳 "{\"projectName\":\"BLS主机心跳日志\",\"apiBaseUrl\":\"http://127.0.0.1:3000\",\"lastActiveAt\":1768566165572}"
```
字段说明(每条心跳记录):
- `projectName`:项目名称(用于拼接日志 Key`${projectName}_项目控制台`
- `apiBaseUrl`:目标项目对外提供的 API 地址(基地址,后端将基于它拼接 `apiName`
- `lastActiveAt`:活跃时间戳(毫秒)。建议每 **3 秒**刷新一次。
在线/离线判定BLS Project Console 使用):
-`now - lastActiveAt > 10_000ms`,则认为该应用 **离线**
- 否则认为 **在线**
建议:
- `lastActiveAt` 使用 `Date.now()` 生成(毫秒)
- 建议对 `项目心跳` 做长度控制(可选):例如每次写入后执行 `LTRIM 项目心跳 -2000 -1` 保留最近 2000 条
去重提示:
- `项目心跳` 为 LIST 时,外部项目周期性 `RPUSH` 会产生多条重复记录
- BLS Project Console 后端会按 `projectName` 去重,保留 `lastActiveAt` 最新的一条作为项目状态
### 2.2 `${projectName}_项目控制台`
- Redis 数据类型:**LIST**(作为项目向控制台追加的"消息队列/日志队列"
- 写入方式(推荐 FIFO`RPUSH ${projectName}_项目控制台 <json>`
value推荐格式一条 JSON 字符串,表示"错误/调试信息"或日志记录。
推荐 JSON Schema字段尽量保持稳定便于控制台解析
```json
{
"timestamp": "2026-01-12T12:34:56.789Z",
"level": "info",
"message": "连接成功",
"metadata": {
"module": "redis",
"host": "127.0.0.1"
}
}
```
字段说明:
- `timestamp`ISO-8601 时间字符串
- `level`:建议取值 `info|warn|error|debug`(小写)
- `message`:日志文本
- `metadata`:可选对象(附加信息)
## 3. 项目列表管理(重要)
### 3.1 迁移机制(仅用于旧数据导入)
BLS Project Console 支持从旧格式自动迁移到新格式:
- **旧格式**:每个项目独立的心跳键 `${projectName}_项目心跳`
- **新格式**:统一的项目列表键 `项目心跳`LIST 类型,每个元素为 JSON 字符串)
迁移过程:
1. 扫描所有 `${projectName}_项目心跳`
2. 提取 `apiBaseUrl``lastActiveAt` 字段
3. 写入到 `项目心跳`LIST
4. 可选:删除旧键
重要说明(与当前代码实现一致):
- 迁移不会自动后台执行,需要通过接口触发:`POST /api/projects/migrate`
- 迁移的目的只是“从历史 `${projectName}_项目心跳` 导入一次,生成 `项目心跳` 列表”
- 迁移完成后,如果外部项目仍然只更新旧 Key`项目心跳` 不会自动跟随更新;要想实时更新,外部项目必须直接更新 `项目心跳`
### 3.2 新格式项目列表结构
`项目心跳` 为 LIST列表元素为 JSON 字符串;其“逻辑结构”如下:
```json
[
{
"projectName": "订单系统",
"apiBaseUrl": "http://127.0.0.1:4001",
"lastActiveAt": 1760000000000
},
{
"projectName": "用户服务",
"apiBaseUrl": "http://127.0.0.1:4002",
"lastActiveAt": 1760000000001
}
]
```
### 3.3 外部项目对接建议
外部项目应当:
1. 定期写入 `项目心跳`RPUSH 自己的心跳记录;允许产生多条记录,由控制台按 projectName 去重)
2. 追加 `${projectName}_项目控制台` 日志
## 4. 命令下发方式HTTP API 控制)
控制台不再通过 Redis 写入控制指令队列;改为由 BLS Project Console 后端根据目标项目心跳里的 `apiBaseUrl` 直接调用目标项目 HTTP API。
### 4.1 控制台输入格式
一行文本按空格拆分:
- 第一个 token`apiName`(接口名/路径片段)
- 剩余 token参数列表字符串数组
示例:
- `reload`
- `reload force`
- `user/refreshCache tenantA`
### 4.2 目标项目需要提供的 API
后端默认使用 `POST` 调用:
- `POST {apiBaseUrl}/{apiName}`
请求体JSON示例
```json
{
"commandId": "cmd-1700000000000-abc123",
"timestamp": "2026-01-13T00:00:00.000Z",
"source": "BLS Project Console",
"apiName": "reload",
"args": ["force"],
"argsText": "force"
}
```
字段说明:
- `commandId`:唯一命令标识符
- `timestamp`命令发送时间ISO-8601 格式)
- `source`:命令来源标识
- `apiName`API 接口名
- `args`:参数数组
- `argsText`:参数文本(空格连接)
返回建议:
- 2xx 表示成功
- 非 2xx 表示失败(控制台会展示 upstreamStatus 与部分返回内容)
### 4.3 在线/离线判定
发送命令前,系统会检查项目在线状态:
-`项目心跳` 列表读取 `lastActiveAt`
-`now - lastActiveAt > 10_000ms`,则认为该应用 **离线**,拒绝发送命令
- 否则认为 **在线**,允许发送命令
## 5. 与本项目代码的对应关系
- **后端 `/api/projects`**:只从 `项目心跳`LIST读取项目列表返回所有项目及其在线状态
- **后端 `/api/commands`**:从 `项目心跳` 中查找目标项目的 `apiBaseUrl/lastActiveAt`,在线时调用目标项目 API
- **后端 `/api/logs`**:读取 `${projectName}_项目控制台`LIST并基于 `项目心跳` 中该项目的 `lastActiveAt` 计算在线/离线与 API 地址信息
## 6. 兼容与错误处理建议
- JSON 解析失败:外部项目应记录错误,并丢弃该条消息(避免死循环阻塞消费)。
- 消息过长:建议控制单条消息大小(例如 < 64KB
- 字符编码:统一 UTF-8。
- 心跳超时:建议外部项目每 3 秒更新一次心跳,避免被误判为离线。
## 7. 数据迁移工具(旧数据导入)
如果需要从旧格式迁移到新格式,可使用以下 API
```bash
POST /api/projects/migrate
Content-Type: application/json
{
"deleteOldKeys": false,
"dryRun": false
}
```
参数说明:
- `deleteOldKeys`:是否删除旧格式键(默认 false
- `dryRun`:是否仅模拟运行(默认 false
返回示例:
```json
{
"success": true,
"message": "数据迁移完成",
"migrated": 2,
"projects": [...],
"listKey": "项目心跳",
"deleteOldKeys": false
}
```

45
docs/测试报告.md Normal file
View File

@@ -0,0 +1,45 @@
# 测试报告
## 基本信息
- 运行时间: 2026-01-29
- 运行方式: 控制台启动 `npm run dev`,运行约 60 秒后 Ctrl + C 终止
- 测试目标: 验证 Kafka 消费与入库链路,定位无入库原因
## 控制台关键日志
```
{"level":"error","message":"Message processing failed","timestamp":1769734880590,"context":{"error":"[\n {\n \"expected\": \"number\",\n \"code\": \"invalid_type\",\n \"path\": [\n \"hotel_id\"\n ],\n \"message\": \"Invalid input: expected number, received string\"\n }\n]","type":"PARSE_ERROR","stack":"ZodError: ...","rawPayload":"{\"ts_ms\":1769692878011,\"hotel_id\":\"2147\",\"room_id\":\"8209\",\"device_id\":\"099008129081\",\"direction\":\"上报\",\"cmd_word\":\"36\",\"frame_id\":52496,...}","validationIssues":[{"expected":"number","code":"invalid_type","path":["hotel_id"],"message":"Invalid input: expected number, received string"}]}}
```
## 结论
- 数据未入库的直接原因: Kafka 消息在解析阶段触发 Zod 校验失败,`hotel_id` 为字符串类型而非文档要求的 Number导致 `PARSE_ERROR`,数据库插入流程未执行。
## 与文档格式的一致性检查
对照 [kafka_format.md](file:///e:/Project_Class/BLS/Web_BLS_RCUAction_Server/docs/kafka_format.md):
- `hotel_id`: 文档要求 Number但实测为字符串 (示例: `"2147"`),不一致。
- `cmd_word`: 文档要求 `"0x36"`/`"0x0F"`,实测为 `"36"`,不一致。
- `control_list`: 文档要求 Array/可选,但实测为 `null`,不一致。
- 其余关键字段如 `ts_ms`, `room_id`, `device_id`, `direction`, `udp_raw` 均存在。
## 已增强的控制台错误输出
为了便于定位异常,以下模块已经增加详细错误输出到 PowerShell 控制台:
- Kafka 处理异常: 输出 `type`, `stack`, `rawPayload`, `validationIssues`, `dbContext`
- 数据库插入异常: 输出 `schema`, `table`, `rowsLength`
- Redis 入队与重试异常: 输出详细错误信息
相关改动文件:
- [index.js](file:///e:/Project_Class/BLS/Web_BLS_RCUAction_Server/bls-rcu-action-backend/src/index.js)
- [databaseManager.js](file:///e:/Project_Class/BLS/Web_BLS_RCUAction_Server/bls-rcu-action-backend/src/db/databaseManager.js)
- [errorQueue.js](file:///e:/Project_Class/BLS/Web_BLS_RCUAction_Server/bls-rcu-action-backend/src/redis/errorQueue.js)
## 建议修改方向
以下为解决无入库问题的可选方案,由你决定是否执行:
1. 上游严格按文档输出:
- `hotel_id` 改为 Number
- `cmd_word` 改为 `"0x36"` / `"0x0F"`
- `control_list``[]` 或省略字段,避免 `null`
2. 下游放宽校验并做类型转换:
-`hotel_id` 支持字符串并转换为 Number
- 继续兼容 `cmd_word = "36"` 的写法
- `control_list/device_list/fault_list` 接受 `null` 并转为空数组
当前代码已兼容 `cmd_word="36"``control_list=null`,但 `hotel_id` 仍按文档严格要求 Number。