5.3 KiB
Purpose
定义 ProbeStore 的批量查询方法:getLatestChecksMap、getAllTargetStats,以及 getSummary 和 createTargetsResponse 的 N+1 查询优化规范。同时约定单次查询操作使用 db.query() 利用内置缓存。
Requirements
Requirement: 批量查询最新检查结果
系统 SHALL 提供 getLatestChecksMap 方法,通过单次 SQL 查询获取所有 target 的最新一次 check 结果,返回 Map 结构供调用方按 target_id 索引。
Scenario: 获取所有目标的最新检查
- WHEN 调用
getLatestChecksMap() - THEN 系统 SHALL 执行子查询找到每个 target_id 的 MAX(timestamp),再 JOIN 回 check_results 获取完整行,返回
Map<number, StoredCheckResult | null>
Scenario: 目标无历史记录
- WHEN 某 target 在 check_results 表中无任何记录
- THEN 该 target_id 在返回的 Map 中 SHALL 不存在对应的 key
Requirement: 批量查询目标统计
系统 SHALL 提供 getAllTargetStats 方法,通过单次 SQL GROUP BY 聚合查询获取所有 target 的拨测统计(totalChecks 和 availability)。availability 计算精度 SHALL 与 getTargetStats 一致,统一使用 Math.round(value * 100) / 100 保留两位小数。
Scenario: 获取所有目标的聚合统计
- WHEN 调用
getAllTargetStats() - THEN 系统 SHALL 执行
SELECT target_id, COUNT(*), SUM(CASE WHEN matched=1 THEN 1 ELSE 0 END) FROM check_results GROUP BY target_id,在内存中计算 availability 并返回Map<number, { totalChecks, availability }>
Scenario: 目标无历史记录
- WHEN 某 target 在 check_results 表中无任何记录
- THEN 该 target_id 在返回的 Map 中 SHALL 不存在对应的 key
Scenario: availability 精度
- WHEN 计算 availability(upCount / totalChecks * 100)
- THEN 结果 SHALL 使用
Math.round(value * 100) / 100四舍五入保留两位小数,与getTargetStats方法一致
Requirement: summary 查询使用批量方法
getSummary 方法 SHALL 使用 getLatestChecksMap 一次性获取所有 target 的最新检查结果,而非对每个 target 逐条查询。
Scenario: 统计总览使用批量查询
- WHEN 调用
store.getSummary() - THEN 系统 SHALL 调用
getLatestChecksMap()一次获取所有最新结果,在内存中遍历统计 up/down 数量,而非循环 N 次调用getLatestCheck()
Requirement: targets 列表使用批量方法
handleTargets(routes/targets.ts 中生成 TargetStatus[] 的逻辑)SHALL 使用 getLatestChecksMap、getAllTargetStats 和 getAllRecentSamples 替代逐目标查询,消除 N+1 查询。
Scenario: 目标列表使用批量查询
- WHEN 处理
GET /api/targets请求 - THEN 系统 SHALL 分别调用
getLatestChecksMap()、getAllTargetStats()、getAllRecentSamples(30)批量获取数据,在内存中组装 TargetStatus 数组,而非对每个 target 逐条查询数据库
Scenario: 目标无采样数据
- WHEN 某 target 在 getAllRecentSamples 返回的 Map 中不存在
- THEN 该 target 的 recentSamples SHALL 为空数组
Requirement: 批量查询所有目标的最近采样数据
系统 SHALL 提供 getAllRecentSamples(limit: number) 方法,通过单次 SQL 查询获取所有 target 的最近 N 条采样数据,返回 Map<number, Array<{ timestamp: string; duration_ms: number | null; matched: number }>> 结构。
Scenario: 获取所有目标的最近采样
- WHEN 调用
getAllRecentSamples(30) - THEN 系统 SHALL 通过单次 SQL 查询获取每个 target 最近 30 条记录,返回按 target_id 索引的 Map
Scenario: 目标无历史记录
- WHEN 某 target 在 check_results 表中无任何记录
- THEN 该 target_id 在返回的 Map 中 SHALL 不存在对应的 key
Scenario: 采样数据排序
- WHEN 获取采样数据
- THEN 每个 target 的记录 SHALL 按 timestamp 降序排列(最新在前)
Requirement: prepared statement 使用 query() 缓存
ProbeStore 中不涉及事务内复用的单次读/写操作 SHALL 使用 this.db.query() 而非 this.db.prepare(),利用 bun:sqlite 内置的 statement 缓存机制。
Scenario: insertCheckResult 使用 query
- WHEN 写入一条检查结果
- THEN
insertCheckResultSHALL 使用this.db.query("INSERT INTO ...").run(...)而非this.db.prepare("INSERT INTO ...").run(...)
Scenario: getHistory 查询使用 query
- WHEN 查询历史记录(包括 COUNT 和分页查询)
- THEN
getHistorySHALL 使用this.db.query(...)而非this.db.prepare(...)
Scenario: getTargetStats 查询使用 query
- WHEN 查询单目标统计
- THEN
getTargetStatsSHALL 使用this.db.query(...)而非this.db.prepare(...)
Scenario: getTrend 查询使用 query
- WHEN 查询趋势数据
- THEN
getTrendSHALL 使用this.db.query(...)而非this.db.prepare(...)
Scenario: getRecentSamples 查询使用 query
- WHEN 查询采样数据
- THEN
getRecentSamplesSHALL 使用this.db.query(...)而非this.db.prepare(...)
Scenario: syncTargets 事务保持 prepare(例外)
- WHEN 同步 targets 配置(事务内多次复用 insertStmt/updateStmt/deleteStmt)
- THEN
syncTargets方法 SHALL 保持使用this.db.prepare(),因需要在事务闭包内持有引用