task-98: 真实启动、健康检查、核心请求和外部依赖调用验证执行器
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
/**
|
||||
* 健康检查执行器(Task 98)。
|
||||
*
|
||||
* 把真实启动、健康检查、核心请求、外部依赖调用四个验收探针收敛为
|
||||
* 可注入探针的确定性流程:探针按序执行,全部产出结果与耗时。
|
||||
*
|
||||
* 语义:
|
||||
* - 单个探针失败(返回非 ok 输出或抛错)计入 failed,不中断其余探针,
|
||||
* 健康检查输出全量报告;ok=false 当且仅当存在 failed;
|
||||
* - 空探针列表视为通过;单探针不依赖批量路径;
|
||||
* - maxProbes 限制执行的探针数量(超出部分安全跳过),执行有界;
|
||||
* - timeoutMs 为单个探针设置超时上限:超时按失败计入,不泄漏未完成
|
||||
* 的探测任务(探针异步任务自行收尾);
|
||||
* - 校验失败 fail-fast:probes 非数组、id 为空、probe 非函数、maxProbes/
|
||||
* timeoutMs 非正数、探针返回非字符串均抛错;失败可恢复——修复探针后
|
||||
* 同一 runner 重跑即全绿。
|
||||
*/
|
||||
export interface HealthProbe {
|
||||
id: string
|
||||
label: string
|
||||
probe: () => Promise<string> | string
|
||||
}
|
||||
|
||||
export interface HealthCheckOptions {
|
||||
probes: HealthProbe[]
|
||||
/** 执行的探针数量上限,必须为正数,默认 100 */
|
||||
maxProbes?: number
|
||||
/** 单个探针超时(毫秒),必须为正数,默认 30_000 */
|
||||
timeoutMs?: number
|
||||
}
|
||||
|
||||
export interface ProbeResult {
|
||||
id: string
|
||||
label: string
|
||||
ok: boolean
|
||||
output: string
|
||||
durationMs: number
|
||||
}
|
||||
|
||||
export interface HealthCheckResult {
|
||||
ok: boolean
|
||||
passed: ProbeResult[]
|
||||
failed: ProbeResult[]
|
||||
totalDurationMs: number
|
||||
}
|
||||
|
||||
export interface HealthCheckRunner {
|
||||
runAll: () => Promise<HealthCheckResult>
|
||||
}
|
||||
|
||||
export function createHealthCheckRunner(options: HealthCheckOptions): HealthCheckRunner {
|
||||
if (!Array.isArray(options.probes)) {
|
||||
throw new Error('probes 必须是数组')
|
||||
}
|
||||
for (const probe of options.probes) {
|
||||
if (typeof probe.id !== 'string' || probe.id.length === 0) {
|
||||
throw new Error('probe id 不能为空')
|
||||
}
|
||||
if (typeof probe.probe !== 'function') {
|
||||
throw new Error('probe 必须是函数')
|
||||
}
|
||||
}
|
||||
const maxProbes = options.maxProbes ?? 100
|
||||
const timeoutMs = options.timeoutMs ?? 30_000
|
||||
if (!(maxProbes > 0)) {
|
||||
throw new Error('maxProbes 必须为正数: ' + maxProbes)
|
||||
}
|
||||
if (!(timeoutMs > 0)) {
|
||||
throw new Error('timeoutMs 必须为正数: ' + timeoutMs)
|
||||
}
|
||||
|
||||
async function runOne(probe: HealthProbe): Promise<ProbeResult> {
|
||||
const start = Date.now()
|
||||
let output = ''
|
||||
let failed = false
|
||||
let timer: ReturnType<typeof setTimeout> | null = null
|
||||
try {
|
||||
const raw = await Promise.race([
|
||||
Promise.resolve(probe.probe()),
|
||||
new Promise<string>((resolve) => {
|
||||
timer = setTimeout(() => resolve(''), timeoutMs)
|
||||
}),
|
||||
])
|
||||
if (timer != null) clearTimeout(timer)
|
||||
output = raw
|
||||
} catch (error) {
|
||||
if (timer != null) clearTimeout(timer)
|
||||
failed = true
|
||||
output = error instanceof Error ? error.message : String(error)
|
||||
}
|
||||
const durationMs = Date.now() - start
|
||||
if (typeof output !== 'string') {
|
||||
throw new Error('output 必须是字符串')
|
||||
}
|
||||
const ok = !failed && output !== ''
|
||||
return { id: probe.id, label: probe.label, ok, output, durationMs }
|
||||
}
|
||||
|
||||
async function runAll(): Promise<HealthCheckResult> {
|
||||
const start = Date.now()
|
||||
const passed: ProbeResult[] = []
|
||||
const failed: ProbeResult[] = []
|
||||
for (let i = 0; i < options.probes.length; i++) {
|
||||
if (i >= maxProbes) break
|
||||
const result = await runOne(options.probes[i])
|
||||
if (result.ok) {
|
||||
passed.push(result)
|
||||
} else {
|
||||
failed.push(result)
|
||||
}
|
||||
}
|
||||
return {
|
||||
ok: failed.length === 0,
|
||||
passed,
|
||||
failed,
|
||||
totalDurationMs: Date.now() - start,
|
||||
}
|
||||
}
|
||||
|
||||
return { runAll }
|
||||
}
|
||||
Reference in New Issue
Block a user