# 后端开发 ## 库使用优先级 | 优先级 | 来源 | 典型用途 | | ------ | ------------ | -------------------------------------------------------------- | | 1 | Bun 内置 API | `Bun.serve`、`bun:sqlite`、`Bun.spawn`、`Bun.file`、`Bun.YAML` | | 2 | es-toolkit | 类型判断、深度比较、错误判断、并发控制、集合操作 | | 3 | 标准 Web API | `Object.fromEntries`、`Headers`、`fetch`、`AbortController` | | 4 | 主流三方库 | cheerio、xpath、@xmldom/xmldom | | 5 | 自行实现 | 仅在以上都无法满足时 | 新增依赖前必须先检查上述每一层是否已有可用方案。 ## API 路由开发 路由文件位于 `src/server/routes/`,每个端点一个文件。路由通过 `server.ts` 的 `Bun.serve({ routes })` 声明式注册,使用 per-method handler 对象。 新增路由步骤: 1. 在 `src/server/routes/` 下创建 `.ts`。 2. 实现 handler 函数并 export。 3. 在 `server.ts` 的 `routes` 对象中注册路径和 method handler。 4. 在 `tests/server/app.test.ts` 中添加集成测试。 请求参数校验使用 `middleware.ts` 提供的 `validateTargetId`、`validateTimeRange`、`validatePagination`、`validateDashboardWindow`、`validateRecentLimit`、`validateMetricsBucket`。 ## 共享 helpers | 函数 | 用途 | | ------------------------------- | ------------------------------------ | | `createApiError(error, status)` | 构造 API 错误体 | | `createHeaders(mode, init)` | 创建响应 Headers,生产模式附加安全头 | | `createHealthResponse()` | 构造健康检查响应 | | `formatDuration(ms)` | 毫秒转为可读时长字符串 | | `jsonResponse(body, options)` | JSON 响应构造 | | `mapCheckResult(row, type)` | 数据库行转 API CheckResult | ## 类型规范 - 共享类型以 `src/shared/api.ts` 为唯一源头。 - 严格联合类型优先于宽类型。 - 存储层类型与 API 类型分离。 - checker 具体类型在各自目录定义,中间层通过 base interface 和 registry 完成类型擦除。 - 纯类型导入使用 `import type`。 ## 配置契约与校验 配置加载流程固定为:`unknown -> AuthoringProbeConfig -> NormalizedProbeConfig -> ValidatedProbeConfig -> ResolvedConfig`。 | 层级 | 职责 | | ---------- | ------------------------------------------------ | | Authoring | 用户 YAML 可书写形态,允许变量引用和 expect 简写 | | Normalized | 变量替换和 expect 简写展开后的契约校验形态 | | Validated | 通过契约校验和语义校验的形态 | | Resolved | checker `resolve()` 后的运行期配置 | Ajv 保持严格拒绝模式:`allErrors: true`、不启用类型强制转换、不注入默认值、不自动删除未知字段。默认对象策略是 `additionalProperties: false`,只有明确的动态键值表可以开放任意键名。 新增或修改配置字段时必须同步更新 TypeBox schema fragments、`probe-config.schema.json`、语义 validator、测试和对应用户文档,并运行 `bun run schema:check`。 ## 数据存储 存储层基于 `bun:sqlite`,WAL 模式运行,数据库文件位于配置的 `dataDir` 下。 | 方法 | 用途 | | ------------------------------------------ | ---------------------------------- | | `syncTargets(targets)` | 启动期同步 targets | | `insertCheckResult()` | 写入单条检查结果 | | `getTargets()` | 查询全部 targets | | `getLatestChecksMap()` | 批量获取每个 target 的最新检查结果 | | `getAllTargetWindowStats(from, to)` | 批量获取窗口基础计数 | | `getDashboardIncidentStates(from, to)` | 获取 Dashboard 窗口状态序列 | | `getAllRecentSamples(limit)` | 批量获取最近采样 | | `getTargetCheckpoints(targetId, from, to)` | 获取单目标窗口检查点序列 | | `getTargetDurations(targetId, from, to)` | 获取单目标成功耗时数组 | | `getHistory()` | 分页查询历史记录 | | `prune(retentionMs)` | 清理过期数据 | 数据库只负责存储、筛选、排序、分页、LIMIT 和基础聚合。指标语义在后端应用层实现。 ## 拨测引擎 - 按 interval 分组,每组独立定时触发。 - 使用 `es-toolkit/Semaphore` 限制全局最大并发数。 - 通过 `checkerRegistry.get(target.type)` 选择 runner。 - 每次检查创建 `AbortController` 并按 `target.timeoutMs` 触发 abort。 - 状态变化通过注入的 `Logger` 输出结构化日志。 ## 日志模块 后端运行时代码统一通过 `Logger` 接口输出日志,禁止直接使用 `console.*`。配置加载失败前使用 `ConsoleFallbackLogger`。 | 实现 | 用途 | | ----------------------- | --------------------------------------------- | | `PinoLoggerWrapper` | 生产运行时,封装 Pino、pino-pretty、pino-roll | | `NoopLogger` | 静默丢弃日志 | | `MemoryLogger` | 测试替身 | | `ConsoleFallbackLogger` | 配置加载失败前的降级日志 | 敏感信息会自动 redact `authorization`、`cookie`、`set-cookie`、`authToken`、`key`、`password`、`token`、`apiKey` 及其嵌套路径。 ## expect 系统 共享断言基础设施位于 `src/server/checker/expect/`。新增或修改 checker 的 expect 字段时,按以下原则选择模型: | 模型 | 用途 | 典型字段 | | --------------------- | ---------------------------- | ------------------------------------------------------------------- | | enum / boolean | 状态类结果,结果集合小且稳定 | HTTP status、Cmd exitCode、TCP connected、UDP responded、ICMP alive | | `ValueMatcher` | 数字指标和字符串元数据 | durationMs、rowCount、finishReason、usage | | `ContentExpectations` | 返回内容或半结构化内容 | body、stdout、stderr、banner、response、output、result | | `KeyedExpectations` | 动态键值断言 | headers、DB rows 列值 | 详细 checker 开发流程见 [Checker 开发](checker-development.md)。 ## 错误模型 | 类型 | 结构 | | ------------ | ----------------------------------- | ------------------------------------------------------- | | API 错误 | `{ error: "描述", status: }` | | CheckFailure | `{ kind: "error" | "mismatch", phase, path, expected?, actual?, message }` | expect 校验失败记录首个失败原因;网络、超时、进程崩溃统一为 `kind: "error"`。