1
0

feat: target 软删除机制,配置移除时保留历史数据

This commit is contained in:
2026-05-20 00:43:39 +08:00
parent 9b53c746f6
commit b591dcca97
7 changed files with 294 additions and 38 deletions

View File

@@ -5,11 +5,11 @@
## Requirements
### Requirement: SQLite 数据库初始化
系统 SHALL 使用 Bun 内置 `bun:sqlite` 模块在配置的数据目录下创建 SQLite 数据库文件,并以 WAL 模式运行。数据库 schema MUST 支持 typed checker target 和结构化检查结果targets 表 MUST 包含 `grp` 列存储分组信息,且 targets 表的 `name``description` 列 MUST 允许 NULL。
系统 SHALL 使用 Bun 内置 `bun:sqlite` 模块在配置的数据目录下创建 SQLite 数据库文件,并以 WAL 模式运行。数据库 schema MUST 支持 typed checker target 和结构化检查结果targets 表 MUST 包含 `grp` 列存储分组信息`active` 列标记活跃状态,且 targets 表的 `name``description` 列 MUST 允许 NULL。check_results 表的外键约束 SHALL 使用 `ON DELETE RESTRICT`
#### Scenario: 首次启动创建数据库
- **WHEN** 指定的数据目录下不存在数据库文件
- **THEN** 系统 SHALL 创建数据库文件并初始化 targets 表和 check_results 表check_results 表包含 idINTEGER PRIMARY KEY AUTOINCREMENT、target_idTEXT NOT NULL、timestampTEXT NOT NULL、matchedINTEGER NOT NULL、duration_msREAL、observationTEXT、failureTEXT不包含 status_detail 列,不包含 success 列
- **THEN** 系统 SHALL 创建数据库文件并初始化 targets 表(含 active INTEGER NOT NULL DEFAULT 1 列)和 check_results 表(外键约束为 ON DELETE RESTRICTcheck_results 表包含 idINTEGER PRIMARY KEY AUTOINCREMENT、target_idTEXT NOT NULL、timestampTEXT NOT NULL、matchedINTEGER NOT NULL、duration_msREAL、observationTEXT、failureTEXT不包含 status_detail 列,不包含 success 列
#### Scenario: targets name 列允许 NULL
- **WHEN** 系统首次创建 targets 表
@@ -26,19 +26,27 @@
#### Scenario: 外键约束
- **THEN** 系统 SHALL 启用 `PRAGMA foreign_keys = ON`
#### Scenario: 级联删除
- **THEN** check_results 表的外键约束 SHALL 使用 `ON DELETE CASCADE`,确保删除目标时自动清理关联结果记录
#### Scenario: 级联删除改为限制删除
- **THEN** check_results 表的外键约束 SHALL 使用 `ON DELETE RESTRICT`,确保删除 target 时数据库层面阻止操作而非级联删除关联记录
### Requirement: targets 表同步
系统 SHALL 在启动时将 YAML 配置中的目标列表同步到 SQLite targets 表,并持久化 target 类型、展示名称元信息、展示摘要、领域配置、调度配置、expect 配置、分组信息和目标说明。
系统 SHALL 在启动时将 YAML 配置中的目标列表同步到 SQLite targets 表,并持久化 target 类型、展示名称元信息、展示摘要、领域配置、调度配置、expect 配置、分组信息和目标说明。配置中不存在的 target SHALL 被标记为非活跃而非删除。
#### Scenario: 首次同步目标
- **WHEN** 数据库为空且 YAML 中定义了 N 个 typed target
- **THEN** 系统 SHALL 将所有目标插入 targets 表,包含 name、description、type、target、config、interval_ms、timeout_ms、expect 和 grp,其中 name 和 description 均可为 NULL
- **THEN** 系统 SHALL 将所有目标插入 targets 表,包含 name、description、type、target、config、interval_ms、timeout_ms、expect、grp 和 active=1,其中 name 和 description 均可为 NULL
#### Scenario: 配置变更后重新同步
- **WHEN** YAML 配置发生变更(新增、删除或修改目标)后重启
- **THEN** 系统 SHALL 根据 id 字段匹配:新增的插入、删除的移除、修改的更新(含 name、description 和 grp 字段)
- **THEN** 系统 SHALL 根据 id 字段匹配:新增的插入active=1、删除的设置 active=0不删除行、修改的更新(含 name、description 和 grp 字段),已存在的目标 SHALL 设置 active=1
#### Scenario: 配置中移除目标
- **WHEN** YAML 配置中移除了某个 target该 target 在数据库中存在且 active=1
- **THEN** 系统 SHALL 将该 target 的 active 设置为 0保留该行及所有关联的 check_results
#### Scenario: 配置中恢复已移除目标
- **WHEN** YAML 配置中重新添加了之前移除的 target数据库中 active=0
- **THEN** 系统 SHALL 将该 target 的 active 设置为 1并更新其他字段历史 check_results 保留不变
#### Scenario: 未配置 name
- **WHEN** YAML target 未配置 `name`
@@ -79,11 +87,11 @@
- **THEN** 系统 SHALL 使用索引快速定位,无需全表扫描
### Requirement: 目标列表按分组排序
系统 SHALL 保证 targets 查询结果按分组排序返回。
系统 SHALL 保证 targets 查询结果按分组排序返回,且仅返回活跃目标
#### Scenario: 分组排序查询
- **WHEN** 查询所有 targets
- **THEN** 结果 SHALL 将 "default" 分组目标排在首位,其余分组按 YAML 配置中首次出现的顺序(即 id 自增顺序)排列
- **THEN** 结果 SHALL 仅返回 active=1 的目标,将 "default" 分组目标排在首位,其余分组按 YAML 配置中首次出现的顺序(即 id 自增顺序)排列
### Requirement: 聚合查询支持
数据存储 SHALL 支持按时间段获取指标计算所需数据,用于后端应用层计算可用率、平均耗时、延迟范围、趋势分桶和可靠性指标。
@@ -105,30 +113,34 @@
- **THEN** 系统 SHALL 基于 latestCheck.matched 判定目标 UP 或 DOWNmatched=true 为 UPmatched=false 为 DOWN
### Requirement: Dashboard 数据查询支持
ProbeStore SHALL 提供 Dashboard 聚合响应所需的批量取数能力。
ProbeStore SHALL 提供 Dashboard 聚合响应所需的批量取数能力,且所有查询 SHALL 仅涉及活跃 target
#### Scenario: 批量获取最新检查
- **WHEN** Dashboard API 需要计算当前 up/down 和 lastCheckTime
- **THEN** Store SHALL 支持批量获取每个 target 的最新检查记录,避免 N+1 查询
- **THEN** Store SHALL 支持批量获取每个活跃 target 的最新检查记录,避免 N+1 查询
#### Scenario: 批量获取窗口统计基础数据
- **WHEN** Dashboard API 需要计算各 target 在指定 window 内的 totalChecks、upChecks、downChecks 和 availability
- **THEN** Store SHALL 支持按 target_id 批量返回指定时间窗口内的基础计数数据
- **THEN** Store SHALL 支持按 target_id 批量返回指定时间窗口内的基础计数数据,仅包含活跃 target
#### Scenario: 批量获取最近样本
- **WHEN** Dashboard API 需要展示 recentSamples 和计算 capped currentStreak
- **THEN** Store SHALL 支持批量获取每个 target 最近 recentLimit 条检查记录,按 target_id 分组且每组按 timestamp 降序排列
- **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 升序排列,供后端应用层计算状态翻转
- **THEN** Store SHALL 支持获取指定时间窗口内所有活跃 target 的 `{ target_id, timestamp, matched }` 序列,按 target_id 和 timestamp 升序排列,供后端应用层计算状态翻转
### Requirement: 单目标指标取数支持
ProbeStore SHALL 提供单目标 metrics 响应所需的取数能力。
ProbeStore SHALL 提供单目标 metrics 响应所需的取数能力。仅活跃 target 的指标 SHALL 可查询。
#### Scenario: 获取目标检查点序列
- **WHEN** Metrics API 需要计算趋势分桶、故障段、MTTR、最长故障、故障次数和连续状态
- **THEN** Store SHALL 支持获取指定 target 在 from 到 to 时间范围内的 `{ timestamp, matched, duration_ms }` 数组,按 timestamp 升序排列
- **THEN** Store SHALL 支持获取指定活跃 target 在 from 到 to 时间范围内的 `{ timestamp, matched, duration_ms }` 数组,按 timestamp 升序排列
#### Scenario: 目标不活跃
- **WHEN** 查询 inactive target 的指标
- **THEN** Store SHALL 返回 nullgetTargetById 不匹配 active=1 的条件)
#### Scenario: 无检查记录
- **WHEN** 时间窗口内无检查记录
@@ -180,7 +192,7 @@ ProbeStore SHALL 提供 `getTargetDurations(targetId, from, to)` 方法,返回
- **THEN** targets.config SHALL 存储 JSON包含 exec、args、cwd、env、maxOutputBytes
### Requirement: 数据清理方法
ProbeStore SHALL 提供 `prune(retentionMs: number)` 方法,删除超过保留时长的历史检查结果并返回删除行数。
ProbeStore SHALL 提供 `prune(retentionMs: number)` 方法,删除超过保留时长的历史检查结果并返回删除行数,同时清理已无关联检查结果的非活跃目标行
#### Scenario: 清理过期数据
- **WHEN** 调用 `prune(604800000)`7 天毫秒数)
@@ -193,3 +205,15 @@ ProbeStore SHALL 提供 `prune(retentionMs: number)` 方法,删除超过保留
#### Scenario: 清理不影响保留期内数据
- **WHEN** 调用 `prune()` 且存在保留期内和保留期外的记录
- **THEN** 系统 SHALL 仅删除保留期外的记录,保留期内的记录 SHALL 不受影响
#### Scenario: 清理空壳非活跃目标
- **WHEN** prune 执行完毕后,存在 active=0 的 target 且该 target 在 check_results 表中无任何关联记录
- **THEN** 系统 SHALL 删除该空壳 target 行
#### Scenario: 非活跃目标仍有历史数据时不清理
- **WHEN** 存在 active=0 的 target 但该 target 在 check_results 表中仍有关联记录
- **THEN** 系统 SHALL NOT 删除该 target 行
#### Scenario: 活跃目标永不清理
- **WHEN** 存在 active=1 的 target 且该 target 在 check_results 表中无关联记录
- **THEN** 系统 SHALL NOT 删除该 target 行