1
0

refactor: 移除 success 字段,简化为 matched 单层判定模型

This commit is contained in:
2026-05-11 13:12:55 +08:00
parent 548b44d28e
commit 35ba56888b
93 changed files with 3893 additions and 103 deletions

View File

@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-05-09

View File

@@ -0,0 +1,130 @@
## Context
Gateway Checker 当前是一个 Bun + React 全栈脚手架,仅包含 demo 验证逻辑(`/api/demo` 端点 + 前端展示连接状态)。项目已有完整的开发、构建、打包、测试链路。需要将其转化为一个可用的 HTTP 拨测工具。
现有基础设施:
- Bun 后端:路由框架(`createFetchHandler`)、服务启动(`startServer`)、运行时配置解析(`readRuntimeConfig`
- React 前端Vite + React + TypeScript开发期通过 Vite proxy 转发 `/api/*`
- 构建Vite 前端构建 + Bun 单 executable 打包
- 测试Bun test + smoke test
## Goals / Non-Goals
**Goals:**
- 提供完整的 HTTP 拨测能力YAML 配置 → 定时并发拨测 → 结果持久化 → 可视化展示
- 支持灵活的拨测配置per-target interval、自定义 method/header/body、expect 校验
- 前端 Dashboard 实时展示:总览统计、目标状态列表、历史记录、延迟趋势图
- 保持现有项目架构风格和构建打包链路
- 零外部运行时依赖新增(仅前端 recharts
**Non-Goals:**
- 不做告警通知(邮件/短信/Webhook仅 Dashboard 展示
- 不做数据自动清理/过期策略,保留全部历史记录
- 不做 SSE/WebSocket 实时推送,用轮询即可
- 不做拨测目标动态增删(需修改 YAML 后重启)
- 不做认证/鉴权
- 不做分布式/集群部署
## Decisions
### 1. 配置管理YAML 统一配置 + 单 CLI 参数
**选择**所有配置server、数据目录、拨测默认值、目标列表统一到 YAML 文件CLI 只接受一个参数即配置文件路径。
**替代方案**
- CLI 参数 + 环境变量覆盖部分配置 → 配置分散,维护成本高
- TOML 格式 → Bun 无内置支持,需引入依赖
**理由**
- 用户明确要求"配置统一到 YAML 文件"
- `Bun.YAML.parse()` 内置支持,零依赖
- 单参数 CLI 最简洁:`./gateway-checker ./probes.yaml`
### 2. 数据存储SQLitebun:sqlite
**选择**:使用 Bun 内置 `bun:sqlite` 模块WAL 模式运行。
**替代方案**
- JSONL 文件追加 → 聚合查询需全表扫描,趋势计算复杂
- 外部 SQLite 库better-sqlite3→ bun:sqlite 已内置,无需引入
**理由**
- 趋势分析需要 `AVG(latency) GROUP BY hour` 等聚合查询SQL 原生支持
- bun:sqlite 是 Bun 内置模块,不违反"不引入新依赖"约束
- WAL 模式支持并发读写
-`.db` 文件,便于管理
### 3. 调度模型:按 interval 分组 + 组内并发
**选择**:将所有 target 按其 interval 值分组,每组一个 `setInterval` timer组内使用 `Promise.all` 并发拨测。
**替代方案**
- 全局统一 tick → 无法支持 per-target interval
- 每个 target 独立 timer → 目标多时 timer 数量大,资源浪费
- 使用调度队列(如 BullMQ→ 过度设计
**理由**
- 支持 per-target interval满足不同服务不同频率的需求
- 相同 interval 的目标共享 timertimer 数量 = 不同 interval 值的数量
- 组内并发保证批量效率,组间隔离互不影响
### 4. 前端更新策略:轮询
**选择**:前端每 5-10 秒轮询 `/api/summary``/api/targets`
**替代方案**
- SSE 服务端推送 → 实现复杂,拨测间隔 15-60s 级别无必要
- WebSocket → 更复杂,过度设计
**理由**
- 拨测间隔本身是 15-60s5s 轮询延迟完全可接受
- 实现简单,无需维护长连接状态
- Dashboard 面板按需加载趋势数据(展开详情时请求)
### 5. 趋势图recharts
**选择**:引入 recharts 作为前端图表库。
**替代方案**
- 纯 SVG 手写 sparkline → 零依赖但代码量大,交互能力有限
- Chart.js → 非 React 原生,需要 wrapper
- D3 → 过于底层
**理由**
- 用户确认允许引入轻量图表库
- recharts 是 React 原生图表库,与现有 React 技术栈一致
- 支持折线图、迷你 Sparkline满足需求
- 社区活跃,文档完善
### 6. 目标状态判定模型
**选择**:两层判定——`success`(请求是否完成)+ `matched`(是否符合 expect 规则)。
```
● UP = success ✓ && matched ✓
● DOWN = !success || !matched
```
**理由**
- 区分"网络不可达"和"返回了非预期状态码"两种故障场景
- expect 规则可选,不配置时 matched 默认为 true
- 前端可以根据 `success`/`matched` 分别展示不同故障原因
### 7. 数据库 Schema 设计
**targets 表**:从 YAML 同步初始化,运行时只读。
**check_results 表**:只追加写入,索引 `(target_id, timestamp)` 加速历史查询。
**理由**
- targets 从 YAML 来,不提供运行时动态增删(符合 Non-Goals
- check_results 追加写入,无需更新/删除,简单可靠
- 按时间范围查询是最高频操作,复合索引覆盖
## Risks / Trade-offs
- **[YAML 格式错误导致启动失败]** → 解析时做完整校验,输出清晰错误信息(字段缺失、格式不对、值非法等),提前失败而非运行时出错
- **[并发拨测对目标服务器压力]** → 每组内 Promise.all 并发,但同一 group 的 tick 间隔内不会重复拨测。如果用户配置了大量目标且 interval 很短,可能对目标产生压力,这是用户配置责任
- **[SQLite 数据文件增长]** → 当前不清理,长期运行会增长。预留清理策略接口,后续可通过配置保留天数
- **[recharts 包体积]** → recharts gzip 后约 70KB会增加前端 bundle 大小。对于内部工具可接受
- **[拨测请求超时阻塞]** → 使用 `AbortController` + `setTimeout` 实现超时,避免单个慢请求阻塞整组
- **[进程重启后丢失 timer 状态]** → 拨测是幂等的(无状态定时任务),重启后立即开始新一轮即可,无需恢复状态

View File

@@ -0,0 +1,36 @@
## Why
项目当前只有 demo 验证链路(`/api/demo` + 前端展示连接状态),缺少核心业务逻辑。需要一个 HTTP 拨测工具,通过 YAML 配置文件定义拨测目标URL、method、header、body、期望条件等后端按配置定时、并行批量拨测结果持久化到本地 SQLite前端 Dashboard 展示各目标实时状态、可用率、延迟趋势等。
## What Changes
- **清理 demo 样例代码**:移除 `/api/demo` 路由、`DemoResponse` 类型、前端 demo 展示逻辑,保留路由框架、服务启动、构建打包链路和 `/health` 端点
- **新增 YAML 配置文件解析**:使用 Bun 内置 `Bun.YAML.parse()` 读取拨测规则文件,包含 server 配置、数据目录、全局默认值和拨测目标列表
- **简化 CLI 参数**:只保留一个命令行参数——配置文件路径,所有配置统一到 YAML 文件
- **新增 SQLite 数据存储**:使用 `bun:sqlite` 存储拨测目标(从 YAML 同步)和拨测结果(追加写入),支持索引查询
- **新增拨测调度引擎**:按 target 的 interval 分组,每组独立 timer组内 `Promise.all` 并发拨测,支持 expect 校验(状态码、响应体、延迟阈值)
- **新增 REST API 层**:提供总览统计、目标列表含当前状态、历史记录、趋势聚合等接口
- **新增前端 Dashboard**:使用 React 组件展示统计卡片、目标列表表格(含状态圆点和迷你趋势线)、可展开详情面板(含完整趋势图),通过轮询 5-10s 更新数据
- **引入 recharts 依赖**:用于趋势图和迷你 Sparkline 可视化
## Capabilities
### New Capabilities
- `probe-config`: YAML 配置文件格式定义、解析校验与 CLI 启动流程
- `probe-engine`: 拨测调度引擎——按 interval 分组定时、并发拨测、expect 校验、结果存储
- `probe-data-store`: SQLite 数据存储——targets 同步、results 追加、索引与聚合查询
- `probe-api`: REST API 层——总览统计、目标列表含状态、历史记录、趋势聚合
- `probe-dashboard`: React 前端 Dashboard——统计卡片、目标表格、详情面板、趋势图
### Modified Capabilities
- `fullstack-app-runtime`: CLI 参数从 `--host/--port` 简化为单个配置文件路径参数;移除 `/api/demo` 路由;新增 `/api/*` 拨测相关 API 路由
- `frontend-development-workflow`: 前端从 demo 展示页面替换为拨测 Dashboard移除 `/api/demo` 相关代理场景
## Impact
- **代码变更**`src/server/app.ts` 路由重写、`src/server/config.ts` 简化、`src/shared/api.ts` 类型重写、`src/web/` 前端全部重写
- **新增模块**`src/server/checker/` 目录engine、fetcher、store、config-loader、types
- **新增依赖**`recharts`(前端图表)
- **无新增外部依赖**YAML 解析使用 Bun 内置 `Bun.YAML`SQLite 使用 Bun 内置 `bun:sqlite`
- **构建打包**:现有 single executable 打包链路不变YAML 配置文件为外部文件不嵌入 executable
- **API 变更****BREAKING** 移除 `/api/demo`,新增 `/api/summary``/api/targets``/api/targets/:id/history``/api/targets/:id/trend`

View File

@@ -0,0 +1,12 @@
## MODIFIED Requirements
### Requirement: 前端开发期 API 代理
前端开发服务器 SHALL 在本地开发期间将 `/api/*` 请求代理到 Bun 后端服务。
#### Scenario: 前端开发期调用拨测 API
- **WHEN** 浏览器从 Vite 开发源请求 `/api/summary``/api/targets` 等拨测 API
- **THEN** Vite SHALL 将请求转发到 Bun 后端服务,且不需要浏览器 CORS 配置
#### Scenario: 开发期访问非 API 前端路由
- **WHEN** 浏览器从 Vite 开发源请求非 API 前端路由
- **THEN** Vite SHALL 将该请求作为前端应用流量处理,而不是转发到后端

View File

@@ -0,0 +1,35 @@
## MODIFIED Requirements
### Requirement: Bun HTTP 运行时
系统 SHALL 运行一个 Bun HTTP server由单个进程提供后端 API、健康检查、生产静态资源和 SPA fallback 行为。
#### Scenario: 启动运行时服务器
- **WHEN** server 进程成功启动
- **THEN** 它 SHALL 监听 YAML 配置文件中指定的 host 和 port并记录实际 server URL
#### Scenario: 通过 YAML 配置提供运行时参数
- **WHEN** 通过 YAML 配置文件提供 host、port、数据目录等参数
- **THEN** server SHALL 使用该值,且不需要重新构建
#### Scenario: CLI 只接受配置文件路径
- **WHEN** 用户通过命令行启动程序
- **THEN** 系统 SHALL 只接受一个命令行参数作为 YAML 配置文件路径
#### Scenario: 提供拨测相关 API
- **WHEN** server 启动完成
- **THEN** 系统 SHALL 提供 `/api/summary``/api/targets``/api/targets/:id/history``/api/targets/:id/trend` 端点
### Requirement: HTTP method 语义
系统 SHALL 为运行时端点提供明确的 HTTP method 语义,避免不支持的 method 被错误地当作成功请求处理。
#### Scenario: GET 请求访问运行时端点
- **WHEN** 客户端使用 `GET` 请求 `/health``/api/*` 端点
- **THEN** Bun server SHALL 返回对应端点的成功响应
#### Scenario: HEAD 请求访问运行时端点
- **WHEN** 客户端使用 `HEAD` 请求 `/health``/api/*` 端点
- **THEN** Bun server SHALL 返回与 `GET` 相同的成功状态和 headers但 MUST NOT 返回响应体
#### Scenario: 不支持的 method 访问运行时端点
- **WHEN** 客户端使用不支持的 method 请求 `/health``/api/*` 端点
- **THEN** Bun server SHALL 返回 405 状态码和 Allow header

View File

@@ -0,0 +1,59 @@
## ADDED Requirements
### Requirement: 总览统计 API
系统 SHALL 提供 `GET /api/summary` 端点,返回所有目标的总体统计信息。
#### Scenario: 获取总览统计
- **WHEN** 客户端请求 `GET /api/summary`
- **THEN** 系统 SHALL 返回 JSON 包含 total总目标数、up正常数、down异常数、avgLatencyMs所有目标平均延迟、lastCheckTime最近一次拨测时间
### Requirement: 目标列表 API
系统 SHALL 提供 `GET /api/targets` 端点,返回所有目标及其最新状态和统计摘要。
#### Scenario: 获取目标列表
- **WHEN** 客户端请求 `GET /api/targets`
- **THEN** 系统 SHALL 返回 JSON 数组每个元素包含目标基本信息、最近一次拨测结果timestamp、success、statusCode、latencyMs、error、matched和统计摘要totalChecks、availability、avgLatencyMs、p99LatencyMs
#### Scenario: 目标无历史记录
- **WHEN** 某目标尚未执行过任何拨测
- **THEN** 其 latestCheck 为 nullstats 中 totalChecks 为 0
### Requirement: 历史记录 API
系统 SHALL 提供 `GET /api/targets/:id/history` 端点,返回指定目标的最近 N 条拨测记录。
#### Scenario: 获取最近历史记录
- **WHEN** 客户端请求 `GET /api/targets/1/history?limit=20`
- **THEN** 系统 SHALL 返回最多 20 条拨测记录,按时间倒序排列
#### Scenario: 使用默认 limit
- **WHEN** 客户端请求 `GET /api/targets/1/history`(未指定 limit
- **THEN** 系统 SHALL 默认返回最近 20 条记录
### Requirement: 趋势聚合 API
系统 SHALL 提供 `GET /api/targets/:id/trend` 端点,返回指定目标按小时聚合的趋势数据。
#### Scenario: 获取 24 小时趋势
- **WHEN** 客户端请求 `GET /api/targets/1/trend?hours=24`
- **THEN** 系统 SHALL 返回按小时分组的聚合数据,每个数据点包含 hour、avgLatencyMs、availability、totalChecks
#### Scenario: 使用默认时间范围
- **WHEN** 客户端请求 `GET /api/targets/1/trend`(未指定 hours
- **THEN** 系统 SHALL 默认返回最近 24 小时的趋势数据
### Requirement: 保留健康检查端点
系统 SHALL 保留 `GET /health` 端点,不受拨测功能影响。
#### Scenario: 访问健康检查
- **WHEN** 客户端请求 `GET /health`
- **THEN** 系统 SHALL 返回与之前格式一致的健康检查响应
### Requirement: API 错误处理
系统 SHALL 对不存在的目标 ID 和无效参数返回适当的 HTTP 错误响应。
#### Scenario: 查询不存在的目标
- **WHEN** 客户端请求 `GET /api/targets/999/history`
- **THEN** 系统 SHALL 返回 404 状态码和错误信息
#### Scenario: 无效的 limit 参数
- **WHEN** 客户端请求 `GET /api/targets/1/history?limit=abc`
- **THEN** 系统 SHALL 返回 400 状态码和错误信息

View File

@@ -0,0 +1,53 @@
## ADDED Requirements
### Requirement: YAML 配置文件格式
系统 SHALL 支持通过 YAML 配置文件定义全部运行参数,包括 server 配置、数据目录、拨测默认值和拨测目标列表。
#### Scenario: 完整配置文件解析
- **WHEN** 系统启动并读取包含 server、defaults、targets 的 YAML 配置文件
- **THEN** 系统 SHALL 正确解析所有字段并用于初始化服务
#### Scenario: 最简配置文件解析
- **WHEN** 系统读取只包含 targets 列表的 YAML 配置文件(省略 server 和 defaults
- **THEN** 系统 SHALL 使用内置默认值填充未指定的字段host=127.0.0.1, port=3000, dir=./data, interval=30s, timeout=10s, method=GET
#### Scenario: per-target 配置覆盖全局默认值
- **WHEN** 某个 target 指定了 interval、timeout 或 method
- **THEN** 该 target SHALL 使用其自身的值,不受 defaults 影响
### Requirement: CLI 参数
系统 SHALL 通过单一命令行参数接受 YAML 配置文件路径。
#### Scenario: 指定配置文件启动
- **WHEN** 用户执行 `./gateway-checker ./probes.yaml`
- **THEN** 系统 SHALL 读取并解析指定路径的 YAML 文件作为配置
#### Scenario: 未提供配置文件路径
- **WHEN** 用户启动程序时未提供任何命令行参数
- **THEN** 系统 SHALL 以错误退出并提示需要指定配置文件路径
#### Scenario: 配置文件不存在
- **WHEN** 用户指定的配置文件路径不存在
- **THEN** 系统 SHALL 以错误退出并提示文件不存在
### Requirement: 配置校验
系统 SHALL 在启动时对 YAML 配置进行完整校验,校验失败时以非零状态退出并输出清晰的错误信息。
#### Scenario: target 缺少必填字段
- **WHEN** YAML 中某个 target 缺少 name 或 url 字段
- **THEN** 系统 SHALL 以错误退出,提示哪个 target 缺少哪个字段
#### Scenario: target name 重复
- **WHEN** YAML 中存在两个 name 相同的 target
- **THEN** 系统 SHALL 以错误退出,提示重复的 name
#### Scenario: interval 格式非法
- **WHEN** interval 或 timeout 值不是有效的时长格式(如 `30s``5m`
- **THEN** 系统 SHALL 以错误退出并提示格式错误
### Requirement: YAML 配置使用 Bun 内置解析
系统 SHALL 使用 Bun 内置的 `Bun.YAML.parse()` 解析配置文件,不引入外部 YAML 解析库。
#### Scenario: 解析 YAML 内容
- **WHEN** 系统读取 YAML 文件内容
- **THEN** 系统 SHALL 调用 `Bun.YAML.parse()` 将内容解析为配置对象

View File

@@ -0,0 +1,69 @@
## ADDED Requirements
### Requirement: 总览统计卡片
Dashboard SHALL 在页面顶部展示总览统计卡片,包含总目标数、正常数、异常数和平均延迟。
#### Scenario: 展示统计卡片
- **WHEN** 用户打开 Dashboard 页面
- **THEN** 页面顶部 SHALL 显示 4 个统计卡片:全部目标数、正常目标数、异常目标数、所有目标平均延迟
#### Scenario: 统计数据自动刷新
- **WHEN** 页面处于打开状态
- **THEN** 统计卡片 SHALL 每 5-10 秒自动刷新数据
### Requirement: 目标列表表格
Dashboard SHALL 展示所有拨测目标的列表表格包含名称、URL、当前状态、最新延迟和迷你趋势线。
#### Scenario: 展示目标列表
- **WHEN** 用户打开 Dashboard 页面
- **THEN** 页面 SHALL 显示表格每行包含目标名称、URL、状态指示圆点● UP / ● DOWN、最新延迟值、迷你 Sparkline 趋势线
#### Scenario: 状态指示圆点
- **WHEN** 目标最近一次拨测 success=true 且 matched=true
- **THEN** 状态圆点 SHALL 显示为绿色UP
- **WHEN** 目标最近一次拨测 success=false 或 matched=false
- **THEN** 状态圆点 SHALL 显示为红色DOWN
### Requirement: 可展开的目标详情面板
Dashboard SHALL 支持在目标列表中展开某行,显示该目标的详细状态、统计摘要、趋势图和最近历史记录。
#### Scenario: 展开目标详情
- **WHEN** 用户点击目标列表中的某一行
- **THEN** 该行下方 SHALL 展开详情面板包含可用率百分比、平均延迟、P99 延迟、24 小时延迟趋势折线图、最近 5-10 条拨测记录列表
#### Scenario: 收起目标详情
- **WHEN** 用户再次点击已展开的目标行
- **THEN** 详情面板 SHALL 收起
#### Scenario: 趋势图按需加载
- **WHEN** 用户展开某个目标的详情面板
- **THEN** 系统 SHALL 此时请求该目标的趋势数据,而非页面加载时预加载所有目标的趋势数据
### Requirement: 历史记录展示
Dashboard SHALL 在目标详情面板中展示最近的拨测记录,包含时间、状态码、延迟和成功/失败标记。
#### Scenario: 展示历史记录
- **WHEN** 用户展开目标详情面板
- **THEN** 面板 SHALL 显示最近拨测记录列表每条包含时间戳、HTTP 状态码(或错误信息)、延迟毫秒数、成功/失败图标
### Requirement: 趋势图可视化
Dashboard SHALL 使用 recharts 库渲染趋势图,包括目标列表中的迷你 Sparkline 和详情面板中的完整折线图。
#### Scenario: 表格行内迷你趋势线
- **WHEN** 目标列表表格渲染
- **THEN** 每行 SHALL 包含一个基于 recharts 的迷你折线图,展示最近的延迟趋势
#### Scenario: 详情面板完整趋势图
- **WHEN** 用户展开目标详情面板
- **THEN** 面板 SHALL 展示基于 recharts 的完整折线图X 轴为时间小时Y 轴为平均延迟,并标注可用率
### Requirement: 页面加载与错误状态
Dashboard SHALL 正确处理加载状态和 API 错误。
#### Scenario: 首次加载
- **WHEN** 页面首次加载且数据尚未返回
- **THEN** 页面 SHALL 显示加载状态指示
#### Scenario: API 请求失败
- **WHEN** 前端轮询 API 请求失败
- **THEN** 页面 SHALL 显示错误提示,并在下一次轮询周期自动重试

View File

@@ -0,0 +1,56 @@
## ADDED Requirements
### Requirement: SQLite 数据库初始化
系统 SHALL 使用 Bun 内置 `bun:sqlite` 模块在配置的数据目录下创建 SQLite 数据库文件,并以 WAL 模式运行。
#### Scenario: 首次启动创建数据库
- **WHEN** 指定的数据目录下不存在数据库文件
- **THEN** 系统 SHALL 创建数据库文件并初始化 targets 和 check_results 表
#### Scenario: 数据目录不存在
- **WHEN** 配置的数据目录路径不存在
- **THEN** 系统 SHALL 自动创建该目录
#### Scenario: 数据库已存在时启动
- **WHEN** 数据库文件已存在
- **THEN** 系统 SHALL 直接打开数据库,不重新建表
### Requirement: targets 表同步
系统 SHALL 在启动时将 YAML 配置中的目标列表同步到 SQLite targets 表。
#### Scenario: 首次同步目标
- **WHEN** 数据库为空且 YAML 中定义了 N 个目标
- **THEN** 系统 SHALL 将所有目标插入 targets 表
#### Scenario: 配置变更后重新同步
- **WHEN** YAML 配置发生变更(新增、删除或修改目标)后重启
- **THEN** 系统 SHALL 根据 name 字段匹配:新增的插入、删除的移除、修改的更新
### Requirement: check_results 表追加写入
系统 SHALL 将每次拨测结果追加写入 check_results 表,不更新或删除已有记录。
#### Scenario: 写入拨测结果
- **WHEN** 一次拨测完成
- **THEN** 系统 SHALL 插入一条包含 target_id、timestamp、success、status_code、latency_ms、error、matched 的记录
### Requirement: 时间范围查询索引
系统 SHALL 在 check_results 表上创建 (target_id, timestamp) 复合索引,加速按目标和时间范围的查询。
#### Scenario: 查询某目标的历史记录
- **WHEN** 查询指定 target_id 的最近 N 条记录
- **THEN** 系统 SHALL 使用索引快速定位,无需全表扫描
### Requirement: 聚合查询支持
数据存储 SHALL 支持按时间段聚合查询用于计算可用率、平均延迟、P99 延迟等统计指标。
#### Scenario: 计算目标可用率
- **WHEN** 查询某目标在指定时间范围内的可用率
- **THEN** 系统 SHALL 返回 UP (success=true AND matched=true) 的记录数占总记录数的百分比
#### Scenario: 计算目标平均延迟
- **WHEN** 查询某目标在指定时间范围内的平均延迟
- **THEN** 系统 SHALL 返回 latency_ms 的平均值(仅计算 success=true 的记录)
#### Scenario: 按小时聚合趋势数据
- **WHEN** 查询某目标在指定时间范围内的趋势数据
- **THEN** 系统 SHALL 返回按小时分组的聚合数据,包括每小时的平均延迟和可用率

View File

@@ -0,0 +1,83 @@
## ADDED Requirements
### Requirement: 按 interval 分组调度
系统 SHALL 将拨测目标按 interval 值分组,每组使用独立的定时器进行调度。
#### Scenario: 相同 interval 的目标共享定时器
- **WHEN** 多个 target 配置了相同的 interval如 30s
- **THEN** 系统 SHALL 使用同一个 `setInterval` 定时器,每次 tick 并发拨测所有该组目标
#### Scenario: 不同 interval 的目标各自调度
- **WHEN** target A 配置 15s intervaltarget B 配置 30s interval
- **THEN** 系统 SHALL 创建两个独立定时器,分别按各自频率调度
### Requirement: 组内并发拨测
系统 SHALL 在每次调度 tick 时,使用 `Promise.all` 并发执行同组内所有目标的拨测。
#### Scenario: 同组目标并发执行
- **WHEN** 调度器触发一次 tick该组有 3 个目标
- **THEN** 系统 SHALL 同时发起 3 个 HTTP 请求,而非顺序执行
#### Scenario: 单个目标失败不影响同组其他目标
- **WHEN** 同组中某个目标的拨测请求超时或失败
- **THEN** 其他目标的拨测 SHALL 正常完成并记录结果
### Requirement: HTTP 拨测执行
系统 SHALL 对每个目标执行 HTTP 请求,支持 GET、POST、PUT、DELETE、PATCH、HEAD 方法,并携带配置的 headers 和 body。
#### Scenario: 执行 GET 请求
- **WHEN** 目标配置 method 为 GET
- **THEN** 系统 SHALL 发送 GET 请求到目标 URL
#### Scenario: 执行 POST 请求带 body
- **WHEN** 目标配置 method 为 POST 且指定了 body 和 Content-Type header
- **THEN** 系统 SHALL 发送带指定 body 的 POST 请求
#### Scenario: 携带自定义 headers
- **WHEN** 目标配置了 headers如 Authorization
- **THEN** 系统 SHALL 在请求中包含所有配置的 headers
### Requirement: 请求超时控制
系统 SHALL 对每次拨测请求实施超时控制,超时时间使用目标配置的 timeout 值。
#### Scenario: 请求超时
- **WHEN** 拨测请求在 timeout 时间内未收到响应
- **THEN** 系统 SHALL 中止该请求,记录为失败并标注超时错误
#### Scenario: 请求在超时前完成
- **WHEN** 拨测请求在 timeout 时间内收到响应
- **THEN** 系统 SHALL 正常记录响应结果
### Requirement: expect 校验
系统 SHALL 在拨测完成后根据目标的 expect 配置校验响应,校验结果记入 check result。
#### Scenario: 校验状态码
- **WHEN** 目标配置了 `expect.status: [200, 201]`
- **THEN** 系统 SHALL 检查响应状态码是否在列表中,将匹配结果记录到 matched 字段
#### Scenario: 校验响应体包含
- **WHEN** 目标配置了 `expect.bodyContains: "healthy"`
- **THEN** 系统 SHALL 检查响应体是否包含该文本,将匹配结果记录到 matched 字段
#### Scenario: 校验延迟阈值
- **WHEN** 目标配置了 `expect.maxLatencyMs: 3000`
- **THEN** 系统 SHALL 检查实际延迟是否超过阈值,将匹配结果记录到 matched 字段
#### Scenario: 无 expect 配置
- **WHEN** 目标未配置任何 expect 规则
- **THEN** 系统 SHALL 将 matched 字段设为 true
#### Scenario: 多条 expect 规则
- **WHEN** 目标同时配置了 status、bodyContains 和 maxLatencyMs
- **THEN** 系统 SHALL 所有规则全部通过时 matched 为 true任一不通过则为 false
### Requirement: 拨测结果记录
系统 SHALL 在每次拨测完成后,将结果写入 SQLite 数据存储,包含 target_id、timestamp、success、status_code、latency_ms、error、matched 字段。
#### Scenario: 成功拨测结果记录
- **WHEN** 拨测请求成功完成(收到 HTTP 响应)
- **THEN** 系统 SHALL 记录 success=true、status_code、latency_ms、matched
#### Scenario: 失败拨测结果记录
- **WHEN** 拨测请求失败(网络错误、超时等)
- **THEN** 系统 SHALL 记录 success=false、error 信息status_code 和 latency_ms 为 null

View File

@@ -0,0 +1,62 @@
## 1. 项目准备与依赖
- [x] 1.1 清理 demo 代码:移除 /api/demo 路由、DemoResponse 类型、前端 demo 展示逻辑
- [x] 1.2 安装 recharts 依赖
- [x] 1.3 创建 src/server/checker/ 目录结构和类型定义文件 types.ts
- [x] 1.4 创建示例 YAML 配置文件 probes.example.yaml
## 2. 配置解析层
- [x] 2.1 实现 YAML 配置类型定义ProbeConfig、TargetConfig、ExpectConfig 等)
- [x] 2.2 实现 config-loader.ts读取文件 + Bun.YAML.parse + 配置校验必填字段、name 唯一性、interval 格式、port 范围)
- [x] 2.3 重写 src/server/config.tsCLI 只接受配置文件路径参数,从 YAML 读取 host/port/dataDir
- [x] 2.4 为配置解析和校验编写完整测试
## 3. 数据存储层
- [x] 3.1 实现 store.tsSQLite 初始化建表、WAL 模式、复合索引)、数据目录自动创建
- [x] 3.2 实现 targets 表同步逻辑(根据 name 匹配:新增插入、删除移除、修改更新)
- [x] 3.3 实现 check_results 追加写入方法
- [x] 3.4 实现查询方法:按 target+时间范围查询、按小时聚合趋势、计算可用率/平均延迟/P99
- [x] 3.5 为数据存储层编写完整测试(初始化、同步、写入、查询、聚合)
## 4. 拨测引擎
- [x] 4.1 实现 fetcher.tsHTTP 请求执行method/header/body+ AbortController 超时控制
- [x] 4.2 实现 expect 校验逻辑status 列表匹配、bodyContains、maxLatencyMs
- [x] 4.3 实现 engine.ts按 interval 分组 → setInterval → Promise.all 并发拨测 → 结果写入 store
- [x] 4.4 为 fetcher 和 expect 校验编写完整测试(使用 mock HTTP server
- [x] 4.5 为调度引擎编写完整测试(分组逻辑、并发执行、单目标失败隔离)
## 5. API 路由层
- [x] 5.1 定义 src/shared/api.ts 响应类型SummaryResponse、TargetStatus、CheckResult、TrendPoint
- [x] 5.2 重写 src/server/app.ts注册新 API 路由(/api/summary、/api/targets、/api/targets/:id/history、/api/targets/:id/trend保留 /health
- [x] 5.3 实现 API 错误处理(目标不存在返回 404、参数无效返回 400
- [x] 5.4 为 API 路由编写完整测试(各端点正常响应、边界情况、错误处理)
## 6. 前端 Dashboard
- [x] 6.1 创建前端组件目录结构 src/web/components/ 和 src/web/hooks/
- [x] 6.2 实现 hooksuseSummary轮询 /api/summary、useTargets轮询 /api/targets、useTrend按需加载趋势数据
- [x] 6.3 实现 StatusDot 组件(绿色 UP / 红色 DOWN 圆点)
- [x] 6.4 实现 SummaryCards 组件4 个统计卡片)
- [x] 6.5 实现 SparklineChart 组件recharts 迷你折线图)
- [x] 6.6 实现 TrendChart 组件recharts 完整折线图,含时间轴和双 Y 轴)
- [x] 6.7 实现 TargetRow 组件表格行名称、URL、状态、延迟、Sparkline可展开
- [x] 6.8 实现 TargetDetail 组件(展开面板:统计摘要、趋势图、历史记录列表)
- [x] 6.9 实现 TargetTable 组件(组合 TargetRow 和 TargetDetail
- [x] 6.10 重写 App.tsx组合 SummaryCards + TargetTable处理加载和错误状态
- [x] 6.11 重写 styles.cssDashboard 布局样式(卡片、表格、详情面板、响应式)
- [x] 6.12 更新 vite.config.ts 代理配置确保 /api/* 转发
## 7. 集成与启动流程
- [x] 7.1 重写 src/server/dev.ts 和 src/server/server.ts启动流程为 读取配置 → 初始化 store → 同步 targets → 启动 engine → 启动 HTTP server
- [x] 7.2 更新构建脚本确保 recharts 正确打包进 executable
- [x] 7.3 更新 README.md新的 CLI 用法、YAML 配置说明、API 端点文档、项目结构变更
## 8. 端到端验证
- [x] 8.1 更新 smoke test 脚本适配新的 API 端点和前端路由
- [x] 8.2 手动验证完整流程YAML 配置 → 启动 → 拨测执行 → Dashboard 展示