feat: 将 demo 项目转化为 HTTP 拨测监控工具
新增 YAML 配置解析(Bun 内置 YAML)、SQLite 数据存储(bun:sqlite)、按 interval 分组并发拨测引擎、REST API(/api/summary、/api/targets、/api/targets/:id/history、/api/targets/:id/trend)、React 前端 Dashboard(统计卡片、目标表格、可展开详情面板、recharts 趋势图)。CLI 简化为仅接受配置文件路径。移除 /api/demo 路由和相关 demo 代码。保留 /health、静态资源服务和 SPA fallback。
This commit is contained in:
87
src/server/checker/engine.ts
Normal file
87
src/server/checker/engine.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import type { CheckResult, ResolvedTarget } from "./types";
|
||||
import type { ProbeStore } from "./store";
|
||||
import { fetchTarget } from "./fetcher";
|
||||
|
||||
export class ProbeEngine {
|
||||
private timers: ReturnType<typeof setInterval>[] = [];
|
||||
private store: ProbeStore;
|
||||
private targetNameToId: Map<string, number> = new Map();
|
||||
|
||||
constructor(store: ProbeStore, targets: ResolvedTarget[]) {
|
||||
this.store = store;
|
||||
this.targets = targets;
|
||||
this.refreshCache();
|
||||
}
|
||||
|
||||
start(): void {
|
||||
const groups = this.groupByInterval(this.targets);
|
||||
|
||||
for (const [intervalMs, groupTargets] of groups) {
|
||||
void this.probeGroup(groupTargets);
|
||||
|
||||
const timer = setInterval(() => {
|
||||
void this.probeGroup(groupTargets);
|
||||
}, intervalMs);
|
||||
|
||||
this.timers.push(timer);
|
||||
}
|
||||
}
|
||||
|
||||
stop(): void {
|
||||
for (const timer of this.timers) {
|
||||
clearInterval(timer);
|
||||
}
|
||||
this.timers = [];
|
||||
}
|
||||
|
||||
private groupByInterval(targets: ResolvedTarget[]): Map<number, ResolvedTarget[]> {
|
||||
const groups = new Map<number, ResolvedTarget[]>();
|
||||
|
||||
for (const target of targets) {
|
||||
const group = groups.get(target.intervalMs) ?? [];
|
||||
group.push(target);
|
||||
groups.set(target.intervalMs, group);
|
||||
}
|
||||
|
||||
return groups;
|
||||
}
|
||||
|
||||
private async probeGroup(targets: ResolvedTarget[]): Promise<void> {
|
||||
const results = await Promise.allSettled(targets.map((t) => this.probeOne(t)));
|
||||
|
||||
for (const result of results) {
|
||||
if (result.status === "fulfilled") {
|
||||
this.writeResult(result.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async probeOne(target: ResolvedTarget): Promise<CheckResult> {
|
||||
return fetchTarget(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,
|
||||
});
|
||||
}
|
||||
|
||||
private refreshCache(): void {
|
||||
this.targetNameToId.clear();
|
||||
for (const target of this.store.getTargets()) {
|
||||
this.targetNameToId.set(target.name, target.id);
|
||||
}
|
||||
}
|
||||
|
||||
private targets: ResolvedTarget[];
|
||||
}
|
||||
Reference in New Issue
Block a user