113 lines
5.5 KiB
Markdown
113 lines
5.5 KiB
Markdown
## Context
|
||
|
||
当前 expect 校验通过 `checkExpect()` 函数(`src/server/checker/fetcher.ts`)实现,仅支持 status 白名单、bodyContains 子串匹配、maxLatencyMs 延迟阈值。body 校验能力薄弱,无法处理 JSON 结构化数据和 HTML/XML 页面内容校验。
|
||
|
||
本次设计将 body 校验扩展为五种可组合方法,并引入操作符系统统一提取值的比较逻辑。同时新增响应头校验。
|
||
|
||
项目约束:Bun 1.3.13 运行时、TypeScript、SQLite 持久化、YAML 配置格式。
|
||
|
||
## Goals / Non-Goals
|
||
|
||
**Goals:**
|
||
- body 校验支持五大方法:contains、regex、json、css、xpath,任意 AND 组合
|
||
- 操作符系统:equals(默认)、contains、match、empty、exists、gte、lte、gt、lt
|
||
- 响应头校验 headers
|
||
- 保持 matched/success 两层判定模型不变
|
||
- 所有新逻辑有完整单元测试
|
||
|
||
**Non-Goals:**
|
||
- 不支持 json/csv/xpath 的 OR 组合(当前全 AND)
|
||
- 不支持 JSONPath 的通配符/过滤器(`.items[*].name`、`.items[?(@.price>10)]`)
|
||
- 不支持 CSS 伪类选择器(如 `:nth-child`)
|
||
- 不改变前端 Dashboard UI
|
||
- 不做告警通知
|
||
|
||
## Decisions
|
||
|
||
### D1: body 分组嵌套结构
|
||
|
||
选择 `expect.body.<method>` 而非平铺 `expect.bodyXxx`。
|
||
|
||
**理由**:五种 body 方法语义上同属一层,嵌套结构比平铺更清晰,YAML 可读性更好。代价是将 `bodyContains` 从 `ExpectConfig` 顶层迁移至 `body.contains`,属于 **BREAKING** 变更,但项目尚在早期,影响极小。
|
||
|
||
**替代方案**:平铺 `expect.bodyContains`、`expect.bodyRegex` 等。不选,因随着方法增多字段名会越来越长且缺乏层次。
|
||
|
||
### D2: 操作符采用"标量=equals,对象=显式"的二态模型
|
||
|
||
```yaml
|
||
json:
|
||
$.status: ok # 标量 → equals
|
||
$.data.count: # 对象 → 显式操作符
|
||
gte: 1
|
||
```
|
||
|
||
**理由**:90% 的拨测场景只需要等值比较,标量语法最简洁。需要复杂比较时展开为对象,二态在同一个 map 中共存,无需额外字段指示意图。
|
||
|
||
**替代方案**:每个规则必须是 `{ path, operator, value }` 对象。过于冗长,不如二态模型灵活。
|
||
|
||
### D3: CSS 选择器通过 `attr` 切换提取维度
|
||
|
||
```yaml
|
||
css:
|
||
"div.status": OK # 默认 textContent
|
||
"meta[name=build-hash]": # 提取属性
|
||
attr: content
|
||
empty: false
|
||
```
|
||
|
||
**理由**:99% 的 CSS 选择器场景只需要 textContent。通过可选的 `attr` 字段覆盖属性提取场景,保持常见用法最简。
|
||
|
||
**替代方案**:在选择器字符串中编码(如 `meta[name=build]@content`)。不选,语法污染。
|
||
|
||
### D4: 依赖选型 cheerio + xpath + @xmldom/xmldom
|
||
|
||
| 包 | 用途 | 选型理由 |
|
||
|----|------|---------|
|
||
| cheerio | CSS 选择器 HTML 解析 | npm 27M+ 周下载,jQuery API 熟悉度高,依赖树由同一组织维护 |
|
||
| xpath | XPath 1.0 引擎 | npm 600K+ 周下载,轻量,业界标准 |
|
||
| @xmldom/xmldom | xpath 的 DOM 实现 | 2M+ 周下载,xmldom 官方维护 |
|
||
|
||
**替代方案**:jsdom(体积大,~200KB)、linkedom(不支持 XPath)。不选。
|
||
|
||
cheerio 和 xpath 使用不同的 DOM 模型,同一个 HTML body 需要各自解析。拨测场景(秒级频率,非高并发 HTML 解析)性能开销可忽略。
|
||
|
||
### D5: body 方法按需解析,短路 AND 执行
|
||
|
||
整体 checkExpect 执行顺序为 `status → headers → body → maxLatencyMs`,均为 AND 短路。body 内部执行顺序:
|
||
|
||
```
|
||
body 内部:
|
||
1. contains → 文本匹配,失败立即返回
|
||
2. regex → 文本匹配,失败立即返回
|
||
3. json → 仅当 json 配置存在时解析 JSON(否则跳过)
|
||
4. css → 仅当 css 配置存在时解析 HTML(cheerio)
|
||
5. xpath → 仅当 xpath 配置存在时解析 HTML/XML(xmldom)
|
||
|
||
解析失败(JSON.parse 异常、cheerio 加载失败)→ matched=false
|
||
```
|
||
|
||
**理由**:避免不必要的解析开销(例如只配了 contains 时不解析 JSON/HTML)。AND 短路语义与现有 expect 规则保持一致。
|
||
|
||
### D6: 操作符的类型转换策略
|
||
|
||
| 操作符 | 提取值类型 | 转换逻辑 |
|
||
|--------|-----------|---------|
|
||
| equals | 保留原类型 | strict === 比较 |
|
||
| contains | 强制 toString() | actual.toString().includes(expected) |
|
||
| match | 强制 toString() | new RegExp(pattern).test(actual.toString()) |
|
||
| empty | - | null、undefined、""、[]、{} → 判定为空 |
|
||
| exists | - | undefined vs 非 undefined |
|
||
| gte/lte/gt/lt | 强制 Number() | Number(actual) >= expected |
|
||
|
||
CSS/XPath 提取的值始终是 string,数字比较时自动 Number() 转换。JSON 提取的值保留原类型(number/boolean/null/string)。
|
||
|
||
单个提取值可配置多个操作符(如 `{gte: 10, lte: 100}`),所有操作符全部通过才算该字段通过,语义为 AND。
|
||
|
||
## Risks / Trade-offs
|
||
|
||
- **[兼容性风险]** `bodyContains` → `body.contains` 是 BREAKING 变更 → 通过 README 和示例配置文件说明,现有用户量极小,影响可控
|
||
- **[性能风险]** cheerio 和 xpath 各自解析 HTML → 同一 body 可能解析两次 → 拨测场景下无需缓存,单次解析耗时 <5ms,整体影响可忽略
|
||
- **[JSONPath 功能局限]** 自实现简易路径解析不支持通配符和过滤器 → 通过文档说明限制,后续可按需增强
|
||
- **[XPath 浏览器兼容]** xpath 使用 xmldom 而非浏览器原生 evaluate → 语义上等价,测试覆盖保证行为正确
|
||
- **[依赖体积]** 新增 3 个包增加约 95KB → 这是 executable 构建,Bun compile 会打包进二进制,对最终产物影响有限
|