1
0

refactor: 后端架构加固 — 泛型化、批量查询、bootstrap 统一、路径修复与 pageSize 上限

- CheckerDefinition 泛型化,HTTP/Command checker 移除 resolved target 断言
- 新增 ProbeStore.getAllRecentSamples 消除 targets 路由 N+1 查询
- 统一 getAllTargetStats 与 getTargetStats 的 availability 精度
- Engine rejected 结果写入 internal error 记录,提升可观测性
- 新增 bootstrap.ts 统一 dev/production 启动序列
- dataDir 相对路径改为基于配置文件目录解析
- validatePagination 增加 pageSize 上限 200 校验
- 修复 ErrorBoundary override 标记
- 更新 README/DEVELOPMENT 文档,新增完整测试覆盖
This commit is contained in:
2026-05-13 18:15:46 +08:00
parent 6ea185315f
commit 147a2559ae
30 changed files with 930 additions and 129 deletions

View File

@@ -1,8 +1,8 @@
import { isError } from "es-toolkit";
import { resolve } from "node:path";
import type { CheckResult, RawTargetConfig, ResolvedTargetBase } from "../../types";
import type { Checker, CheckerContext, CheckerValidationInput, ResolveContext } from "../types";
import type { CheckResult, RawTargetConfig } from "../../types";
import type { CheckerContext, CheckerDefinition, CheckerValidationInput, ResolveContext } from "../types";
import type { CommandExpectConfig, CommandTargetConfig, ResolvedCommandTarget } from "./types";
import { checkDuration } from "../../expect/duration";
@@ -13,15 +13,14 @@ import { commandCheckerSchemas } from "./schema";
import { checkTextRules } from "./text";
import { validateCommandConfig } from "./validate";
export class CommandChecker implements Checker {
export class CommandChecker implements CheckerDefinition<ResolvedCommandTarget> {
readonly configKey = "command";
readonly schemas = commandCheckerSchemas;
readonly type = "command";
async execute(target: ResolvedTargetBase, ctx: CheckerContext): Promise<CheckResult> {
const t = target as ResolvedCommandTarget;
async execute(t: ResolvedCommandTarget, ctx: CheckerContext): Promise<CheckResult> {
const timestamp = new Date().toISOString();
const start = performance.now();
@@ -169,7 +168,7 @@ export class CommandChecker implements Checker {
};
}
resolve(target: RawTargetConfig, context: ResolveContext): ResolvedTargetBase {
resolve(target: RawTargetConfig, context: ResolveContext): ResolvedCommandTarget {
const t = target as RawTargetConfig & { command: CommandTargetConfig; type: "command" };
const commandDefaults = context.defaults["command"] as undefined | { cwd?: string; maxOutputBytes?: string };
@@ -197,8 +196,7 @@ export class CommandChecker implements Checker {
} satisfies ResolvedCommandTarget;
}
serialize(target: ResolvedTargetBase): { config: string; target: string } {
const t = target as ResolvedCommandTarget;
serialize(t: ResolvedCommandTarget): { config: string; target: string } {
const parts = [t.command.exec, ...t.command.args];
return {
config: JSON.stringify({

View File

@@ -1,7 +1,7 @@
import { isError } from "es-toolkit";
import type { CheckResult, RawTargetConfig, ResolvedTargetBase } from "../../types";
import type { Checker, CheckerContext, CheckerValidationInput, ResolveContext } from "../types";
import type { CheckResult, RawTargetConfig } from "../../types";
import type { CheckerContext, CheckerDefinition, CheckerValidationInput, ResolveContext } from "../types";
import type { HttpExpectConfig, HttpTargetConfig, ResolvedHttpTarget } from "./types";
import { checkDuration } from "../../expect/duration";
@@ -16,15 +16,14 @@ const CHARSET_RE = /charset="?([^";\s]+)"?/i;
const REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308]);
const SENSITIVE_HEADERS = new Set(["authorization", "cookie"]);
export class HttpChecker implements Checker {
export class HttpChecker implements CheckerDefinition<ResolvedHttpTarget> {
readonly configKey = "http";
readonly schemas = httpCheckerSchemas;
readonly type = "http";
async execute(target: ResolvedTargetBase, ctx: CheckerContext): Promise<CheckResult> {
const t = target as ResolvedHttpTarget;
async execute(t: ResolvedHttpTarget, ctx: CheckerContext): Promise<CheckResult> {
const timestamp = new Date().toISOString();
const expect = t.expect;
const start = performance.now();
@@ -117,7 +116,7 @@ export class HttpChecker implements Checker {
}
}
resolve(target: RawTargetConfig, context: ResolveContext): ResolvedTargetBase {
resolve(target: RawTargetConfig, context: ResolveContext): ResolvedHttpTarget {
const t = target as RawTargetConfig & { http: HttpTargetConfig; type: "http" };
const httpDefaults = context.defaults["http"] as
| undefined
@@ -145,8 +144,7 @@ export class HttpChecker implements Checker {
} satisfies ResolvedHttpTarget;
}
serialize(target: ResolvedTargetBase): { config: string; target: string } {
const t = target as ResolvedHttpTarget;
serialize(t: ResolvedHttpTarget): { config: string; target: string } {
return {
config: JSON.stringify({
body: t.http.body,

View File

@@ -3,18 +3,18 @@ import type { TSchema } from "@sinclair/typebox";
import type { ConfigValidationIssue } from "../schema/issues";
import type { CheckResult, DefaultsConfig, RawTargetConfig, ResolvedTargetBase } from "../types";
export type Checker = CheckerDefinition;
export type Checker<TResolved extends ResolvedTargetBase = ResolvedTargetBase> = CheckerDefinition<TResolved>;
export interface CheckerContext {
signal: AbortSignal;
}
export interface CheckerDefinition {
export interface CheckerDefinition<TResolved extends ResolvedTargetBase = ResolvedTargetBase> {
readonly configKey: string;
execute(target: ResolvedTargetBase, ctx: CheckerContext): Promise<CheckResult>;
resolve(target: RawTargetConfig, context: ResolveContext): ResolvedTargetBase;
execute(target: TResolved, ctx: CheckerContext): Promise<CheckResult>;
resolve(target: RawTargetConfig, context: ResolveContext): TResolved;
readonly schemas: CheckerSchemas;
serialize(target: ResolvedTargetBase): { config: string; target: string };
serialize(target: TResolved): { config: string; target: string };
readonly type: string;
validate(input: CheckerValidationInput): ConfigValidationIssue[];
}