1
0

feat: 重构为多类型 checker 通用框架,支持 HTTP 与命令检查

- 引入 typed target 判别联合,支持 http 与 command 两种 checker
- expect 重构为有序规则数组,按配置顺序快速失败并生成结构化 failure
- 新增 command runner,支持 exec + args 本地命令执行
- 引入全局并发限制 maxConcurrentChecks 和 size 解析 (KB/MB/GB)
- HTTP/command 各自独立 expect pipeline,应用领域默认成功语义
- SQLite schema、API、Dashboard 全链路调整为 checker 通用契约
- 补充完整测试覆盖(192 tests),更新 README 与示例配置
This commit is contained in:
2026-05-10 22:25:21 +08:00
parent 599d973cbd
commit b8810f1182
46 changed files with 3562 additions and 1062 deletions

View File

@@ -1,15 +1,21 @@
import type { CheckResult, ResolvedTarget } from "./types";
import type { ProbeStore } from "./store";
import { fetchTarget } from "./fetcher";
import { runHttpCheck } from "./fetcher";
import { runCommandCheck } from "./command-runner";
export class ProbeEngine {
private timers: ReturnType<typeof setInterval>[] = [];
private store: ProbeStore;
private targets: ResolvedTarget[];
private targetNameToId: Map<string, number> = new Map();
private maxConcurrentChecks: number;
private running = 0;
private queue: Array<() => void> = [];
constructor(store: ProbeStore, targets: ResolvedTarget[]) {
constructor(store: ProbeStore, targets: ResolvedTarget[], maxConcurrentChecks?: number) {
this.store = store;
this.targets = targets;
this.maxConcurrentChecks = maxConcurrentChecks ?? 10;
this.refreshCache();
}
@@ -46,8 +52,36 @@ export class ProbeEngine {
return groups;
}
private async acquire(): Promise<void> {
if (this.running < this.maxConcurrentChecks) {
this.running++;
return;
}
return new Promise<void>((resolve) => {
this.queue.push(resolve);
});
}
private release(): void {
const next = this.queue.shift();
if (next) {
next();
} else {
this.running--;
}
}
private async probeGroup(targets: ResolvedTarget[]): Promise<void> {
const results = await Promise.allSettled(targets.map((t) => this.probeOne(t)));
const results = await Promise.allSettled(
targets.map(async (target) => {
await this.acquire();
try {
return await this.runCheck(target);
} finally {
this.release();
}
}),
);
for (const result of results) {
if (result.status === "fulfilled") {
@@ -56,23 +90,27 @@ export class ProbeEngine {
}
}
private async probeOne(target: ResolvedTarget): Promise<CheckResult> {
return fetchTarget(target);
private async runCheck(target: ResolvedTarget): Promise<CheckResult> {
switch (target.type) {
case "http":
return runHttpCheck(target);
case "command":
return runCommandCheck(target);
}
}
private writeResult(result: CheckResult): void {
const targetId = this.targetNameToId.get(result.targetName);
if (!targetId) return;
this.store.insertCheckResult({
targetId,
timestamp: result.timestamp,
success: result.success,
statusCode: result.statusCode,
latencyMs: result.latencyMs,
error: result.error,
matched: result.matched,
durationMs: result.durationMs,
statusDetail: result.statusDetail,
failure: result.failure,
});
}
@@ -82,6 +120,4 @@ export class ProbeEngine {
this.targetNameToId.set(target.name, target.id);
}
}
private targets: ResolvedTarget[];
}