1
0

refactor: checker 模块内聚化 — 每个 checker 自包含于独立目录

将 checker 架构重构为完全内聚模式:每个 checker 目录包含自身的
types、schema、validate、execute、expect 和 index,新增 checker
只需创建一个目录并在 runner/index.ts 添加一行注册。

主要变更:
- runner/shared/ 拆分:断言基础设施迁入 checker/expect/,
  body.ts 迁入 http/,text.ts 迁入 command/
- config-contract/ 重命名为 schema/,schema.ts → builder.ts
- size.ts + parseDuration 合并为 utils.ts
- 顶层 types.ts 改为 base interface + index signature,
  checker 专属类型下沉到各自 types.ts
- runner/index.ts 改为显式数组注册模式
- 更新 DEVELOPMENT.md 项目结构和开发新 Checker 指南
This commit is contained in:
2026-05-13 14:38:21 +08:00
parent c396c29402
commit bb6b2bc20b
52 changed files with 789 additions and 820 deletions

View File

@@ -34,33 +34,44 @@ src/
history.ts GET /api/targets/:id/history
trend.ts GET /api/targets/:id/trend
checker/
types.ts 类型定义
types.ts 基础类型定义ResolvedTargetBase、RawTargetConfig、DefaultsConfig 等 base interface
config-loader.ts YAML 配置解析、契约校验、语义校验与运行期解析
config-contract/ TypeBox + Ajv 配置契约、schema fragments、issue 渲染和 schema 导出入口
schema/ TypeBox + Ajv 配置契约、schema fragments、issue 渲染和 schema 导出入口
builder.ts 全量 JSON Schema 组装(遍历 registry 生成)
fragments.ts 共享 TypeBox schema 片段duration、size、operator 等)
validate.ts Ajv 契约校验入口
issues.ts 校验问题类型与渲染
types.ts schema 层类型
export.ts JSON Schema 文件导出
store.ts SQLite 数据存储
engine.ts 调度引擎(按 interval 分组的 es-toolkit groupBy + Semaphore 并发控制)
size.ts 大小单位解析
utils.ts 共享工具函数parseSize、parseDuration
expect/ 共享 expect 断言基础设施(跨 checker 复用)
types.ts ExpectResult 等共享断言类型
failure.ts 失败信息构造errorFailure、mismatchFailure
operator.ts 操作符系统applyOperator、checkExpectValue、evaluateJsonPath
duration.ts 耗时断言checkDuration
validate-operator.ts 操作符语义校验validateOperatorObject、isJsonValue
runner/ Checker 统一抽象与注册机制
types.ts CheckerDefinition、CheckerContext、ResolveContext
types.ts CheckerDefinition、CheckerContext、CheckerSchemas、ResolveContext
registry.ts CheckerRegistry 注册中心
index.ts 注册入口(registerCheckers
shared/ 共享 expect 断言和启动期 validator跨 checker 复用
failure.ts 失败信息类型
operator.ts 操作符系统applyOperator、evaluateJsonPath
duration.ts 耗时断言
text.ts 文本规则断言
index.ts 注册入口(显式数组 + 循环注册
http/ HTTP Checker自包含模块
index.ts 模块入口re-export HttpChecker
types.ts HTTP 专属类型ResolvedHttpTarget、HttpTargetConfig、HttpExpectConfig 等
schema.ts HTTP TypeBox 契约defaults、target.http、expect
execute.ts HttpChecker 类resolve/execute/serialize/validate
expect.ts HTTP 专用断言checkStatus、checkHeaders
validate.ts HTTP 语义校验URL、body rules、header operators 等)
body.ts Body 规则断言JSONPath/XPath/CSS/contains/regex
validate.ts 共享 operator/text/body 语义校验
http/ HTTP Checker 子包
contract.ts HTTP defaults、target.http、expect TypeBox 契约
runner.ts HttpCheckerresolve/execute/serialize
expect.ts HTTP 专用断言status/headers
validate.ts HTTP 专属启动期语义校验
command/ Command Checker 子包
contract.ts Command defaults、target.command、expect TypeBox 契约
runner.ts CommandCheckerresolve/execute/serialize
expect.ts Command 专用断言exitCode
validate.ts Command 专属启动期语义校验
command/ Command Checker自包含模块
index.ts 模块入口re-export CommandChecker
types.ts Command 专属类型ResolvedCommandTarget、CommandTargetConfig、CommandExpectConfig 等)
schema.ts Command TypeBox 契约defaults、target.command、expect
execute.ts CommandChecker 类resolve/execute/serialize/validate
expect.ts Command 专用断言checkExitCode
validate.ts Command 语义校验text rules 等)
text.ts 文本规则断言checkTextRules
shared/
api.ts 前后端共享 TypeScript 类型
web/ Vite + React 前端 Dashboard
@@ -149,15 +160,19 @@ export function handleXxx(params, store: ProbeStore, method: string, mode: Runti
- **严格联合类型**优先于宽类型:如 `phase: "status" | "duration" | ...` 而非 `phase: string`
- **后端内部扩展**`checker/types.ts``CheckResult` 通过 `extends` 共享版本的 `ApiCheckResult` 增加 `targetName` 等内部字段
- 存储层类型(`StoredTarget``StoredCheckResult`)独立定义,与 API 类型分离
- 配置类型按生命周期区分YAML 解析后的 `RawProbeConfig`、已通过契约与语义校验的 `ValidatedProbeConfig`、运行期使用的 `ResolvedConfig`/`ResolvedTarget`
- **Checker 类型分层**
- `checker/types.ts` 定义 base interface`ResolvedTargetBase``RawTargetConfig``DefaultsConfig`),使用 index signature 支持扩展
- 各 checker 在自己的 `types.ts` 中定义具体类型(如 `ResolvedHttpTarget``ResolvedCommandTarget`),满足 base interface 约束
- 中间层engine、store、config-loader只依赖 base interface不感知具体 checker 类型
- Checker 内部通过 `as` 类型断言将 base 窄化为具体类型
### 1.6 配置契约与校验
配置加载流程固定为:`unknown -> RawProbeConfig -> ValidatedProbeConfig -> ResolvedConfig`
`config-loader.ts` 只负责 YAML 解析、契约调度、公共语义校验和最终运行期解析checker 专属规则必须下沉到对应 checker 的 `contract.ts``validate.ts`
`config-loader.ts` 只负责 YAML 解析、契约调度、公共语义校验和最终运行期解析checker 专属规则必须下沉到对应 checker 的 `schema.ts``validate.ts`
契约层使用 `src/server/checker/config-contract/` 中的 TypeBox fragments 生成 JSON Schema并用 Ajv 执行启动期校验。Ajv 必须保持严格拒绝模式:`allErrors: true`、不启用类型强制转换、不注入默认值、不自动删除未知字段。
契约层使用 `src/server/checker/schema/` 中的 TypeBox fragments 生成 JSON Schema并用 Ajv 执行启动期校验。Ajv 必须保持严格拒绝模式:`allErrors: true`、不启用类型强制转换、不注入默认值、不自动删除未知字段。
默认对象策略是 `additionalProperties: false`。只有明确声明的动态键值表可以开放任意键名,例如 `http.headers``defaults.http.headers``expect.headers``command.env`
@@ -167,9 +182,7 @@ export function handleXxx(params, store: ProbeStore, method: string, mode: Runti
### 1.7 开发新 Checker
Checker 是本项目的核心扩展单元。得益于插件式注册架构,完成一个新 checker 并注册后,**配置契约组装、引擎调度、数据存储、API 层会自动走 registry 委托链路**,无需在这些中间层添加新的 type 分支
当前 checker 执行链路已经注册化,但新增 checker 仍需更新中央类型定义、默认注册入口、前端展示常量、配置示例、用户/开发文档和测试。下文清单以这些必要更新为准。
Checker 是本项目的核心扩展单元。架构设计目标是**完全内聚**:每个 checker 是 `src/server/checker/runner/<type>/` 下的自包含目录,包含该 checker 所需的全部类型、schema、校验、执行逻辑和断言。新增一个 checker 只需创建一个目录并在 `runner/index.ts` 中添加一行注册
以下以新增 `tcp` 类型 checker 为例,说明完整的开发步骤。
@@ -178,108 +191,74 @@ Checker 是本项目的核心扩展单元。得益于插件式注册架构,完
```
checkerRegistry单例
├── registerCheckers() ← 注册入口,所有 checker 在此集中注册
│ ├── HttpChecker
│ ├── CommandChecker
│ └── TcpChecker ← 新增
├── runner/index.ts ← 显式数组注册,新增 checker 只需一行
│ ├── new HttpChecker()
│ ├── new CommandChecker()
│ └── new TcpChecker() ← 新增
├── config-contract/schema.ts ← 自动遍历 registry 生成全量 JSON Schema
├── config-loader.ts ← 自动遍历 registry 调用 validate() + resolve()
├── engine.ts ← 自动按 target.type 分发到 execute()
── store.ts ← 自动按 target.type 分发到 serialize()
├── schema/builder.ts ← 自动遍历 registry 生成全量 JSON Schema
├── schema/validate.ts ← 自动遍历 registry 构建 Ajv 校验
├── config-loader.ts ← 自动遍历 registry 调用 validate() + resolve()
── engine.ts ← 自动按 target.type 分发到 execute()
└── store.ts ← 自动按 target.type 分发到 serialize()
```
每个 checker `src/server/checker/runner/<type>/` 下的自包含模块,包含四个文件
每个 checker 目录的标准文件结构
| 文件 | 职责 |
| ------------- | ------------------------------------------------------------------------------------- |
| `contract.ts` | TypeBox 契约 schemaconfig / defaults / expect 三部分) |
| `index.ts` | 模块入口re-export Checker 类 |
| `types.ts` | Checker 专属类型ResolvedXxxTarget、XxxTargetConfig、XxxExpectConfig 等) |
| `schema.ts` | TypeBox 契约 schemaconfig / defaults / expect 三部分) |
| `validate.ts` | 启动期语义校验JSON Schema 无法表达的规则) |
| `runner.ts` | Checker 类resolve默认值合并 + 解析、execute执行检查、serializeDB 持久化) |
| `execute.ts` | Checker 类resolve默认值合并 + 解析、execute执行检查、serializeDB 持久化) |
| `expect.ts` | Checker 专用断言函数 |
| `*.ts` | 其他 checker 专属逻辑(如 http/body.ts、command/text.ts |
#### 1.7.2 步骤一:定义类型
#### 1.7.2 步骤一:创建 Checker 目录与类型
`src/server/checker/types.ts`添加 checker 专属类型接口,并更新联合类型
`src/server/checker/runner/tcp/types.ts`定义 checker 专属类型:
```typescript
// 1. 添加 TargetConfigYAML 中 target.tcp 字段的原始类型)
import type { ResolvedTargetBase } from "../../types";
export interface TcpTargetConfig {
host: string;
port: number;
timeout?: number;
connectTimeout?: number;
}
// 2. 添加 ExpectConfig 扩展(如果 checker 有专属 expect 字段)
export interface TcpExpectConfig {
connected?: boolean;
maxDurationMs?: number;
}
// 3. 添加 DefaultsConfigdefaults.tcp 字段)
export interface TcpDefaultsConfig {
timeout?: number;
connectTimeout?: number;
}
// 4. 添加 Resolved 变体(运行期已合并默认值、已解析路径)
export interface ResolvedTcpTarget {
type: "tcp";
name: string;
group: string;
intervalMs: number;
timeoutMs: number;
export interface ResolvedTcpTarget extends ResolvedTargetBase {
expect?: TcpExpectConfig;
tcp: {
connectTimeout: number;
host: string;
port: number;
connectTimeout: number;
};
expect?: TcpExpectConfig;
type: "tcp";
}
```
然后更新以下联合类型:
```typescript
// TargetConfig 联合 — 新增一个分支
export type TargetConfig = BaseTargetConfig &
(
| { http: HttpTargetConfig; type: "http" }
| { command: CommandTargetConfig; type: "command" }
| { tcp: TcpTargetConfig; type: "tcp" } // ← 新增
);
// ResolvedTarget 联合
export type ResolvedTarget = ResolvedHttpTarget | ResolvedCommandTarget | ResolvedTcpTarget; // ← 新增
// DefaultsConfig — 新增可选字段
export interface DefaultsConfig {
interval?: string;
timeout?: string;
http?: HttpDefaultsConfig;
command?: CommandDefaultsConfig;
tcp?: TcpDefaultsConfig; // ← 新增
}
// TargetType 联合
export type TargetType = "command" | "http" | "tcp"; // ← 新增
// ExpectConfig — 如有专属字段则扩展
export interface ExpectConfig {
// ... 现有字段
connected?: boolean; // ← TcpChecker 专属(如果复用公共字段则不需要)
}
```
**注意**:不需要修改顶层 `checker/types.ts`。base interface 使用 index signature`[key: string]: unknown`checker 专属类型通过 `extends ResolvedTargetBase` 自动兼容。
#### 1.7.3 步骤二:创建 TypeBox 契约 Schema
`src/server/checker/runner/tcp/contract.ts` 中定义三部分 schema
`src/server/checker/runner/tcp/schema.ts` 中定义三部分 schema
```typescript
import { Type } from "@sinclair/typebox";
import type { CheckerSchemas } from "../types";
import { sizeSchema } from "../../config-contract/fragments"; // 复用共享 fragments
export const tcpCheckerSchemas: CheckerSchemas = {
// target.tcp 字段的 schema
config: Type.Object(
{
host: Type.String(),
@@ -289,7 +268,6 @@ export const tcpCheckerSchemas: CheckerSchemas = {
{ additionalProperties: false },
),
// defaults.tcp 字段的 schema
defaults: Type.Object(
{
connectTimeout: Type.Optional(Type.Integer({ minimum: 100 })),
@@ -297,7 +275,6 @@ export const tcpCheckerSchemas: CheckerSchemas = {
{ additionalProperties: false },
),
// target.expect 中 tcp 专属字段的 schema如果无专属字段则用 Type.Object({})
expect: Type.Object(
{
connected: Type.Optional(Type.Boolean()),
@@ -307,12 +284,11 @@ export const tcpCheckerSchemas: CheckerSchemas = {
};
```
**可复用的共享 fragments**(来自 `config-contract/fragments.ts`
**可复用的共享 fragments**(来自 `schema/fragments.ts`
| Fragment | 用途 |
| ---------------------------- | ---------------------------------------------- |
| `durationSchema` | 时长字符串(`"30s"``"5m"``"500ms"` |
| `httpMethodSchema` | HTTP 方法枚举 |
| `sizeSchema` | 大小单位(字符串如 `"10MB"` 或整数) |
| `statusCodePatternSchema` | 状态码(`100`-`599``"2xx"` |
| `stringMapSchema` | `Record<string, string>`(用于 headers / env |
@@ -328,79 +304,64 @@ export const tcpCheckerSchemas: CheckerSchemas = {
`src/server/checker/runner/tcp/validate.ts` 中实现 JSON Schema 无法表达的语义规则:
```typescript
import type { ConfigValidationIssue } from "../../config-contract/issues";
import type { ConfigValidationIssue } from "../../schema/issues";
import type { CheckerValidationInput } from "../types";
import { issue } from "../../config-contract/issues";
import { issue } from "../../schema/issues";
export function validateTcpConfig(input: CheckerValidationInput): ConfigValidationIssue[] {
const issues: ConfigValidationIssue[] = [];
// 1. 校验 defaults.tcp如需要
const defaults = input.defaults.tcp;
if (defaults) {
// 语义校验示例connectTimeout 不能超过某个上限
}
// 2. 遍历所有 tcp 类型的 target
for (const target of input.targets) {
if (target.type !== "tcp") continue;
const name = target.name;
const tcp = target["tcp"] as { host?: string } | undefined;
// 校验 target.tcp 中的语义规则
const tcp = (target as any).tcp;
if (tcp) {
// 示例host 不能为空字符串
if (typeof tcp.host === "string" && tcp.host.trim() === "") {
issues.push(issue("invalid-value", "tcp.host", "host 不能为空字符串", name));
}
if (tcp && typeof tcp.host === "string" && tcp.host.trim() === "") {
issues.push(issue("invalid-value", "tcp.host", "host 不能为空字符串", name));
}
// 校验 expect如有公共部分可使用 shared/validate.ts 的工具函数)
// validateBodyRules、validateTextRules、validateOperatorObject 等
}
return issues;
}
```
**共享校验工具**`runner/shared/validate.ts`
**共享校验工具**`expect/validate-operator.ts`
| 函数 | 用途 |
| --------------------------------------------------------- | --------------------------------- |
| `validateBodyRules(body, path, targetName)` | 校验 body 规则数组 |
| `validateTextRules(rules, path, targetName)` | 校验文本规则数组stdout/stderr |
| `validateOperatorObject(ops, path, targetName, options?)` | 校验操作符对象 |
| `validateJsonPath(path, rulePath, targetName)` | 校验 JSONPath 格式 |
| 函数 | 用途 |
| --------------------------------------------------------- | ---------------------- |
| `validateOperatorObject(ops, path, targetName, options?)` | 校验操作符对象 |
| `isJsonValue(value)` | 判断是否为合法 JSON 值 |
#### 1.7.5 步骤四:实现 Checker 类
`src/server/checker/runner/tcp/runner.ts` 中实现 `CheckerDefinition` 接口的全部成员:
`src/server/checker/runner/tcp/execute.ts` 中实现 `CheckerDefinition` 接口的全部成员:
```typescript
import type { CheckResult, ResolvedTarget, TargetConfig } from "../../types";
import type { CheckResult, RawTargetConfig, ResolvedTargetBase } from "../../types";
import type { Checker, CheckerContext, CheckerValidationInput, ResolveContext } from "../types";
import { tcpCheckerSchemas } from "./contract";
import { checkDuration } from "../../expect/duration";
import { errorFailure } from "../../expect/failure";
import { tcpCheckerSchemas } from "./schema";
import type { ResolvedTcpTarget, TcpExpectConfig, TcpTargetConfig } from "./types";
import { validateTcpConfig } from "./validate";
export class TcpChecker implements Checker {
readonly configKey = "tcp"; // YAML 中 target.tcp / defaults.tcp 的键名
readonly type = "tcp"; // target.type 的判别值
readonly configKey = "tcp";
readonly type = "tcp";
readonly schemas = tcpCheckerSchemas;
// 启动期语义校验入口
validate(input: CheckerValidationInput): ConfigValidationIssue[] {
validate(input: CheckerValidationInput) {
return validateTcpConfig(input);
}
// 将原始配置解析为运行期配置(合并默认值、解析路径和单位)
resolve(target: TargetConfig, context: ResolveContext): ResolvedTarget {
const t = target as TargetConfig & { tcp: TcpTargetConfig; type: "tcp" };
const defaults = context.defaults.tcp;
resolve(target: RawTargetConfig, context: ResolveContext): ResolvedTargetBase {
const t = target as RawTargetConfig & { tcp: TcpTargetConfig; type: "tcp" };
const defaults = context.defaults["tcp"] as { connectTimeout?: number } | undefined;
return {
expect: target.expect,
expect: target.expect as TcpExpectConfig | undefined,
group: target.group ?? "default",
intervalMs: context.defaultIntervalMs,
name: t.name,
@@ -414,33 +375,33 @@ export class TcpChecker implements Checker {
} satisfies ResolvedTcpTarget;
}
// 执行实际检查,评估 expect返回 CheckResult
async execute(target: ResolvedTarget, ctx: CheckerContext): Promise<CheckResult> {
async execute(target: ResolvedTargetBase, ctx: CheckerContext): Promise<CheckResult> {
const t = target as ResolvedTcpTarget;
const timestamp = new Date().toISOString();
const start = performance.now();
try {
// 执行检查逻辑(如 TCP 连接
// ...
// 评估 expect 规则
// 首个失败即停止,返回 failure
// 执行 TCP 连接检查...
const durationMs = Math.round(performance.now() - start);
return {
durationMs,
failure: null,
matched: true,
statusDetail: "TCP connected",
targetName: t.name,
timestamp,
};
const durationResult = checkDuration(durationMs, t.expect?.maxDurationMs);
if (!durationResult.matched) {
return {
durationMs,
failure: durationResult.failure,
matched: false,
statusDetail: "TCP connected",
targetName: t.name,
timestamp,
};
}
return { durationMs, failure: null, matched: true, statusDetail: "TCP connected", targetName: t.name, timestamp };
} catch (error) {
const durationMs = Math.round(performance.now() - start);
return {
durationMs,
failure: errorFailure("connection", "connection", isError(error) ? error.message : String(error)),
failure: errorFailure("connection", "connection", String(error)),
matched: false,
statusDetail: null,
targetName: t.name,
@@ -449,11 +410,10 @@ export class TcpChecker implements Checker {
}
}
// 序列化为 DB 存储格式
serialize(target: ResolvedTarget): { config: string; target: string } {
serialize(target: ResolvedTargetBase): { config: string; target: string } {
const t = target as ResolvedTcpTarget;
return {
config: JSON.stringify({ host: t.tcp.host, port: t.tcp.port, connectTimeout: t.tcp.connectTimeout }),
config: JSON.stringify({ connectTimeout: t.tcp.connectTimeout, host: t.tcp.host, port: t.tcp.port }),
target: `${t.tcp.host}:${t.tcp.port}`,
};
}
@@ -464,7 +424,7 @@ export class TcpChecker implements Checker {
- 只做默认值合并、路径解析、单位转换,**不执行校验**
- 返回 `satisfies ResolvedXxxTarget` 确保类型正确
- 通过 `context.defaults[this.configKey]` 访问 checker 专属默认值
- 通过 `context.defaults[this.configKey]` 访问 checker 专属默认值(需 `as` 断言为具体类型)
**`execute()` 规范**
@@ -475,43 +435,59 @@ export class TcpChecker implements Checker {
- 异常时使用 `errorFailure(phase, path, message)` 构造 failure
- 不匹配时使用 `mismatchFailure(phase, path, expected, actual, message)` 构造 failure
**可用的共享断言工具**`runner/shared/`
**可用的共享断言工具**`checker/expect/`
| 模块 | 函数 | 用途 |
| ------------- | ----------------------------------------------------- | ---------------------- |
| `failure.ts` | `errorFailure(phase, path, msg)` | 构造错误类型 failure |
| `failure.ts` | `mismatchFailure(phase, path, expected, actual, msg)` | 构造不匹配类型 failure |
| `duration.ts` | `checkDuration(ms, maxMs?)` | 耗时断言 |
| `body.ts` | `checkBodyExpect(body, rules)` | Body 规则断言 |
| `text.ts` | `checkTextRules(text, rules, phase)` | 文本规则断言 |
| `operator.ts` | `applyOperator(actual, operator)` | 执行单个操作符比较 |
| `operator.ts` | `evaluateJsonPath(json, path)` | JSONPath 提取 |
| 模块 | 函数 | 用途 |
| ---------------------- | ----------------------------------------------------- | ---------------------- |
| `failure.ts` | `errorFailure(phase, path, msg)` | 构造错误类型 failure |
| `failure.ts` | `mismatchFailure(phase, path, expected, actual, msg)` | 构造不匹配类型 failure |
| `duration.ts` | `checkDuration(ms, maxMs?)` | 耗时断言 |
| `operator.ts` | `applyOperator(actual, operator)` | 执行单个操作符比较 |
| `operator.ts` | `evaluateJsonPath(json, path)` | JSONPath 提取 |
| `validate-operator.ts` | `validateOperatorObject(ops, path, name)` | 操作符语义校验 |
#### 1.7.6 步骤五:注册 Checker
**Checker 专属断言**(如需要)放在同目录的 `expect.ts` 中,参考 `http/expect.ts`checkStatus、checkHeaders`command/expect.ts`checkExitCode
`src/server/checker/runner/index.ts` 中注册:
#### 1.7.6 步骤五:创建模块入口并注册
创建 `src/server/checker/runner/tcp/index.ts`
```typescript
import { TcpChecker } from "./tcp/runner"; // ← 新增导入
export { TcpChecker } from "./execute";
```
export function registerCheckers(registry = checkerRegistry): void {
registry.register(new HttpChecker());
registry.register(new CommandChecker());
registry.register(new TcpChecker()); // ← 新增注册
`src/server/checker/runner/index.ts` 中添加一行导入和一个数组元素:
```typescript
import { CommandChecker } from "./command";
import { HttpChecker } from "./http";
import { TcpChecker } from "./tcp"; // ← 新增
import { CheckerRegistry } from "./registry";
const checkers = [new HttpChecker(), new CommandChecker(), new TcpChecker()]; // ← 新增
export function createDefaultCheckerRegistry(): CheckerRegistry {
const registry = new CheckerRegistry();
for (const checker of checkers) {
registry.register(checker);
}
return registry;
}
export const checkerRegistry = createDefaultCheckerRegistry();
```
注册后,以下管线会通过 registry 自动委托,**无需新增 type 分支**
| 模块 | 自动行为 |
| ----------------------------- | ------------------------------------------------------------------------ |
| `config-contract/schema.ts` | 遍历 registry 生成全量 JSON Schemadefaults.tcp + target.tcp + expect |
| `config-contract/validate.ts` | 按注册 checker 构建 Ajv 校验,自动识别 `type: tcp` |
| `config-loader.ts` | 遍历 registry 调用每个 checker 的 `validate()` + `resolve()` |
| `engine.ts` | 按 `target.type` 从 registry 取对应 checker 执行 `execute()` |
| `store.ts` | 按 `target.type` 从 registry 取对应 checker 执行 `serialize()` |
| 模块 | 自动行为 |
| -------------------- | ------------------------------------------------------------------------ |
| `schema/builder.ts` | 遍历 registry 生成全量 JSON Schemadefaults.tcp + target.tcp + expect |
| `schema/validate.ts` | 按注册 checker 构建 Ajv 校验,自动识别 `type: tcp` |
| `config-loader.ts` | 遍历 registry 调用每个 checker 的 `validate()` + `resolve()` |
| `engine.ts` | 按 `target.type` 从 registry 取对应 checker 执行 `execute()` |
| `store.ts` | 按 `target.type` 从 registry 取对应 checker 执行 `serialize()` |
注意:自动适配指上述中间层不需要新增 `switch/case` 或类型分支;开发者仍需按后续步骤更新类型、注册、前端展示、示例、文档和测试。
注意:自动适配指上述中间层不需要新增 `switch/case` 或类型分支;开发者仍需按后续步骤更新前端展示常量、配置示例、文档和测试。
#### 1.7.7 步骤六:更新前端展示
@@ -546,13 +522,14 @@ export function registerCheckers(registry = checkerRegistry): void {
#### 1.7.10 完整检查清单
```
□ src/server/checker/types.ts — 新增类型接口 + 更新联合类型
□ src/server/checker/runner/tcp/contract.ts — TypeBox schemas
□ src/server/checker/runner/tcp/types.ts — 专属类型extends ResolvedTargetBase
□ src/server/checker/runner/tcp/schema.ts — TypeBox schemas
□ src/server/checker/runner/tcp/validate.ts — 语义校验
□ src/server/checker/runner/tcp/runner.ts — Checker 类
□ src/server/checker/runner/tcp/expect.ts — 专用断言(如需要)
□ src/server/checker/runner/index.ts — 注册
□ src/web/constants/target-type-display.ts — 前端类型标签
□ src/server/checker/runner/tcp/execute.ts — Checker 类
□ src/server/checker/runner/tcp/expect.ts — 专用断言(如需要)
□ src/server/checker/runner/tcp/index.ts — 模块入口re-export
□ src/server/checker/runner/index.ts — 注册(一行导入 + 一个数组元素)
□ src/web/constants/target-type-display.ts — 前端类型标签
□ src/web/constants/target-table-filters.ts — 前端类型筛选
□ tests/ — 契约 + 校验 + resolve + execute + 注册 测试
□ probes.example.yaml — 配置示例
@@ -597,7 +574,7 @@ export function registerCheckers(registry = checkerRegistry): void {
### 1.10 expect 断言系统
两层模型:**观测值收集** → **规则校验**
两层模型:**观测值收集** → **规则校验**共享断言基础设施位于 `checker/expect/`checker 专属断言位于各自目录。
**HTTP 校验流程**
@@ -617,7 +594,7 @@ CommandChecker.execute → 收集观测(exitCode/stdout/stderr/durationMs)
→ 首个失败即停止
```
**Body 规则类型**
**Body 规则类型**`runner/http/body.ts`
- `contains`:文本包含匹配
- `regex`正则表达式匹配注意body 正则字段为 `regex`,不是 `match`
@@ -625,7 +602,9 @@ CommandChecker.execute → 收集观测(exitCode/stdout/stderr/durationMs)
- `css`cheerio CSS 选择器 + 操作符比较
- `xpath`XPath 节点提取 + 操作符比较
**操作符**`equals`(深度比较,`es-toolkit/isEqual`)、`contains``match`(正则)、`empty``isNil`+`isEmptyObject`)、`exists``gte`/`lte`/`gt`/`lt`
**文本规则**`runner/command/text.ts`stdout/stderr 文本匹配,支持 `contains``match`(正则)、操作符比较
**操作符**`expect/operator.ts``equals`(深度比较,`es-toolkit/isEqual`)、`contains``match`(正则)、`empty``isNil`+`isEmptyObject`)、`exists``gte`/`lte`/`gt`/`lt`
### 1.11 错误模式