# 后端开发

本文档说明 DiAL 后端的 API、配置加载、存储、拨测引擎、日志、expect 和错误模型开发约定。

适用场景：修改 `src/server/`、`src/shared/api.ts`、后端测试、配置契约、API 响应、store、engine、logger 或 expect 基础设施。

## 库使用优先级

| 优先级 | 来源         | 典型用途                                                       |
| ------ | ------------ | -------------------------------------------------------------- |
| 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/` 下创建 `<name>.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.md)。

## 错误模型

- API 错误：`{ error: "描述", status: <code> }`
- CheckFailure：`{ kind: "error" | "mismatch", phase, path, expected?, actual?, message }`

expect 校验失败记录首个失败原因；网络、超时、进程崩溃统一为 `kind: "error"`。

## 后端测试与验证

| 变更类型               | 测试重点                                 |
| ---------------------- | ---------------------------------------- |
| API 路由               | `tests/server/app.test.ts` 集成行为      |
| 配置 schema 或语义校验 | schema 导出、合法配置、非法配置          |
| store                  | SQLite 写入、查询、分页、聚合和清理      |
| engine                 | 调度、并发、超时、结果写入和状态变化日志 |
| expect 基础设施        | matcher 语义、快速失败、错误信息         |
| checker runner         | 见 [Checker 开发](checker.md#测试要求)   |

后端运行时代码统一通过注入的 Logger 输出日志，禁止直接使用 `console.*`。新增或修改后端逻辑通常需要运行 `bun run check`；影响构建产物或前后端集成时运行 `bun run verify`。

## 更新触发条件

修改后端 API、共享类型、配置契约、store、engine、logger、expect 基础设施、错误模型或后端测试规范时，必须更新本文档。
