feat: 配置变量系统与 target id/name 双字段标识
- 新增顶层 variables 段支持 string/number/boolean 字面量
- target 字符串字段支持 、、{...} 转义语法
- 变量解析优先级: variables -> process.env -> 默认值 -> 报错
- 完整引用保留原始类型,部分引用拼接为字符串
- 变量替换在 YAML 解析后、AJV 校验前执行
- 替换仅作用于 targets,跳过 id/type 字段
- target 新增必填 id 字段作为唯一标识,name 改为可选展示名称
- 数据库存储/API/前端全面迁移到 id 标识
- 统一 checker 运行时类型检查为 es-toolkit predicates
- 同步 delta specs 到主 specs,归档 config-variables 变更
This commit is contained in:
@@ -36,6 +36,7 @@ src/
|
||||
checker/
|
||||
types.ts 基础类型定义(ResolvedTargetBase、RawTargetConfig、DefaultsConfig、CheckResult 等基础 interface)
|
||||
config-loader.ts YAML 配置解析、契约校验、语义校验与运行期解析(输出 ResolvedConfig)
|
||||
variables.ts 配置 variables 提取、target 字符串变量替换和 unresolved-variable issue 生成
|
||||
schema/ TypeBox + Ajv 配置契约、schema fragments、issue 渲染和 schema 导出入口
|
||||
builder.ts 全量 JSON Schema 组装(遍历 registry 生成)
|
||||
fragments.ts 共享 TypeBox schema 片段(duration、size、operator 等)
|
||||
@@ -99,7 +100,8 @@ probe-config.schema.json 用户配置 JSON Schema 导出物(用于 IDE 自动
|
||||
启动流程:
|
||||
dev.ts / main.ts → readRuntimeConfig(cli args, 仅提取 configPath)
|
||||
→ bootstrap({ configPath, mode })
|
||||
→ loadConfig(yaml) → ResolvedConfig{ host, port, dataDir, maxConcurrentChecks, retentionMs, targets }
|
||||
→ loadConfig(yaml:YAML 解析 → 变量替换 → 契约校验 → 语义校验 → resolve)
|
||||
→ ResolvedConfig{ host, port, dataDir, maxConcurrentChecks, retentionMs, targets }
|
||||
→ ProbeStore(db) → store.syncTargets(targets)
|
||||
→ ProbeEngine(store, targets, maxConcurrentChecks, retentionMs) → engine.start()
|
||||
→ startServer({ config, mode, store })
|
||||
@@ -192,7 +194,7 @@ export function handleMetrics(idStr: string, url: URL, store: ProbeStore, mode:
|
||||
- **共享类型**以 `src/shared/api.ts` 为唯一源头,前后端共同引用
|
||||
- 前端不得 `import src/server/` 下的任何文件
|
||||
- **严格联合类型**优先于宽类型:如 `phase: "status" | "duration" | ...` 而非 `phase: string`
|
||||
- **后端内部扩展**:`checker/types.ts` 中 `CheckResult` 通过 `extends` 共享版本的 `ApiCheckResult` 增加 `targetName` 等内部字段
|
||||
- **后端内部扩展**:`checker/types.ts` 中 `CheckResult` 通过 `extends` 共享版本的 `ApiCheckResult` 增加 `targetId` 等内部字段
|
||||
- 存储层类型(`StoredTarget`、`StoredCheckResult`)独立定义,与 API 类型分离
|
||||
- **Checker 类型分层**:
|
||||
- `checker/types.ts` 定义 base interface(`ResolvedTargetBase`、`RawTargetConfig`、`DefaultsConfig`),使用 index signature 支持扩展
|
||||
@@ -204,7 +206,9 @@ export function handleMetrics(idStr: string, url: URL, store: ProbeStore, mode:
|
||||
|
||||
### 1.6 配置契约与校验
|
||||
|
||||
配置加载流程固定为:`unknown -> RawProbeConfig -> ValidatedProbeConfig -> ResolvedConfig`。
|
||||
配置加载流程固定为:`unknown -> 变量替换 -> RawProbeConfig -> ValidatedProbeConfig -> ResolvedConfig`。
|
||||
|
||||
变量替换阶段由 `variables.ts` 负责,在 YAML 解析之后、AJV 契约校验之前执行。顶层 `variables` 支持 string/number/boolean 字面量,target 字符串字段支持 `${key}`、`${key|default}` 和 `$${key}`,解析优先级为 `variables -> process.env -> 默认值`;替换范围仅限 `targets`,且跳过 `id` 和 `type` 字段。
|
||||
|
||||
`config-loader.ts` 只负责 YAML 解析、契约调度、公共语义校验和最终运行期解析;checker 专属规则必须下沉到对应 checker 的 `schema.ts` 和 `validate.ts`。
|
||||
|
||||
@@ -222,7 +226,7 @@ export function handleMetrics(idStr: string, url: URL, store: ProbeStore, mode:
|
||||
|
||||
契约层使用 `src/server/checker/schema/` 中的 TypeBox fragments 生成 JSON Schema,并用 Ajv 执行启动期校验。Ajv 必须保持严格拒绝模式:`allErrors: true`、不启用类型强制转换、不注入默认值、不自动删除未知字段。
|
||||
|
||||
默认对象策略是 `additionalProperties: false`。只有明确声明的动态键值表可以开放任意键名,例如 `http.headers`、`defaults.http.headers`、`expect.headers`、`cmd.env`。
|
||||
默认对象策略是 `additionalProperties: false`。只有明确声明的动态键值表可以开放任意键名,例如 `variables`、`http.headers`、`defaults.http.headers`、`expect.headers`、`cmd.env`。
|
||||
|
||||
契约校验和语义 validator 都必须返回 `ConfigValidationIssue[]`,不要在 validator 内直接拼接最终用户错误字符串。最终错误由 `formatConfigIssues()` 统一渲染,错误路径需要尽量包含 `targetName` 或 `defaults`/root 路径。
|
||||
|
||||
@@ -425,7 +429,7 @@ TcpChecker implements Checker
|
||||
|
||||
| 方法 | 用途 |
|
||||
| ------------------------------------------ | ----------------------------------------------------------- |
|
||||
| `syncTargets(targets)` | 启动期同步 targets(基于 name 做 upsert + delete 事务) |
|
||||
| `syncTargets(targets)` | 启动期同步 targets(基于配置 `id` 做 upsert + delete 事务) |
|
||||
| `insertCheckResult()` | 写入单条检查结果 |
|
||||
| `getTargets()` | 查询全部 targets(default 分组优先排序) |
|
||||
| `getLatestChecksMap()` | 批量获取每个 target 的最新检查结果(单次 SQL 聚合) |
|
||||
@@ -459,8 +463,8 @@ TcpChecker implements Checker
|
||||
|
||||
**Schema**:
|
||||
|
||||
- `targets` 表:name(UNIQUE)、type、target(展示摘要)、config(JSON)、interval_ms、timeout_ms、expect(JSON)、grp
|
||||
- `check_results` 表:target_id(FK CASCADE)、timestamp、matched(0/1)、duration_ms、status_detail、failure(JSON)
|
||||
- `targets` 表:id(TEXT PRIMARY KEY,配置 target id)、name(展示名称)、type、target(展示摘要)、config(JSON)、interval_ms、timeout_ms、expect(JSON)、grp
|
||||
- `check_results` 表:target_id(TEXT FK CASCADE,引用配置 target id)、timestamp、matched(0/1)、duration_ms、status_detail、failure(JSON)
|
||||
- 复合索引:`(target_id, timestamp)`
|
||||
|
||||
### 1.9 拨测引擎
|
||||
@@ -469,7 +473,7 @@ TcpChecker implements Checker
|
||||
- **并发控制**:`es-toolkit/Semaphore` 限制全局最大并发数(`maxConcurrentChecks`,默认 20),`acquire()` 阻塞等待
|
||||
- **Runner 选择**:`engine.runCheck()` 通过 `checkerRegistry.get(target.type)` 获取 checker,并调用 `checker.execute(target, { signal })`
|
||||
- **超时控制**:`ProbeEngine` 为每次检查创建 `AbortController` 并按 `target.timeoutMs` 触发 abort;checker 必须使用 `CheckerContext.signal` 感知超时,HTTP 将 signal 传给 `fetch()`,Cmd 在 signal abort 时 `proc.kill()`
|
||||
- **结果写入**:检查结果通过 `store.insertCheckResult()` 写入 SQLite,engine 通过 `targetNameToId` 缓存 name→id 映射
|
||||
- **结果写入**:检查结果通过 `store.insertCheckResult()` 写入 SQLite,engine 基于配置 target id 确认目标仍存在
|
||||
- **异常可观测**:`probeGroup()` 对 `Promise.allSettled` 的 rejected 结果通过索引关联 target,并写入 `phase:"internal"` 的失败记录
|
||||
- **数据清理**:当 `retentionMs > 0` 时,engine 启动时立即执行一次 `store.prune()`,之后每小时定时执行,按 `timestamp` 清理过期数据
|
||||
- **生命周期**:`start()`/`stop()` 管理定时器(含调度定时器和清理定时器),`stop()` 清理所有 `setInterval`
|
||||
|
||||
Reference in New Issue
Block a user