# 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
}
```
