- 合并 20+ 细粒度 spec 为粗粒度主题规范:dashboard、data-store、probe-engine、probe-api、probe-config 等 - 删除完全冗余规范:data-retention(被 probe-engine+data-store 覆盖)、backend-code-quality(DEVELOPMENT.md 已记录) - 补充 http-checker 规范至完整标准(配置+执行+expect+校验+observation),匹配代码 440 行实现 - 清理 tcp/udp/llm checker 规范中已废弃 defaults 配置段的残留 Scenario - 清理 checker-cohesion-structure 中的实现路径引用(src/server/...) - 统一所有 spec 格式(## Purpose 开头,去除 # Capability/Title 形式) - 更新 prompt-spec-review.md 审查提示文档
19 KiB
Purpose
定义拨测系统的 REST API 端点:Dashboard 聚合 API、单目标指标 API、历史记录 API、Meta 信息 API、共享类型定义和 API 错误处理。
Requirements
Requirement: Dashboard 聚合 API
系统 SHALL 提供 GET /api/dashboard 端点,返回 Dashboard 首屏所需的总览统计和目标列表数据。targets 列表 SHALL 仅包含活跃目标。
Scenario: 获取 Dashboard 数据
- WHEN 客户端请求
GET /api/dashboard?window=24h&recentLimit=30 - THEN 系统 SHALL 返回 JSON 包含 summary 和 targets 字段,targets 仅包含 active=1 的目标
Scenario: summary 字段
- WHEN Dashboard 响应包含 summary
- THEN summary SHALL 仅统计活跃目标:total(活跃目标数)、up(活跃正常目标数)、down(活跃异常目标数)、lastCheckTime(最近一次检查时间)、incidents(活跃目标在指定窗口内异常事件数)、window(from/to/label)字段
Scenario: targets 字段
- WHEN Dashboard 响应包含 targets
- THEN targets 数组中每个元素 SHALL 为活跃目标,包含目标基本信息(id、name、description、group、type、target、interval)、latestCheck、stats、currentStreak 和 recentSamples 字段,其中 name 和 description 均为 null 或字符串
Scenario: target name 字段为 null
- WHEN 某个 target 未配置
name或显式配置name: null - THEN Dashboard targets 响应中对应元素 SHALL 返回
name: null
Scenario: target description 字段
- WHEN 某个 target 配置了
description - THEN Dashboard targets 响应中对应元素 SHALL 返回该 description 值
Scenario: window 参数缺失
- WHEN 客户端请求
GET /api/dashboard未提供 window 参数 - THEN 系统 SHALL 默认使用 window=
24h
Scenario: window 参数语义
- WHEN 系统处理 Dashboard 请求
- THEN 系统 SHALL 以服务端当前时间作为 window.to,以 window 参数换算 window.from,并在响应中回显 window.from、window.to、window.label
Scenario: window 参数有效值
- WHEN 客户端请求 Dashboard 端点并指定 window 参数
- THEN 系统 SHALL 接受
24h作为有效值;其他值 SHALL 返回 400 状态码和错误信息
Scenario: 不支持的 window 参数
- WHEN 客户端请求
GET /api/dashboard?window=abc - THEN 系统 SHALL 返回 400 状态码和错误信息
Scenario: recentLimit 参数缺失
- WHEN 客户端请求
GET /api/dashboard?window=24h未提供 recentLimit 参数 - THEN 系统 SHALL 默认使用 recentLimit=30
Scenario: 不支持的 recentLimit 参数
- WHEN 客户端请求
GET /api/dashboard?recentLimit=0或超过系统上限的 recentLimit - THEN 系统 SHALL 返回 400 状态码和错误信息
Scenario: 目标无历史记录
- WHEN 某目标尚未执行过任何拨测
- THEN 其 latestCheck 为 null,recentSamples 为空数组,stats.totalChecks 为 0,stats.availability 为 0,currentStreak 为 null
Requirement: Dashboard 指标字段
Dashboard API SHALL 返回基于时间窗口计算的目标统计和连续状态字段。
Scenario: 目标 stats 字段
- WHEN Dashboard 响应包含目标 stats
- THEN stats SHALL 包含 totalChecks、upChecks、downChecks、availability 字段,且这些字段 SHALL 基于请求 window 对应的时间范围计算
Scenario: 目标 currentStreak 字段
- WHEN Dashboard 响应包含目标 currentStreak
- THEN currentStreak SHALL 为
{ up: boolean, count: number, capped?: boolean }或 null
Scenario: currentStreak 达到 recentLimit
- WHEN 连续状态次数达到 recentLimit 上限
- THEN currentStreak.capped SHALL 为 true
Scenario: recentSamples 字段
- WHEN Dashboard 响应包含 recentSamples
- THEN 每个 recentSamples 元素 SHALL 包含 timestamp、durationMs、up 字段,其中 up 为 boolean 且等于 matched
Requirement: 单目标指标 API
系统 SHALL 提供 GET /api/targets/:id/metrics 端点,返回单个目标在指定时间窗口内的概览统计和趋势数据。仅活跃目标的指标 SHALL 可查询。
Scenario: 获取目标指标
- WHEN 客户端请求
GET /api/targets/1/metrics?from=ISO&to=ISO&bucket=1h且该目标为活跃目标 - THEN 系统 SHALL 返回 JSON 对象包含 targetId、window、stats、trend 字段
Scenario: from 或 to 参数缺失
- WHEN 客户端请求
GET /api/targets/1/metrics未提供 from 或 to 参数 - THEN 系统 SHALL 返回 400 状态码和错误信息
Scenario: 目标不存在或非活跃
- WHEN 客户端请求
GET /api/targets/999/metrics?from=ISO&to=ISO且该目标不存在或 active=0 - THEN 系统 SHALL 返回 404 状态码和错误信息
Scenario: 无效的目标 ID
- WHEN 客户端请求
GET /api/targets/abc/metrics?from=ISO&to=ISO - THEN 系统 SHALL 返回 400 状态码和错误信息
Scenario: bucket 参数缺失
- WHEN 客户端请求
GET /api/targets/1/metrics?from=ISO&to=ISO未提供 bucket 参数 - THEN 系统 SHALL 默认使用 bucket=
1h
Scenario: 不支持的 bucket 参数
- WHEN 客户端请求
GET /api/targets/1/metrics?from=ISO&to=ISO&bucket=5m - THEN 系统 SHALL 返回 400 状态码和错误信息
Requirement: 指标统计字段
单目标指标 API SHALL 返回基于时间窗口计算的完整统计字段。
Scenario: stats 字段
- WHEN metrics 响应包含 stats
- THEN stats SHALL 包含 totalChecks、upChecks、downChecks、availability、avgDurationMs、p95DurationMs、p99DurationMs、mttr、longestOutage、incidentCount、currentStreak 字段
Scenario: trend 字段
- WHEN metrics 响应包含 trend
- THEN trend SHALL 为数组,每个元素包含 bucketStart、avgDurationMs、minDurationMs、maxDurationMs、availability、totalChecks、upChecks、downChecks 字段
Requirement: P95/P99 延迟计算
系统 SHALL 在后端应用层计算 P95 和 P99 延迟百分位数。
Scenario: 正常计算 P95
- WHEN 时间窗口内存在成功检查记录(matched=1 且 duration_ms 不为 null)
- THEN 系统 SHALL 取出所有成功检查的 duration_ms,在后端应用层排序后取第 95 百分位值返回为 p95DurationMs
Scenario: 正常计算 P99
- WHEN 时间窗口内存在成功检查记录
- THEN 系统 SHALL 取第 99 百分位值返回为 p99DurationMs
Scenario: 无成功检查记录
- WHEN 时间窗口内无 matched=1 且 duration_ms 不为 null 的记录
- THEN p95DurationMs 和 p99DurationMs SHALL 返回 null
Scenario: 百分位计算方法
- WHEN 计算第 N 百分位
- THEN 系统 SHALL 将 duration_ms 升序排列,取 index = ceil(count * N / 100) - 1 位置的值
Requirement: MTTR 计算
系统 SHALL 在后端应用层计算平均恢复时间(Mean Time To Recovery)。
Scenario: 存在已恢复的故障段
- WHEN 时间窗口内存在至少一个已恢复的故障段(连续 matched=0 后跟 matched=1)
- THEN 系统 SHALL 计算所有已恢复故障段的平均持续时间(从首个 matched=0 的 timestamp 到恢复后首个 matched=1 的 timestamp 之差),返回为 mttr(毫秒)
Scenario: 无已恢复的故障段
- WHEN 时间窗口内无已恢复的故障段(全部正常,或当前仍在故障中且无历史恢复)
- THEN mttr SHALL 返回 null
Scenario: 当前正在故障中
- WHEN 时间窗口内最后一段故障尚未恢复
- THEN 该未恢复的故障段 SHALL 不计入 MTTR 平均值
Scenario: 窗口起始即为故障且后续恢复
- WHEN 时间窗口内第一条记录即为 matched=0(故障跨越了 from 边界),且该故障段在窗口内恢复
- THEN 该故障段 SHALL 不计入 MTTR 平均值(因无法确定真实故障开始时间),但 SHALL 计入 incidentCount
Requirement: 最长故障时长
系统 SHALL 在后端应用层计算时间窗口内最长的单次故障持续时间。
Scenario: 存在故障段
- WHEN 时间窗口内存在故障段
- THEN 系统 SHALL 返回最长故障段的持续时间为 longestOutage(毫秒)
Scenario: 无故障
- WHEN 时间窗口内无 matched=0 的记录
- THEN longestOutage SHALL 返回 null
Scenario: 窗口起始即为故障
- WHEN 时间窗口内第一条记录即为 matched=0
- THEN 该故障段的持续时间 SHALL 从 from 参数开始计算
Scenario: 当前正在故障中
- WHEN 最后一段故障尚未恢复
- THEN 该故障段的持续时间 SHALL 计算为从故障开始到时间窗口 to 参数的时间差
Requirement: 故障事件计数
系统 SHALL 在后端应用层计算时间窗口内的故障事件次数。
Scenario: 计算故障事件数
- WHEN 时间窗口内存在状态翻转(matched 从 1 变为 0)
- THEN 系统 SHALL 返回翻转次数为 incidentCount
Scenario: 无故障事件
- WHEN 时间窗口内所有检查均为 matched=1
- THEN incidentCount SHALL 返回 0
Scenario: 窗口起始即为故障
- WHEN 时间窗口内第一条记录即为 matched=0 且无前序记录可判断翻转
- THEN 该故障 SHALL 计为 1 次事件
Scenario: 连续异常只计一次
- WHEN 某目标连续 10 次 matched=0
- THEN 该连续异常段 SHALL 仅计为 1 次事件
Requirement: 当前连续状态
系统 SHALL 返回目标当前的连续状态信息。
Scenario: 当前连续正常
- WHEN 目标最近的检查记录连续为 matched=1
- THEN currentStreak SHALL 返回
{ up: true, count: N },N 为连续正常的检查次数
Scenario: 当前连续异常
- WHEN 目标最近的检查记录连续为 matched=0
- THEN currentStreak SHALL 返回
{ up: false, count: N },N 为连续异常的检查次数
Scenario: 连续状态达到取数上限
- WHEN 连续状态次数达到后端取数或计算上限
- THEN currentStreak SHALL 返回
{ up: boolean, count: N, capped: true },前端据此展示上限标记
Scenario: 无检查记录
- WHEN 目标没有任何检查记录
- THEN currentStreak SHALL 返回 null
Requirement: 趋势数据应用层分桶
系统 SHALL 在后端应用层按 UTC 小时分桶生成趋势数据。
Scenario: 按小时生成趋势
- WHEN metrics 请求 bucket=
1h - THEN 系统 SHALL 按 UTC 小时生成 trend 数组,每个点包含该小时内的 totalChecks、upChecks、downChecks、availability、avgDurationMs、minDurationMs、maxDurationMs
Scenario: 小时内无成功检查
- WHEN 某小时内存在检查记录但无成功检查记录
- THEN avgDurationMs、minDurationMs、maxDurationMs SHALL 返回 null,availability SHALL 基于 upChecks/totalChecks 返回 0
Scenario: 小时内无检查记录
- WHEN 某小时内没有任何检查记录
- THEN 系统 MAY 不返回该小时对应的 trend 点
Requirement: 无数据口径
系统 SHALL 对无数据窗口返回稳定的空指标口径。
Scenario: 窗口内无检查记录
- WHEN 指定时间窗口内没有任何检查记录
- THEN stats SHALL 返回 totalChecks=0、upChecks=0、downChecks=0、availability=0、avgDurationMs=null、p95DurationMs=null、p99DurationMs=null、mttr=null、longestOutage=null、incidentCount=0、currentStreak=null,trend SHALL 返回空数组
Requirement: 历史记录 API
系统 SHALL 保留 GET /api/targets/:id/history 端点,支持时间范围筛选和分页返回指定目标的拨测记录。仅活跃目标的历史记录 SHALL 可查询。
Scenario: 获取指定时间范围内的历史记录
- WHEN 客户端请求
GET /api/targets/1/history?from=ISO&to=ISO&page=1&pageSize=20且该目标为活跃目标 - THEN 系统 SHALL 返回带分页信息的历史记录,包含 items、total、page、pageSize,按时间倒序排列
Scenario: 使用默认分页参数
- WHEN 客户端请求
GET /api/targets/1/history?from=ISO&to=ISO(未指定 page 或 pageSize) - THEN 系统 SHALL 使用默认 page=1, pageSize=20
Scenario: from 或 to 参数缺失
- WHEN 客户端请求
GET /api/targets/1/history未提供 from 或 to 参数 - THEN 系统 SHALL 返回 400 状态码和错误信息
Scenario: 目标不存在或非活跃
- WHEN 客户端请求
GET /api/targets/999/history?from=ISO&to=ISO且该目标不存在或 active=0 - THEN 系统 SHALL 返回 404 状态码和错误信息
Requirement: 新增共享类型
系统 SHALL 在 src/shared/api.ts 中定义 Dashboard 和 Metrics 相关共享类型。CheckResult SHALL 包含 durationMs(null | number)、failure(CheckFailure | null)、matched(boolean)、detail(null | string)、observation(Record<string, unknown> | null)、timestamp(string)。其中 detail 替代原 statusDetail 字段名。
Scenario: DashboardResponse 类型
- WHEN 前后端共享
DashboardResponse类型 - THEN 该类型 SHALL 包含 summary 和 targets 字段
Scenario: TargetStatus 类型
- WHEN 前后端共享
TargetStatus类型 - THEN 该类型 SHALL 包含目标基本信息字段(id、name、description、group、type、target、interval)、stats(totalChecks、upChecks、downChecks、availability)、currentStreak 和 recentSamples 字段,其中 name 和 description 类型均为 null 或字符串
Scenario: TargetMetricsResponse 类型
- WHEN 前后端共享
TargetMetricsResponse类型 - THEN 该类型 SHALL 包含 targetId、window、stats 和 trend 字段
Scenario: TrendPoint 类型
- WHEN 前后端共享
TrendPoint类型 - THEN 该类型 SHALL 包含 bucketStart、avgDurationMs、minDurationMs、maxDurationMs、availability、totalChecks、upChecks、downChecks 字段
Scenario: CheckResult 类型变更
- WHEN 前端或后端引用 CheckResult 类型
- THEN 该类型 SHALL 包含
timestamp: string、matched: boolean、durationMs: number | null、detail: string | null、observation: Record<string, unknown> | null、failure字段,不包含 statusDetail 字段,不包含 success 字段
Scenario: RecentSample 类型
- WHEN 前后端共享
RecentSample类型 - THEN 该类型 SHALL 包含
timestamp: string、durationMs: number | null、up: boolean字段,其中 up 为 boolean 且等于 matched
Scenario: HistoryResponse 类型
- WHEN 前后端共享
HistoryResponse类型 - THEN 该类型 SHALL 包含
items: CheckResult[]、total: number、page: number、pageSize: number字段
Scenario: API 序列化构造 detail
- WHEN API 路由序列化 StoredCheckResult 为 API 响应
- THEN 系统 SHALL 从 StoredCheckResult 中反序列化 observation,根据 target type 通过 checkerRegistry 获取对应 checker 并调用 buildDetail(observation) 动态生成 detail 字段
Scenario: mapCheckResult 接收 type 参数
- WHEN 序列化辅助函数 mapCheckResult 被调用
- THEN 函数 SHALL 接收 target type 参数,用于从 registry 获取对应 checker 调用 buildDetail
Scenario: Dashboard API 传递 type
- WHEN Dashboard 路由序列化 latestCheck
- THEN 路由 SHALL 将 target.type 传递给 mapCheckResult
Scenario: History API 传递 type
- WHEN History 路由序列化历史记录列表
- THEN 路由 SHALL 将已查询的 target.type 传递给 mapCheckResult
Requirement: 保留健康检查端点
系统 SHALL 保留 GET /health 端点,不受拨测功能影响。
Scenario: 访问健康检查
- WHEN 客户端请求
GET /health - THEN 系统 SHALL 返回与之前格式一致的健康检查响应
Requirement: API 错误处理
系统 SHALL 对不存在的目标 ID、非活跃目标、无效参数和超出范围的分页参数返回适当的 HTTP 错误响应。非活跃目标与不存在的目标 SHALL 返回相同的 404 响应。
Scenario: 查询不存在的目标
- WHEN 客户端请求
GET /api/targets/999/metrics?from=ISO&to=ISO - THEN 系统 SHALL 返回 404 状态码和错误信息
Scenario: 查询非活跃目标
- WHEN 客户端请求
GET /api/targets/<id>/metrics?from=ISO&to=ISO且该目标 active=0 - THEN 系统 SHALL 返回 404 状态码和错误信息
Scenario: 无效的 from/to 参数
- WHEN 客户端请求
GET /api/targets/1/metrics?from=invalid&to=ISO - THEN 系统 SHALL 返回 400 状态码和错误信息
Scenario: from 晚于 to
- WHEN 客户端请求
GET /api/targets/1/metrics?from=<较晚时间>&to=<较早时间> - THEN 系统 SHALL 返回 400 状态码和错误信息,提示 from 必须早于 to
Scenario: 无效的分页参数
- WHEN 客户端请求
GET /api/targets/1/history?from=ISO&to=ISO&page=abc - THEN 系统 SHALL 返回 400 状态码和错误信息
Scenario: pageSize 超过上限
- WHEN 客户端请求
GET /api/targets/1/history?from=ISO&to=ISO&pageSize=201 - THEN 系统 SHALL 返回 400 状态码和错误信息,提示 pageSize 不能超过 200
Scenario: pageSize 等于上限
- WHEN 客户端请求
GET /api/targets/1/history?from=ISO&to=ISO&pageSize=200 - THEN 系统 SHALL 正常返回数据
Scenario: 无效的目标 ID
- WHEN 客户端请求
GET /api/targets/abc/metrics?from=ISO&to=ISO - THEN 系统 SHALL 返回 400 状态码和错误信息
Requirement: 失败信息 API 契约
系统 SHALL 通过 API 返回结构化 failure 信息,供 Dashboard 展示和后续排查。
Scenario: 返回 expect 不匹配信息
- WHEN 最近一次检查结果包含 failure.kind=
mismatch - THEN
/api/targets和/api/targets/:id/historySHALL 返回该 failure 的 kind、phase、path、expected、actual、message 字段
Scenario: 无失败信息
- WHEN 检查结果 matched=true
- THEN API SHALL 返回 failure 为 null
Requirement: Meta 信息 API
系统 SHALL 提供 GET /api/meta 端点,返回系统运行时元数据,包括应用版本号和 checker 类型列表。未匹配 method SHALL 按 API 通配符处理为 JSON 404。
Scenario: 获取 checker 类型列表和版本号
- WHEN 客户端请求
GET /api/meta - THEN 系统 SHALL 返回 JSON
{ checkerTypes: string[], version: string },其中checkerTypes包含所有已注册的 checker 类型标识符,version为当前运行实例的MAJOR.MINOR.PATCH应用版本
Scenario: 类型列表来源
- WHEN 系统启动并注册了 checker
- THEN
/api/meta返回的checkerTypesSHALL 与CheckerRegistry.supportedTypes完全一致
Scenario: 版本号来源
- WHEN 系统启动并确定应用版本
- THEN
/api/meta返回的versionSHALL 与启动时注入的应用版本完全一致
Scenario: 不支持的 method 请求
- WHEN 客户端使用 POST/PUT/DELETE/HEAD 等未声明 method 请求
/api/meta - THEN
/api/*通配符 SHALL 返回 JSON 404 响应
Requirement: MetaResponse 共享类型
系统 SHALL 在 src/shared/api.ts 中定义 MetaResponse 类型。
Scenario: MetaResponse 类型定义
- WHEN 前后端引用
MetaResponse类型 - THEN 该类型 SHALL 包含
checkerTypes: string[]和version: string字段