1
0

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:
2026-05-17 00:37:54 +08:00
parent 366b3211c8
commit 7926514986
53 changed files with 1538 additions and 333 deletions

View File

@@ -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(yamlYAML 解析 → 变量替换 → 契约校验 → 语义校验 → 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()` | 查询全部 targetsdefault 分组优先排序) |
| `getLatestChecksMap()` | 批量获取每个 target 的最新检查结果(单次 SQL 聚合) |
@@ -459,8 +463,8 @@ TcpChecker implements Checker
**Schema**
- `targets` 表:nameUNIQUE、type、target展示摘要、configJSON、interval_ms、timeout_ms、expectJSON、grp
- `check_results`target_idFK CASCADE、timestamp、matched0/1、duration_ms、status_detail、failureJSON
- `targets` 表:idTEXT PRIMARY KEY配置 target id、name展示名称、type、target展示摘要、configJSON、interval_ms、timeout_ms、expectJSON、grp
- `check_results`target_idTEXT FK CASCADE,引用配置 target id、timestamp、matched0/1、duration_ms、status_detail、failureJSON
- 复合索引:`(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` 触发 abortchecker 必须使用 `CheckerContext.signal` 感知超时HTTP 将 signal 传给 `fetch()`Cmd 在 signal abort 时 `proc.kill()`
- **结果写入**:检查结果通过 `store.insertCheckResult()` 写入 SQLiteengine 通过 `targetNameToId` 缓存 name→id 映射
- **结果写入**:检查结果通过 `store.insertCheckResult()` 写入 SQLiteengine 基于配置 target id 确认目标仍存在
- **异常可观测**`probeGroup()``Promise.allSettled` 的 rejected 结果通过索引关联 target并写入 `phase:"internal"` 的失败记录
- **数据清理**:当 `retentionMs > 0`engine 启动时立即执行一次 `store.prune()`,之后每小时定时执行,按 `timestamp` 清理过期数据
- **生命周期**`start()`/`stop()` 管理定时器(含调度定时器和清理定时器),`stop()` 清理所有 `setInterval`