1
0

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:
2026-05-12 15:15:36 +08:00
parent 696db6ffb5
commit f7facb7232
24 changed files with 868 additions and 368 deletions

View File

@@ -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();
}
}),
);