1
0
Files
DiAL/DEVELOPMENT.md
lanyuanxiaoyao f7facb7232 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 后端开发指引
2026-05-12 15:15:36 +08:00

12 KiB
Raw Blame History

DiAL 开发文档

本文档面向 DiAL 项目的开发者,介绍项目结构、构建流程、测试、代码规范等内容。

用户使用说明请参阅 README.md

项目结构

src/
  server/
    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 配置解析与校验
      store.ts          SQLite 数据存储
      fetcher.ts        HTTP 拨测执行
      command-runner.ts 命令行拨测执行
      size.ts           大小单位解析
      engine.ts         调度引擎(按 interval 分组的 es-toolkit groupBy + Semaphore 并发控制)
      expect/
        http.ts         HTTP 响应断言
        command.ts      命令行输出断言
        body.ts         HTTP body 断言JSONPath/XPath/CSS类型判断使用 es-toolkit
        failure.ts      失败信息类型
  shared/
    api.ts          前后端共享 TypeScript 类型
  web/              Vite + React 前端 Dashboard
    components/     UI 组件表格、分组、Drawer、状态条等
    constants/      常量定义(列配置、类型映射、排序/筛选/颜色阈值函数)
    hooks/          TanStack Query 数据层useTargetDetail 集成轮询/条件查询)
    utils/          前端工具函数
scripts/            开发、构建和 smoke test 脚本
tests/              Bun test 测试
openspec/           OpenSpec 变更与规格文档

构建 executable

bun run build

构建流程:

  1. 运行 vite build,输出前端资源到 dist/web
  2. 生成临时 .build/static-assets.ts,嵌入 Vite 产物
  3. 生成临时 .build/server-entry.ts,作为生产入口
  4. 运行 Bun.build({ compile }),输出 dist/dial-server

运行 executable

./dist/dial-server probes.yaml

代码质量

bun run lint
bun run format:check
bun run format
bun run check
  • check 依次运行 typechecklintformat:check 和单元测试。

测试

bun run check
bun run verify
  • check 适合日常开发包含类型检查、lint、格式检查和单元测试。
  • verify 先运行 check,再重新构建生产 executable 并运行 smoke test。

前后端边界

前端只通过 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.servebun:sqliteBun.spawnBun.fileBun.YAML
2 es-toolkit 类型判断(isPlainObject/isNil/isEmptyObject)、深度比较(isEqual)、错误判断(isError)、并发控制(Semaphore)、集合操作(groupBy
3 标准 Web API Object.fromEntriesHeadersfetchAbortController
4 主流三方库 cheerioHTML 解析、xpath + @xmldom/xmldomXML 解析)
5 自行实现 仅在以上都无法满足时(如 parseDurationparseSizeevaluateJsonPath 等专项逻辑)

API 路由开发

路由文件位于 src/server/routes/每个端点一个文件。handler 函数签名统一为:

export function handleXxx(params, store: ProbeStore, method: string, mode: RuntimeMode): Response;

请求处理流程

  1. app.tscreateFetchHandler 作为总入口,根据 URL pattern 匹配路由
  2. API 路由统一经过 guardGetHead 做方法检查(仅允许 GET/HEAD
  3. 各 handler 内部通过 middleware.ts 提供的 validateTargetIdvalidateTimeRangevalidatePagination 做参数校验
  4. 校验函数返回 Response 表示校验失败(直接返回),返回数据对象表示通过
  5. 业务逻辑通过 store 查询数据,用 helpers.tsjsonResponsemapCheckResultformatDuration 等格式化输出

新增路由步骤

  1. src/server/routes/ 下创建 <name>.ts
  2. 实现 handler 函数并 export
  3. app.tshandleApiRoute 中注册路径匹配和调用
  4. tests/server/app.test.ts 中添加对应测试

共享工具

  • helpers.ts:跨路由共用的响应工具函数(jsonResponsecreateHeaderscreateApiErrormapCheckResultformatDurationcreateHealthResponse
  • middleware.tsAPI 参数校验函数(guardGetHeadvalidateTargetIdvalidateTimeRangevalidatePagination
  • static.ts:生产模式下的静态资源服务与 SPA fallback

类型定义规范

  • 共享类型src/shared/api.ts 为唯一源头,前后端共同引用
  • 前端不得 import src/server/ 下的任何文件
  • 严格联合类型优先于宽类型:如 phase: "status" | "duration" | ... 而非 phase: string
  • 后端内部扩展checker/types.tsCheckResult 通过 extends 共享版本的 ApiCheckResult 增加 targetName 等内部字段
  • 存储层类型(StoredTargetStoredCheckResult)独立定义,与 API 类型分离

数据存储规范

基于 bun:sqliteWAL 模式运行,数据库文件位于配置的 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

  • targetsnameUNIQUE、type、target展示摘要、configJSON、interval_ms、timeout_ms、expectJSON、grp
  • check_resultstarget_idFK CASCADE、timestamp、matched0/1、duration_ms、status_detail、failureJSON
  • 复合索引:(target_id, timestamp)

拨测引擎

  • 调度ProbeEnginees-toolkit/groupBy 按 interval 分组,每组独立 setInterval 定时触发
  • 并发控制es-toolkit/Semaphore 限制全局最大并发数(maxConcurrentChecksacquire() 阻塞等待
  • Runner 选择engine.runCheck()target.type 分发到 runHttpCheckrunCommandCheck
  • 超时控制HTTP 用 AbortControllerCommand 用 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:正则表达式匹配
  • jsonJSONPath 提取 + 操作符比较(使用 es-toolkit/isPlainObject 区分纯值和操作符)
  • csscheerio CSS 选择器 + 操作符比较
  • xpathXPath 节点提取 + 操作符比较

操作符equals(深度比较,es-toolkit/isEqual)、containsmatch(正则)、emptyisNil+isEmptyObject)、existsgte/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.tssrc/server/checker/store.ts
  • 使用 bun:test 框架(describe/test/expect),测试数据库用临时目录 + tmpdir()
  • 新增 store 方法必须编写单元测试;新增 API 端点必须在 app.test.ts 中添加集成测试
  • 测试后清理:afterAllstore.close() + rm(tempDir, { recursive: true })

前端样式规范

前端基于 TDesign React 构建UI样式开发遵循以下优先级从高到低

  1. 使用 TDesign 组件:布局、间距、排版优先使用 TDesign 组件(如 Space、Divider、Typography
  2. 使用 TDesign 组件 props:通过组件的 props 参数控制外观(如 themevariantsize
  3. 使用 TDesign CSS tokens:颜色、间距、字体等使用 --td-* CSS 变量(如 --td-success-color--td-comp-margin-xxl
  4. 在 styles.css 中定义 CSS 类:无法通过上述方式满足的样式需求,集中定义在 styles.css
  5. 自行开发组件:仅在 TDesign 无法满足需求时自行开发

红线

  • 严禁在组件中使用 style 属性内联调整样式
  • 严禁通过 CSS 覆盖 TDesign 组件内部类名(如 .t-tab-panel),如需定制使用组件的 className prop
  • 严禁使用 !important
  • 颜色统一使用 TDesign CSS tokens--td-success-color--td-error-color--td-warning-color 等),不使用硬编码色值

已知限制

当前不做告警通知、数据自动清理、拨测目标动态增删、认证鉴权和分布式部署。Command 类型拨测不支持 Windows 环境。