- 新增 GET /api/dashboard 合并原 summary+targets 首屏接口 - 新增 GET /api/targets/:id/metrics 合并原 stats+trend 概览接口 - 后端指标纯函数:可用率、百分位、故障段分析、连续状态、UTC 小时分桶 - ProbeStore 窗口取数方法替代全量历史查询 - SummaryCards 扩展为 4 卡片(新增异常事件数)+ 数据新鲜度展示 - 表格新增「连续」列(Tag 渲染 capped 状态) - OverviewTab 重构为 2×4 Statistic 多维度布局 - TrendChart 改为延迟范围面积图 + 红色异常标记点 - 删除旧路由(summary/targets/trend)和 computeTrendStats - 同步 delta specs 到主 specs 并归档变更
8.9 KiB
Purpose
定义基于 SQLite 的拨测数据持久化存储:targets 同步(含分组信息)、check_results 追加写入、Dashboard 和 Metrics 数据查询支持、延迟百分位取数、时间范围和分页查询、索引与聚合查询。
Requirements
Requirement: SQLite 数据库初始化
系统 SHALL 使用 Bun 内置 bun:sqlite 模块在配置的数据目录下创建 SQLite 数据库文件,并以 WAL 模式运行。数据库 schema MUST 支持 typed checker target 和结构化检查结果,targets 表 MUST 包含 grp 列存储分组信息。
Scenario: 首次启动创建数据库
- WHEN 指定的数据目录下不存在数据库文件
- THEN 系统 SHALL 创建数据库文件并初始化 targets 表和 check_results 表,check_results 表包含 id(INTEGER PRIMARY KEY AUTOINCREMENT)、target_id(INTEGER NOT NULL)、timestamp(TEXT NOT NULL)、matched(INTEGER NOT NULL)、duration_ms(REAL)、status_detail(TEXT)、failure(TEXT),不包含 success 列
Scenario: 数据目录不存在
- WHEN 配置的数据目录路径不存在
- THEN 系统 SHALL 自动创建该目录
Scenario: 数据库已存在时启动
- WHEN 数据库文件已存在
- THEN 系统 SHALL 直接打开数据库,不重新建表
Scenario: 外键约束
- THEN 系统 SHALL 启用
PRAGMA foreign_keys = ON
Scenario: 级联删除
- THEN check_results 表的外键约束 SHALL 使用
ON DELETE CASCADE,确保删除目标时自动清理关联结果记录
Requirement: targets 表同步
系统 SHALL 在启动时将 YAML 配置中的目标列表同步到 SQLite targets 表,并持久化 target 类型、展示摘要、领域配置、调度配置、expect 配置和分组信息。
Scenario: 首次同步目标
- WHEN 数据库为空且 YAML 中定义了 N 个 typed target
- THEN 系统 SHALL 将所有目标插入 targets 表,包含 name、type、target、config、interval_ms、timeout_ms、expect 和 grp
Scenario: 配置变更后重新同步
- WHEN YAML 配置发生变更(新增、删除或修改目标)后重启
- THEN 系统 SHALL 根据 name 字段匹配:新增的插入、删除的移除、修改的更新(含 grp 字段)
Requirement: check_results 表追加写入
系统 SHALL 将每次检查结果追加写入 check_results 表,不更新或删除已有记录。
Scenario: 写入检查结果
- WHEN 一次 checker 执行完成
- THEN 系统 SHALL 插入一条包含 target_id、timestamp、matched、duration_ms、status_detail、failure 的记录
Scenario: 写入结构化失败信息
- WHEN checker 执行失败或 expect 不匹配
- THEN 系统 SHALL 将首个失败原因序列化写入 failure 字段
Requirement: 时间范围查询索引
系统 SHALL 在 check_results 表上创建 (target_id, timestamp) 复合索引,加速按目标和时间范围的查询。
Scenario: 查询某目标的历史记录
- WHEN 查询指定 target_id 的最近 N 条记录
- THEN 系统 SHALL 使用索引快速定位,无需全表扫描
Requirement: 目标列表按分组排序
系统 SHALL 保证 targets 查询结果按分组排序返回。
Scenario: 分组排序查询
- WHEN 查询所有 targets
- THEN 结果 SHALL 将 "default" 分组目标排在首位,其余分组按 YAML 配置中首次出现的顺序(即 id 自增顺序)排列
Requirement: 聚合查询支持
数据存储 SHALL 支持按时间段获取指标计算所需数据,用于后端应用层计算可用率、平均耗时、延迟范围、趋势分桶和可靠性指标。
Scenario: 轻数据库计算边界
- WHEN 实现指标相关数据查询
- THEN 数据库 SHALL 主要负责存储、过滤、排序、分页、LIMIT 和标准 SQL 基础聚合,业务指标语义 SHALL 在后端应用层计算
Scenario: 可使用的基础 SQL 聚合
- WHEN 查询需要减少返回数据量
- THEN 系统 MAY 使用标准 SQL 的 COUNT、SUM(CASE)、AVG、MIN、MAX、GROUP BY 等基础能力
Scenario: 避免数据库承载业务语义
- WHEN 实现状态翻转、故障段、MTTR、最长故障、连续状态、百分位或趋势分桶
- THEN 系统 SHALL 在后端应用层实现这些规则,不依赖 SQLite 专有函数或复杂窗口函数承载业务语义
Scenario: UP/DOWN 判定
- WHEN 系统需要判定目标当前状态
- THEN 系统 SHALL 基于 latestCheck.matched 判定目标 UP 或 DOWN:matched=true 为 UP,matched=false 为 DOWN
Requirement: Dashboard 数据查询支持
ProbeStore SHALL 提供 Dashboard 聚合响应所需的批量取数能力。
Scenario: 批量获取最新检查
- WHEN Dashboard API 需要计算当前 up/down 和 lastCheckTime
- THEN Store SHALL 支持批量获取每个 target 的最新检查记录,避免 N+1 查询
Scenario: 批量获取窗口统计基础数据
- WHEN Dashboard API 需要计算各 target 在指定 window 内的 totalChecks、upChecks、downChecks 和 availability
- THEN Store SHALL 支持按 target_id 批量返回指定时间窗口内的基础计数数据
Scenario: 批量获取最近样本
- WHEN Dashboard API 需要展示 recentSamples 和计算 capped currentStreak
- THEN Store SHALL 支持批量获取每个 target 最近 recentLimit 条检查记录,按 target_id 分组且每组按 timestamp 降序排列
Scenario: 获取 Dashboard 异常事件序列
- WHEN Dashboard API 需要计算 incidents
- THEN Store SHALL 支持获取指定时间窗口内所有 target 的
{ target_id, timestamp, matched }序列,按 target_id 和 timestamp 升序排列,供后端应用层计算状态翻转
Requirement: 单目标指标取数支持
ProbeStore SHALL 提供单目标 metrics 响应所需的取数能力。
Scenario: 获取目标检查点序列
- WHEN Metrics API 需要计算趋势分桶、故障段、MTTR、最长故障、故障次数和连续状态
- THEN Store SHALL 支持获取指定 target 在 from 到 to 时间范围内的
{ timestamp, matched, duration_ms }数组,按 timestamp 升序排列
Scenario: 无检查记录
- WHEN 时间窗口内无检查记录
- THEN Store SHALL 返回空数组
Requirement: 目标延迟百分位取数
ProbeStore SHALL 提供 getTargetDurations(targetId, from, to) 方法,返回时间窗口内所有成功检查的 duration_ms 数组。
Scenario: 获取延迟数据
- WHEN 调用
getTargetDurations(targetId, from, to) - THEN 系统 SHALL 返回该目标在时间范围内所有 matched=1 且 duration_ms 不为 null 的 duration_ms 值数组
Scenario: 延迟数据排序
- WHEN 获取延迟数据
- THEN 返回数组 SHALL 按 duration_ms 升序排列,供后端应用层计算 P95/P99
Scenario: 无成功检查
- WHEN 时间窗口内无 matched=1 且 duration_ms 不为 null 的记录
- THEN 系统 SHALL 返回空数组
Requirement: 历史记录时间范围和分页查询
系统 SHALL 继续支持按时间范围筛选并分页查询历史记录。
Scenario: 按时间范围筛选历史记录
- WHEN 查询指定 target 在 from 到 to 时间范围内的历史记录
- THEN 系统 SHALL 返回该时间范围内的记录,按 timestamp 降序排列
Scenario: 分页查询历史记录
- WHEN 查询指定 page 和 pageSize 的历史记录
- THEN 系统 SHALL 返回对应页的数据和总记录数
Requirement: 目标展示摘要持久化
数据存储 SHALL 为每个 target 持久化一个领域无关的展示摘要字段 target。
Scenario: HTTP target 展示摘要
- WHEN 同步 HTTP target
- THEN targets.target SHALL 存储该 target 的 URL
Scenario: cmd target 展示摘要
- WHEN 同步 cmd target
- THEN targets.target SHALL 存储由 exec 和 args 组成的命令摘要
Scenario: HTTP target config 序列化
- WHEN 同步 HTTP target
- THEN targets.config SHALL 存储 JSON,包含 url、method、headers、body、maxBodyBytes、ignoreSSL、maxRedirects
Scenario: cmd target config 序列化
- WHEN 同步 cmd target
- THEN targets.config SHALL 存储 JSON,包含 exec、args、cwd、env、maxOutputBytes
Requirement: 数据清理方法
ProbeStore SHALL 提供 prune(retentionMs: number) 方法,删除超过保留时长的历史检查结果并返回删除行数。
Scenario: 清理过期数据
- WHEN 调用
prune(604800000)(7 天毫秒数) - THEN 系统 SHALL 删除
check_results表中timestamp早于当前时间减去 604800000 毫秒的所有记录,并返回实际删除的行数
Scenario: 无过期数据
- WHEN 调用
prune()但所有记录都在保留期内 - THEN 系统 SHALL 返回 0,不删除任何记录
Scenario: 清理不影响保留期内数据
- WHEN 调用
prune()且存在保留期内和保留期外的记录 - THEN 系统 SHALL 仅删除保留期外的记录,保留期内的记录 SHALL 不受影响