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

@@ -9,10 +9,19 @@
```text
src/
server/
app.ts Bun HTTP 路由API + 静态资源 + SPA fallback
app.ts Bun HTTP 路由入口(路由分发 + API 汇聚
config.ts CLI 参数解析
dev.ts 开发期启动入口
server.ts HTTP server 启动
helpers.ts 共享响应格式化工具jsonResponse、createHeaders 等)
middleware.ts API 参数校验中间件guardGetHead、validateTargetId 等)
static.ts 静态资源服务 与 SPA fallback
routes/ API 路由 handler按端点拆分
health.ts GET /health
summary.ts GET /api/summary
targets.ts GET /api/targets
history.ts GET /api/targets/:id/history
trend.ts GET /api/targets/:id/trend
checker/
types.ts 类型定义
config-loader.ts YAML 配置解析与校验
@@ -20,11 +29,11 @@ src/
fetcher.ts HTTP 拨测执行
command-runner.ts 命令行拨测执行
size.ts 大小单位解析
engine.ts 调度引擎(按 interval 分组、组内并发
engine.ts 调度引擎(按 interval 分组的 es-toolkit groupBy + Semaphore 并发控制
expect/
http.ts HTTP 响应断言
command.ts 命令行输出断言
body.ts HTTP body 断言JSONPath/XPath/CSS
body.ts HTTP body 断言JSONPath/XPath/CSS,类型判断使用 es-toolkit
failure.ts 失败信息类型
shared/
api.ts 前后端共享 TypeScript 类型
@@ -82,6 +91,149 @@ bun run verify
前端只通过 HTTP 调用后端API 路径为 `/api/*`。共享类型放在 `src/shared`,前端不得 import `src/server` 的运行时实现。
## 后端开发指引
### 架构概览
```
启动流程:
dev.ts → readRuntimeConfig(cli args) → loadConfig(yaml)
→ ProbeStore(db) → ProbeEngine(store, targets) → startServer(store)
运行时:
定时器(tick) → ProbeEngine.probeGroup()
→ HTTP: fetcher.ts / Command: command-runner.ts
→ expect/*.ts 校验 → store.insertCheckResult()
HTTP 请求:
Request → app.ts(路由分发) → routes/*.ts(handler)
→ middleware.ts(参数校验) → helpers.ts(响应格式化) → Response
```
### 库使用优先级
后端代码开发遵循严格的库选择顺序:
| 优先级 | 来源 | 典型用途 |
| ------ | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| 1 | Bun 内置 API | `Bun.serve``bun:sqlite``Bun.spawn``Bun.file``Bun.YAML` |
| 2 | es-toolkit | 类型判断(`isPlainObject`/`isNil`/`isEmptyObject`)、深度比较(`isEqual`)、错误判断(`isError`)、并发控制(`Semaphore`)、集合操作(`groupBy` |
| 3 | 标准 Web API | `Object.fromEntries``Headers``fetch``AbortController` |
| 4 | 主流三方库 | cheerioHTML 解析、xpath + @xmldom/xmldomXML 解析) |
| 5 | 自行实现 | 仅在以上都无法满足时(如 `parseDuration``parseSize``evaluateJsonPath` 等专项逻辑) |
### API 路由开发
路由文件位于 `src/server/routes/`每个端点一个文件。handler 函数签名统一为:
```typescript
export function handleXxx(params, store: ProbeStore, method: string, mode: RuntimeMode): Response;
```
**请求处理流程**
1. `app.ts``createFetchHandler` 作为总入口,根据 URL pattern 匹配路由
2. API 路由统一经过 `guardGetHead` 做方法检查(仅允许 GET/HEAD
3. 各 handler 内部通过 `middleware.ts` 提供的 `validateTargetId``validateTimeRange``validatePagination` 做参数校验
4. 校验函数返回 `Response` 表示校验失败(直接返回),返回数据对象表示通过
5. 业务逻辑通过 `store` 查询数据,用 `helpers.ts``jsonResponse``mapCheckResult``formatDuration` 等格式化输出
**新增路由步骤**
1.`src/server/routes/` 下创建 `<name>.ts`
2. 实现 handler 函数并 export
3.`app.ts``handleApiRoute` 中注册路径匹配和调用
4.`tests/server/app.test.ts` 中添加对应测试
### 共享工具
- **`helpers.ts`**:跨路由共用的响应工具函数(`jsonResponse``createHeaders``createApiError``mapCheckResult``formatDuration``createHealthResponse`
- **`middleware.ts`**API 参数校验函数(`guardGetHead``validateTargetId``validateTimeRange``validatePagination`
- **`static.ts`**:生产模式下的静态资源服务与 SPA fallback
### 类型定义规范
- **共享类型**以 `src/shared/api.ts` 为唯一源头,前后端共同引用
- 前端不得 `import src/server/` 下的任何文件
- **严格联合类型**优先于宽类型:如 `phase: "status" | "duration" | ...` 而非 `phase: string`
- **后端内部扩展**`checker/types.ts``CheckResult` 通过 `extends` 共享版本的 `ApiCheckResult` 增加 `targetName` 等内部字段
- 存储层类型(`StoredTarget``StoredCheckResult`)独立定义,与 API 类型分离
### 数据存储规范
基于 `bun:sqlite`WAL 模式运行,数据库文件位于配置的 `dataDir` 下。
**Statement 使用规范**
| 场景 | 方式 | 原因 |
| -------------- | -------------------------------------- | ---------------------------------------- |
| 单次读/写 | `this.db.query(sql).get()/all()/run()` | bun:sqlite 内置 statement 缓存,自动复用 |
| 事务内多次复用 | `this.db.prepare(sql)` 缓存为局部变量 | 事务闭包中需要持有引用 |
**查询优化**
- 避免 N+1 查询:批量场景优先用单次 SQL 聚合GROUP BY、子查询 JOIN+ 内存组装
- 新增批量查询方法时必须编写对应单元测试
- `getSummary()``GET /api/targets` 的响应组装已通过 `getLatestChecksMap` + `getAllTargetStats` 实现批量查询
**Schema**
- `targets`nameUNIQUE、type、target展示摘要、configJSON、interval_ms、timeout_ms、expectJSON、grp
- `check_results`target_idFK CASCADE、timestamp、matched0/1、duration_ms、status_detail、failureJSON
- 复合索引:`(target_id, timestamp)`
### 拨测引擎
- **调度**`ProbeEngine``es-toolkit/groupBy` 按 interval 分组,每组独立 `setInterval` 定时触发
- **并发控制**`es-toolkit/Semaphore` 限制全局最大并发数(`maxConcurrentChecks``acquire()` 阻塞等待
- **Runner 选择**`engine.runCheck()``target.type` 分发到 `runHttpCheck``runCommandCheck`
- **超时控制**HTTP 用 `AbortController`Command 用 `setTimeout` + `proc.kill()`
- **结果写入**:检查结果通过 `store.insertCheckResult()` 写入 SQLiteengine 通过 `targetNameToId` 缓存 name→id 映射
### expect 断言系统
两层模型:**观测值收集** → **规则校验**
**HTTP 校验流程**
```
runHttpCheck → 收集观测(statusCode/headers/body/durationMs)
→ checkHttpExpect → status → duration → headers → body(可选)
→ 首个失败即停止,返回 CheckFailure
```
**Command 校验流程**
```
runCommandCheck → 收集观测(exitCode/stdout/stderr/durationMs)
→ checkCommandExpect → exitCode → duration → stdout → stderr
→ 首个失败即停止
```
**Body 规则类型**
- `contains`:文本包含匹配
- `regex`:正则表达式匹配
- `json`JSONPath 提取 + 操作符比较(使用 `es-toolkit/isPlainObject` 区分纯值和操作符)
- `css`cheerio CSS 选择器 + 操作符比较
- `xpath`XPath 节点提取 + 操作符比较
**操作符**`equals`(深度比较,`es-toolkit/isEqual`)、`contains``match`(正则)、`empty``isNil`+`isEmptyObject`)、`exists``gte`/`lte`/`gt`/`lt`
### 错误模式
- **API 错误**`{ error: "描述", status: <code> }`,状态码 400/404/405/503
- **CheckFailure**`{ kind: "error"|"mismatch", phase, path, expected?, actual?, message }`
- **错误处理**expect 校验失败记录首个失败原因;网络/超时/进程崩溃统一为 `kind:"error"`
- **日志**:解析失败等非致命异常用 `console.warn`,启动失败用 `console.error` + `process.exit(1)`
### 测试规范
- 测试文件与源文件对应:`tests/server/checker/store.test.ts``src/server/checker/store.ts`
- 使用 `bun:test` 框架(`describe`/`test`/`expect`),测试数据库用临时目录 + `tmpdir()`
- 新增 store 方法必须编写单元测试;新增 API 端点必须在 `app.test.ts` 中添加集成测试
- 测试后清理:`afterAll``store.close()` + `rm(tempDir, { recursive: true })`
## 前端样式规范
前端基于 TDesign React 构建UI样式开发遵循以下优先级从高到低