feat: 初始化后端服务基础架构与核心组件
- 添加项目基础结构,包括 .gitignore、package.json、Docker 配置和环境变量示例 - 实现核心模块:Kafka 消费者、PostgreSQL 数据库管理器、Redis 客户端与错误队列 - 添加工具类:日志记录器、指标收集器、UUID 生成器 - 实现数据处理器,支持 0x36 上报和 0x0F 命令的解析与存储 - 添加数据库初始化脚本和分区管理,支持按时间范围分区 - 引入 Zod 数据验证和 Vitest 单元测试框架 - 提供完整的项目文档,包括数据库设计、Kafka 格式规范和 Redis 集成协议
This commit is contained in:
153
docs/kafka_format.md
Normal file
153
docs/kafka_format.md
Normal 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
78
docs/project.md
Normal 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
|
||||
Topic:blwlog4Nodejs-rcu-action-topic
|
||||
92
docs/readme.md
Normal file
92
docs/readme.md
Normal 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": "..." }`
|
||||
|
||||
273
docs/redis-integration-protocol.md
Normal file
273
docs/redis-integration-protocol.md
Normal 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
|
||||
```
|
||||
|
||||
> 约束:每个需要关联本控制台的外部项目,必须在同一个 Redis(DB15)中:
|
||||
|
||||
> - 更新 `项目心跳`(项目列表 + 心跳信息)
|
||||
> - 追加 `${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
45
docs/测试报告.md
Normal 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。
|
||||
Reference in New Issue
Block a user