- 修复 build script 引用已删除的 registerCheckers,恢复生产构建 - 生产入口添加 SIGINT/SIGTERM 优雅关闭(与 dev.ts 一致) - 新增 runtime.retention 配置(默认 7d),ProbeStore.prune() 定时清理过期数据 - parseDuration 扩展支持 h/d 单位 - 新增前端 ErrorBoundary 组件,防止渲染错误白屏 - Vite codeSplitting.groups 拆分 vendor chunks(业务代码 1180KB → 47KB) - 同步 delta specs 到主规范
144 lines
7.2 KiB
Markdown
144 lines
7.2 KiB
Markdown
## Purpose
|
||
|
||
定义基于 SQLite 的拨测数据持久化存储:targets 同步(含分组信息)、check_results 追加写入、结构化采样数据查询、时间范围和分页查询、索引与聚合查询。
|
||
|
||
## 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 提供 `getRecentSamples` 方法替代 `getSparkline`,返回包含状态信息的结构化采样数据。
|
||
|
||
#### Scenario: 获取最近采样数据
|
||
- **WHEN** 调用 `getRecentSamples(targetId, 30)`
|
||
- **THEN** 系统 SHALL 返回最多 30 条记录,每条包含 timestamp、duration_ms、matched
|
||
|
||
#### Scenario: 采样数据排序
|
||
- **WHEN** 获取采样数据
|
||
- **THEN** 记录 SHALL 按 timestamp 降序排列(最新在前)
|
||
|
||
### Requirement: 趋势数据时间范围查询
|
||
系统 SHALL 支持按任意时间范围查询趋势聚合数据,替代固定 hours 参数。
|
||
|
||
#### Scenario: 按时间范围查询趋势
|
||
- **WHEN** 查询指定 target 在 from 到 to 时间范围内的趋势数据
|
||
- **THEN** 系统 SHALL 返回按小时分组的聚合数据,包括每小时的 avgDurationMs、availability 和 totalChecks
|
||
|
||
### Requirement: 历史记录时间范围和分页查询
|
||
系统 SHALL 支持按时间范围筛选并分页查询历史记录。
|
||
|
||
#### Scenario: 按时间范围筛选历史记录
|
||
- **WHEN** 查询指定 target 在 from 到 to 时间范围内的历史记录
|
||
- **THEN** 系统 SHALL 返回该时间范围内的记录,按 timestamp 降序排列
|
||
|
||
#### Scenario: 分页查询历史记录
|
||
- **WHEN** 查询指定 page 和 pageSize 的历史记录
|
||
- **THEN** 系统 SHALL 返回对应页的数据和总记录数
|
||
|
||
### Requirement: 聚合查询支持
|
||
数据存储 SHALL 支持按时间段聚合查询,用于计算可用率、平均耗时、P99 耗时等统计指标。
|
||
|
||
#### Scenario: 计算目标可用率
|
||
- **WHEN** 查询某目标在指定时间范围内的可用率
|
||
- **THEN** 系统 SHALL 返回 matched=true 的记录数占总记录数的百分比
|
||
|
||
#### Scenario: 计算目标平均耗时
|
||
- **WHEN** 查询某目标在指定时间范围内的平均耗时
|
||
- **THEN** 系统 SHALL 返回 duration_ms 的平均值(仅计算 matched=true 的记录)
|
||
|
||
#### Scenario: 按小时聚合趋势数据
|
||
- **WHEN** 查询某目标在指定时间范围内的趋势数据
|
||
- **THEN** 系统 SHALL 返回按小时分组的聚合数据,包括每小时的平均耗时和可用率
|
||
|
||
#### Scenario: UP/DOWN 判定
|
||
- **THEN** 系统 SHALL 基于 latestCheck.matched 判定目标 UP 或 DOWN:matched=true 为 UP,matched=false 为 DOWN
|
||
|
||
### Requirement: 目标展示摘要持久化
|
||
数据存储 SHALL 为每个 target 持久化一个领域无关的展示摘要字段 `target`。
|
||
|
||
#### Scenario: HTTP target 展示摘要
|
||
- **WHEN** 同步 HTTP target
|
||
- **THEN** targets.target SHALL 存储该 target 的 URL
|
||
|
||
#### Scenario: command target 展示摘要
|
||
- **WHEN** 同步 command 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: command target config 序列化
|
||
- **WHEN** 同步 command 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 不受影响
|