refactor: 全面优化后端代码质量与架构
- app.ts 单体路由拆分为 routes/ + helpers + middleware + static 独立模块 - 类型去重:CheckFailure/CheckResult 以 shared/api.ts 为唯一源头,收紧 phase 联合类型 - es-toolkit 替换:isPlainObject/isNil/isEmptyObject/isEqual/isError/Semaphore/groupBy - Bun 内置 API:Object.fromEntries 替代手写 headersToRecord - bun:sqlite 规范:prepare() → query() 利用内置缓存,避免 N+1 查询 - 新增 getLatestChecksMap/allGetTargetStats 批量查询方法 - 新增 backend-code-quality/api-route-separation/batch-data-queries 规范 - 补充 openspec/config.yaml 后端开发规范与 DEVELOPMENT.md 后端开发指引
This commit is contained in:
@@ -2,32 +2,31 @@ import type { CheckResult, ResolvedTarget } from "./types";
|
||||
import type { ProbeStore } from "./store";
|
||||
import { runHttpCheck } from "./fetcher";
|
||||
import { runCommandCheck } from "./command-runner";
|
||||
import { groupBy, Semaphore } from "es-toolkit";
|
||||
|
||||
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> = [];
|
||||
private semaphore: Semaphore;
|
||||
|
||||
constructor(store: ProbeStore, targets: ResolvedTarget[], maxConcurrentChecks?: number) {
|
||||
this.store = store;
|
||||
this.targets = targets;
|
||||
this.maxConcurrentChecks = maxConcurrentChecks ?? 20;
|
||||
this.semaphore = new Semaphore(maxConcurrentChecks ?? 20);
|
||||
this.refreshCache();
|
||||
}
|
||||
|
||||
start(): void {
|
||||
const groups = this.groupByInterval(this.targets);
|
||||
const groups = groupBy(this.targets, (t) => t.intervalMs);
|
||||
|
||||
for (const [intervalMs, groupTargets] of groups) {
|
||||
for (const [intervalMs, groupTargets] of Object.entries(groups)) {
|
||||
void this.probeGroup(groupTargets);
|
||||
|
||||
const timer = setInterval(() => {
|
||||
void this.probeGroup(groupTargets);
|
||||
}, intervalMs);
|
||||
}, Number(intervalMs));
|
||||
|
||||
this.timers.push(timer);
|
||||
}
|
||||
@@ -40,45 +39,14 @@ export class ProbeEngine {
|
||||
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 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(async (target) => {
|
||||
await this.acquire();
|
||||
await this.semaphore.acquire();
|
||||
try {
|
||||
return await this.runCheck(target);
|
||||
} finally {
|
||||
this.release();
|
||||
this.semaphore.release();
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user