feat: 重构项目心跳数据结构并实现项目列表API

- 新增统一项目列表Redis键和迁移工具
- 实现GET /api/projects端点获取项目列表
- 实现POST /api/projects/migrate端点支持数据迁移
- 更新前端ProjectSelector组件使用真实项目数据
- 扩展projectStore状态管理
- 更新相关文档和OpenSpec规范
- 添加测试用例验证新功能
This commit is contained in:
2026-01-13 19:45:05 +08:00
parent 19e65d78dc
commit 282f7268ed
66 changed files with 4378 additions and 456 deletions

View File

@@ -0,0 +1,65 @@
## Context
命令下发从 Redis 队列模式迁移为 HTTP API 调用模式。
约束:
- 输入约定来自 UI用户输入一行文本按空格拆分。
- 需要通过 `targetProjectName` 将命令路由到目标项目的 `baseUrl`
## Goals / Non-Goals
- Goals
- 控制台通过后端统一出站调用目标项目 API避免浏览器跨域/鉴权差异)
- 统一请求格式,便于目标项目实现与观测
- 明确失败行为(目标项目不可达/超时/非 2xx
- Non-Goals
- 不定义目标项目内部如何执行命令
- 不在本次变更中引入鉴权体系(如需后续可另开 change
## Decision: baseUrl mapping via env JSON
改为由目标项目的 Redis 心跳模块提供 `apiBaseUrl`
### Heartbeat key
- Redis key: `${projectName}_项目心跳`
- Redis type: STRING
- Value: JSON string
示例:
```json
{
"apiBaseUrl": "http://127.0.0.1:4001",
"lastActiveAt": 1760000000000
}
```
### Liveness rule
- 目标项目应每 3 秒刷新一次 `lastActiveAt`(毫秒时间戳)
- 若后端检测到 `now - lastActiveAt > 10_000`,则视为离线并拒绝下发
## Decision: request shape
- UI 输入:`<apiName> <arg1> <arg2> ...`
- 后端对目标项目调用:默认 `POST ${baseUrl}/${apiName}`
- bodyJSON
```json
{
"commandId": "cmd-...",
"timestamp": "ISO-8601",
"source": "BLS Project Console",
"apiName": "reload",
"args": ["a", "b"],
"argsText": "a b"
}
```
## Timeouts / retries
- 单次请求设置合理超时(例如 5s
- 默认不重试(避免重复执行产生副作用);如需重试需目标项目提供幂等语义。

View File

@@ -0,0 +1,28 @@
# Change: Command control via HTTP API (remove Redis control queue)
## Why
当前命令下发使用 Redis LIST `${targetProjectName}_控制` 作为控制队列;现需改为通过 HTTP API 直接调用目标项目,从而简化接入与减少 Redis 在“控制通道”上的耦合。
## What Changes
- **BREAKING**:命令下发不再写入 Redis `${projectName}_控制` 队列;目标项目不再需要读取控制指令 Key。
- 控制台输入规则调整:发送前按空格拆分,首个 token 为 `apiName`(接口名/路径片段),其余为参数。
- 后端 `/api/commands` 改为:通过 Redis 心跳信息解析目标项目 `apiBaseUrl`,再调用 `${apiBaseUrl}/${apiName}`(默认 POST
- 目标项目需在 Redis 写入心跳:包含 `apiBaseUrl` 与活跃时间戳(建议 3 秒刷新);后端若连续 10 秒未更新则视为离线。
- 更新对接文档:`docs/redis-integration-protocol.md` 去除“读取控制指令队列”的要求,仅保留“写状态+写控制台日志”两类 Key并新增“目标项目需暴露 HTTP 控制 API”的约定。
- 调整对接文档:使用“心跳”(包含在线判定与 API 地址)作为唯一在线来源。
## Impact
- Affected specs: command
- Affected code:
- src/backend/routes/commands.js
- src/frontend/components/Console.vue
- .env.example新增 API 调用相关可选变量)
- docs/redis-integration-protocol.md
## Migration
- 目标项目:停止读取 `${projectName}_控制`;改为提供 HTTP 接口(由 `apiName` 路由到对应动作)。
- 控制台:发送命令格式从“整行命令”迁移到“接口名 + 参数”。

View File

@@ -0,0 +1,66 @@
## MODIFIED Requirements
### Requirement: Command Sending
The system SHALL send console commands to a target project via HTTP API calls (not via Redis control queues).
#### Scenario: Sending a command to a target project API
- **WHEN** the user enters a command line in the console
- **AND** the command line contains at least one token
- **THEN** the system SHALL treat the first token as `apiName`
- **AND** the system SHALL treat remaining tokens as arguments
- **AND** the backend SHALL invoke the target project's HTTP endpoint derived from `apiName`
- **AND** the user SHALL receive a success confirmation if the target project returns a successful response
#### Scenario: Rejecting an empty command
- **WHEN** the user tries to send an empty command line
- **THEN** the system SHALL display an error message
- **AND** the backend SHALL NOT attempt any outbound call
### Requirement: Target Routing
The system SHALL route outbound calls by `targetProjectName` using the target project's Redis heartbeat info.
#### Scenario: Target project is not configured
- **WHEN** the user sends a command to a target project
- **AND** the backend cannot resolve `apiBaseUrl` from the target project's heartbeat
- **THEN** the backend SHALL return an error response
- **AND** the outbound call SHALL NOT be attempted
#### Scenario: Target project is offline
- **WHEN** the user sends a command to a target project
- **AND** the last heartbeat update is older than 10 seconds
- **THEN** the backend SHALL treat the target project as offline
- **AND** the outbound call SHALL NOT be attempted
### Requirement: Error Handling
The system SHALL handle target API failures gracefully.
#### Scenario: Target API is unreachable or times out
- **WHEN** the backend invokes the target project API
- **AND** the target project cannot be reached or times out
- **THEN** the backend SHALL return a failure response to the frontend
#### Scenario: Target API returns a non-success status
- **WHEN** the target project returns a non-2xx HTTP status
- **THEN** the backend SHALL return a failure response
- **AND** it SHOULD include the upstream status and message for debugging
## REMOVED Requirements
### Requirement: Command Sending to Redis
**Reason**: Command control channel is migrated to HTTP API calls.
**Migration**: Target projects must expose HTTP endpoints; they no longer consume `${projectName}_控制`.
### Requirement: Command Response Handling from Redis
**Reason**: Responses are returned directly via HTTP responses.
**Migration**: Any additional async responses should be written to the project's console/log channel (e.g., Redis console log LIST) if needed.

View File

@@ -0,0 +1,27 @@
## 1. OpenSpec
- [ ] 1.1 Add command spec delta for HTTP control (remove Redis control queue)
- [ ] 1.2 Add design notes for baseUrl mapping + request format
- [ ] 1.3 Run `openspec validate update-command-control-api --strict`
## 2. Backend
- [ ] 2.1 Add Redis heartbeat parsing (apiBaseUrl + lastActiveAt) and offline detection (10s)
- [ ] 2.2 Update POST `/api/commands` to parse `apiName` + args and call target HTTP API using heartbeat apiBaseUrl
- [ ] 2.3 Return structured result to frontend (success/failed + upstream status/body)
## 3. Frontend
- [ ] 3.1 Update Console send behavior + UI copy to reflect API control
- [ ] 3.2 Validate input format (needs at least apiName)
## 4. Docs
- [ ] 4.1 Update docs/redis-integration-protocol.md: remove control queue; add HTTP control API section
- [ ] 4.2 Update .env.example with new optional variables (timeouts/offline threshold)
## 5. Verify
- [ ] 5.1 Run `npm run lint`
- [ ] 5.2 Run `npm run build`
- [ ] 5.3 Smoke test backend endpoints