Compare commits
95 Commits
767f26617e
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 2f8fd8bd9c | |||
| 3390eb5e8d | |||
| 145bb8fd04 | |||
| 358f8d011a | |||
| c2dcfab80c | |||
| f38286d74d | |||
| 08b61cbf47 | |||
| c120690cf1 | |||
| 77c6015b3a | |||
| c1db793073 | |||
| 714b635aef | |||
| a6504d5a62 | |||
| 483cdc596b | |||
| 4f33fba793 | |||
| 6601ab458d | |||
| cfca03b4d6 | |||
| cf847ccd7a | |||
| 6e53c8130d | |||
| 6ca8b36542 | |||
| 79358ba50d | |||
| e448cb4654 | |||
| 5238dbe77d | |||
| 007d74934d | |||
| 0d709c7681 | |||
| b432581444 | |||
| ccd16a583e | |||
| 8eac814cc6 | |||
| f3df3a203b | |||
| 60a54b483f | |||
| 6098be2d9e | |||
| b591dcca97 | |||
| 9b53c746f6 | |||
| 375dd3492b | |||
| 22c06820fa | |||
| 12cd05b04e | |||
| 8d8549d07f | |||
| 7a635a0a9f | |||
| 349896bd02 | |||
| 52262a31f6 | |||
| 550c427814 | |||
| c51bc5a0d8 | |||
| 393e8da5fd | |||
| 0a9a9016be | |||
| 31fd3a2a43 | |||
| f7193e98ff | |||
| 7926514986 | |||
| 366b3211c8 | |||
| e924732a02 | |||
| 04c24e6796 | |||
| 146cef982e | |||
| c36df94e59 | |||
| f8d563c668 | |||
| 88f4119a4e | |||
| c46ab14cce | |||
| 8793fbd786 | |||
| 2b08f81a0d | |||
| 86b8cf1950 | |||
| d6a77b2c6e | |||
| 28e46b8431 | |||
| 9904f198aa | |||
| c61a4a6091 | |||
| 1c5cfafda6 | |||
| e983e5d75d | |||
| 0fa2c0c811 | |||
| 6e485cc991 | |||
| bcfac52112 | |||
| 31aeee6d60 | |||
| a62007083d | |||
| 76b47006fe | |||
| 147a2559ae | |||
| 6ea185315f | |||
| ecd47748d2 | |||
| bcfb907bd3 | |||
| 26f0bfe104 | |||
| bb6b2bc20b | |||
| c396c29402 | |||
| aade0bbff7 | |||
| 7b20b59b79 | |||
| bce0f8e7a8 | |||
| 2fd0f206be | |||
| 87d946a441 | |||
| ad87be6956 | |||
| 9a71b7967c | |||
| a5cf6065c2 | |||
| ce8baae3d1 | |||
| e1c33b4002 | |||
| f7facb7232 | |||
| 696db6ffb5 | |||
| c677b4f756 | |||
| 9f2b906063 | |||
| 3e8d01715f | |||
| 3b9006345e | |||
| f48e39a615 | |||
| 48b40238b8 | |||
| 3fa1b3957e |
8
.claude/settings.json
Normal file
8
.claude/settings.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"tdesign-mcp-server": {
|
||||
"command": "bunx",
|
||||
"args": ["tdesign-mcp-server@latest"]
|
||||
}
|
||||
}
|
||||
}
|
||||
15
.dockerignore
Normal file
15
.dockerignore
Normal file
@@ -0,0 +1,15 @@
|
||||
.build
|
||||
.claude
|
||||
.codex
|
||||
.env
|
||||
.env.*
|
||||
.git
|
||||
.opencode
|
||||
.agents
|
||||
.DS_Store
|
||||
coverage
|
||||
data
|
||||
dist
|
||||
node_modules
|
||||
*.log
|
||||
*.bun-build
|
||||
32
.gitattributes
vendored
Normal file
32
.gitattributes
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
# 跨平台行尾规范
|
||||
# 所有文本文件统一用 LF(Unix 风格),避免 CRLF/LF 混用
|
||||
* text=auto eol=lf
|
||||
|
||||
# 二进制文件不转换行尾
|
||||
*.png binary
|
||||
*.jpg binary
|
||||
*.jpeg binary
|
||||
*.gif binary
|
||||
*.ico binary
|
||||
*.svg binary
|
||||
*.pdf binary
|
||||
*.zip binary
|
||||
*.gz binary
|
||||
*.tar binary
|
||||
*.woff binary
|
||||
*.woff2 binary
|
||||
*.ttf binary
|
||||
*.eot binary
|
||||
*.mp4 binary
|
||||
*.mov binary
|
||||
*.mp3 binary
|
||||
|
||||
# Shell 脚本必须 LF
|
||||
*.sh text eol=lf
|
||||
|
||||
# Windows 批处理必须 CRLF
|
||||
*.bat text eol=crlf
|
||||
*.cmd text eol=crlf
|
||||
|
||||
# 锁定文件(如 package-lock.json)保持 LF
|
||||
*.lock text eol=lf
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -403,11 +403,15 @@ cython_debug/
|
||||
!.claude/settings.json
|
||||
.opencode
|
||||
.codex
|
||||
.pi/*
|
||||
!.pi/mcp.json
|
||||
!.pi/extensions
|
||||
openspec/changes/archive
|
||||
temp
|
||||
.agents
|
||||
skills-lock.json
|
||||
.worktrees
|
||||
data/
|
||||
!scripts/build/
|
||||
backend/bin
|
||||
backend/server
|
||||
@@ -421,3 +425,4 @@ backend/cmd/desktop/rsrc_windows_*.syso
|
||||
# Bun
|
||||
.build/
|
||||
*.bun-build
|
||||
dist/release/
|
||||
|
||||
1
.husky/commit-msg
Executable file
1
.husky/commit-msg
Executable file
@@ -0,0 +1 @@
|
||||
bunx commitlint --edit $1
|
||||
1
.husky/pre-commit
Executable file
1
.husky/pre-commit
Executable file
@@ -0,0 +1 @@
|
||||
bunx lint-staged
|
||||
4
.lintstagedrc.json
Normal file
4
.lintstagedrc.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"*.{ts,tsx}": ["eslint --fix"],
|
||||
"*.{md,json,yaml,yml}": ["prettier --write"]
|
||||
}
|
||||
19
.pi/extensions/pi-permission-system/config.json
Normal file
19
.pi/extensions/pi-permission-system/config.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/gotgenes/pi-permission-system/main/schemas/permissions.schema.json",
|
||||
"permission": {
|
||||
"*": "allow",
|
||||
"write": "allow",
|
||||
"edit": "allow",
|
||||
"bash": {
|
||||
"*": "allow",
|
||||
"npm *": "deny",
|
||||
"npx *": "deny",
|
||||
"pnpm *": "deny",
|
||||
"pnpx *": "deny"
|
||||
},
|
||||
"external_directory": {
|
||||
"*": "ask",
|
||||
"/tmp/*": "allow"
|
||||
}
|
||||
}
|
||||
}
|
||||
8
.pi/mcp.json
Normal file
8
.pi/mcp.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"tdesign-mcp-server": {
|
||||
"command": "bunx",
|
||||
"args": ["tdesign-mcp-server@latest"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,3 +7,7 @@ bun.lock
|
||||
.opencode/
|
||||
.claude/
|
||||
.codex/
|
||||
.agents/
|
||||
skills-lock.json
|
||||
data/
|
||||
probe-config.schema.json
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
{
|
||||
"printWidth": 120
|
||||
"printWidth": 120,
|
||||
"semi": true,
|
||||
"singleQuote": false,
|
||||
"trailingComma": "all",
|
||||
"bracketSpacing": true,
|
||||
"arrowParens": "always",
|
||||
"endOfLine": "lf",
|
||||
"tabWidth": 2,
|
||||
"useTabs": false
|
||||
}
|
||||
|
||||
10
.vscode/settings.json
vendored
Normal file
10
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"editor.tabSize": 2,
|
||||
"editor.insertSpaces": true,
|
||||
"editor.detectIndentation": false,
|
||||
|
||||
"files.eol": "\n",
|
||||
"files.encoding": "utf8",
|
||||
"files.insertFinalNewline": true,
|
||||
"files.trimTrailingWhitespace": true
|
||||
}
|
||||
45
Dockerfile
Normal file
45
Dockerfile
Normal file
@@ -0,0 +1,45 @@
|
||||
# syntax=docker/dockerfile:1.7
|
||||
|
||||
ARG BUN_IMAGE=oven/bun:1-alpine
|
||||
ARG ALPINE_IMAGE=alpine:3.22
|
||||
|
||||
FROM ${BUN_IMAGE} AS deps
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json bun.lock ./
|
||||
RUN bun install --frozen-lockfile
|
||||
|
||||
FROM deps AS build
|
||||
WORKDIR /app
|
||||
|
||||
COPY . .
|
||||
|
||||
ARG TARGETARCH
|
||||
RUN set -eux; \
|
||||
case "${TARGETARCH:-$(uname -m)}" in \
|
||||
amd64|x86_64) export BUN_TARGET=bun-linux-x64-musl ;; \
|
||||
arm64|aarch64) export BUN_TARGET=bun-linux-arm64-musl ;; \
|
||||
*) echo "不支持的架构: ${TARGETARCH:-$(uname -m)}" >&2; exit 1 ;; \
|
||||
esac; \
|
||||
bun run build
|
||||
|
||||
FROM ${ALPINE_IMAGE} AS runtime
|
||||
|
||||
RUN apk add --no-cache ca-certificates iputils-ping libgcc libstdc++ tzdata \
|
||||
&& addgroup -S dial \
|
||||
&& adduser -S -G dial -h /nonexistent -s /sbin/nologin dial \
|
||||
&& mkdir -p /etc/dial /data/dial \
|
||||
&& chown -R dial:dial /data/dial
|
||||
|
||||
COPY --from=build --chmod=0755 /app/dist/dial-server /usr/local/bin/dial-server
|
||||
COPY --chmod=0644 docker/probes.yaml /etc/dial/probes.yaml
|
||||
|
||||
USER dial
|
||||
WORKDIR /data/dial
|
||||
|
||||
EXPOSE 3000
|
||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
|
||||
CMD wget -q -O - "http://127.0.0.1:3000/health" >/dev/null || exit 1
|
||||
|
||||
ENTRYPOINT ["/usr/local/bin/dial-server"]
|
||||
CMD ["/etc/dial/probes.yaml"]
|
||||
184
LICENSE
Normal file
184
LICENSE
Normal file
@@ -0,0 +1,184 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction, and
|
||||
distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by the copyright
|
||||
owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all other entities
|
||||
that control, are controlled by, or are under common control with that entity.
|
||||
For the purposes of this definition, "control" means (i) the power, direct or
|
||||
indirect, to cause the direction or management of such entity, whether by
|
||||
contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity exercising
|
||||
permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications, including
|
||||
but not limited to software source code, documentation source, and configuration
|
||||
files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical transformation or
|
||||
translation of a Source form, including but not limited to compiled object code,
|
||||
generated documentation, and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or Object form,
|
||||
made available under the License, as indicated by a copyright notice that is
|
||||
included in or attached to the work (an example is provided in the Appendix
|
||||
below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object form, that
|
||||
is based on (or derived from) the Work and for which the editorial revisions,
|
||||
annotations, elaborations, or other modifications represent, as a whole, an
|
||||
original work of authorship. For the purposes of this License, Derivative Works
|
||||
shall not include works that remain separable from, or merely link (or bind by
|
||||
name) to the interfaces of, the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including the original version
|
||||
of the Work and any modifications or additions to that Work or Derivative Works
|
||||
thereof, that is intentionally submitted to Licensor for inclusion in the Work
|
||||
by the copyright owner or by an individual or Legal Entity authorized to submit
|
||||
on behalf of the copyright owner. For the purposes of this definition,
|
||||
"submitted" means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems, and
|
||||
issue tracking systems that are managed by, or on behalf of, the Licensor for
|
||||
the purpose of discussing and improving the Work, but excluding communication
|
||||
that is conspicuously marked or otherwise designated in writing by the copyright
|
||||
owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
|
||||
of whom a Contribution has been received by Licensor and subsequently
|
||||
incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of this
|
||||
License, each Contributor hereby grants to You a perpetual, worldwide,
|
||||
non-exclusive, no-charge, royalty-free, irrevocable copyright license to
|
||||
reproduce, prepare Derivative Works of, publicly display, publicly perform,
|
||||
sublicense, and distribute the Work and such Derivative Works in Source or
|
||||
Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of this License,
|
||||
each Contributor hereby grants to You a perpetual, worldwide, non-exclusive,
|
||||
no-charge, royalty-free, irrevocable (except as stated in this section) patent
|
||||
license to make, have made, use, offer to sell, sell, import, and otherwise
|
||||
transfer the Work, where such license applies only to those patent claims
|
||||
licensable by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s) with the Work
|
||||
to which such Contribution(s) was submitted. If You institute patent litigation
|
||||
against any entity (including a cross-claim or counterclaim in a lawsuit)
|
||||
alleging that the Work or a Contribution incorporated within the Work
|
||||
constitutes direct or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate as of the date
|
||||
such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the Work or
|
||||
Derivative Works thereof in any medium, with or without modifications, and in
|
||||
Source or Object form, provided that You meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or Derivative Works a copy of
|
||||
this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices stating that
|
||||
You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works that You
|
||||
distribute, all copyright, patent, trademark, and attribution notices from the
|
||||
Source form of the Work, excluding those notices that do not pertain to any part
|
||||
of the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its distribution, then
|
||||
any Derivative Works that You distribute must include a readable copy of the
|
||||
attribution notices contained within such NOTICE file, excluding those notices
|
||||
that do not pertain to any part of the Derivative Works, in at least one of the
|
||||
following places: within a NOTICE text file distributed as part of the
|
||||
Derivative Works; within the Source form or documentation, if provided along
|
||||
with the Derivative Works; or, within a display generated by the Derivative
|
||||
Works, if and wherever such third-party notices normally appear. The contents of
|
||||
the NOTICE file are for informational purposes only and do not modify the
|
||||
License. You may add Your own attribution notices within Derivative Works that
|
||||
You distribute, alongside or as an addendum to the NOTICE text from the Work,
|
||||
provided that such additional attribution notices cannot be construed as
|
||||
modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and may provide
|
||||
additional or different license terms and conditions for use, reproduction, or
|
||||
distribution of Your modifications, or for any such Derivative Works as a whole,
|
||||
provided Your use, reproduction, and distribution of the Work otherwise complies
|
||||
with the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise, any
|
||||
Contribution intentionally submitted for inclusion in the Work by You to the
|
||||
Licensor shall be under the terms and conditions of this License, without any
|
||||
additional terms or conditions. Notwithstanding the above, nothing herein shall
|
||||
supersede or modify the terms of any separate license agreement you may have
|
||||
executed with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade names,
|
||||
trademarks, service marks, or product names of the Licensor, except as required
|
||||
for reasonable and customary use in describing the origin of the Work and
|
||||
reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or agreed to in
|
||||
writing, Licensor provides the Work (and each Contributor provides its
|
||||
Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied, including, without limitation, any warranties
|
||||
or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any risks
|
||||
associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory, whether in
|
||||
tort (including negligence), contract, or otherwise, unless required by
|
||||
applicable law (such as deliberate and grossly negligent acts) or agreed to in
|
||||
writing, shall any Contributor be liable to You for damages, including any
|
||||
direct, indirect, special, incidental, or consequential damages of any character
|
||||
arising as a result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill, work stoppage,
|
||||
computer failure or malfunction, or any and all other commercial damages or
|
||||
losses), even if such Contributor has been advised of the possibility of such
|
||||
damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing the Work or
|
||||
Derivative Works thereof, You may choose to offer, and charge a fee for,
|
||||
acceptance of support, warranty, indemnity, or other liability obligations
|
||||
and/or rights consistent with this License. However, in accepting such
|
||||
obligations, You may act only on Your own behalf and on Your sole
|
||||
responsibility, not on behalf of any other Contributor, and only if You agree to
|
||||
indemnify, defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason of your
|
||||
accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following boilerplate
|
||||
notice, with the fields enclosed by brackets "[]" replaced with your own
|
||||
identifying information. (Don't include the brackets!) The text should be
|
||||
enclosed in the appropriate comment syntax for the file format. We also
|
||||
recommend that a file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier identification within
|
||||
third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
301
README.md
301
README.md
@@ -1,278 +1,99 @@
|
||||
# Gateway Checker
|
||||
<h1 align="center">DiAL</h1>
|
||||
|
||||
基于 Bun + TypeScript 的多类型拨测监控工具。通过 YAML 配置文件定义 HTTP 和命令行拨测目标,后端按配置定时并发拨测,结果持久化到本地 SQLite,前端 Dashboard 展示各目标实时状态、可用率、耗时趋势等。
|
||||
<p align="center">
|
||||
<strong>轻量级多类型拨测监控工具</strong>
|
||||
</p>
|
||||
|
||||
## 项目结构
|
||||
<p align="center">
|
||||
基于 Bun + TypeScript 构建 · YAML 配置驱动 · 内置 Dashboard
|
||||
</p>
|
||||
|
||||
```text
|
||||
src/
|
||||
server/
|
||||
app.ts Bun HTTP 路由(API + 静态资源 + SPA fallback)
|
||||
config.ts CLI 参数解析
|
||||
dev.ts 开发期启动入口
|
||||
server.ts HTTP server 启动
|
||||
checker/
|
||||
types.ts 类型定义
|
||||
config-loader.ts YAML 配置解析与校验
|
||||
store.ts SQLite 数据存储
|
||||
fetcher.ts HTTP 拨测执行
|
||||
command-runner.ts 命令行拨测执行
|
||||
size.ts 大小单位解析
|
||||
engine.ts 调度引擎(按 interval 分组、组内并发)
|
||||
expect/
|
||||
http.ts HTTP 响应断言
|
||||
command.ts 命令行输出断言
|
||||
body.ts HTTP body 断言(JSONPath/XPath/CSS)
|
||||
failure.ts 失败信息类型
|
||||
shared/
|
||||
api.ts 前后端共享 TypeScript 类型
|
||||
web/ Vite + React 前端 Dashboard
|
||||
components/ UI 组件(卡片、分组、模态框、状态条等)
|
||||
constants/ 常量定义(类型映射等)
|
||||
hooks/ 数据轮询和详情管理 hooks
|
||||
utils/ 前端工具函数
|
||||
scripts/ 开发、构建和 smoke test 脚本
|
||||
tests/ Bun test 测试
|
||||
openspec/ OpenSpec 变更与规格文档
|
||||
```
|
||||
---
|
||||
|
||||
DiAL 是一个自托管的拨测监控工具,支持 **HTTP**、**命令行**、**数据库**、**TCP**、**UDP**、**DNS**、**ICMP** 和 **LLM** 多种拨测类型。通过 YAML 配置文件定义拨测目标,后端定时并发执行拨测并将结果持久化到本地 SQLite,前端 Dashboard 展示各目标的实时状态、可用率和耗时趋势。
|
||||
|
||||
## 功能亮点
|
||||
|
||||
- 多类型拨测:HTTP、Cmd、DB、TCP、UDP、DNS、ICMP、LLM
|
||||
- 丰富校验规则:状态码、响应头、JSONPath、CSS 选择器、XPath、正则匹配、数值比较等
|
||||
- 结构化观测数据:HTTP body 预览、TCP/UDP 响应摘要、ICMP 丢包率、CMD 输出、LLM token 用量等
|
||||
- 内置 Dashboard:实时状态、可用率统计、趋势图、最近状态条、手动/自动刷新、版本号展示
|
||||
- 多主题支持:系统、明亮、黑暗三种主题模式
|
||||
- 自托管部署:本地 SQLite 存储,无需额外数据库服务
|
||||
|
||||
## 应用截图
|
||||
|
||||
| | 亮色 | 暗色 |
|
||||
| ------ | --------------------------------------------------- | ------------------------------------------------- |
|
||||
| 主页 |  |  |
|
||||
| 详情页 |  |  |
|
||||
|
||||
## 快速开始
|
||||
|
||||
**前置条件:** [Bun](https://bun.sh/) >= 1.0
|
||||
|
||||
ICMP checker 依赖系统 `ping` 命令。精简容器镜像需额外安装,例如 Alpine 可安装 `iputils-ping`。
|
||||
|
||||
```bash
|
||||
git clone https://github.com/your-org/DiAL.git
|
||||
cd DiAL
|
||||
bun install
|
||||
cp probes.example.yaml probes.yaml
|
||||
bun run dev probes.yaml
|
||||
```
|
||||
|
||||
`bun run dev` 会同时启动 Bun 后端和 Vite 前端。开发期请打开 Vite 前端地址 `http://127.0.0.1:5173`。
|
||||
`bun run dev` 会同时启动 Vite 开发服务器(`http://127.0.0.1:5173`)和 API 服务器(`http://127.0.0.1:3000`),访问前端地址即可使用 Dashboard。
|
||||
|
||||
也可以分别运行:
|
||||
|
||||
```bash
|
||||
bun run dev:server probes.yaml
|
||||
bun run dev:web
|
||||
```
|
||||
|
||||
## 配置文件
|
||||
|
||||
程序通过 YAML 配置文件定义所有运行参数:
|
||||
## 最小配置示例
|
||||
|
||||
```yaml
|
||||
server:
|
||||
host: "127.0.0.1"
|
||||
port: 3000
|
||||
dataDir: "/tmp/probes_data"
|
||||
|
||||
runtime:
|
||||
maxConcurrentChecks: 20
|
||||
|
||||
defaults:
|
||||
interval: "5s"
|
||||
timeout: "10s"
|
||||
http:
|
||||
method: GET
|
||||
maxBodyBytes: "100MB"
|
||||
command:
|
||||
maxOutputBytes: "100MB"
|
||||
# yaml-language-server: $schema=./probe-config.schema.json
|
||||
|
||||
targets:
|
||||
- name: "Baidu"
|
||||
- id: "baidu-home"
|
||||
name: "Baidu"
|
||||
type: http
|
||||
http:
|
||||
url: "https://www.baidu.com"
|
||||
expect:
|
||||
status: [200]
|
||||
maxDurationMs: 10000
|
||||
|
||||
- name: "JSON API 示例"
|
||||
type: http
|
||||
http:
|
||||
url: "https://httpbin.org/json"
|
||||
expect:
|
||||
status: [200]
|
||||
headers:
|
||||
Content-Type:
|
||||
contains: "application/json"
|
||||
body:
|
||||
- contains: "slideshow"
|
||||
- json:
|
||||
path: "$.slideshow.title"
|
||||
equals: "Sample Slide Show"
|
||||
|
||||
- name: "HTML 页面示例"
|
||||
type: http
|
||||
http:
|
||||
url: "https://httpbin.org/html"
|
||||
expect:
|
||||
status: [200]
|
||||
body:
|
||||
- contains: "Moby-Dick"
|
||||
- xpath:
|
||||
path: "/html/body/h1/text()"
|
||||
equals: "Herman Melville - Moby-Dick"
|
||||
|
||||
- name: "Nginx 进程检查"
|
||||
type: command
|
||||
command:
|
||||
exec: "pgrep"
|
||||
args: ["nginx"]
|
||||
expect:
|
||||
exitCode: [0]
|
||||
stdout:
|
||||
- match: "\\d+"
|
||||
durationMs:
|
||||
lte: 5000
|
||||
```
|
||||
|
||||
### 配置说明
|
||||
完整配置、checker、expect 和部署说明参见 [用户文档](docs/user/README.md)、[配置文件](docs/user/configuration.md)、[Checker 参考](docs/user/checkers/README.md) 和 [校验规则](docs/user/expectations.md)。
|
||||
|
||||
- **server**: 服务配置(均可省略,使用默认值)
|
||||
- `host`: 监听地址,默认 `127.0.0.1`
|
||||
- `port`: 监听端口,默认 `3000`
|
||||
- `dataDir`: 数据目录,默认 `./data`
|
||||
- **runtime**: 运行时配置
|
||||
- `maxConcurrentChecks`: 最大并发拨测数,默认 `20`
|
||||
- **defaults**: 全局默认值(均可省略)
|
||||
- `interval`: 拨测间隔,默认 `30s`
|
||||
- `timeout`: 超时时间,默认 `10s`
|
||||
- `http`: HTTP 类型默认值
|
||||
- `method`: HTTP 方法,默认 `GET`
|
||||
- `maxBodyBytes`: 响应体最大字节数,默认 `100MB`
|
||||
- `command`: Command 类型默认值
|
||||
- `maxOutputBytes`: 输出最大字节数,默认 `100MB`
|
||||
- **targets**: 拨测目标列表(必填)
|
||||
- `name`: 目标名称(必填,唯一)
|
||||
- `type`: 目标类型,`http` 或 `command`(必填)
|
||||
- `group`: 分组名称(可选,默认 `"default"`)
|
||||
- `http`: HTTP 拨测配置(type 为 http 时必填)
|
||||
- `url`: 目标 URL
|
||||
- `method`、`headers`、`body`: 请求参数
|
||||
- `command`: 命令行拨测配置(type 为 command 时必填)
|
||||
- `exec`: 可执行文件名或路径
|
||||
- `args`: 命令行参数列表
|
||||
- `env`: 环境变量覆盖(可选,继承进程环境变量并合并覆盖)
|
||||
- `cwd`: 工作目录(可选,相对于配置文件所在目录解析,默认 `.`)
|
||||
- `interval`、`timeout`: 覆盖全局默认值
|
||||
- `expect`: 期望校验
|
||||
- `status`: 可接受的状态码列表(HTTP)
|
||||
- `exitCode`: 可接受的退出码列表(Command)
|
||||
- `headers`: 响应头校验(HTTP,支持 `equals`、`contains` 等操作符)
|
||||
- `maxDurationMs`: 最大耗时阈值(毫秒)
|
||||
- `body`: HTTP 响应体校验(数组,可组合使用)
|
||||
- `contains`: 响应体包含的文本
|
||||
- `match`: 响应体匹配的正则表达式
|
||||
- `json`: JSONPath 提取值比较(`path` + 比较操作符)
|
||||
- `css`: CSS 选择器提取 HTML 元素比较
|
||||
- `xpath`: XPath 提取 XML/HTML 节点比较
|
||||
- `stdout` / `stderr`: Command 输出校验(数组,同 body 格式)
|
||||
- 比较操作符:`equals`(默认)、`contains`、`match`(正则)、`empty`、`exists`、`gte`、`lte`、`gt`、`lt`
|
||||
|
||||
大小说明:`maxBodyBytes` 和 `maxOutputBytes` 支持单位 `KB`、`MB`、`GB`,也可直接使用数字(字节数)。
|
||||
|
||||
时长格式支持:`30s`、`5m`、`500ms`
|
||||
|
||||
## API 端点
|
||||
|
||||
| 端点 | 说明 |
|
||||
| ----------------------------------------------------------------- | --------------------------------------- |
|
||||
| `GET /health` | 健康检查 |
|
||||
| `GET /api/summary` | 总览统计(total/up/down/lastCheckTime) |
|
||||
| `GET /api/targets` | 目标列表及最新状态、分组和采样数据 |
|
||||
| `GET /api/targets/:id/history?from=ISO&to=ISO&page=1&pageSize=20` | 指定目标的拨测记录(时间范围 + 分页) |
|
||||
| `GET /api/targets/:id/trend?from=ISO&to=ISO` | 指定目标的按小时聚合趋势 |
|
||||
|
||||
### 响应字段
|
||||
|
||||
**SummaryResponse**: `total`、`up`、`down`、`lastCheckTime`
|
||||
|
||||
**TargetStatus**: `id`、`name`、`type`(http/command)、`target`(URL 或命令摘要)、`group`、`interval`、`latestCheck`、`stats`、`recentSamples`
|
||||
|
||||
**RecentSample**: `timestamp`、`durationMs`、`up`
|
||||
|
||||
**CheckResult**: `timestamp`、`matched`、`durationMs`、`statusDetail`、`failure`
|
||||
|
||||
**CheckFailure**: `kind`(error/mismatch)、`phase`、`path`、`expected`、`actual`、`message`
|
||||
|
||||
**TargetStats**: `totalChecks`、`availability`
|
||||
|
||||
**TrendPoint**: `hour`、`avgDurationMs`、`availability`、`totalChecks`
|
||||
|
||||
**HistoryResponse**: `items`(CheckResult[])、`total`、`page`、`pageSize`
|
||||
|
||||
### 错误响应
|
||||
|
||||
API 错误返回 `ApiErrorResponse` 格式:
|
||||
|
||||
```json
|
||||
{ "error": "描述信息", "status": 400 }
|
||||
```
|
||||
|
||||
| 状态码 | 触发场景 |
|
||||
| ------ | ----------------------------------------------------------------------- |
|
||||
| 400 | 参数格式错误(无效 ID、from/to 缺失或格式错误、page/pageSize 非正整数) |
|
||||
| 404 | 目标不存在、API 路由未匹配 |
|
||||
| 405 | 非 GET 方法请求 API 路由 |
|
||||
|
||||
## 代码质量
|
||||
|
||||
```bash
|
||||
bun run lint
|
||||
bun run format:check
|
||||
bun run format
|
||||
bun run check
|
||||
```
|
||||
|
||||
- `check` 依次运行 `typecheck`、`lint`、`format:check` 和单元测试。
|
||||
|
||||
## 构建 executable
|
||||
## 生产运行
|
||||
|
||||
```bash
|
||||
bun run build
|
||||
./dist/dial-server ./probes.yaml
|
||||
```
|
||||
|
||||
构建流程:
|
||||
Docker、跨平台发布包和运行时注意事项参见 [部署文档](docs/user/deployment.md)。
|
||||
|
||||
1. 运行 `vite build`,输出前端资源到 `dist/web`
|
||||
2. 生成临时 `.build/static-assets.ts`,嵌入 Vite 产物
|
||||
3. 生成临时 `.build/server-entry.ts`,作为生产入口
|
||||
4. 运行 `Bun.build({ compile })`,输出 `dist/gateway-checker`
|
||||
## 文档导航
|
||||
|
||||
运行 executable:
|
||||
| 入口 | 内容 |
|
||||
| -------------------------------------------- | ------------------------------------------- |
|
||||
| [文档总览](docs/README.md) | 全部文档入口和文档归属矩阵 |
|
||||
| [用户文档](docs/user/README.md) | 配置、部署、expect、排障和 checker 使用入口 |
|
||||
| [配置文件](docs/user/configuration.md) | YAML 结构、变量、server、targets 通用字段 |
|
||||
| [Checker 参考](docs/user/checkers/README.md) | 所有 checker 的配置、expect 和示例 |
|
||||
| [校验规则](docs/user/expectations.md) | expect 规则、状态判定、failure、observation |
|
||||
| [部署文档](docs/user/deployment.md) | 构建、Docker、发布包和容器运行边界 |
|
||||
| [故障排查](docs/user/troubleshooting.md) | 常见运行问题和排查入口 |
|
||||
| [开发文档](docs/development/README.md) | 开发入口、常用命令、质量门禁和专题索引 |
|
||||
|
||||
## 开发
|
||||
|
||||
```bash
|
||||
./dist/gateway-checker probes.yaml
|
||||
bun run check # schema:check + typecheck + lint + test
|
||||
bun run verify # check + build
|
||||
```
|
||||
|
||||
## 运行参数
|
||||
开发入口参见 [开发文档](docs/development/README.md)。新增或修改 checker 前请先阅读 [Checker 开发](docs/development/checker.md)。
|
||||
|
||||
CLI 只接受一个参数:YAML 配置文件路径。
|
||||
## License
|
||||
|
||||
```bash
|
||||
./dist/gateway-checker ./probes.yaml
|
||||
```
|
||||
|
||||
## 测试
|
||||
|
||||
```bash
|
||||
bun run check
|
||||
bun run verify
|
||||
```
|
||||
|
||||
- `check` 适合日常开发,包含类型检查、lint、格式检查和单元测试。
|
||||
- `verify` 先运行 `check`,再重新构建生产 executable 并运行 smoke test。
|
||||
|
||||
## 前后端边界
|
||||
|
||||
前端只通过 HTTP 调用后端,API 路径为 `/api/*`。共享类型放在 `src/shared`,前端不得 import `src/server` 的运行时实现。
|
||||
|
||||
## 目标状态判定
|
||||
|
||||
单层判定模型,适用于 HTTP 和 Command 两种类型:
|
||||
|
||||
- **matched**: 是否符合 expect 规则(无 expect 时默认为 true)
|
||||
- **UP** = matched
|
||||
- **DOWN** = NOT matched
|
||||
|
||||
执行失败(网络错误、超时、进程崩溃)和 expect 不匹配都统一为 `matched=false`,通过 `failure.kind` 区分(`"error"` vs `"mismatch"`)。
|
||||
|
||||
## 已知限制
|
||||
|
||||
当前不做告警通知、数据自动清理、拨测目标动态增删、认证鉴权和分布式部署。Command 类型拨测不支持 Windows 环境。
|
||||
Apache-2.0
|
||||
|
||||
BIN
assets/screenshot/dark_detail.png
Normal file
BIN
assets/screenshot/dark_detail.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 340 KiB |
BIN
assets/screenshot/dark_index.png
Normal file
BIN
assets/screenshot/dark_index.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 349 KiB |
BIN
assets/screenshot/light_detail.png
Normal file
BIN
assets/screenshot/light_detail.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 460 KiB |
BIN
assets/screenshot/light_index.png
Normal file
BIN
assets/screenshot/light_index.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 484 KiB |
3
bunfig.toml
Normal file
3
bunfig.toml
Normal file
@@ -0,0 +1,3 @@
|
||||
[test]
|
||||
preload = ["./tests/setup.ts"]
|
||||
exclude = ["./tests/e2e/**"]
|
||||
8
commitlint.config.js
Normal file
8
commitlint.config.js
Normal file
@@ -0,0 +1,8 @@
|
||||
export default {
|
||||
extends: ["@commitlint/config-conventional"],
|
||||
rules: {
|
||||
"subject-case": [0],
|
||||
"subject-full-stop": [0],
|
||||
"type-enum": [2, "always", ["feat", "fix", "refactor", "docs", "style", "test", "chore"]],
|
||||
},
|
||||
};
|
||||
17
docker/probes.yaml
Normal file
17
docker/probes.yaml
Normal file
@@ -0,0 +1,17 @@
|
||||
# yaml-language-server: $schema=../probe-config.schema.json
|
||||
|
||||
server:
|
||||
listen:
|
||||
host: "${DIAL_HOST|0.0.0.0}"
|
||||
port: "${DIAL_PORT|3000}"
|
||||
storage:
|
||||
dataDir: "${DIAL_DATA_DIR|/data/dial}"
|
||||
|
||||
targets:
|
||||
- id: "self-health"
|
||||
name: "DiAL 自检"
|
||||
type: http
|
||||
http:
|
||||
url: "http://127.0.0.1:${DIAL_PORT|3000}/health"
|
||||
expect:
|
||||
status: [200]
|
||||
127
docs/README.md
Normal file
127
docs/README.md
Normal file
@@ -0,0 +1,127 @@
|
||||
# DiAL 文档
|
||||
|
||||
本文档是 DiAL 的文档路由入口。AI 工具和开发者应先阅读本文件判断本次任务需要读取和更新哪些专题文档,再按任务类型读取最小必要上下文。
|
||||
|
||||
## 目录索引
|
||||
|
||||
```text
|
||||
docs/
|
||||
README.md
|
||||
development/
|
||||
README.md
|
||||
architecture.md
|
||||
backend.md
|
||||
frontend.md
|
||||
release.md
|
||||
checker.md
|
||||
user/
|
||||
README.md
|
||||
configuration.md
|
||||
deployment.md
|
||||
expectations.md
|
||||
troubleshooting.md
|
||||
checkers/
|
||||
README.md
|
||||
http.md
|
||||
cmd.md
|
||||
db.md
|
||||
tcp.md
|
||||
udp.md
|
||||
icmp.md
|
||||
dns.md
|
||||
llm.md
|
||||
```
|
||||
|
||||
`docs/prompts/` 是提示词资产目录,不属于常规开发流程和用户使用文档。代码、配置、部署或 checker 变更不需要更新该目录,除非任务明确要求维护提示词资产。
|
||||
|
||||
## 入口文档
|
||||
|
||||
| 入口 | 定位 |
|
||||
| ------------------------------------------- | ------------------------------------------ |
|
||||
| [项目 README](../README.md) | 项目整体介绍、快速开始、核心能力、文档引导 |
|
||||
| [开发文档](development/README.md) | 开发入口、全局规则、常用命令、质量门禁 |
|
||||
| [用户文档](user/README.md) | 用户使用入口、配置、部署、expect、排障 |
|
||||
| [Checker 用户参考](user/checkers/README.md) | 各 checker 的配置项、expect 字段和示例 |
|
||||
|
||||
## 按任务阅读路径
|
||||
|
||||
| 任务 | 必读文档 |
|
||||
| ----------------------------------- | ------------------------------------------------------------------------------------------------------------ |
|
||||
| 修改项目介绍或快速开始 | [项目 README](../README.md)、本文档 |
|
||||
| 修改开发流程、质量门禁或工程规则 | [开发文档](development/README.md)、本文档、[OpenSpec 配置](../openspec/config.yaml) |
|
||||
| 修改架构边界或启动流程 | [开发文档](development/README.md)、[架构与边界](development/architecture.md) |
|
||||
| 修改后端 API、store、engine、logger | [开发文档](development/README.md)、[后端开发](development/backend.md) |
|
||||
| 修改前端 | [开发文档](development/README.md)、[前端开发](development/frontend.md) |
|
||||
| 新增或修改 checker | [Checker 开发](development/checker.md)、[Checker 用户参考](user/checkers/README.md)、相近 checker 文档 |
|
||||
| 修改配置 schema | [配置文件](user/configuration.md)、[后端开发](development/backend.md)、相关 checker 文档 |
|
||||
| 修改 expect 或状态模型 | [校验规则](user/expectations.md)、[后端开发](development/backend.md)、[Checker 开发](development/checker.md) |
|
||||
| 修改构建、Docker、release | [构建与发布](development/release.md)、[部署文档](user/deployment.md) |
|
||||
| 修改故障处理或运行依赖 | [故障排查](user/troubleshooting.md)、相关用户文档 |
|
||||
| 修改文档规则或文档目录结构 | 本文档、[OpenSpec 配置](../openspec/config.yaml) |
|
||||
|
||||
## 文档归属矩阵
|
||||
|
||||
| 变更类型 | 默认更新位置 |
|
||||
| -------------------------------------------------------------- | -------------------------------------------------------------- |
|
||||
| 项目定位、核心能力、快速开始、顶层文档导航 | `README.md` |
|
||||
| 文档路由、文档更新规则、文档归属矩阵 | `docs/README.md`、`openspec/config.yaml` |
|
||||
| 开发入口、常用命令、质量门禁、全局工程规则、OpenSpec 约定 | `docs/development/README.md` |
|
||||
| 架构边界、启动流程、运行时流程、前后端边界 | `docs/development/architecture.md` |
|
||||
| 后端 API、共享类型、store、engine、logger、expect 基础设施 | `docs/development/backend.md` |
|
||||
| 前端技术栈、组件、样式、数据层、前端测试 | `docs/development/frontend.md` |
|
||||
| checker 开发机制、schema/validate/resolve/execute/expect 约定 | `docs/development/checker.md` |
|
||||
| 构建、发布、Dockerfile、脚本、前后端静态资源集成 | `docs/development/release.md` |
|
||||
| YAML 顶层结构、server、variables、targets 通用字段 | `docs/user/configuration.md` |
|
||||
| checker 配置、expect 字段、示例、用户可见 checker 行为 | `docs/user/checkers/<type>.md`、`docs/user/checkers/README.md` |
|
||||
| ValueMatcher、ContentExpectations、KeyedExpectations、状态模型 | `docs/user/expectations.md` |
|
||||
| 构建产物运行、Docker 参数、发布包、运行时依赖 | `docs/user/deployment.md` |
|
||||
| 常见运行问题、依赖命令、容器权限、配置校验问题 | `docs/user/troubleshooting.md` |
|
||||
|
||||
## development 文档如何更新
|
||||
|
||||
开发文档解释“如何实现和维护”。代码变更影响开发者理解、开发流程、测试方式或架构边界时,必须更新 `docs/development/` 对应文档。
|
||||
|
||||
- 全局规则、常用命令、质量门禁、目录边界、OpenSpec 约定更新到 `docs/development/README.md`。
|
||||
- 架构图、启动链路、运行时流程、前后端边界更新到 `docs/development/architecture.md`。
|
||||
- 后端 API、配置加载、store、engine、logger、expect 基础设施和后端测试规范更新到 `docs/development/backend.md`。
|
||||
- 前端技术栈、组件边界、数据流、样式规则和前端测试规范更新到 `docs/development/frontend.md`。
|
||||
- checker 开发机制、文件结构、schema、validate、resolve、execute、expect、测试 checklist 更新到 `docs/development/checker.md`。
|
||||
- 构建、Docker、release、脚本和发布验证更新到 `docs/development/release.md`。
|
||||
- 不新增“杂项”开发文档;优先把内容放入上述最贴近的专题,确需新增专题时先更新本文档和 `openspec/config.yaml`。
|
||||
|
||||
## user 文档如何更新
|
||||
|
||||
用户文档解释“如何使用”和“用户能观察到什么”。变更影响用户配置、运行、部署、checker 行为、expect 规则、状态结果或排障方式时,必须更新 `docs/user/` 对应文档。
|
||||
|
||||
- 配置事实来源是 TypeBox schema、`probe-config.schema.json`、语义校验器和测试;`docs/user/configuration.md` 负责解释顶层结构和通用字段。
|
||||
- checker 专属字段和示例只在 `docs/user/checkers/<type>.md` 完整展开,`docs/user/checkers/README.md` 只维护类型索引和选择建议。
|
||||
- expect 断言模型、UP/DOWN、`failure`、`observation`、快速失败顺序更新到 `docs/user/expectations.md`。
|
||||
- Docker、生产运行、发布包和运行时依赖更新到 `docs/user/deployment.md`。
|
||||
- 常见错误和排查路径更新到 `docs/user/troubleshooting.md`。
|
||||
- 用户文档避免解释内部实现细节,需要实现细节时链接到 `docs/development/`。
|
||||
|
||||
## 文档影响分析
|
||||
|
||||
每次代码变更都必须执行文档影响分析。
|
||||
|
||||
```text
|
||||
代码或配置变更
|
||||
-> 用户能感知吗?更新 docs/user/ 或 README.md
|
||||
-> 开发者需要知道吗?更新 docs/development/
|
||||
-> 文档规则变化吗?更新 docs/README.md 和 openspec/config.yaml
|
||||
-> 都不是?收尾说明写明无需更新文档及原因
|
||||
```
|
||||
|
||||
同一事实只在最贴近读者的文档中完整展开,其他文档使用链接引用。根目录 README 保持轻量,不承载完整配置参考、checker 表或实现教程。
|
||||
|
||||
## 收尾说明示例
|
||||
|
||||
```text
|
||||
文档影响分析:本次修改了 HTTP checker 的配置字段,已更新 docs/user/checkers/http.md、docs/user/configuration.md 和 probe-config.schema.json。
|
||||
```
|
||||
|
||||
无需更新文档时:
|
||||
|
||||
```text
|
||||
文档影响分析:本次仅调整内部测试 helper,未改变用户可见行为、配置、架构边界或开发流程,因此无需更新文档。
|
||||
```
|
||||
115
docs/development/README.md
Normal file
115
docs/development/README.md
Normal file
@@ -0,0 +1,115 @@
|
||||
# 开发文档
|
||||
|
||||
本文档是 DiAL 的开发入口。AI 工具和开发者应先阅读 [`../README.md`](../README.md) 判断文档归属,再阅读本文和最小必要专题。
|
||||
|
||||
适用场景:修改源码、测试、构建脚本、开发流程、架构边界、checker 开发机制或项目工程规则。
|
||||
|
||||
## 专题索引
|
||||
|
||||
| 文档 | 内容 |
|
||||
| ---------------------------------- | ------------------------------------------------------------------------------------------------- |
|
||||
| [architecture.md](architecture.md) | 项目结构、启动流程、运行时流程、HTTP 请求流程、前后端边界 |
|
||||
| [backend.md](backend.md) | 后端库优先级、API 路由、共享 helpers、类型规范、配置契约、store、engine、logger、expect、错误模型 |
|
||||
| [frontend.md](frontend.md) | React、TDesign、TanStack Query、组件、样式和前端测试规范 |
|
||||
| [checker.md](checker.md) | 新增或修改 checker 的实现机制、测试要求、文档同步和 checklist |
|
||||
| [release.md](release.md) | 开发服务、前后端集成、构建、Docker、release、脚本、环境变量 |
|
||||
| [../README.md](../README.md) | 文档路由、文档归属矩阵、development/user 文档更新规则 |
|
||||
|
||||
## 常用命令
|
||||
|
||||
| 命令 | 说明 |
|
||||
| -------------------------------- | ---------------------------------------- |
|
||||
| `bun install` | 安装依赖 |
|
||||
| `bun run dev probes.yaml` | 启动双进程开发环境 |
|
||||
| `bun run dev:server probes.yaml` | 仅启动后端 API server |
|
||||
| `bun run dev:web` | 仅启动 Vite dev server |
|
||||
| `bun run schema` | 生成 `probe-config.schema.json` |
|
||||
| `bun run schema:check` | 检查导出 schema 是否同步 |
|
||||
| `bun run typecheck` | TypeScript 类型检查 |
|
||||
| `bun run lint` | ESLint 和 Prettier 格式检查 |
|
||||
| `bun run format` | Prettier 自动格式化 |
|
||||
| `bun test` | 运行全部测试 |
|
||||
| `bun run check` | `schema:check + typecheck + lint + test` |
|
||||
| `bun run build` | 构建生产可执行文件 |
|
||||
| `bun run verify` | `check + build` 完整验证 |
|
||||
| `bun run release` | 跨平台发布打包 |
|
||||
| `bun run clean` | 清理构建缓存与产物 |
|
||||
|
||||
## 质量门禁
|
||||
|
||||
代码变更必须按影响范围执行验证。
|
||||
|
||||
| 变更类型 | 必跑命令 |
|
||||
| -------------------------------- | --------------------------------------------------------- |
|
||||
| 常规代码变更 | `bun run check` |
|
||||
| 构建、部署、发布、前后端集成变更 | `bun run verify` |
|
||||
| 配置 schema 变化 | `bun run schema`、`bun run schema:check`、`bun run check` |
|
||||
| checker 新增或修改 | `bun run schema`、`bun run schema:check`、`bun run check` |
|
||||
| 仅文档变更 | 检查链接、索引和文档归属一致性 |
|
||||
|
||||
正式提交或影响构建产物时优先运行 `bun run verify`。如果因环境限制无法执行完整验证,必须在收尾说明中记录未执行项和原因。
|
||||
|
||||
## 全局工程规则
|
||||
|
||||
- 使用中文编写注释、文档和项目内交流内容。
|
||||
- 仅使用 `bun` 作为包管理器,禁止使用 npm、pnpm、yarn。
|
||||
- 运行工具使用 `bunx`,禁止使用 npx、pnpx。
|
||||
- 新增代码优先复用已有组件、工具和依赖库,不引入新依赖;确需新增依赖时先说明原因。
|
||||
- 后端优先使用 Bun 内置 API,其次是 es-toolkit、标准 Web API、主流三方库,最后才自行实现。
|
||||
- 前端样式优先使用 TDesign 组件、组件 props、TDesign CSS tokens、`styles.css` CSS 类,最后才自行开发组件。
|
||||
- 前端禁止组件内联 `style`、覆盖 TDesign 内部类名、使用 `!important`、硬编码色值。
|
||||
- 当前项目未上线,不需要为旧行为做向前兼容,除非用户明确要求。
|
||||
|
||||
## 包管理、依赖与提交
|
||||
|
||||
- 仅使用 `bun` 安装依赖和运行项目脚本,锁文件为 `bun.lock`。
|
||||
- 新增依赖前先确认 Bun 内置 API、es-toolkit、标准 Web API、现有三方库和项目公共工具是否已满足需求。
|
||||
- Git 提交信息使用中文,格式为 `类型: 简短描述`。
|
||||
- 提交类型限定为 `feat`、`fix`、`refactor`、`docs`、`style`、`test`、`chore`。
|
||||
- 多行提交描述时,标题和正文之间空一行。
|
||||
|
||||
## 目录边界
|
||||
|
||||
| 目录 | 约定 |
|
||||
| ------------------- | ---------------------------------------------------------- |
|
||||
| `src/server/` | Bun 后端代码,不能 import `src/web/`,HTML import 集成除外 |
|
||||
| `src/web/` | React Dashboard,不能 import `src/server/` 运行时实现 |
|
||||
| `src/shared/` | 前后端共享 TypeScript 类型 |
|
||||
| `scripts/` | 独立运行脚本,可 import 项目源码 |
|
||||
| `tests/` | 测试目录,结构镜像 `src/` |
|
||||
| `docs/user/` | 用户使用、配置、部署、checker 和排障文档 |
|
||||
| `docs/development/` | 架构、后端、前端、发布和 checker 开发文档 |
|
||||
| `openspec/` | OpenSpec 变更管理与规格文档 |
|
||||
|
||||
## 文档影响分析
|
||||
|
||||
每次代码变更都必须执行文档影响分析。
|
||||
|
||||
| 如果变更影响 | 更新 |
|
||||
| --------------------------------------------------- | ------------------------------------------ |
|
||||
| 用户可见行为、配置、checker、expect、部署、状态模型 | `docs/user/` 对应文档 |
|
||||
| 开发流程、架构、测试、构建发布、checker 开发机制 | `docs/development/` 对应文档 |
|
||||
| 项目定位、快速开始、核心能力列表、文档导航 | `README.md` |
|
||||
| 文档同步规则或文档归属矩阵 | `docs/README.md` 和 `openspec/config.yaml` |
|
||||
|
||||
如果无需更新文档,必须在收尾说明中说明原因。详细规则见 [文档总览](../README.md)。
|
||||
|
||||
## OpenSpec 协作规则
|
||||
|
||||
- 本项目 OpenSpec 使用 `fast-drive` schema,变更文档只包含 `design.md` 和 `tasks.md`,不创建 `proposal.md` 或 `specs/*.md`。
|
||||
- `design.md` 是 scope、requirements、decisions、guardrails、execution direction 和 verification expectations 的 source of truth。
|
||||
- `tasks.md` 必须从 `design.md` 派生,一行一个 checkbox 任务。
|
||||
- 实现阶段按 `tasks.md` 顺序执行,完成后立即标记任务状态。
|
||||
|
||||
## 事实来源
|
||||
|
||||
| 主题 | 事实来源 |
|
||||
| -------------- | ---------------------------------------------------------- |
|
||||
| 代码结构和实现 | `src/`、`scripts/`、`tests/` |
|
||||
| 配置 schema | TypeBox fragments、`probe-config.schema.json`、schema 测试 |
|
||||
| 项目全局规则 | `openspec/config.yaml`、本文档、本目录专题文档 |
|
||||
| checker 流程 | [checker.md](checker.md) |
|
||||
|
||||
## 更新触发条件
|
||||
|
||||
修改常用命令、质量门禁、全局工程规则、目录边界、OpenSpec 协作方式或开发文档索引时,必须更新本文档。
|
||||
114
docs/development/architecture.md
Normal file
114
docs/development/architecture.md
Normal file
@@ -0,0 +1,114 @@
|
||||
# 架构与边界
|
||||
|
||||
本文档说明 DiAL 的项目结构、启动链路、运行时流程、HTTP 请求流程和前后端边界。
|
||||
|
||||
适用场景:修改目录边界、启动流程、运行时调度、HTTP server、前后端集成方式或主要模块职责。
|
||||
|
||||
## 项目结构
|
||||
|
||||
```text
|
||||
src/
|
||||
server/
|
||||
bootstrap.ts
|
||||
config.ts
|
||||
dev.ts
|
||||
logger.ts
|
||||
main.ts
|
||||
server.ts
|
||||
helpers.ts
|
||||
middleware.ts
|
||||
version.ts
|
||||
routes/
|
||||
checker/
|
||||
config-loader.ts
|
||||
variables.ts
|
||||
schema/
|
||||
store.ts
|
||||
engine.ts
|
||||
expect/
|
||||
runner/
|
||||
shared/
|
||||
api.ts
|
||||
web/
|
||||
app.tsx
|
||||
main.tsx
|
||||
styles.css
|
||||
components/
|
||||
constants/
|
||||
hooks/
|
||||
utils/
|
||||
scripts/
|
||||
tests/
|
||||
docs/
|
||||
openspec/
|
||||
probe-config.schema.json
|
||||
```
|
||||
|
||||
## 启动流程
|
||||
|
||||
```text
|
||||
dev.ts / main.ts
|
||||
-> readRuntimeConfig(cli args)
|
||||
-> bootstrap({ configPath, mode })
|
||||
-> loadConfig(yaml)
|
||||
-> createRuntimeLogger(logging)
|
||||
-> ProbeStore(db)
|
||||
-> store.syncTargets(targets)
|
||||
-> ProbeEngine(...).start()
|
||||
-> startServer({ config, mode, store, logger })
|
||||
-> 注册 SIGINT/SIGTERM shutdown
|
||||
```
|
||||
|
||||
`loadConfig()` 的处理顺序:YAML 解析 -> Authoring normalize(变量替换 + expect 简写展开)-> Normalized 契约校验 -> 语义校验 -> resolve。
|
||||
|
||||
## 运行时流程
|
||||
|
||||
```text
|
||||
定时器 tick
|
||||
-> ProbeEngine.probeGroup()
|
||||
-> checkerRegistry.get(target.type).execute()
|
||||
-> runner/*/expect.ts 校验
|
||||
-> engine.writeResult()
|
||||
-> store.insertCheckResult()
|
||||
```
|
||||
|
||||
数据清理由 engine 定时调用 `store.prune(retentionMs)`,每小时执行一次。
|
||||
|
||||
## HTTP 请求流程
|
||||
|
||||
```text
|
||||
Request
|
||||
-> Bun.serve routes 声明式匹配
|
||||
-> routes/*.ts handler
|
||||
-> middleware.ts 参数校验
|
||||
-> helpers.ts 响应格式化
|
||||
-> Response
|
||||
```
|
||||
|
||||
生产模式下,非 API 路径由 fetch fallback 处理静态资源和 SPA fallback。开发模式下,Vite proxy 将 `/api` 和 `/health` 请求转发到 Bun API server。
|
||||
|
||||
## 前后端边界
|
||||
|
||||
- 前端只通过 HTTP 调用后端,API 路径为 `/api/*`。
|
||||
- 共享类型放在 `src/shared/`。
|
||||
- 前端不得 import `src/server/` 的运行时实现。
|
||||
- 后端不得依赖 `src/web/` 运行时代码,HTML import 集成除外。
|
||||
|
||||
## 主要模块职责
|
||||
|
||||
| 模块 | 职责 |
|
||||
| ------------------------------------- | ------------------------------------------- |
|
||||
| `src/server/bootstrap.ts` | 统一启动引导和 shutdown 编排 |
|
||||
| `src/server/server.ts` | Bun HTTP server 和 routes 注册 |
|
||||
| `src/server/routes/` | API handler,按端点拆分 |
|
||||
| `src/server/checker/config-loader.ts` | YAML 解析、契约校验、语义校验、resolve 调度 |
|
||||
| `src/server/checker/store.ts` | SQLite 数据存储 |
|
||||
| `src/server/checker/engine.ts` | 定时调度、并发控制、结果写入、数据清理 |
|
||||
| `src/server/checker/runner/` | 各 checker 自包含实现 |
|
||||
| `src/server/checker/expect/` | 跨 checker 复用的断言基础设施 |
|
||||
| `src/web/` | React Dashboard |
|
||||
| `src/shared/api.ts` | 前后端共享 API 类型 |
|
||||
|
||||
## 更新触发条件
|
||||
|
||||
修改项目结构、启动流程、运行时流程、HTTP 请求流程、前后端边界或主要模块职责时,必须更新本文档。
|
||||
142
docs/development/backend.md
Normal file
142
docs/development/backend.md
Normal file
@@ -0,0 +1,142 @@
|
||||
# 后端开发
|
||||
|
||||
本文档说明 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 基础设施、错误模型或后端测试规范时,必须更新本文档。
|
||||
221
docs/development/checker.md
Normal file
221
docs/development/checker.md
Normal file
@@ -0,0 +1,221 @@
|
||||
# Checker 开发
|
||||
|
||||
Checker 是 DiAL 的核心扩展单元。每个 checker 是 `src/server/checker/runner/<type>/` 下的自包含目录,包含类型、schema、语义校验、执行逻辑、序列化和断言。
|
||||
|
||||
适用场景:新增 checker、修改 checker 配置或 expect、调整 checker 注册机制、改动 checker 测试或用户文档同步规则。
|
||||
|
||||
新增或修改 checker 前必须阅读 [开发入口](README.md)、[配置文件](../user/configuration.md)、[校验规则](../user/expectations.md) 和 [Checker 用户文档](../user/checkers/README.md)。还应阅读现有同类 checker 的实现和测试,例如 `src/server/checker/runner/http/` 与 `tests/server/checker/runner/http/`。
|
||||
|
||||
## 设计原则
|
||||
|
||||
- 每个 checker 必须自包含在 `src/server/checker/runner/<type>/`。
|
||||
- checker 专属类型、schema、validate、execute、expect、normalize 和协议辅助逻辑放在同一目录。
|
||||
- 注册只修改 `src/server/checker/runner/index.ts`,中间层不新增 type switch。
|
||||
- schema 层只描述契约,语义规则放入 `validate.ts`。
|
||||
- `resolve()` 只做默认值填充、路径解析和单位转换,不执行校验。
|
||||
- `execute()` 必须支持 `CheckerContext.signal` 超时取消。
|
||||
- expect 字段必须选择合适断言模型,不为了统一而滥用 ValueMatcher。
|
||||
- failure phase 命名遵循去单位后缀规则,例如 `durationMs` 对应 `duration`。
|
||||
|
||||
## 架构目标
|
||||
|
||||
```text
|
||||
checkerRegistry
|
||||
├── runner/index.ts
|
||||
├── schema/builder.ts
|
||||
├── schema/validate.ts
|
||||
├── config-loader.ts
|
||||
├── engine.ts
|
||||
└── store.ts
|
||||
```
|
||||
|
||||
注册后,中间层通过 registry 自动委托 schema 生成、契约校验、配置 normalize、配置 resolve、执行和序列化。新增 checker 不应在中间层新增 `switch/case` 或类型分支。
|
||||
|
||||
## 标准文件结构
|
||||
|
||||
| 文件 | 职责 |
|
||||
| -------------- | ------------------------------------------------------- |
|
||||
| `index.ts` | 模块入口,re-export Checker 类 |
|
||||
| `types.ts` | Checker 专属类型 |
|
||||
| `schema.ts` | TypeBox 契约 schema,包含 config 和 expect |
|
||||
| `validate.ts` | 启动期语义校验 |
|
||||
| `normalize.ts` | Checker 专属 authoring expect 归一化 |
|
||||
| `execute.ts` | Checker 类,实现 normalize、resolve、execute、serialize |
|
||||
| `expect.ts` | Checker 专用断言函数 |
|
||||
| 其他文件 | 协议解析、编码、provider 适配、平台命令封装等专属逻辑 |
|
||||
|
||||
## 类型定义
|
||||
|
||||
在 `types.ts` 中定义:
|
||||
|
||||
- `RawXxxTargetConfig`
|
||||
- `RawXxxExpectConfig`
|
||||
- `ResolvedXxxExpectConfig`
|
||||
- `ResolvedXxxTarget extends ResolvedTargetBase`
|
||||
|
||||
不需要修改顶层 `checker/types.ts`。base interface 使用 index signature 支持扩展。
|
||||
|
||||
## Schema
|
||||
|
||||
checker 必须提供 `CheckerSchemas`,包含 Authoring 和 Normalized 两套 config/expect 片段。Authoring 描述用户 YAML 可写 DSL,Normalized 描述 normalizer 输出。
|
||||
|
||||
常用 fragments:
|
||||
|
||||
| Fragment | 用途 |
|
||||
| ----------------------------------- | ------------------------- |
|
||||
| `durationSchema` | 时长字符串 |
|
||||
| `sizeSchema` | 大小单位 |
|
||||
| `statusCodePatternSchema` | HTTP 状态码或范围 |
|
||||
| `stringMapSchema` | headers、env 等字符串映射 |
|
||||
| `createValueMatcherSchema()` | ValueMatcher |
|
||||
| `createContentExpectationsSchema()` | ContentExpectations |
|
||||
| `createKeyedExpectationsSchema()` | KeyedExpectations |
|
||||
|
||||
默认对象策略为 `additionalProperties: false`。只有明确的动态键值表可以开放任意键名。
|
||||
|
||||
## 语义校验
|
||||
|
||||
在 `validate.ts` 中实现 JSON Schema 无法表达的规则,统一返回 `ConfigValidationIssue[]`,不要直接拼接最终错误字符串。
|
||||
|
||||
共享校验工具包括:
|
||||
|
||||
| 函数 | 用途 |
|
||||
| -------------------------------- | ---------------------------- |
|
||||
| `validateRawValueExpectation` | 校验 Raw ValueExpectation |
|
||||
| `validateRawContentExpectations` | 校验 ContentExpectations |
|
||||
| `validateRawKeyedExpectations` | 校验 KeyedExpectations |
|
||||
| `validateJsonPath` | 校验项目支持的 JSONPath 子集 |
|
||||
| `isJsonValue` | 判断合法 JSON value |
|
||||
|
||||
## normalize 规范
|
||||
|
||||
`normalize()` 在 `CheckerDefinition` 中定义为必需方法,负责将 authoring expect DSL 转换为 normalized 形态。输入为变量已解析后的 target,输出为适配 normalized schema 的 target。该方法在 `resolve()` 和 normalized contract 校验之前执行。
|
||||
|
||||
在 `normalize.ts` 中实现 `normalizeTargetExpect` 函数,`execute.ts` 中的 `normalize` 方法委托到该函数。
|
||||
|
||||
共享 normalize helper 位于 `src/server/checker/expect/normalize.ts`:
|
||||
|
||||
| 函数 | 用途 |
|
||||
| ------------------ | -------------------------------------------------------- |
|
||||
| `compactExpect` | 合并两个 expect record,过滤 undefined 字段 |
|
||||
| `normalizeValue` | ValueMatcher 原始值简写展开为 `{equals: value}` |
|
||||
| `normalizeContent` | ContentExpectations 简写展开为 normalized 形态 |
|
||||
| `normalizeKeyed` | KeyedExpectations 对象形态展开为 `[{key, matcher}]` 数组 |
|
||||
|
||||
```typescript
|
||||
import { compactExpect, normalizeContent, normalizeKeyed, normalizeValue } from "../../expect/normalize";
|
||||
|
||||
export function normalizeTargetExpect(target: RawTargetConfig): RawTargetConfig {
|
||||
if (target.expect === undefined || !isPlainObject(target.expect)) return target;
|
||||
const raw = target.expect as Record<string, unknown>;
|
||||
return {
|
||||
...target,
|
||||
expect: compactExpect(raw, {
|
||||
/* checker 专属字段映射 */
|
||||
}),
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
expect 字段的归一化规则:ValueMatcher 字段调用 `normalizeValue()`,ContentExpectations 字段调用 `normalizeContent()`,KeyedExpectations 字段调用 `normalizeKeyed()`,boolean/enum/array 等非断言模型字段直接透传。
|
||||
|
||||
## resolve 规范
|
||||
|
||||
`resolve()` 只做内置默认值填充、路径解析、单位转换,不执行校验。输入已经通过 Normalized schema 和语义校验,expect 已是 normalized 形态。
|
||||
|
||||
```typescript
|
||||
const expect = target.expect as ResolvedXxxExpectConfig | undefined;
|
||||
const resolvedExpect: ResolvedXxxExpectConfig = expect
|
||||
? { ...expect, status: expect.status ?? [200] }
|
||||
: { status: [200] };
|
||||
```
|
||||
|
||||
返回值使用 `satisfies ResolvedXxxTarget` 确保类型正确。
|
||||
|
||||
## execute 规范
|
||||
|
||||
- 始终记录 `timestamp` 和 `start = performance.now()`。
|
||||
- 通过 `ctx.signal` 支持超时取消。
|
||||
- 首个 expect 失败即停止,返回带 `failure` 的结果。
|
||||
- 成功时 `failure: null, matched: true`。
|
||||
- 异常时使用 `errorFailure()`。
|
||||
- 不匹配时使用 `mismatchFailure()`。
|
||||
- `expected` 参数应传用户可读值,必要时使用 `displayValueExpectation()`。
|
||||
|
||||
## expect 字段选择
|
||||
|
||||
| 场景 | 模型 |
|
||||
| ------------------------------------ | ------------------- |
|
||||
| 状态类结果且集合小而稳定 | enum 或 boolean |
|
||||
| 单值数字指标或字符串元数据 | ValueMatcher |
|
||||
| 文本、JSON、HTML、XML 或半结构化内容 | ContentExpectations |
|
||||
| 动态键值表 | KeyedExpectations |
|
||||
|
||||
不要为了统一而把状态类字段改成 ValueMatcher。一个 expect 字段只能对应一种断言模型。
|
||||
|
||||
## 注册
|
||||
|
||||
1. 创建 `src/server/checker/runner/<type>/index.ts`。
|
||||
2. 在 `src/server/checker/runner/index.ts` 添加导入。
|
||||
3. 在 registry 初始化数组中添加 checker 实例。
|
||||
|
||||
注册后,schema builder、validate、config-loader、engine、store 会自动按 registry 分发。
|
||||
|
||||
## 测试要求
|
||||
|
||||
测试文件放在 `tests/server/checker/runner/<type>/`,结构镜像源文件。
|
||||
|
||||
| 测试类别 | 覆盖内容 |
|
||||
| -------------- | ---------------------------------------------------- |
|
||||
| 契约测试 | TypeBox schema 与 JSON Schema 导出一致性 |
|
||||
| 语义校验测试 | 合法和非法配置 |
|
||||
| normalize 测试 | authoring expect 简写展开和 normalized contract 通过 |
|
||||
| resolve 测试 | 默认值合并、路径解析、单位转换 |
|
||||
| execute 测试 | 成功、失败、超时、expect 组合 |
|
||||
| 注册测试 | registry 注册行为 |
|
||||
| 配置加载测试 | 含新 checker 的 YAML 完整加载流程 |
|
||||
|
||||
## 文档和 schema 更新
|
||||
|
||||
新增或修改 checker 时通常需要更新:
|
||||
|
||||
- `probes.example.yaml`
|
||||
- `probe-config.schema.json`,通过 `bun run schema` 生成
|
||||
- `docs/user/checkers/<type>.md`
|
||||
- `docs/user/checkers/README.md`
|
||||
- `docs/user/expectations.md`,仅当断言模型、状态模型或通用规则变化
|
||||
- `docs/user/configuration.md`,仅当 target 通用字段或配置加载形态变化
|
||||
- `docs/development/checker.md`,仅当 checker 开发机制、测试要求或 checklist 变化
|
||||
- `docs/README.md` 和 `openspec/config.yaml`,仅当文档同步规则变化
|
||||
|
||||
## 验证命令
|
||||
|
||||
新增或修改 checker 后通常需要运行:
|
||||
|
||||
```bash
|
||||
bun run schema
|
||||
bun run schema:check
|
||||
bun run check
|
||||
```
|
||||
|
||||
影响构建、Docker 或发布包时追加运行 `bun run verify`。
|
||||
|
||||
## 完成检查清单
|
||||
|
||||
```text
|
||||
□ checker 类型、schema、validate、normalize、resolve、execute、serialize 已实现
|
||||
□ checker 已在 runner/index.ts 注册
|
||||
□ 配置契约、语义校验和 JSON Schema 导出已同步
|
||||
□ probes.example.yaml 已添加或更新示例
|
||||
□ tests/server/checker/runner/<type>/ 已覆盖契约、校验、normalize、resolve、execute、注册和配置加载
|
||||
□ docs/user/checkers/<type>.md 已添加或更新
|
||||
□ docs/user/checkers/README.md 已添加或更新
|
||||
□ 文档影响分析已完成,必要文档已同步
|
||||
□ bun run schema 和 bun run schema:check 已通过
|
||||
□ bun run check 已通过
|
||||
□ bun run verify 已通过或记录未执行原因
|
||||
```
|
||||
|
||||
## 更新触发条件
|
||||
|
||||
修改 checker 开发机制、目录结构、schema/validate/normalize/resolve/execute/expect 约定、测试要求、验证命令或文档同步 checklist 时,必须更新本文档。
|
||||
130
docs/development/frontend.md
Normal file
130
docs/development/frontend.md
Normal file
@@ -0,0 +1,130 @@
|
||||
# 前端开发
|
||||
|
||||
本文档说明 DiAL Dashboard 的 React、TDesign、TanStack Query、组件、样式和前端测试约定。
|
||||
|
||||
适用场景:修改 `src/web/`、前端共享类型使用方式、Dashboard 数据流、组件结构、样式规则或前端测试。
|
||||
|
||||
## 技术栈
|
||||
|
||||
| 层面 | 技术 | 用途 |
|
||||
| ------ | ------------------------------------- | ---------------------------------------------- |
|
||||
| 框架 | React 19 | UI 组件开发 |
|
||||
| 构建 | Bun HTML import + Vite dev server | 开发服务与生产构建 |
|
||||
| 语言 | TypeScript 6 | 类型安全 |
|
||||
| UI 库 | TDesign React + tdesign-icons-react | UI 组件与图标 |
|
||||
| 数据层 | TanStack Query + React Query Devtools | 服务端状态管理与自动轮询 |
|
||||
| 图表 | Recharts | 拨测趋势图 |
|
||||
| 动画 | @number-flow/react | 倒计时数字滚动过渡 |
|
||||
| 路由 | 无 | 单页面 Dashboard,通过 Drawer/Tab 做页面内导航 |
|
||||
|
||||
不引入 React Router 或额外状态管理库。TanStack Query 承担服务端状态,组件内状态使用 `useState`。
|
||||
|
||||
## 组件树与数据流
|
||||
|
||||
```text
|
||||
main.tsx
|
||||
└── StrictMode
|
||||
└── ErrorBoundary
|
||||
└── QueryClientProvider
|
||||
├── App
|
||||
│ ├── useThemePreference()
|
||||
│ ├── useDashboard(refreshInterval)
|
||||
│ ├── SummaryCards
|
||||
│ └── TargetBoard
|
||||
│ └── TargetGroup[]
|
||||
│ └── PrimaryTable
|
||||
│ └── TargetDetailDrawer
|
||||
│ └── useTargetDetail()
|
||||
│ ├── OverviewTab
|
||||
│ └── HistoryTab
|
||||
└── ReactQueryDevtools
|
||||
```
|
||||
|
||||
## TanStack Query 规范
|
||||
|
||||
Query key 使用 structured array,排序为 scope -> id -> 参数。
|
||||
|
||||
```typescript
|
||||
const queryKeys = {
|
||||
dashboard: () => ["dashboard", "24h", 30] as const,
|
||||
meta: () => ["meta"] as const,
|
||||
metrics: (targetId: number, from: string, to: string, bucket: "auto" | MetricsBucket) =>
|
||||
["metrics", targetId, from, to, bucket] as const,
|
||||
history: (targetId: number, from: string, to: string, page: number) => ["history", targetId, from, to, page] as const,
|
||||
};
|
||||
```
|
||||
|
||||
全局面板级查询可持续刷新,详情级查询必须按 Drawer 状态和 Tab 状态条件启用。
|
||||
|
||||
## fetch 封装
|
||||
|
||||
统一使用 `fetch`,不引入 axios。错误抛异常,由 TanStack Query 的 `error` 状态承接。
|
||||
|
||||
```typescript
|
||||
async function fetchJson<T>(url: string): Promise<T> {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||||
return response.json() as Promise<T>;
|
||||
}
|
||||
```
|
||||
|
||||
## 组件开发规范
|
||||
|
||||
- 每个 React 组件一个 `.tsx` 文件,文件名使用 PascalCase。
|
||||
- 组件 props 定义为 `interface XxxProps`,紧邻组件函数声明。
|
||||
- 类型从 `../../shared/api` 导入,使用 `import type`。
|
||||
- 展示组件放在 `components/`,通过 props 接收数据,通过回调返回事件。
|
||||
- 容器逻辑放在 hooks 中,组件只做数据消费。
|
||||
- 列定义、排序器、筛选器、颜色阈值等常量放在 `constants/`。
|
||||
- 时间处理等纯函数放在 `utils/`。
|
||||
|
||||
## 现有组件
|
||||
|
||||
| 组件 | 用途 |
|
||||
| -------------------- | ----------------------------------------------------------------- |
|
||||
| `App` | 根组件,Layout + HeadMenu 骨架、主题模式、刷新控制、Skeleton 加载 |
|
||||
| `ErrorBoundary` | React 错误边界 |
|
||||
| `SummaryCards` | 总览统计卡片 |
|
||||
| `TargetBoard` | 按分组渲染目标表格列表 |
|
||||
| `TargetGroup` | 单个分组 Card + PrimaryTable |
|
||||
| `TargetDetailDrawer` | 目标详情抽屉 |
|
||||
| `OverviewTab` | 目标详情概览 |
|
||||
| `HistoryTab` | 目标历史记录表格和分页 |
|
||||
| `TrendChart` | 趋势折线图 |
|
||||
| `StatusDot` | 圆形状态指示点 |
|
||||
| `StatusBar` | 最近采样状态条 |
|
||||
| `RefreshCountdown` | Header 刷新倒计时和手动刷新按钮 |
|
||||
|
||||
## 样式规范
|
||||
|
||||
前端基于 TDesign React 构建 UI,样式开发优先级:
|
||||
|
||||
1. TDesign 组件
|
||||
2. TDesign 组件 props
|
||||
3. TDesign CSS tokens(`--td-*`)
|
||||
4. `styles.css` CSS 类
|
||||
5. 自行开发组件
|
||||
|
||||
红线:
|
||||
|
||||
- 严禁在组件中使用 `style` 属性内联调整样式。
|
||||
- 严禁通过 CSS 覆盖 TDesign 组件内部类名。
|
||||
- 严禁使用 `!important`。
|
||||
- 颜色统一使用 TDesign CSS tokens,不使用硬编码色值。
|
||||
|
||||
## 前端测试与验证
|
||||
|
||||
- 测试目录为 `tests/web/`,结构对应 `src/web/`。
|
||||
- 单元测试重点覆盖 `constants/`、`utils/` 和 hooks 中的纯逻辑。
|
||||
- 组件测试使用 jsdom 和 `@testing-library/react`。
|
||||
- 测试用户行为而非实现细节。
|
||||
- 只 mock 系统边界,例如 `fetch`。
|
||||
- 使用真实的 QueryClientProvider 包裹依赖 TanStack Query 的组件。
|
||||
- 异步错误断言使用 helper 或显式 try/catch,避免依赖 Bun `expect(...).rejects` 与 `await-thenable` 规则的类型不匹配。
|
||||
- 组件测试环境由 `tests/setup.ts` 和 `bunfig.toml` preload 提供,包含 ResizeObserver、IntersectionObserver、matchMedia、attachEvent 和 Recharts mock。
|
||||
|
||||
前端逻辑变更通常需要运行 `bun run check`。影响生产静态资源、前后端集成或构建流程时运行 `bun run verify`。
|
||||
|
||||
## 更新触发条件
|
||||
|
||||
修改前端技术栈、组件边界、数据流、样式规则、测试环境或前端验证方式时,必须更新本文档。
|
||||
127
docs/development/release.md
Normal file
127
docs/development/release.md
Normal file
@@ -0,0 +1,127 @@
|
||||
# 构建与发布
|
||||
|
||||
本文档说明开发服务、前后端集成、生产构建、Docker 镜像、跨平台 release 和相关脚本维护方式。
|
||||
|
||||
适用场景:修改 `scripts/`、构建流程、Dockerfile、静态资源集成、release 打包、运行时环境变量或部署产物。
|
||||
|
||||
## 开发期运行
|
||||
|
||||
```bash
|
||||
bun run dev probes.yaml
|
||||
```
|
||||
|
||||
`scripts/dev.ts` 同时启动两个进程:
|
||||
|
||||
| 进程 | 用途 |
|
||||
| --------------- | ------------------------------------------------- |
|
||||
| Bun API server | 后端 API 服务,`--watch` 监听后端文件变更自动重启 |
|
||||
| Vite dev server | 前端 SPA、HMR、模块热替换 |
|
||||
|
||||
也可以单独启动:
|
||||
|
||||
```bash
|
||||
bun run dev:server probes.yaml
|
||||
bun run dev:web
|
||||
```
|
||||
|
||||
## 前后端集成
|
||||
|
||||
开发模式下,Vite 通过 proxy 将 `/api/*` 和 `/health` 转发到 Bun。
|
||||
|
||||
生产模式下,前端通过 Vite 构建为静态资源,通过 `import with { type: "file" }` 嵌入 Bun 可执行文件。非 API 路径由 fetch fallback 处理:有文件扩展名的返回静态资源或 404,无扩展名的返回 SPA index.html。
|
||||
|
||||
## 构建
|
||||
|
||||
```bash
|
||||
bun run build
|
||||
```
|
||||
|
||||
构建流程:
|
||||
|
||||
```text
|
||||
1. Vite build -> dist/web/
|
||||
2. Code generation -> .build/static-assets.ts + .build/server-entry.ts
|
||||
3. Bun compile -> dist/dial-server
|
||||
```
|
||||
|
||||
构建参数:
|
||||
|
||||
| 环境变量 | 说明 |
|
||||
| -------------- | ---------------- |
|
||||
| `BUN_TARGET` | 交叉编译目标平台 |
|
||||
| `BUILD_TARGET` | 交叉编译目标平台 |
|
||||
|
||||
## Docker 镜像
|
||||
|
||||
Docker 镜像使用 Alpine 多阶段构建,保持与生产单可执行文件交付模型一致。
|
||||
|
||||
```text
|
||||
oven/bun:1-alpine -> bun install --frozen-lockfile
|
||||
-> BUN_TARGET=bun-linux-*-musl bun run build
|
||||
-> dist/dial-server
|
||||
|
||||
alpine -> 仅复制 /usr/local/bin/dial-server
|
||||
-> 安装 ca-certificates、iputils-ping、libgcc、libstdc++、tzdata
|
||||
-> 使用非 root dial 用户运行
|
||||
```
|
||||
|
||||
Dockerfile 通过 `TARGETARCH` 选择 Bun compile target。
|
||||
|
||||
| `TARGETARCH` | `BUN_TARGET` |
|
||||
| ------------ | ---------------------- |
|
||||
| `amd64` | `bun-linux-x64-musl` |
|
||||
| `arm64` | `bun-linux-arm64-musl` |
|
||||
|
||||
## Release
|
||||
|
||||
```bash
|
||||
bun run release
|
||||
bun run release --target linux-x64
|
||||
bun run release --target linux-x64,windows-x64,darwin-arm64
|
||||
```
|
||||
|
||||
release 流程:
|
||||
|
||||
```text
|
||||
1. Vite build -> dist/web/
|
||||
2. Code generation -> .build/
|
||||
3. 多目标 Bun compile -> dist/release/binaries/
|
||||
4. tar.gz 打包 -> dist/release/packages/
|
||||
```
|
||||
|
||||
支持的平台见 [用户部署文档](../user/deployment.md#跨平台发布包)。
|
||||
|
||||
## 脚本说明
|
||||
|
||||
| 脚本 | 文件 | 说明 |
|
||||
| ---------------------- | ----------------------------------- | ------------------------------ |
|
||||
| `bun run dev` | `scripts/dev.ts` | 双进程开发服务 |
|
||||
| `bun run dev:server` | `src/server/dev.ts` | 仅启动后端 API server |
|
||||
| `bun run dev:web` | Vite CLI | 仅启动 Vite dev server |
|
||||
| `bun run build` | `scripts/build.ts` | Vite -> codegen -> Bun compile |
|
||||
| `bun run release` | `scripts/release.ts` | 多目标交叉编译和打包 |
|
||||
| `bun run schema` | `scripts/generate-config-schema.ts` | 生成配置 JSON Schema |
|
||||
| `bun run schema:check` | `scripts/generate-config-schema.ts` | 检查配置 JSON Schema 同步 |
|
||||
| `bun run clean` | `scripts/clean.ts` | 清理构建缓存与临时文件 |
|
||||
|
||||
## 维护约定
|
||||
|
||||
- `scripts/build-common.ts` 中的 import specifier 输出必须使用 `/` 分隔符。
|
||||
- 跨平台路径测试不得用当前平台 `path.sep` 伪装其他平台,应使用 `node:path.win32` 或等价注入方式模拟。
|
||||
- 如本地 Docker 环境不支持 buildx 或多架构模拟,需在变更记录中说明未执行原因。
|
||||
|
||||
## 发布验证
|
||||
|
||||
| 变更类型 | 验证方式 |
|
||||
| ---------------- | --------------------------------------- |
|
||||
| 构建脚本 | `bun run verify` |
|
||||
| release 脚本 | `bun run release` 或指定受影响 target |
|
||||
| Dockerfile | 本地 `docker build`,无法执行时说明原因 |
|
||||
| 静态资源集成 | `bun run build`,必要时启动产物手动验证 |
|
||||
| 配置 schema 同步 | `bun run schema:check` |
|
||||
|
||||
影响用户部署方式、Docker 运行参数、发布包内容或运行时依赖时,必须同步更新 [用户部署文档](../user/deployment.md)。
|
||||
|
||||
## 更新触发条件
|
||||
|
||||
修改开发服务、前后端集成、构建产物、Docker 镜像、release target、脚本参数或发布验证方式时,必须更新本文档。
|
||||
@@ -7,7 +7,6 @@
|
||||
| 文件 | 用途 |
|
||||
| ------------------------------------------------------ | ------------------------------------------------------------------------ |
|
||||
| [prompt-smart-merge.md](prompt-smart-merge.md) | 批量合并 `dev*` 分支到目标分支,含规则探测、依赖分析、冲突处理、安全回退 |
|
||||
| [prompt-spec-review.md](prompt-spec-review.md) | 审查和整理 `openspec/specs/` 下的稳定规范,提升可检索性和一致性 |
|
||||
| [prompt-proposal-review.md](prompt-proposal-review.md) | 审查 proposal/design/tasks/specs 与讨论、代码现状、OpenSpec 规范的一致性 |
|
||||
| [prompt-apply-review.md](prompt-apply-review.md) | 审查 apply 后代码、测试、变更文档的一致性,并补齐遗漏或回写文档 |
|
||||
|
||||
@@ -85,7 +84,7 @@
|
||||
- 是否以代码、文档、讨论或用户确认为准
|
||||
- 何时必须使用提问工具确认
|
||||
- 删除、重写前是否必须备份
|
||||
- 改动后是否必须同步 README、测试、变更文档
|
||||
- 改动后是否必须同步相关用户文档、开发文档、测试、变更文档
|
||||
|
||||
### 4. 计划与执行分离
|
||||
|
||||
@@ -124,7 +123,7 @@
|
||||
- 作用域边界:改什么,不改什么
|
||||
- 真相来源优先级:代码 / README / spec / 讨论 / 用户确认
|
||||
- 风险动作边界:删除、重写、提交、推送、回退、stash、merge 等
|
||||
- 同步要求:测试、README、变更文档、现有 spec 是否要同步
|
||||
- 同步要求:测试、用户文档、开发文档、变更文档、现有 spec 是否要同步
|
||||
- 降级规则:信息不足时如何处理
|
||||
|
||||
避免:
|
||||
@@ -142,7 +141,7 @@
|
||||
|
||||
推荐做法:
|
||||
|
||||
- 先读仓库规则来源,如 `README.md`、配置、架构文档、近期提交、任务入口
|
||||
- 先读仓库规则来源,如 `README.md`、`DEVELOPMENT.md`、`CONTRIBUTING.md`、`docs/README.md`、配置、架构文档、近期提交、任务入口
|
||||
- 先读直接相关 artifacts,再扩展到相关代码和测试
|
||||
- 需要探测时,要求 AI 先探测再决定,不把仓库结构写死在提示词里
|
||||
|
||||
|
||||
@@ -1,25 +1,57 @@
|
||||
审查 OpenSpec apply 完成后以及后续手动修补后的实际实现,判断代码、测试、变更文档是否一致,识别偏离、漏记和可优化点,并将确认后的实际变更同步回变更文档,按以下流程执行。
|
||||
审查 OpenSpec apply 完成后以及后续手动修补后的实际变更,判断实际产物、验证结果和变更文档是否与 `design.md` source of truth 一致,识别偏离、漏记和可优化点,并将确认后的实际变更同步回变更文档,按以下流程执行。
|
||||
|
||||
## 约束
|
||||
|
||||
- 先审查再修复;未经用户确认,不修改代码或变更文档
|
||||
- 默认按 `spec-driven` workflow 审查;识别 change 后先确认 `schemaName`;若实际 schema 不同,说明差异,仅对实际存在的 artifacts 做审查
|
||||
- 优先使用当前会话中的实现说明、测试结论、手动修补记录和已生成的变更文档;仅在无法明确 change、`schemaName`、改动范围或修补来源时,再用提问工具或 OpenSpec 命令补充定位
|
||||
- 不要因为代码已经存在就自动以代码为准;先判断差异属于"文档要求未实现"、"测试后新增修补"还是"意外偏离/回归"
|
||||
- 每批代码或文档修改执行前用提问工具获得用户确认
|
||||
- 先审查再修复;未经用户确认,不修改实际产物或变更文档
|
||||
- 默认按 `fast-drive` workflow 审查;识别 change 后先确认 `schemaName`;若实际 schema 不同,说明差异,仅对实际存在的 artifacts 做审查
|
||||
- 在 `fast-drive` workflow 下,核心 artifacts 是 `design.md` 和 `tasks.md`;不要要求存在 `proposal.md` 或 `specs/*.md`
|
||||
- 在 `fast-drive` workflow 下,`design.md` 是 scope、requirements、decisions、guardrails、execution direction 和 verification expectations 的 source of truth,`tasks.md` 是 apply 进度和验证闭环的 tracking 文件
|
||||
- 禁止同步或修改 `openspec/specs/` 下的主规范;若实际 schema 包含 `specs/*.md`,也只允许修改本次 change 目录下实际存在的 spec artifacts,主规范同步属于 archive 阶段,不在此提示词范围内
|
||||
- 优先使用当前会话中的执行说明、验证结论、手动修补记录和已生成的变更文档;仅在无法明确 change、`schemaName`、改动范围或修补来源时,再用提问工具或 OpenSpec 命令补充定位
|
||||
- 不要因为实际产物已经存在就自动以实际产物为准;先判断差异属于“design 要求未完成”、“验证后新增修补”、“合理落地细化”还是“意外偏离/回归”
|
||||
- 每批实际产物或文档修改执行前用提问工具获得用户确认
|
||||
- 删除/重写前用提问工具获得用户确认,并先备份原文件为 `{file}.bak.{timestamp}`
|
||||
- 若修改代码涉及新逻辑、模块结构、API、实体或用户可见行为,同步更新测试、相关变更文档和 README
|
||||
- 若修改实际产物涉及新行为、流程、接口、内容、数据、配置、责任边界或用户可见结果,同步更新验证材料、相关变更文档和必要的文档/沟通材料
|
||||
|
||||
## 1. 收集
|
||||
|
||||
并行读取:
|
||||
读取约束:
|
||||
|
||||
- 本次 change 的实际 artifacts;在 `spec-driven` 下通常包括 `proposal.md`、`design.md`、`tasks.md`、`specs/*.md`
|
||||
- 当前会话中与本次变更相关的实现说明、apply 过程中的偏离、测试失败、手动修补原因、待确认事项
|
||||
- 与本次变更相关的代码和测试文件;优先依据 `git diff --name-only`、`git diff --name-only --cached`、`tasks.md`、Impact、失败测试栈定位;若工作区已干净,再结合文档和代码模块反推
|
||||
- 最近一次相关测试命令、测试结果、失败信息和修补后的验证结果
|
||||
- `openspec/config.yaml`
|
||||
- 与本次改动相关的 README、架构文档,以及现有 `openspec/specs/**/spec.md` 中与本次变更相关的规范,相关性来源包括:`proposal.md` 的 `Capabilities` / `Modified Capabilities`、手动修补涉及的受影响能力、`design.md` / Impact 中提到的模块、相关代码对应的现有能力
|
||||
- 直接使用 Read 工具并行读取文件,禁止使用 subagent/Task 工具做文件读取和内容转发
|
||||
- 不原样输出文件内容,仅在步骤 2 输出审查结论
|
||||
|
||||
分步收集:
|
||||
|
||||
a) 先并行读取核心入口和配置,确定范围:
|
||||
|
||||
- 本次 change 的 `design.md`
|
||||
- 本次 change 的 `tasks.md`
|
||||
- workflow context/configuration,例如存在时读取 `openspec/config.yaml`
|
||||
- 若可定位到 schema,读取对应 schema;`fast-drive` 下优先读取 `openspec/schemas/fast-drive/schema.yaml`
|
||||
|
||||
b) 从 `design.md` 提取审查基准:
|
||||
|
||||
- `Context`
|
||||
- `Discussion Notes`
|
||||
- `Requirements`
|
||||
- `Goals / Non-Goals`
|
||||
- `Execution Guardrails`
|
||||
- `Affected Areas`
|
||||
- `Decisions`
|
||||
- `Execution Plan`
|
||||
- `Verification Plan`
|
||||
- `Risks / Trade-offs`
|
||||
- `Open Questions`
|
||||
|
||||
c) 从 `tasks.md` 提取任务状态、已完成项、未完成项、验证任务和文档/沟通任务;重点记录所有已标记完成的 `- [x]` 或等价完成状态。
|
||||
|
||||
d) 获取实际改动范围:若在版本控制工作区中,优先使用 `git diff --name-only`、`git diff --name-only --cached`;若工作区已干净或不适用版本控制,再结合 `design.md`、`tasks.md`、验证记录和执行记录反推。
|
||||
|
||||
e) 并行读取实际改动范围、`Affected Areas`、`Execution Plan`、`Verification Plan` 涉及的实际产物、参考材料、验证材料、流程说明、配置、文档或沟通材料。
|
||||
|
||||
f) 收集当前会话中与本次变更相关的执行说明、apply 过程中的偏离、验证失败、手动修补原因、验证命令或检查结果、待确认事项。
|
||||
|
||||
g) 若实际 schema 不是 `fast-drive`,只读取实际存在的 artifacts;若存在 `proposal.md`、`specs/*.md`,再按该 schema 的要求补充读取和审查。
|
||||
|
||||
若当前上下文无法明确 change 或文档路径:
|
||||
|
||||
@@ -28,63 +60,75 @@
|
||||
|
||||
若已明确 change,但尚未确认 `schemaName`,先读取 change 元数据或执行 `openspec status --change "{name}" --json` 确认。
|
||||
|
||||
若缺少测试结果或手动修补记录,明确说明本次无法可靠判断部分差异的来源,仅能基于代码与文档现状审查。
|
||||
若缺少验证结果或手动修补记录,明确说明本次无法可靠判断部分差异的来源,仅能基于实际产物与文档现状审查。
|
||||
|
||||
## 2. 分析
|
||||
|
||||
按以下优先级检查:
|
||||
|
||||
| 优先级 | 维度 | 检查点 |
|
||||
| ------ | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| P0 | 实际实现与测试结论 | 当前代码的真实行为是什么;apply 后是否有手动改动或测试后修补;测试是否证明这些实现有效;若缺少测试结果,标记相关结论为"未验证";检查是否存在回归、未覆盖场景或被掩盖的问题 |
|
||||
| P1 | 文档同步性 | 对实际存在的 artifacts 检查:已落地的实现、测试后新增修补、边界处理、异常路径、验证结论是否已同步回变更文档;若影响模块结构、API、实体或用户可见行为,再检查 README 是否同步 |
|
||||
| P2 | 文档要求覆盖 | 对实际存在的 artifacts 检查:文档中承诺的目标、方案、Requirement、Scenario 是否都已实现;在 `spec-driven` 下重点检查 `proposal.md`、`design.md`、`specs/*.md`、`tasks.md` |
|
||||
| P3 | 实现质量 | 代码结构、复用、命名、复杂度、错误处理、测试质量、与项目现有模式的一致性是否存在明显问题或可优化点 |
|
||||
| 优先级 | 维度 | 检查点 |
|
||||
| ------ | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| P0 | 实际变更与验证结论 | 当前实际产物的真实状态是什么;apply 后是否有手动改动或验证后修补;验证是否证明这些变更有效;若缺少验证结果,标记相关结论为“未验证”;检查是否存在回归、未覆盖场景或被掩盖的问题 |
|
||||
| P1 | `design.md` 一致性 | 实际变更是否符合 `Requirements`、`Goals / Non-Goals`、`Execution Guardrails`、`Decisions`、`Execution Plan` 和 `Verification Plan`;`Open Questions` 是否已明确区分 blocking / non-blocking 或写出 `None`;是否违反被明确否决的方案、用户偏好或约束 |
|
||||
| P2 | `tasks.md` 真实性 | 已完成任务是否真的完成并完成必要验证;未完成任务是否仍然必要;apply 或手动修补是否引入了需要补充的新任务、验证任务或文档/沟通任务 |
|
||||
| P3 | 文档回写完整性 | 已落地的实际变更、验证后新增修补、边界处理、异常路径、验证结论、实际触达产物是否已同步回 `design.md` 和 `tasks.md`;若影响行为、流程、接口、内容、数据、配置、责任边界或用户可见结果,再检查必要的文档/沟通材料是否同步 |
|
||||
| P4 | 质量与可维护性 | 实际产物的结构、清晰度、一致性、可维护性、风险处理、移交质量、验证质量、与现有模式的一致性是否存在明显问题或可优化点 |
|
||||
| P5 | Schema 兼容性 | 对实际存在的 artifacts 检查是否符合其 schema;若不是 `fast-drive`,仅按实际 artifacts 检查,不凭空要求 `fast-drive` 专属结构;最终 artifacts 是否仍保留模板注释、空表格行或占位任务文本 |
|
||||
|
||||
分析时区分三类差异:
|
||||
分析时区分四类差异:
|
||||
|
||||
- 文档要求已明确,但代码未实现或实现不完整 → 需补充代码或测试
|
||||
- 代码因测试暴露问题、手动修补或合理落地细化而新增/变更 → 需回写文档
|
||||
- 代码与文档不一致,且无法判断应以哪边为准 → 列入待确认清单
|
||||
- `design.md` 要求已明确,但实际变更未完成或完成不充分 → 需补充实际工作或验证
|
||||
- 实际变更因验证暴露问题、手动修补或合理落地细化而新增/变更 → 需回写 `design.md` 和/或 `tasks.md`
|
||||
- 实际变更与 `design.md` 不一致,且无法判断应以哪边为准 → 列入待确认清单
|
||||
- `tasks.md` 状态与实际完成情况或验证结果不一致 → 修正任务状态或补充任务
|
||||
|
||||
不要把以下情况直接视为合理修补:
|
||||
|
||||
- 通过 `skip`、`only`、弱化断言、绕过错误处理来让测试通过
|
||||
- 为了贴合现有代码而降低已确认的 Requirement 或行为约束
|
||||
- 未经过讨论和验证就扩大功能范围
|
||||
- 通过跳过、弱化或绕过验证来声称变更完成
|
||||
- 为了贴合当前实际产物而降低已确认的 requirement、acceptance criteria 或 guardrail
|
||||
- 未经过讨论和验证就扩大功能、流程、内容或责任范围
|
||||
- 违反 `Execution Guardrails`、被拒绝方案或 `Open Questions` 中尚未解决的 blocker
|
||||
|
||||
重点识别:
|
||||
|
||||
- 文档要求但未落地的功能、场景、异常处理或验证步骤
|
||||
- apply 完成后新增的代码修补、边界处理、接口调整、行为变化未同步到文档
|
||||
- `tasks.md` 标记完成,但代码、测试或文档未闭环
|
||||
- `Modified Capabilities` 应更新但未更新的现有 spec
|
||||
- 代码存在明显的重复、复杂度过高、命名不清、错误处理薄弱、测试质量不足等问题
|
||||
- `design.md` 要求但未落地的结果、流程、内容、场景、异常处理、文档/沟通更新或验证步骤
|
||||
- 实际变更偏离 `Goals / Non-Goals`、`Execution Guardrails`、`Decisions` 或 `Execution Plan` 的地方
|
||||
- apply 完成后新增的修补、边界处理、接口调整、行为变化、流程变化或内容变化未同步到 `design.md`
|
||||
- `Affected Areas` 与实际改动范围不一致,导致新会话无法复盘真实影响面
|
||||
- `Verification Plan` 中要求的验证、质量检查、审阅、批准、沟通检查或 manual checks 未执行或未记录
|
||||
- `tasks.md` 标记完成,但实际产物、验证、文档或沟通未闭环
|
||||
- `design.md` 或 `tasks.md` 仍保留 `<!-- ... -->` 模板注释、空表格行、`Replace with...`、`TBD`、`TODO` 等未解决占位内容
|
||||
- 必要的文档/沟通材料未同步影响行为、流程、接口、内容、数据、配置、责任边界或用户可见结果的变更
|
||||
- 实际产物存在明显的重复、复杂度过高、表达不清、责任不明、风险处理薄弱、验证质量不足等问题
|
||||
- `fast-drive` change 中仍错误依赖 `proposal.md`、`specs/*.md`、`Capabilities` 或 `Modified Capabilities` 的内容
|
||||
|
||||
输出审查结果:
|
||||
|
||||
1. **问题总览表**:问题类型 × 涉及文件数
|
||||
2. **实际改动与修补清单**:本次实现中已落地的主要功能、后续修补和验证结论;若缺少测试结果,对未验证部分单独标记
|
||||
3. **未覆盖清单**:文档要求但未在代码中实现或未充分验证的内容
|
||||
4. **需回写文档清单**:代码和测试中已确认、但文档未体现的实现、修补或约束变化
|
||||
5. **方向待确认清单**:代码与文档不一致,且无法判断应以哪边为准的事项
|
||||
2. **实际变更与修补清单**:本次已落地的主要变更、后续修补和验证结论;若缺少验证结果,对未验证部分单独标记
|
||||
3. **Design 偏离清单**:实际变更未完成、完成不充分或偏离 `design.md` 的内容
|
||||
4. **需回写文档清单**:实际产物和验证中已确认、但 `design.md`、`tasks.md` 或相关文档/沟通材料未体现的变更、修补或约束变化
|
||||
5. **方向待确认清单**:实际变更与 `design.md` 不一致,且无法判断应以哪边为准的事项
|
||||
6. **任务状态问题清单**:未真正完成、状态错误或需补充的新任务
|
||||
7. **测试问题清单**:缺失覆盖、掩盖错误、验证不足或修补后未回归验证的测试问题
|
||||
8. **代码质量/优化清单**:可优化的实现问题和建议
|
||||
9. **逐项分析**:每个问题说明位置、问题、影响、建议和建议修复方向
|
||||
7. **验证问题清单**:缺失覆盖、掩盖错误、验证不足或修补后未回归验证的问题
|
||||
8. **质量/优化清单**:可优化的实际产物问题和建议
|
||||
9. **Schema 差异清单**:实际 schema 与默认 `fast-drive` 不同时,列出因此跳过或改按实际 artifacts 审查的内容
|
||||
10. **逐项分析**:每个问题说明位置、问题、影响、建议和建议修复方向
|
||||
|
||||
若所有清单均为空,输出"审查通过,未发现问题",跳至步骤 5。
|
||||
若所有清单均为空,输出“审查通过,未发现问题”,跳至步骤 5。
|
||||
|
||||
## 3. 计划(用户确认)
|
||||
|
||||
先针对"方向待确认清单"用提问工具逐项向用户确认。
|
||||
先针对“方向待确认清单”用提问工具逐项向用户确认。
|
||||
|
||||
再整理完整修复方案,按类别列出:
|
||||
|
||||
- 代码或测试补充:补实现、补异常处理、补回归测试、修复掩盖错误的测试
|
||||
- 文档回写:同步 `proposal.md`、`design.md`、`tasks.md`、`specs/*.md`、README 中遗漏或过时的内容
|
||||
- 实际工作或验证补充:补完成、补异常处理、补回归验证、修复被弱化或绕过的验证
|
||||
- Design 回写:同步 `design.md` 中遗漏或过时的 requirements、guardrails、affected areas、decisions、execution plan、verification plan、risks 或 open questions
|
||||
- 任务状态修正:修正已完成/未完成状态,补充 apply 后新增但已完成的修补任务或验证任务
|
||||
- 代码质量优化:在不改变目标行为的前提下优化结构、复用、命名或可维护性
|
||||
- 文档/沟通同步:同步行为、流程、接口、内容、数据、配置、责任边界或用户可见结果变化
|
||||
- 质量优化:在不改变目标结果的前提下优化结构、表达、一致性、可维护性或移交质量
|
||||
- Schema 兼容处理:若实际 schema 不是 `fast-drive`,按实际存在的 artifacts 说明额外文档同步项
|
||||
|
||||
对每个拟修改的文件说明:
|
||||
|
||||
@@ -98,33 +142,38 @@
|
||||
|
||||
## 4. 执行
|
||||
|
||||
逐项执行已确认的代码、测试和文档修复。
|
||||
逐项执行已确认的实际产物、验证和文档修复。
|
||||
|
||||
若涉及删除或重写:
|
||||
|
||||
- 先创建备份文件 `{file}.bak.{timestamp}`
|
||||
- 再执行修改
|
||||
|
||||
若修改了代码或测试:
|
||||
若修改了实际产物或验证材料:
|
||||
|
||||
- 同步更新相关变更文档;若影响模块结构、API、实体或用户可见行为,再同步 README
|
||||
- 运行相关测试;若修补影响范围较大,再补充执行受影响的回归测试
|
||||
- 同步更新相关变更文档;若影响行为、流程、接口、内容、数据、配置、责任边界或用户可见结果,再同步必要的文档/沟通材料
|
||||
- 运行或执行相关验证;若修补影响范围较大,再补充执行受影响的回归验证
|
||||
|
||||
若修改了文档:
|
||||
|
||||
- 确认实际存在的变更文档之间保持一致;在 `spec-driven` 下重点检查 `proposal.md`、`design.md`、`tasks.md`、`specs/*.md`
|
||||
- 若 apply 后新增修补改变了能力边界或行为约束,同步更新 `Capabilities` / `Modified Capabilities`
|
||||
- 在 `fast-drive` workflow 下,确认 `design.md` 仍是 source of truth,`tasks.md` 仍从 `design.md` 派生
|
||||
- 确认 `design.md` 的 requirements、guardrails、affected areas、decisions、execution plan、verification plan、risks 和 open questions 与实际变更一致
|
||||
- 确认 `tasks.md` 每个完成任务都有对应实际产物和必要验证,新增修补已补充任务或记录在合适任务中
|
||||
- 禁止将本次 change 内容同步到 `openspec/specs/`,该操作属于 archive 阶段
|
||||
- 在 `fast-drive` workflow 下不创建 `proposal.md` 或 `specs/*.md`;若实际 schema 不是 `fast-drive`,则按实际 schema 的 required artifacts 创建或更新本次 change 目录下的 artifacts
|
||||
|
||||
执行后重新读取所有被修改的代码、测试和文档,并复核:
|
||||
执行后重新读取所有被修改的实际产物、验证材料和文档,并复核:
|
||||
|
||||
- "未覆盖清单" 是否已清空或已标注保留原因
|
||||
- "需回写文档清单" 是否已清空
|
||||
- "方向待确认清单" 是否已清空或已记录用户决策
|
||||
- "任务状态问题清单" 和 "测试问题清单" 是否已清空或已标注残留原因
|
||||
- "代码质量/优化清单" 中哪些已处理,哪些有意延期
|
||||
- “Design 偏离清单” 是否已清空或已标注保留原因
|
||||
- “需回写文档清单” 是否已清空
|
||||
- “方向待确认清单” 是否已清空或已记录用户决策
|
||||
- “任务状态问题清单” 和 “验证问题清单” 是否已清空或已标注残留原因
|
||||
- “质量/优化清单” 中哪些已处理,哪些有意延期
|
||||
- 必要的文档/沟通材料是否已按影响范围同步
|
||||
- 所有模板注释、空表格行和占位文本是否已清空或替换为有效内容
|
||||
|
||||
## 5. 收尾
|
||||
|
||||
列出所有修改的文件、备份文件、测试命令与结果、文档同步摘要和剩余风险。
|
||||
列出所有修改的文件、备份文件、验证命令或检查结果、文档同步摘要和剩余风险。
|
||||
|
||||
若本次因缺少测试结果、修补记录或上下文而降级执行,或有问题因信息不足暂未处理,单独说明。
|
||||
若本次因缺少验证结果、修补记录或上下文而降级执行,或有问题因信息不足暂未处理,单独说明。
|
||||
|
||||
@@ -1,22 +1,48 @@
|
||||
审查本次 OpenSpec 变更文档是否与前序讨论、当前代码现状和 OpenSpec 文档规范一致,识别遗漏、冲突和不合理假设,并给出可执行的补充建议,按以下流程执行。
|
||||
审查本次 OpenSpec 变更文档是否与前序讨论、当前实际状态和实际 OpenSpec workflow 一致,重点检查 `fast-drive` workflow 下的 `design.md` 是否足以在上下文压缩或新会话中指导后续 `apply`,并识别遗漏、冲突和不合理假设,给出可执行的补充建议,按以下流程执行。
|
||||
|
||||
## 约束
|
||||
|
||||
- 仅修改本次变更文档,不修改源码
|
||||
- 默认按 `spec-driven` workflow 审查;识别 change 后先确认 `schemaName`;若实际 schema 不同,说明差异,仅对实际存在的 artifacts 做审查
|
||||
- 优先使用当前会话中的讨论和已生成的变更文档;仅在无法明确 change、`schemaName` 或文档范围时,再用提问工具或 OpenSpec 命令补充定位
|
||||
- 仅修改本次变更文档,不修改实际产物
|
||||
- 默认按 `fast-drive` workflow 审查;识别 change 后先确认 `schemaName`;若实际 schema 不同,说明差异,仅对实际存在的 artifacts 做审查
|
||||
- 在 `fast-drive` workflow 下,核心 artifacts 是 `design.md` 和 `tasks.md`;不要要求存在 `proposal.md` 或 `specs/*.md`
|
||||
- 在 `fast-drive` workflow 下,`design.md` 是 scope、requirements、decisions、guardrails、execution direction 和 verification expectations 的 source of truth,`tasks.md` 必须从 `design.md` 派生
|
||||
- 优先使用当前会话中的讨论、explore/propose 阶段结论和已生成的变更文档;仅在无法明确 change、`schemaName` 或文档范围时,再用提问工具或 OpenSpec 命令补充定位
|
||||
- 每批文档修改建议执行前用提问工具获得用户确认
|
||||
- 删除/重写前用提问工具获得用户确认,并先备份原文件为 `{file}.bak.{timestamp}`
|
||||
|
||||
## 1. 收集
|
||||
|
||||
并行读取:
|
||||
读取约束:
|
||||
|
||||
- 本次 change 的实际 artifacts;在 `spec-driven` 下通常包括 `proposal.md`、`design.md`、`tasks.md`、`specs/*.md`
|
||||
- 当前会话中与本次变更相关的讨论、澄清、边界约束、非目标、待确认事项
|
||||
- 与本次变更直接相关的源码、测试、README、架构文档
|
||||
- `openspec/config.yaml`
|
||||
- 现有 `openspec/specs/**/spec.md` 中与本次变更相关的规范,相关性来源包括:`proposal.md` 的 `Capabilities` / `Modified Capabilities`、讨论中提到的受影响能力、`design.md` / Impact 中提到的模块、相关代码对应的现有能力
|
||||
- 直接使用 Read 工具并行读取文件,禁止使用 subagent/Task 工具做文件读取和内容转发
|
||||
- 不原样输出文件内容,仅在步骤 2 输出审查结论
|
||||
|
||||
分步收集:
|
||||
|
||||
a) 先并行读取核心入口和配置,确定范围:
|
||||
|
||||
- 本次 change 的 `design.md`
|
||||
- 本次 change 的 `tasks.md`
|
||||
- workflow context/configuration,例如存在时读取 `openspec/config.yaml`
|
||||
- 若可定位到 schema,读取对应 schema;`fast-drive` 下优先读取 `openspec/schemas/fast-drive/schema.yaml`
|
||||
|
||||
b) 从 `design.md` 提取审查基准:
|
||||
|
||||
- `Context`
|
||||
- `Discussion Notes`
|
||||
- `Requirements`
|
||||
- `Goals / Non-Goals`
|
||||
- `Execution Guardrails`
|
||||
- `Affected Areas`
|
||||
- `Decisions`
|
||||
- `Execution Plan`
|
||||
- `Verification Plan`
|
||||
- `Risks / Trade-offs`
|
||||
- `Open Questions`
|
||||
|
||||
c) 基于 `Affected Areas`、`Execution Plan`、`Verification Plan`、讨论中提到的受影响范围,并行读取相关实际产物、参考材料、验证材料、流程说明、配置、文档或沟通材料,确认文档是否贴合当前实际状态。
|
||||
|
||||
d) 若实际 schema 不是 `fast-drive`,只读取实际存在的 artifacts;若存在 `proposal.md`、`specs/*.md`,再按该 schema 的要求补充读取和审查。
|
||||
|
||||
若当前上下文无法明确 change 或文档路径:
|
||||
|
||||
@@ -25,48 +51,55 @@
|
||||
|
||||
若已明确 change,但尚未确认 `schemaName`,先读取 change 元数据或执行 `openspec status --change "{name}" --json` 确认。
|
||||
|
||||
若缺少讨论记录,明确说明本次降级为"文档 + 代码现状审查",不做讨论一致性结论。
|
||||
若缺少讨论记录,明确说明本次降级为“文档 + 当前实际状态审查”,不做讨论一致性结论。
|
||||
|
||||
## 2. 分析
|
||||
|
||||
按以下优先级检查:
|
||||
|
||||
| 优先级 | 维度 | 检查点 |
|
||||
| ------ | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| P0 | 讨论一致性 | 仅在存在讨论记录时检查:文档是否完整覆盖已确认的目标、范围、非目标、约束、边界条件、风险、决策点、待办事项;若无讨论记录,标记为"跳过" |
|
||||
| P1 | 代码现实性 | 文档对当前模块、接口、数据结构、命名、依赖、目录结构、复用路径的描述是否准确;是否把"计划变更"误写成"当前现状";是否遗漏真实受影响的现有能力 |
|
||||
| P2 | 文档内部一致性 | 对实际存在的 artifacts 检查是否互相支撑;在 `spec-driven` 下重点检查 `proposal.md`、`design.md`、`tasks.md`、`specs/*.md`;`Capabilities` / `Modified Capabilities` 是否完整;每个 capability 是否有对应 spec;`tasks.md` 是否覆盖 `design.md` 和 `specs/*.md` |
|
||||
| P3 | OpenSpec 合规性 | 对实际存在的 artifacts 检查是否遵循 OpenSpec 格式和术语;`specs/*.md` 是否只描述行为与约束、不混入实现细节;`tasks.md` 是否一行一个任务;是否混入 git 操作任务 |
|
||||
| 优先级 | 维度 | 检查点 |
|
||||
| ------ | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| P0 | 讨论承接性 | 仅在存在讨论记录时检查:`design.md` 是否完整记录已确认的目标、非目标、用户偏好、约束、边界条件、风险、关键决策、被否决方案和待澄清事项;若无讨论记录,标记为“跳过” |
|
||||
| P1 | `design.md` 自包含性 | `design.md` 是否足以让看不到前序对话的执行者继续工作;是否包含完整 required sections;`Open Questions` 是否明确区分 blocking / non-blocking 或写出 `None`;是否存在依赖未记录聊天上下文的隐含要求 |
|
||||
| P2 | 当前状态真实性 | `design.md` 对当前实际产物、流程、接口、内容、数据、配置、依赖、责任边界、参考材料和验证入口的描述是否准确;是否把“计划变更”误写成“当前现状”;`Affected Areas` 是否遗漏真实受影响区域 |
|
||||
| P3 | `tasks.md` 派生性 | `tasks.md` 是否从 `design.md` 派生;是否覆盖 requirements、guardrails、decisions、execution plan 和 verification plan;是否依赖 `proposal.md` 或 `specs/*.md` 中未写入 `design.md` 的内容 |
|
||||
| P4 | OpenSpec 合规性 | 对实际存在的 artifacts 检查是否遵循对应 schema 和 OpenSpec 术语;`tasks.md` 是否一行一个 `- [ ]` checkbox 任务、按 `##` numbered headings 分组、无无关的仓库/版本控制/发布操作任务;`design.md` 是否避免 task checkbox;最终 artifacts 是否仍保留模板注释、空表格行或占位任务文本 |
|
||||
|
||||
分析时区分两类情况:
|
||||
|
||||
- 文档对当前代码现状的描述错误
|
||||
- 文档描述的是预期变更,本来就应当与当前代码不同
|
||||
- 文档对当前实际状态的描述错误
|
||||
- 文档描述的是预期变更,本来就应当与当前状态不同
|
||||
|
||||
重点识别:
|
||||
|
||||
- 讨论中已确定但文档未记录的内容
|
||||
- 文档基于错误现状做出的设计或任务拆分
|
||||
- 文档之间相互冲突的目标、方案、约束、任务
|
||||
- `proposal -> specs -> design -> tasks` 链路中的断点
|
||||
- `Modified Capabilities` 应更新但未更新的现有 spec
|
||||
- 讨论中已确定但 `design.md` 未记录的内容
|
||||
- `design.md` 中缺失或含糊的 requirements、acceptance criteria、guardrails、decisions、verification expectations
|
||||
- `Open Questions` 未明确区分 blocking / non-blocking、与 `tasks.md` 冲突,或包含 apply 前必须解决的 blocker
|
||||
- `tasks.md` 未覆盖 `design.md` 的要求、约束、执行计划、验证计划或文档/沟通更新要求
|
||||
- `tasks.md` 标记了无法验证、跨行、过大、顺序错误或包含无关仓库/版本控制/发布操作的任务
|
||||
- 文档仍保留 `<!-- ... -->` 模板注释、空表格行、`Replace with...`、`TBD`、`TODO` 等未解决占位内容
|
||||
- 文档基于错误当前状态做出的设计或任务拆分
|
||||
- 文档之间相互冲突的目标、方案、约束、任务和验证要求
|
||||
- `fast-drive` change 中仍错误依赖 `proposal.md`、`specs/*.md`、`Capabilities` 或 `Modified Capabilities` 的内容
|
||||
|
||||
输出审查结果:
|
||||
|
||||
1. **问题总览表**:问题类型 × 涉及文档数
|
||||
2. **讨论遗漏清单**:讨论已确定但文档未体现的内容;若缺少讨论记录,标记为"未审查"
|
||||
3. **现实性问题清单**:与当前代码现状不符的描述、假设或影响分析
|
||||
4. **文档冲突清单**:proposal、design、tasks、specs 之间的不一致
|
||||
5. **OpenSpec 规范问题清单**:格式、术语、结构问题
|
||||
6. **待澄清清单**:仅靠讨论和代码仍无法判断的事项
|
||||
7. **逐项分析**:每个问题说明位置、问题、影响、建议
|
||||
8. **补充建议方案**:按文件列出建议补充/修正的内容、原因和可选方案
|
||||
2. **讨论遗漏清单**:讨论已确定但 `design.md` 未体现的内容;若缺少讨论记录,标记为“未审查”
|
||||
3. **Design 自包含性问题清单**:缺失、含糊或无法指导新会话 apply 的内容
|
||||
4. **当前状态问题清单**:与当前实际状态不符的描述、假设或影响分析
|
||||
5. **Tasks 派生与覆盖问题清单**:`tasks.md` 未从 `design.md` 正确派生或覆盖不足的内容
|
||||
6. **文档冲突清单**:`design.md`、`tasks.md` 和实际存在的其他 artifacts 之间的不一致
|
||||
7. **OpenSpec 规范问题清单**:格式、术语、结构问题
|
||||
8. **待澄清清单**:仅靠讨论和当前状态仍无法判断的事项
|
||||
9. **逐项分析**:每个问题说明位置、问题、影响、建议
|
||||
10. **补充建议方案**:按文件列出建议补充/修正的内容、原因和可选方案
|
||||
|
||||
若所有清单均为空,输出"审查通过,未发现问题",跳至步骤 5。
|
||||
若所有清单均为空,输出“审查通过,未发现问题”,跳至步骤 5。
|
||||
|
||||
## 3. 计划(用户确认)
|
||||
|
||||
先针对"待澄清清单"用提问工具逐项向用户确认。
|
||||
先针对“待澄清清单”用提问工具逐项向用户确认。
|
||||
|
||||
再整理完整修复方案,按文件列出:
|
||||
|
||||
@@ -79,7 +112,9 @@
|
||||
|
||||
## 4. 执行
|
||||
|
||||
逐项修改已确认的变更文档,不修改源码。
|
||||
逐项修改已确认的变更文档,不修改实际产物。
|
||||
|
||||
在 `fast-drive` workflow 下,通常只修改本次 change 的 `design.md` 和 `tasks.md`;若实际 schema 存在其他 artifacts,仅在确有必要且用户确认后修改实际存在的 artifacts。
|
||||
|
||||
若涉及删除或重写:
|
||||
|
||||
@@ -88,9 +123,14 @@
|
||||
|
||||
执行后重新读取所有被修改的文档,并复核:
|
||||
|
||||
- "讨论遗漏清单" 是否已清空或已标注保留原因
|
||||
- "现实性问题清单" 是否已清空或已标注为预期变更
|
||||
- "文档冲突清单" 和 "OpenSpec 规范问题清单" 是否已清空
|
||||
- “讨论遗漏清单” 是否已清空或已标注保留原因
|
||||
- “Design 自包含性问题清单” 是否已清空
|
||||
- “当前状态问题清单” 是否已清空或已标注为预期变更
|
||||
- “Tasks 派生与覆盖问题清单” 是否已清空
|
||||
- “文档冲突清单” 是否已清空
|
||||
- “OpenSpec 规范问题清单” 是否已清空
|
||||
- “待澄清清单” 是否已清空或已记录用户决策
|
||||
- 所有模板注释、空表格行和占位文本是否已清空或替换为有效内容
|
||||
|
||||
## 5. 收尾
|
||||
|
||||
|
||||
@@ -1,142 +0,0 @@
|
||||
请审查并整理 `openspec/specs/` 下的稳定规范,使其成为可搜索、边界清晰、无冗余、与当前业务一致的能力索引,按以下流程执行。
|
||||
|
||||
## 约束
|
||||
|
||||
- `openspec/specs/` 描述长期稳定的业务能力、规则和外部行为,不记录变更过程、迁移说明、实现路径、内部类型名、组件 props、样式数值、层级分层等实现细节
|
||||
- 用户可感知或对外暴露的契约可以保留:公开 API 路径、请求/响应字段、协议名、错误码、数据约束、交互结果
|
||||
- `Requirement` 和 `Scenario` 应描述业务能力、外部行为或稳定约束,不以“使用某层/某组件/某库实现”作为标题或核心表述
|
||||
- 不把当前代码自动视为唯一真相;若代码、README、现有 spec 冲突且无法判断应以哪边为准,列入待确认清单,不直接改写规范
|
||||
- 仅删除内容已被其他规范完整覆盖且无独立检索价值的规范;非冗余内容仅迁移、合并、拆分或重命名
|
||||
- 每批重构执行前用提问工具获得用户确认;删除或重写前先备份原文件为 `{file}.bak.{timestamp}`
|
||||
- 命名、Purpose、Requirement 标题都必须保留用户下一次最可能搜索的业务关键词
|
||||
|
||||
## 1. 收集
|
||||
|
||||
并行读取:
|
||||
|
||||
- `openspec/config.yaml`
|
||||
- `README.md`,以及与模块结构、API、架构相关的 README 或文档
|
||||
- `openspec/specs/*/spec.md`
|
||||
|
||||
默认不读取 `openspec/changes/**`、历史 proposal/design/tasks 作为稳定规范整理依据;仅在用户明确要求“连同历史变更一起校对”时再纳入。
|
||||
|
||||
先建立索引,不直接开始改写:
|
||||
|
||||
| 索引 | 内容 |
|
||||
| -------------- | ----------------------------------------------------------------------------- |
|
||||
| `spec_index[]` | 每个 spec 的目录名、Purpose、Requirement 摘要、关键词、外部契约、疑似重叠对象 |
|
||||
| `domain_map[]` | 从 README、API、模块文档中提炼的核心业务域、横切能力和术语 |
|
||||
| `term_map[]` | 同义词、旧名、缩写和推荐标准术语 |
|
||||
| `suspects[]` | 需要进一步对照代码或测试确认的 spec |
|
||||
|
||||
仅对 `suspects[]` 做定向读取:
|
||||
|
||||
- 读取与该 spec 对应的源码、测试、README 或架构文档
|
||||
- 不对 `backend/`、`frontend/` 做无差别逐文件扫描
|
||||
|
||||
判定依据优先级:
|
||||
|
||||
- 当前稳定 spec 与 README 共同支持的事实,可直接视为高置信度
|
||||
- 仅代码可见但 README 和 spec 未体现的内容,先判断它是稳定外部行为还是临时实现细节
|
||||
- 代码、README、现有 spec 互相冲突且无法自动定夺时,进入 `待确认清单`
|
||||
|
||||
## 2. 审查
|
||||
|
||||
按 spec、Requirement、Scenario 三层检查:
|
||||
|
||||
| 维度 | 检查点 |
|
||||
| --------- | --------------------------------------------------------------------------------- |
|
||||
| 过时 | 描述的能力、术语、外部契约是否仍成立;是否存在 `TBD`、`TODO`、占位说明 |
|
||||
| 冲突 | 不同规范是否对同一行为给出不同约束、命名或边界 |
|
||||
| 重复/重叠 | 是否在文件级、Requirement 级、Scenario 级重复描述同一能力 |
|
||||
| 错位 | 内容是否放错能力域;横切规则是否混入实体规范;平台实现是否混入通用能力规范 |
|
||||
| 粒度 | 是否过大导致难检索,或过碎导致回答一个问题必须同时打开多个 spec |
|
||||
| 术语 | 同一概念是否混用多个名字;旧名、别名、缩写是否需要归一并保留检索入口 |
|
||||
| 命名/检索 | 目录名、Purpose、Requirement 标题是否准确;是否能被 README、API、业务术语直接命中 |
|
||||
| 规范性 | 是否使用 SHALL/WHEN/THEN;是否混入变更记录、迁移说明、内部实现或 UI/代码细节 |
|
||||
| 完整性 | Purpose 是否明确;是否存在空目录、非 spec 噪音文件、无清晰归属的孤立规范 |
|
||||
|
||||
重构判定规则:
|
||||
|
||||
- 若两个 spec 回答的是同一个核心问题,或其中一个只是另一个的子集,优先合并
|
||||
- 若一个 spec 混合多个独立检索意图,或同时包含横切规则与业务流程,优先拆分
|
||||
- 若内容正确但目录名、Purpose 或 Requirement 标题不利于检索,优先重命名或改写标题
|
||||
- 若多个术语指向同一概念,统一到一个标准术语,并在 Purpose 或 Requirement 中保留必要别名以支持搜索
|
||||
- 若某段内容只是内部实现细节,且不影响外部行为理解,删除该段而不是为其单独保留 spec
|
||||
- 若某个具体值同时属于外部契约与内部实现,按“是否对调用方可见、是否影响兼容性”判断是否保留
|
||||
|
||||
### 命名约定
|
||||
|
||||
命名优先复用仓库已存在的稳定术语,如 `provider`、`model`、`stats`、`protocol`、`proxy`、`logging`、`validation`、`migration`、`frontend`、`desktop`、`mysql`。
|
||||
|
||||
| 类型 | 模式 | 示例 |
|
||||
| ------------ | ---------------------------------------------------------- | -------------------------------------------------- |
|
||||
| 实体生命周期 | `{entity}-management` | `provider-management`、`model-management` |
|
||||
| 横切能力 | `{concern}` 或 `{concern}-{qualifier}` | `error-handling`、`structured-logging` |
|
||||
| 协议/适配 | `{protocol}-{capability}` 或 `protocol-adapter-{protocol}` | `openai-protocol-proxy`、`protocol-adapter-openai` |
|
||||
| 运行面/入口 | `{surface}` 或 `{surface}-{capability}` | `frontend`、`desktop-app` |
|
||||
| 基础设施 | `{resource}-{operation}` | `database-migration`、`mysql-driver` |
|
||||
|
||||
命名原则:
|
||||
|
||||
- 1-4 个词,保持单一主题
|
||||
- 优先使用业务名词,不使用 `basic`、`general`、`misc`、`info`、`data` 等泛化词
|
||||
- 不使用 `crud`、`list`、`table`、`display` 等实现模式词,除非它本身就是外部契约的一部分
|
||||
- 同一主题的命名模式保持一致,不同时混用多套前后缀
|
||||
|
||||
## 3. 报告
|
||||
|
||||
输出分析结果:
|
||||
|
||||
1. **问题总览表**:问题类型 × 涉及规范数
|
||||
2. **规范关系表**:每个 spec 的主主题、重叠对象、冲突对象、建议动作
|
||||
3. **术语归一表**:旧术语 / 别名 / 缩写 → 推荐标准术语
|
||||
4. **逐项分析**:每个有问题的规范说明位置、问题、影响、建议和目标规范
|
||||
5. **待确认清单**:代码、README、现有 spec 冲突且无法自动定夺的事项
|
||||
6. **重构方案**:按优先级分批
|
||||
7. **重构后目录结构**:预期的新 `openspec/specs/` 目录树
|
||||
|
||||
优先级建议:
|
||||
|
||||
- P0:删除空目录、非 spec 噪音文件、占位内容
|
||||
- P1:删除完全冗余规范;将其内容映射到主规范
|
||||
- P2:合并重复/子集规范;拆分错位或过大规范
|
||||
- P3:重命名目录、改写 Purpose 和 Requirement 标题以提升检索性
|
||||
- P4:修正过时描述,清理实现细节、迁移说明和变更记录
|
||||
|
||||
若所有问题清单为空,输出“审查通过,未发现问题”,跳至步骤 5。
|
||||
|
||||
## 4. 计划(用户确认)
|
||||
|
||||
先针对 `待确认清单` 用提问工具逐项向用户确认。
|
||||
|
||||
再按批次展示完整重构计划,每批必须包含:
|
||||
|
||||
- 操作类型:删除、重命名、迁移、合并、拆分、改写
|
||||
- 路径变化:源路径 → 目标路径
|
||||
- 内容映射:源 spec 的 Requirement / Scenario 将迁移到哪里
|
||||
- 术语处理:哪些旧词保留为检索入口,哪些词统一替换
|
||||
- 执行原因:为什么这样做更利于检索、去重和边界清晰
|
||||
- 验证方式:如何确认没有丢失约束或引入新的冲突
|
||||
|
||||
用提问工具获得当前批次确认后再执行。
|
||||
|
||||
## 5. 执行
|
||||
|
||||
按 P0 → P4 逐批执行已确认的重构。
|
||||
|
||||
执行要求:
|
||||
|
||||
- 合并或拆分时先写目标 spec,再删除或重命名源 spec
|
||||
- 删除前确认其 Requirement 和 Scenario 已被完整保留、迁移或判定为纯冗余
|
||||
- 每批执行后重新读取受影响的 spec,并复核结构和内容
|
||||
|
||||
每批执行后至少验证:
|
||||
|
||||
- 目录结构完整,`openspec/specs/*/spec.md` 可正常读取
|
||||
- 不存在未承接的 Requirement 或 Scenario
|
||||
- Purpose、Requirement 标题、目录名可以直接表达主能力
|
||||
- 不再包含 `TBD`、变更记录、迁移说明、内部实现细节或噪音文件
|
||||
- 若本批涉及代码对照项,相关外部契约描述与当前仓库现状一致,或已列入残留待确认
|
||||
|
||||
收尾时输出:修改文件清单、备份文件清单、最终目录树、残留待确认事项和整理摘要。
|
||||
35
docs/user/README.md
Normal file
35
docs/user/README.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# 用户文档
|
||||
|
||||
本文档是 DiAL 的用户使用入口,说明如何阅读配置、部署、expect 规则、故障排查和各 checker 参考。
|
||||
|
||||
适用场景:编写 YAML 配置、部署 DiAL、理解拨测结果、排查运行问题、查询某个 checker 的字段和示例。
|
||||
|
||||
## 文档索引
|
||||
|
||||
| 文档 | 内容 |
|
||||
| ---------------------------------------- | ------------------------------------------------- |
|
||||
| [configuration.md](configuration.md) | YAML 顶层结构、变量、server、targets 通用字段 |
|
||||
| [deployment.md](deployment.md) | 生产构建、Docker、ICMP 权限、发布包运行方式 |
|
||||
| [expectations.md](expectations.md) | expect 规则、状态判定、failure、observation |
|
||||
| [troubleshooting.md](troubleshooting.md) | 配置校验、变量、ICMP、CMD、Docker、证书和正则问题 |
|
||||
| [checkers/README.md](checkers/README.md) | 各 checker 的配置项、expect 字段和示例 |
|
||||
|
||||
## 按任务阅读
|
||||
|
||||
| 任务 | 建议阅读 |
|
||||
| --------------------- | ---------------------------------------------------------------------- |
|
||||
| 首次运行 | [项目快速开始](../../README.md#快速开始)、[配置文件](configuration.md) |
|
||||
| 编写配置 | [配置文件](configuration.md)、[Checker 参考](checkers/README.md) |
|
||||
| 编写 expect | [校验规则](expectations.md)、对应 checker 文档 |
|
||||
| 容器或生产部署 | [部署](deployment.md)、[故障排查](troubleshooting.md) |
|
||||
| 排查启动或运行问题 | [故障排查](troubleshooting.md)、相关 checker 文档 |
|
||||
| 查询 checker 专属字段 | [Checker 参考](checkers/README.md) |
|
||||
|
||||
## 用户文档更新规则
|
||||
|
||||
- 配置结构、变量、server、probes、targets 通用字段变化时,更新 [configuration.md](configuration.md)。
|
||||
- checker 配置项、expect 字段、示例或运行行为变化时,更新 `checkers/<type>.md` 和 [checkers/README.md](checkers/README.md)。
|
||||
- expect 模型、状态判定、failure、observation 或快速失败顺序变化时,更新 [expectations.md](expectations.md)。
|
||||
- 构建产物运行方式、Docker 参数、镜像内置依赖、发布包结构变化时,更新 [deployment.md](deployment.md)。
|
||||
- 常见错误、运行依赖、权限、证书或配置校验排查方式变化时,更新 [troubleshooting.md](troubleshooting.md)。
|
||||
- 用户文档只解释“如何使用”和“用户能观察到什么”,实现细节放入 [`../development/`](../development/README.md)。
|
||||
49
docs/user/checkers/README.md
Normal file
49
docs/user/checkers/README.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# Checker 参考
|
||||
|
||||
Checker 是 DiAL 的拨测执行单元。每个 target 通过 `type` 选择一个 checker,并配置对应的专属字段和 `expect` 规则。
|
||||
|
||||
适用场景:查询 checker 类型选择、专属配置、expect 字段、示例和各 checker 文档入口。
|
||||
|
||||
## 支持的类型
|
||||
|
||||
| 类型 | 用途 | 文档 |
|
||||
| -------- | -------------------------------------- | ------------------- |
|
||||
| `http` | HTTP/HTTPS 应用层健康检查 | [HTTP](http.md) |
|
||||
| `cmd` | 执行本地命令或脚本 | [Cmd](cmd.md) |
|
||||
| `db` | PostgreSQL/MySQL/SQLite 连接和查询检查 | [DB](db.md) |
|
||||
| `tcp` | TCP 端口可达性和 banner 探测 | [TCP](tcp.md) |
|
||||
| `udp` | UDP payload 请求-响应检查 | [UDP](udp.md) |
|
||||
| `icmp` | 基于系统 `ping` 的存活、延迟、丢包检查 | [ICMP](icmp.md) |
|
||||
| `dns` | 本机解析或指定 DNS server 协议级检查 | [DNS](dns.md) |
|
||||
| `llm` | 大模型服务应用层健康检查 | [LLM](llm.md) |
|
||||
| `ws` | WebSocket 可达性和消息交互检查 | [WS](ws.md) |
|
||||
| `cpu` | 本机 CPU 使用率健康检查 | [CPU](cpu.md) |
|
||||
| `memory` | 本机系统内存使用状况检查 | [Memory](memory.md) |
|
||||
|
||||
## 选择建议
|
||||
|
||||
| 目标 | 推荐 checker |
|
||||
| ---------------------------------- | ------------ |
|
||||
| Web API、网页、HTTP 状态码或响应体 | `http` |
|
||||
| 本机脚本、外部命令、CLI 工具 | `cmd` |
|
||||
| 数据库连接或查询结果 | `db` |
|
||||
| 端口是否可连接、服务 banner | `tcp` |
|
||||
| UDP 服务响应或简单心跳 | `udp` |
|
||||
| 主机可达性、延迟、丢包率 | `icmp` |
|
||||
| 域名解析值、DNS RCODE、TTL、flags | `dns` |
|
||||
| LLM API 是否可用、输出是否符合预期 | `llm` |
|
||||
| WebSocket 可达性或消息交互验证 | `ws` |
|
||||
| 本机 CPU 使用率健康检查 | `cpu` |
|
||||
| 本机系统内存使用状况检查 | `memory` |
|
||||
|
||||
## 通用字段
|
||||
|
||||
所有 checker 都共享 target 通用字段,见 [配置文件](../configuration.md#targets-通用字段)。
|
||||
|
||||
## 通用断言模型
|
||||
|
||||
各 checker 的 `expect` 字段复用 `ValueMatcher`、`ContentExpectations` 和 `KeyedExpectations`。详情见 [校验规则](../expectations.md)。
|
||||
|
||||
## 更新触发条件
|
||||
|
||||
新增、移除或修改 checker 类型、用途、选择建议、通用字段或通用断言模型时,必须更新本文档。checker 专属字段变化还必须同步更新对应 `checkers/<type>.md`。
|
||||
38
docs/user/checkers/cmd.md
Normal file
38
docs/user/checkers/cmd.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# Cmd Checker
|
||||
|
||||
`type: cmd` 用于执行本地命令或脚本,并校验退出码、stdout、stderr 和耗时。
|
||||
|
||||
## 配置项
|
||||
|
||||
| 字段 | 说明 | 必填 | 默认值 |
|
||||
| ---------- | ------------------------------------ | ---- | ------ |
|
||||
| `cmd.exec` | 可执行文件名或路径 | 是 | 无 |
|
||||
| `cmd.args` | 命令行参数列表 | 否 | `[]` |
|
||||
| `cmd.env` | 环境变量覆盖,继承进程环境变量并合并 | 否 | 无 |
|
||||
| `cmd.cwd` | 工作目录,相对于配置文件所在目录 | 否 | 无 |
|
||||
|
||||
## expect 校验项
|
||||
|
||||
| 字段 | 说明 | 必填 | 默认值 |
|
||||
| ------------ | --------------------------------------------- | ---- | ------ |
|
||||
| `exitCode` | 可接受的退出码列表 | 否 | `[0]` |
|
||||
| `stdout` | 标准输出校验,使用 `ContentExpectations` 数组 | 否 | 无 |
|
||||
| `stderr` | 标准错误校验,使用 `ContentExpectations` 数组 | 否 | 无 |
|
||||
| `durationMs` | 完整执行耗时校验,使用 `ValueMatcher` | 否 | 无 |
|
||||
|
||||
## 示例
|
||||
|
||||
```yaml
|
||||
- id: "bun-script"
|
||||
name: "Bun 脚本检查"
|
||||
type: cmd
|
||||
cmd:
|
||||
exec: "bun"
|
||||
args: ["-e", "console.log('ok')"]
|
||||
expect:
|
||||
exitCode: [0]
|
||||
stdout:
|
||||
- contains: "ok"
|
||||
```
|
||||
|
||||
Docker 官方镜像不内置常见外部命令。容器内使用 CMD checker 时,按需通过派生镜像安装依赖命令。
|
||||
74
docs/user/checkers/cpu.md
Normal file
74
docs/user/checkers/cpu.md
Normal file
@@ -0,0 +1,74 @@
|
||||
# CPU Checker
|
||||
|
||||
`type: cpu` 用于检查本机 CPU 使用率,基于两次系统快照计算总体和每核心的忙碌比例。
|
||||
|
||||
## 配置项
|
||||
|
||||
| 字段 | 说明 | 必填 | 默认值 |
|
||||
| -------------------- | -------------------------------- | ---- | ------- |
|
||||
| `cpu.sampleDuration` | CPU 采样窗口,支持时长格式 | 否 | `1s` |
|
||||
| `cpu.includePerCore` | 是否在结果中输出每核心使用率数组 | 否 | `false` |
|
||||
|
||||
`sampleDuration` 必须小于 target 的 `timeout`。
|
||||
|
||||
## expect 校验项
|
||||
|
||||
| 字段 | 说明 | 必填 | 默认值 |
|
||||
| --------------------- | ----------------------------------------------------------------------------------------------- | ---- | ------ |
|
||||
| `usagePercent` | 总体 CPU 使用率,范围 `0-100`,使用 `ValueMatcher` | 否 | 无 |
|
||||
| `idlePercent` | 总体 CPU 空闲率,与 `usagePercent` 互补,两者之和恒为 100(`idlePercent + usagePercent = 100`) | 否 | 无 |
|
||||
| `maxCoreUsagePercent` | 单核心最高使用率,使用 `ValueMatcher` | 否 | 无 |
|
||||
| `minCoreUsagePercent` | 单核心最低使用率,使用 `ValueMatcher` | 否 | 无 |
|
||||
| `durationMs` | 完整执行耗时校验,使用 `ValueMatcher` | 否 | 无 |
|
||||
|
||||
所有百分比字段范围为 `0-100`,表示所有可见逻辑 CPU 的总体比例,不是"核心数 × 100"。
|
||||
|
||||
## 示例
|
||||
|
||||
```yaml
|
||||
- id: "local-cpu"
|
||||
name: "本机 CPU"
|
||||
type: cpu
|
||||
interval: "30s"
|
||||
timeout: "5s"
|
||||
cpu:
|
||||
sampleDuration: "1s"
|
||||
expect:
|
||||
usagePercent:
|
||||
lte: 85
|
||||
maxCoreUsagePercent:
|
||||
lte: 95
|
||||
```
|
||||
|
||||
输出每核心使用率:
|
||||
|
||||
```yaml
|
||||
- id: "local-cpu-detail"
|
||||
name: "本机 CPU 详细"
|
||||
type: cpu
|
||||
cpu:
|
||||
sampleDuration: "2s"
|
||||
includePerCore: true
|
||||
expect:
|
||||
usagePercent:
|
||||
lte: 80
|
||||
```
|
||||
|
||||
## 语义说明
|
||||
|
||||
CPU checker 采集的是 DiAL 进程运行环境通过系统 API(`os.cpus()`)可见的 CPU 视图。在容器中,它可能不等于严格的 cgroup quota 使用率。
|
||||
|
||||
`usagePercent` 和 `idlePercent` 互补,恒等于 100。`sampleDuration` 决定了两次快照之间的等待时间,窗口越长结果越稳定,但会增加 checker 执行耗时。
|
||||
|
||||
## 不支持的功能
|
||||
|
||||
- CPU 温度、电源状态、频率
|
||||
- `userPercent` / `systemPercent`(用户态/系统态占比)
|
||||
- `loadAverage`(系统负载均值)
|
||||
- 进程级 CPU 使用率
|
||||
- Linux cgroup 精确 CPU 计算
|
||||
- `logicalCoreCount` 作为 expect 字段(仅在 observation 中输出)
|
||||
|
||||
## 更新触发条件
|
||||
|
||||
修改 CPU checker 配置、expect 字段、行为或语义时,必须更新本文档。
|
||||
38
docs/user/checkers/db.md
Normal file
38
docs/user/checkers/db.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# DB Checker
|
||||
|
||||
`type: db` 用于数据库连接和查询结果检查,支持 PostgreSQL、MySQL 和 SQLite。
|
||||
|
||||
## 配置项
|
||||
|
||||
| 字段 | 说明 | 必填 | 默认值 |
|
||||
| ---------- | ------------------------------------------------------------- | ---- | ------ |
|
||||
| `db.url` | 数据库连接字符串,支持 `postgres://`、`mysql://`、`sqlite://` | 是 | 无 |
|
||||
| `db.query` | SQL 查询语句,不配置时仅测试连接 | 否 | 无 |
|
||||
|
||||
## expect 校验项
|
||||
|
||||
| 字段 | 说明 | 必填 | 默认值 |
|
||||
| ------------ | ----------------------------------------------------------------------- | ---- | ------ |
|
||||
| `rowCount` | 查询返回行数校验,使用 `ValueMatcher` | 否 | 无 |
|
||||
| `rows` | 查询结果逐行校验,数组内每行为列名到 `KeyedExpectations` 的映射 | 否 | 无 |
|
||||
| `result` | 完整查询结果 `{ rows, rowCount }` 校验,使用 `ContentExpectations` 数组 | 否 | 无 |
|
||||
| `durationMs` | 完整执行耗时校验,使用 `ValueMatcher` | 否 | 无 |
|
||||
|
||||
## 示例
|
||||
|
||||
```yaml
|
||||
- id: "sqlite-query"
|
||||
name: "SQLite 数据库检查"
|
||||
type: db
|
||||
db:
|
||||
url: "sqlite:///path/to/db.sqlite"
|
||||
query: "SELECT COUNT(*) as cnt FROM users WHERE status = 'active'"
|
||||
expect:
|
||||
durationMs:
|
||||
lte: 5000
|
||||
rowCount:
|
||||
gte: 1
|
||||
rows:
|
||||
- cnt:
|
||||
gte: 0
|
||||
```
|
||||
104
docs/user/checkers/dns.md
Normal file
104
docs/user/checkers/dns.md
Normal file
@@ -0,0 +1,104 @@
|
||||
# DNS Checker
|
||||
|
||||
`type: dns` 支持两种解析模式:本机解析器检查和指定 DNS server 协议级检查。
|
||||
|
||||
## resolver 模式
|
||||
|
||||
| 模式 | 说明 |
|
||||
| -------- | ----------------------------------------------------------------------------- |
|
||||
| `system` | 使用本机 DNS 解析器检查域名是否能解析到预期地址 |
|
||||
| `server` | 直接向指定 DNS server 发起 UDP/TCP 深度拨测,检查 RCODE、TTL、flags、记录值等 |
|
||||
|
||||
## `dns.resolver: system` 配置项
|
||||
|
||||
| 字段 | 说明 | 必填 | 默认值 |
|
||||
| -------------- | ----------------------------- | ---- | -------- |
|
||||
| `dns.resolver` | 解析模式 | 是 | `system` |
|
||||
| `dns.name` | 待解析域名 | 是 | 无 |
|
||||
| `dns.family` | 地址族:`any`、`ipv4`、`ipv6` | 否 | `any` |
|
||||
|
||||
### system 模式 expect
|
||||
|
||||
| 字段 | 说明 | 断言模型 |
|
||||
| ------------ | -------------------- | --------------------------------- |
|
||||
| `values` | 解析结果地址集合断言 | DNS 集合(include/exclude/exact) |
|
||||
| `valueCount` | 解析结果数量 | ValueMatcher |
|
||||
| `durationMs` | 解析耗时 | ValueMatcher |
|
||||
|
||||
```yaml
|
||||
- id: "dns-system-api"
|
||||
name: "本机 DNS 解析"
|
||||
type: dns
|
||||
dns:
|
||||
resolver: system
|
||||
name: "api.example.com"
|
||||
family: any
|
||||
expect:
|
||||
values:
|
||||
exact:
|
||||
- "203.0.113.10"
|
||||
durationMs:
|
||||
lte: 500
|
||||
```
|
||||
|
||||
## `dns.resolver: server` 配置项
|
||||
|
||||
| 字段 | 说明 | 必填 | 默认值 |
|
||||
| ---------------------- | --------------------------------- | ---- | -------- |
|
||||
| `dns.resolver` | 解析模式 | 是 | `server` |
|
||||
| `dns.server` | DNS server 地址 | 是 | 无 |
|
||||
| `dns.name` | 查询域名 | 是 | 无 |
|
||||
| `dns.port` | DNS server 端口 | 否 | `53` |
|
||||
| `dns.protocol` | 传输协议:`udp`、`tcp` | 否 | `udp` |
|
||||
| `dns.recordType` | DNS 记录类型 | 否 | `A` |
|
||||
| `dns.recursionDesired` | 是否设置 RD flag | 否 | `true` |
|
||||
| `dns.tcpFallback` | UDP 响应 TC=1 时是否 TCP fallback | 否 | `true` |
|
||||
| `dns.maxResponseBytes` | 响应最大字节数 | 否 | `4KB` |
|
||||
|
||||
`recordType` 可选值:`A`、`AAAA`、`CNAME`、`NS`、`MX`、`TXT`、`SOA`、`SRV`、`CAA`、`PTR`。
|
||||
|
||||
### server 模式 expect
|
||||
|
||||
| 字段 | 说明 | 断言模型 |
|
||||
| -------------------- | ---------------------------------- | --------------------------------- |
|
||||
| `responded` | 是否收到 DNS response | boolean |
|
||||
| `rcode` | 期望 RCODE 列表,如 `NOERROR` | string[] |
|
||||
| `values` | 目标类型记录值集合断言 | DNS 集合(include/exclude/exact) |
|
||||
| `valueCount` | 目标类型记录数量 | ValueMatcher |
|
||||
| `answerCount` | answer section 总记录数 | ValueMatcher |
|
||||
| `ttlMin` | answer 中最小 TTL | ValueMatcher |
|
||||
| `ttlMax` | answer 中最大 TTL | ValueMatcher |
|
||||
| `authoritative` | AA flag | boolean |
|
||||
| `recursionAvailable` | RA flag | boolean |
|
||||
| `truncated` | TC flag | boolean |
|
||||
| `authenticatedData` | AD flag | boolean |
|
||||
| `result` | 完整结构化响应的 JSONPath 兜底断言 | ContentExpectations |
|
||||
| `durationMs` | 完整查询耗时 | ValueMatcher |
|
||||
|
||||
```yaml
|
||||
- id: "dns-server-api"
|
||||
name: "Cloudflare DNS A 记录"
|
||||
type: dns
|
||||
dns:
|
||||
resolver: server
|
||||
server: "1.1.1.1"
|
||||
name: "api.example.com"
|
||||
recordType: A
|
||||
expect:
|
||||
rcode: ["NOERROR"]
|
||||
values:
|
||||
include:
|
||||
- "203.0.113.10"
|
||||
ttlMin:
|
||||
gte: 60
|
||||
durationMs:
|
||||
lte: 200
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 未配置 expect 时,`system` 模式默认要求解析成功且 `valueCount > 0`,`server` 模式默认要求 `NOERROR + valueCount > 0`。
|
||||
- 显式配置非 `NOERROR` rcode(如 `NXDOMAIN`)时,不自动要求 `valueCount > 0`。
|
||||
- `values.exact` 忽略返回顺序。
|
||||
- 对 A/AAAA 查询,CNAME 链不计入 `values`,单独放入 `cnameChain`。
|
||||
- `values` 按记录类型规范化为字符串,例如 MX 为 `"10 mail.example.com"`,SRV 为 `"10 60 443 server.example.com"`。
|
||||
48
docs/user/checkers/http.md
Normal file
48
docs/user/checkers/http.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# HTTP Checker
|
||||
|
||||
`type: http` 用于 HTTP/HTTPS 应用层健康检查。
|
||||
|
||||
## 配置项
|
||||
|
||||
| 字段 | 说明 | 必填 | 默认值 |
|
||||
| ------------------- | ------------------- | ---- | ------- |
|
||||
| `http.url` | 目标 URL | 是 | 无 |
|
||||
| `http.method` | HTTP 方法 | 否 | `GET` |
|
||||
| `http.headers` | 请求头 | 否 | 无 |
|
||||
| `http.body` | 请求体 | 否 | 无 |
|
||||
| `http.ignoreSSL` | 忽略 HTTPS 证书校验 | 否 | `false` |
|
||||
| `http.maxRedirects` | 最大重定向跟随次数 | 否 | `0` |
|
||||
|
||||
## expect 校验项
|
||||
|
||||
| 字段 | 说明 | 必填 | 默认值 |
|
||||
| ------------ | -------------------------------------------------- | ---- | ------- |
|
||||
| `status` | 可接受的状态码列表,支持精确码和范围(如 `"2xx"`) | 否 | `[200]` |
|
||||
| `headers` | 响应头校验,使用 `KeyedExpectations` | 否 | 无 |
|
||||
| `body` | 响应体校验,使用 `ContentExpectations` 数组 | 否 | 无 |
|
||||
| `durationMs` | 完整执行耗时校验,使用 `ValueMatcher` | 否 | 无 |
|
||||
|
||||
## 示例
|
||||
|
||||
```yaml
|
||||
- id: "json-api"
|
||||
name: "JSON API 示例"
|
||||
type: http
|
||||
http:
|
||||
url: "https://httpbin.org/json"
|
||||
headers:
|
||||
Authorization: "Bearer token"
|
||||
expect:
|
||||
status: [200]
|
||||
headers:
|
||||
Content-Type:
|
||||
contains: "application/json"
|
||||
body:
|
||||
- json:
|
||||
path: "$.slideshow.title"
|
||||
equals: "Sample Slide Show"
|
||||
durationMs:
|
||||
lte: 10000
|
||||
```
|
||||
|
||||
HTTP checker 的 `durationMs` 覆盖完整执行,包括重定向、按需响应体读取、解码和 expect 校验。未配置 body expectation、status 失败或 headers 失败时不会读取 body。
|
||||
45
docs/user/checkers/icmp.md
Normal file
45
docs/user/checkers/icmp.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# ICMP Checker
|
||||
|
||||
`type: icmp` 使用系统 `ping` 命令执行 ICMP 探测,支持 Linux、macOS 和 Windows 输出解析。
|
||||
|
||||
## 配置项
|
||||
|
||||
| 字段 | 说明 | 必填 | 默认值 |
|
||||
| ----------------- | ------------------------- | ---- | ------ |
|
||||
| `icmp.host` | 目标主机地址 | 是 | 无 |
|
||||
| `icmp.count` | ICMP 包数量,范围 `1-100` | 否 | `3` |
|
||||
| `icmp.packetSize` | ICMP 包大小,bytes | 否 | `56` |
|
||||
|
||||
## expect 校验项
|
||||
|
||||
| 字段 | 说明 | 必填 | 默认值 |
|
||||
| ------------------- | --------------------------------------------------- | ---- | ------ |
|
||||
| `alive` | 期望主机可达性 | 否 | `true` |
|
||||
| `packetLossPercent` | 丢包率百分比校验,范围 `0-100`,使用 `ValueMatcher` | 否 | 无 |
|
||||
| `avgLatencyMs` | 平均延迟校验,使用 `ValueMatcher` | 否 | 无 |
|
||||
| `maxLatencyMs` | 最大单次延迟校验,使用 `ValueMatcher` | 否 | 无 |
|
||||
| `durationMs` | 完整执行耗时校验,使用 `ValueMatcher` | 否 | 无 |
|
||||
|
||||
## 示例
|
||||
|
||||
```yaml
|
||||
- id: "gateway-icmp"
|
||||
name: "网关 ICMP 可达"
|
||||
type: icmp
|
||||
icmp:
|
||||
host: "10.0.0.1"
|
||||
count: 3
|
||||
packetSize: 56
|
||||
expect:
|
||||
alive: true
|
||||
packetLossPercent:
|
||||
lte: 10
|
||||
avgLatencyMs:
|
||||
lte: 100
|
||||
maxLatencyMs:
|
||||
lte: 300
|
||||
durationMs:
|
||||
lte: 5000
|
||||
```
|
||||
|
||||
容器中运行 ICMP checker 通常需要 `--cap-add=NET_RAW`,详情见 [部署文档](../deployment.md#icmp-权限)。
|
||||
53
docs/user/checkers/llm.md
Normal file
53
docs/user/checkers/llm.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# LLM Checker
|
||||
|
||||
`type: llm` 用于大模型服务应用层健康检查。
|
||||
|
||||
## 配置项
|
||||
|
||||
| 字段 | 说明 | 必填 | 默认值 |
|
||||
| --------------------- | ----------------------------------------------------- | ---- | ------- |
|
||||
| `llm.provider` | 模型提供方:`openai`、`openai-responses`、`anthropic` | 是 | 无 |
|
||||
| `llm.url` | API base URL | 是 | 无 |
|
||||
| `llm.model` | 模型名称 | 是 | 无 |
|
||||
| `llm.prompt` | 单轮 prompt | 是 | 无 |
|
||||
| `llm.mode` | 调用模式:`http` 或 `stream` | 否 | `http` |
|
||||
| `llm.key` | API key,支持 `${VAR}` 变量替换 | 否 | `""` |
|
||||
| `llm.authToken` | Bearer token,仅 `anthropic` provider,与 `key` 互斥 | 否 | 无 |
|
||||
| `llm.headers` | 附加请求头 | 否 | 无 |
|
||||
| `llm.ignoreSSL` | 忽略 HTTPS 证书校验 | 否 | `false` |
|
||||
| `llm.options` | 生成选项 | 否 | 无 |
|
||||
| `llm.providerOptions` | Provider 专属选项 | 否 | 无 |
|
||||
|
||||
`llm.options` 支持 `maxOutputTokens`(默认 `16`)、`temperature`(默认 `0`)、`topP`、`topK`、`presencePenalty`、`frequencyPenalty`、`stopSequences`、`seed`。
|
||||
|
||||
## expect 校验项
|
||||
|
||||
| 字段 | 说明 | 必填 | 默认值 |
|
||||
| ----------------- | --------------------------------------------------------------------------- | ---- | ------- |
|
||||
| `status` | 可接受的状态码列表,支持精确码和范围(如 `"2xx"`) | 否 | `[200]` |
|
||||
| `headers` | 响应头校验,使用 `KeyedExpectations` | 否 | 无 |
|
||||
| `output` | 模型输出校验,使用 `ContentExpectations` 数组 | 否 | 无 |
|
||||
| `finishReason` | finish reason 校验,使用 `ValueMatcher` | 否 | 无 |
|
||||
| `rawFinishReason` | 原始 finish reason 校验,使用 `ValueMatcher` | 否 | 无 |
|
||||
| `usage` | Token usage 校验,支持 `inputTokens`、`outputTokens`、`totalTokens` matcher | 否 | 无 |
|
||||
| `stream` | 流式断言,支持 `completed`、`firstTokenMs` matcher,仅 `mode: stream` | 否 | 无 |
|
||||
| `durationMs` | 完整执行耗时校验,使用 `ValueMatcher` | 否 | 无 |
|
||||
|
||||
## 示例
|
||||
|
||||
```yaml
|
||||
- id: "llm-openai-probe"
|
||||
name: "OpenAI 健康检查"
|
||||
type: llm
|
||||
llm:
|
||||
provider: openai
|
||||
url: "https://api.openai.com/v1"
|
||||
model: "gpt-4o-mini"
|
||||
prompt: "Say OK"
|
||||
key: "${OPENAI_API_KEY}"
|
||||
expect:
|
||||
status: [200]
|
||||
finishReason: "stop"
|
||||
output:
|
||||
- contains: "OK"
|
||||
```
|
||||
119
docs/user/checkers/mem.md
Normal file
119
docs/user/checkers/mem.md
Normal file
@@ -0,0 +1,119 @@
|
||||
# Mem Checker
|
||||
|
||||
`type: mem` 用于检查本机系统级内存使用状况,包括物理内存和交换空间的使用率及字节数。
|
||||
|
||||
## 配置项
|
||||
|
||||
Mem checker 配置为空对象,无需额外参数:
|
||||
|
||||
```yaml
|
||||
mem: {}
|
||||
```
|
||||
|
||||
## expect 校验项
|
||||
|
||||
### 百分比字段
|
||||
|
||||
| 字段 | 说明 | 必填 | 默认值 |
|
||||
| ------------------ | -------------------------------------------------------------------------- | ---- | ------ |
|
||||
| `usagePercent` | 真实内存使用率 = `activeBytes / totalBytes × 100`,不含 buffers/cache 假象 | 否 | 无 |
|
||||
| `usedPercent` | 原始已用百分比 = `usedBytes / totalBytes × 100`,包含 buffers/cache | 否 | 无 |
|
||||
| `freePercent` | 空闲百分比 = `freeBytes / totalBytes × 100` | 否 | 无 |
|
||||
| `activePercent` | 活跃内存百分比 = `activeBytes / totalBytes × 100` | 否 | 无 |
|
||||
| `availablePercent` | 可用内存百分比 = `availableBytes / totalBytes × 100` | 否 | 无 |
|
||||
| `swapUsagePercent` | 交换空间使用率,当系统无交换分区时为 `null` | 否 | 无 |
|
||||
|
||||
所有百分比字段范围为 `0-100`,使用 `ValueMatcher`。
|
||||
|
||||
### 字节字段
|
||||
|
||||
| 字段 | 说明 | 必填 | 默认值 |
|
||||
| ---------------- | ----------------------------------------- | ---- | ------ |
|
||||
| `activeBytes` | 活跃内存字节数 | 否 | 无 |
|
||||
| `usedBytes` | 已用内存字节数(含 buffers/cache) | 否 | 无 |
|
||||
| `freeBytes` | 空闲内存字节数 | 否 | 无 |
|
||||
| `availableBytes` | 可用内存字节数 | 否 | 无 |
|
||||
| `totalBytes` | 物理内存总字节数 | 否 | 无 |
|
||||
| `swapUsedBytes` | 交换空间已用字节数,无交换分区时为 `null` | 否 | 无 |
|
||||
| `swapFreeBytes` | 交换空间空闲字节数,无交换分区时为 `null` | 否 | 无 |
|
||||
| `swapTotalBytes` | 交换空间总字节数,无交换分区时为 `0` | 否 | 无 |
|
||||
| `buffcacheBytes` | 缓冲缓存字节数,部分平台可能为 `null` | 否 | 无 |
|
||||
|
||||
字节字段支持数字(字节数)或大小字符串(如 `"512MB"`、`"1GB"`),使用 `ValueMatcher`。
|
||||
|
||||
### 通用字段
|
||||
|
||||
| 字段 | 说明 | 必填 | 默认值 |
|
||||
| ------------ | ------------------------------------- | ---- | ------ |
|
||||
| `durationMs` | 完整执行耗时校验,使用 `ValueMatcher` | 否 | 无 |
|
||||
|
||||
## 示例
|
||||
|
||||
检查内存使用率不超过 85%:
|
||||
|
||||
```yaml
|
||||
- id: "local-memory"
|
||||
name: "本机内存"
|
||||
type: mem
|
||||
interval: "30s"
|
||||
timeout: "5s"
|
||||
mem: {}
|
||||
expect:
|
||||
usagePercent:
|
||||
lte: 85
|
||||
```
|
||||
|
||||
检查可用内存不低于 4GB:
|
||||
|
||||
```yaml
|
||||
- id: "local-memory-available"
|
||||
name: "可用内存检查"
|
||||
type: mem
|
||||
mem: {}
|
||||
expect:
|
||||
availableBytes:
|
||||
gte: "4GB"
|
||||
```
|
||||
|
||||
同时检查内存和交换空间:
|
||||
|
||||
```yaml
|
||||
- id: "local-memory-swap"
|
||||
name: "内存和交换空间"
|
||||
type: mem
|
||||
mem: {}
|
||||
expect:
|
||||
usagePercent:
|
||||
lte: 80
|
||||
swapUsagePercent:
|
||||
lte: 50
|
||||
```
|
||||
|
||||
## 语义说明
|
||||
|
||||
Mem checker 通过 `systeminformation` 库读取系统内存数据,在 Linux、macOS 和 Windows 上均可运行。
|
||||
|
||||
- **`usagePercent`** 使用 `activeBytes / totalBytes` 计算,反映真实的内存压力,不受 Linux buffers/cache 缓存影响。推荐使用此字段进行内存健康检查。
|
||||
- **`usedPercent`** 使用 `usedBytes / totalBytes` 计算,包含 buffers/cache。在 Linux 上此值通常高于 `usagePercent`。
|
||||
- **Swap 字段**:当系统未配置交换分区时,`swapTotalBytes` 为 `0`,`swapUsagePercent` 为 `null`(非 `0`)。
|
||||
- **`buffcacheBytes`**:反映 Linux 的 buffers + cache 用量,在其他平台上可能为 `null`。
|
||||
|
||||
Mem checker 是即时读取(非采样),无需 `sampleDuration`,执行速度远快于 CPU checker。虽然读取本身很快,但仍受 target `timeout` 约束——若底层系统调用悬挂或阻塞超过 `timeout`,checker 会返回 `mem/timeout` failure。
|
||||
|
||||
## 跨平台注意事项
|
||||
|
||||
- Windows 环境依赖 PowerShell 5+ 获取部分内存指标
|
||||
- `buffcacheBytes` 在非 Linux 平台上可能返回 `null`
|
||||
- 容器环境中内存数据可能不反映 cgroup 内存限制
|
||||
|
||||
## 不支持的功能
|
||||
|
||||
- 进程级内存使用(如 RSS、VSZ)
|
||||
- cgroup/container 内存限制精度
|
||||
- 内存趋势采样和历史记录
|
||||
- 内存条物理布局信息
|
||||
- 详细内存分类(slab、reclaimable、dirty 等)作为 expect 字段
|
||||
|
||||
## 更新触发条件
|
||||
|
||||
修改 Mem checker 配置、expect 字段、行为或语义时,必须更新本文档。
|
||||
35
docs/user/checkers/tcp.md
Normal file
35
docs/user/checkers/tcp.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# TCP Checker
|
||||
|
||||
`type: tcp` 用于 TCP 端口可达性和可选 banner 探测。
|
||||
|
||||
## 配置项
|
||||
|
||||
| 字段 | 说明 | 必填 | 默认值 |
|
||||
| ----------------------- | --------------------------------------------- | ---- | ------- |
|
||||
| `tcp.host` | 目标主机地址 | 是 | 无 |
|
||||
| `tcp.port` | 目标端口,范围 `1-65535` | 是 | 无 |
|
||||
| `tcp.readBanner` | 是否读取服务端 banner | 否 | `false` |
|
||||
| `tcp.bannerReadTimeout` | banner 读取超时,毫秒 | 否 | `2000` |
|
||||
| `tcp.maxBannerBytes` | banner 最大字节数,支持 `KB`、`MB`、`GB` 单位 | 否 | `4KB` |
|
||||
|
||||
## expect 校验项
|
||||
|
||||
| 字段 | 说明 | 必填 | 默认值 |
|
||||
| ------------ | ------------------------------------------------------------------------- | ---- | ------ |
|
||||
| `connected` | 期望连接结果,`true` 可达或 `false` 期望不可达 | 否 | `true` |
|
||||
| `banner` | Banner 内容校验,使用 `ContentExpectations` 数组,需开启 `tcp.readBanner` | 否 | 无 |
|
||||
| `durationMs` | 完整执行耗时校验,使用 `ValueMatcher` | 否 | 无 |
|
||||
|
||||
## 示例
|
||||
|
||||
```yaml
|
||||
- id: "redis-port"
|
||||
name: "Redis 端口可达"
|
||||
type: tcp
|
||||
tcp:
|
||||
host: "127.0.0.1"
|
||||
port: 6379
|
||||
expect:
|
||||
durationMs:
|
||||
lte: 3000
|
||||
```
|
||||
43
docs/user/checkers/udp.md
Normal file
43
docs/user/checkers/udp.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# UDP Checker
|
||||
|
||||
`type: udp` 用于 UDP payload 请求-响应检查。
|
||||
|
||||
## 配置项
|
||||
|
||||
| 字段 | 说明 | 必填 | 默认值 |
|
||||
| ---------------------- | ------------------------------------------ | ---- | ------ |
|
||||
| `udp.host` | 目标主机地址 | 是 | 无 |
|
||||
| `udp.port` | 目标端口,范围 `1-65535` | 是 | 无 |
|
||||
| `udp.payload` | 发送数据 | 否 | `""` |
|
||||
| `udp.encoding` | payload 编码:`text`、`hex`、`base64` | 否 | `text` |
|
||||
| `udp.responseEncoding` | 响应解码:`text`、`hex`、`base64` | 否 | `text` |
|
||||
| `udp.maxResponseBytes` | 响应最大字节数,支持 `KB`、`MB`、`GB` 单位 | 否 | `4KB` |
|
||||
|
||||
## expect 校验项
|
||||
|
||||
| 字段 | 说明 | 必填 | 默认值 |
|
||||
| -------------- | --------------------------------------------- | ---- | ------ |
|
||||
| `responded` | 期望是否收到响应 | 否 | `true` |
|
||||
| `response` | 响应内容校验,使用 `ContentExpectations` 数组 | 否 | 无 |
|
||||
| `responseSize` | 响应字节数校验,使用 `ValueMatcher` | 否 | 无 |
|
||||
| `sourceHost` | 响应来源地址校验,使用 `ValueMatcher` | 否 | 无 |
|
||||
| `sourcePort` | 响应来源端口校验,使用 `ValueMatcher` | 否 | 无 |
|
||||
| `durationMs` | 完整执行耗时校验,使用 `ValueMatcher` | 否 | 无 |
|
||||
|
||||
## 示例
|
||||
|
||||
```yaml
|
||||
- id: "udp-heartbeat"
|
||||
name: "UDP 心跳检测"
|
||||
type: udp
|
||||
udp:
|
||||
host: "127.0.0.1"
|
||||
port: 9000
|
||||
payload: "PING"
|
||||
expect:
|
||||
responded: true
|
||||
response:
|
||||
- contains: "PONG"
|
||||
durationMs:
|
||||
lte: 100
|
||||
```
|
||||
81
docs/user/checkers/ws.md
Normal file
81
docs/user/checkers/ws.md
Normal file
@@ -0,0 +1,81 @@
|
||||
# WS Checker
|
||||
|
||||
`type: ws` 用于 WebSocket 服务可达性检查和消息交互验证。
|
||||
|
||||
## 配置项
|
||||
|
||||
| 字段 | 说明 | 必填 | 默认值 |
|
||||
| -------------------- | ---------------------------------------------- | ---- | ------- |
|
||||
| `ws.url` | 目标 URL,必须以 `ws://` 或 `wss://` 开头 | 是 | 无 |
|
||||
| `ws.headers` | 握手 HTTP 头 | 否 | `{}` |
|
||||
| `ws.subprotocols` | 子协议协商 | 否 | `[]` |
|
||||
| `ws.ignoreSSL` | 忽略 TLS 证书校验 | 否 | `false` |
|
||||
| `ws.send` | 发送的 text 消息,配置后进入请求-响应模式 | 否 | 无 |
|
||||
| `ws.receiveTimeout` | 等待响应超时,毫秒 | 否 | `5000` |
|
||||
| `ws.maxMessageBytes` | 单条消息最大字节数,支持 `KB`、`MB`、`GB` 单位 | 否 | `4KB` |
|
||||
|
||||
## expect 校验项
|
||||
|
||||
| 字段 | 说明 | 必填 | 默认值 |
|
||||
| ------------------ | --------------------------------------------------------------------- | ---- | ------ |
|
||||
| `connected` | 期望连接结果,`true` 可达或 `false` 期望不可达 | 否 | `true` |
|
||||
| `handshakeHeaders` | 握手响应头校验,使用 `KeyedExpectations` | 否 | 无 |
|
||||
| `message` | 收到的消息内容校验,使用 `ContentExpectations` 数组,需配置 `ws.send` | 否 | 无 |
|
||||
| `connectTimeMs` | 连接建立耗时校验,使用 `ValueMatcher` | 否 | 无 |
|
||||
| `durationMs` | 完整执行耗时校验,使用 `ValueMatcher` | 否 | 无 |
|
||||
|
||||
## 两种模式
|
||||
|
||||
不配置 `ws.send` 时只做可达性检查(连接后立即关闭),配置 `ws.send` 后进入请求-响应模式(发送消息并等待首条响应)。
|
||||
|
||||
## 示例
|
||||
|
||||
可达性检查:
|
||||
|
||||
```yaml
|
||||
- id: "ws-reachability"
|
||||
name: "WebSocket 服务可达"
|
||||
type: ws
|
||||
ws:
|
||||
url: "wss://api.example.com/ws"
|
||||
expect:
|
||||
durationMs:
|
||||
lte: 3000
|
||||
```
|
||||
|
||||
带鉴权的请求-响应:
|
||||
|
||||
```yaml
|
||||
- id: "ws-echo"
|
||||
name: "WebSocket Echo 检查"
|
||||
type: ws
|
||||
ws:
|
||||
url: "wss://echo.example.com/ws"
|
||||
headers:
|
||||
Authorization: "Bearer ${TOKEN}"
|
||||
subprotocols: ["json"]
|
||||
send: '{"action":"ping"}'
|
||||
receiveTimeout: 3000
|
||||
expect:
|
||||
handshakeHeaders:
|
||||
Sec-WebSocket-Protocol:
|
||||
equals: "json"
|
||||
message:
|
||||
- json:
|
||||
path: "$.action"
|
||||
equals: "pong"
|
||||
durationMs:
|
||||
lte: 5000
|
||||
```
|
||||
|
||||
期望不可达:
|
||||
|
||||
```yaml
|
||||
- id: "ws-internal-down"
|
||||
name: "内部服务已下线"
|
||||
type: ws
|
||||
ws:
|
||||
url: "ws://internal.monitor:9443/ws"
|
||||
expect:
|
||||
connected: false
|
||||
```
|
||||
135
docs/user/configuration.md
Normal file
135
docs/user/configuration.md
Normal file
@@ -0,0 +1,135 @@
|
||||
# 配置文件
|
||||
|
||||
DiAL 通过 YAML 配置文件定义运行参数和拨测目标。完整可运行示例参见 [`../../probes.example.yaml`](../../probes.example.yaml)。配置 JSON Schema 位于 [`../../probe-config.schema.json`](../../probe-config.schema.json)。
|
||||
|
||||
## 配置结构
|
||||
|
||||
```yaml
|
||||
# yaml-language-server: $schema=./probe-config.schema.json
|
||||
|
||||
server:
|
||||
listen:
|
||||
host: "127.0.0.1"
|
||||
port: "${server_port}"
|
||||
storage:
|
||||
dataDir: "/tmp/probes_data"
|
||||
retention: "${retention}"
|
||||
logging:
|
||||
level: "${log_level|info}"
|
||||
file:
|
||||
path: "<dataDir>/logs/dial.log"
|
||||
|
||||
probes:
|
||||
execution:
|
||||
maxConcurrentChecks: "${max_checks}"
|
||||
|
||||
variables:
|
||||
server_port: 3000
|
||||
retention: "7d"
|
||||
max_checks: 20
|
||||
default_interval: "30s"
|
||||
default_timeout: "10s"
|
||||
|
||||
targets:
|
||||
- id: "baidu-home"
|
||||
name: "Baidu"
|
||||
type: http
|
||||
interval: "${default_interval}"
|
||||
timeout: "${default_timeout}"
|
||||
http:
|
||||
url: "https://www.baidu.com"
|
||||
expect:
|
||||
status: [200]
|
||||
```
|
||||
|
||||
## server.listen
|
||||
|
||||
| 字段 | 说明 | 必填 | 默认值 |
|
||||
| ------ | -------- | ---- | ----------- |
|
||||
| `host` | 监听地址 | 否 | `127.0.0.1` |
|
||||
| `port` | 监听端口 | 否 | `3000` |
|
||||
|
||||
## server.storage
|
||||
|
||||
| 字段 | 说明 | 必填 | 默认值 |
|
||||
| ----------- | ---------------------------------------------------- | ---- | -------- |
|
||||
| `dataDir` | 数据目录,相对路径基于配置文件所在目录解析 | 否 | `./data` |
|
||||
| `retention` | 历史数据保留时长,支持 `ms`、`s`、`m`、`h`、`d` 单位 | 否 | `7d` |
|
||||
|
||||
## probes.execution
|
||||
|
||||
| 字段 | 说明 | 必填 | 默认值 |
|
||||
| --------------------- | -------------- | ---- | ------ |
|
||||
| `maxConcurrentChecks` | 最大并发拨测数 | 否 | `20` |
|
||||
|
||||
## server.logging
|
||||
|
||||
| 字段 | 说明 | 必填 | 默认值 |
|
||||
| ---------------------------------------- | ---------------------------------------------- | ---- | ------------------------- |
|
||||
| `server.logging.level` | 全局日志等级,console 和 file 未指定时继承此值 | 否 | `info` |
|
||||
| `server.logging.console.level` | 控制台日志等级 | 否 | 继承 `level` |
|
||||
| `server.logging.file.level` | 文件日志等级 | 否 | 继承 `level` |
|
||||
| `server.logging.file.path` | 日志文件路径,相对路径基于配置文件目录解析 | 否 | `<dataDir>/logs/dial.log` |
|
||||
| `server.logging.file.rotation.size` | 按大小滚动,支持 `KB`、`MB`、`GB` 单位 | 否 | `50MB` |
|
||||
| `server.logging.file.rotation.frequency` | 按时间滚动:`hourly`、`daily`、`weekly` | 否 | `daily` |
|
||||
| `server.logging.file.rotation.maxFiles` | 保留的归档文件数量,不含活跃日志 | 否 | `14` |
|
||||
|
||||
日志等级支持:`trace`、`debug`、`info`、`warn`、`error`、`fatal`。
|
||||
|
||||
控制台始终输出 pretty 格式,文件始终输出 JSONL 格式并支持滚动。`rotation.size` 和 `rotation.frequency` 任一条件触发即滚动。
|
||||
|
||||
## 内置默认值
|
||||
|
||||
| 字段 | 默认值 | 约束 |
|
||||
| ---------- | ------ | ----------------------- |
|
||||
| `interval` | `30s` | 最小 `10s` |
|
||||
| `timeout` | `10s` | 必须小于等于 `interval` |
|
||||
|
||||
各 checker 专属默认值见 [Checker 参考](checkers/README.md)。
|
||||
|
||||
## variables
|
||||
|
||||
`variables` 是顶层动态键值表,key 必须符合 `[a-zA-Z_][a-zA-Z0-9_]*`,value 仅支持 string、number、boolean。`server`、`probes` 和 `targets` 中的字符串值可引用变量。
|
||||
|
||||
| 语法 | 说明 |
|
||||
| ----------------- | ------------------------------------------ |
|
||||
| `${key}` | 引用 variables 或环境变量 |
|
||||
| `${key\|default}` | variables 和环境变量都不存在时使用默认值 |
|
||||
| `${key\|}` | variables 和环境变量都不存在时使用空字符串 |
|
||||
| `$${key}` | 转义输出字面量 `${key}` |
|
||||
|
||||
解析优先级为 `variables -> process.env -> 默认值`。三者均不存在时配置校验失败。字段值完整等于单个变量引用时会保留 number、boolean、string 类型;部分拼接时统一转为字符串。
|
||||
|
||||
变量替换作用于 `server`、`probes` 和 `targets`,不作用于 `variables` 段自身,且不会替换 `targets[].id` 和 `targets[].type` 字段;对象 key 不参与替换。
|
||||
|
||||
## 配置加载形态
|
||||
|
||||
配置加载内部区分三层形态:
|
||||
|
||||
| 形态 | 说明 |
|
||||
| ----------------- | ------------------------------------------------------------------------------------- |
|
||||
| Authoring Config | 用户 YAML 可书写形态,允许变量引用和 expect 简写 |
|
||||
| Normalized Config | `normalizeAuthoringConfig()` 完成变量替换、expect 简写展开并移除 `variables` 后的形态 |
|
||||
| ResolvedConfig | checker `resolve()` 补默认值并解析 duration、size、路径和运行期环境后的形态 |
|
||||
|
||||
根目录 `probe-config.schema.json` 面向 Authoring Config,因此 VSCode 校验会接受 `server.listen.port: "${server_port|3000}"`、`http.maxRedirects: "${MAX|5}"` 和 `expect.durationMs: 5000` 这类写法。
|
||||
|
||||
## targets 通用字段
|
||||
|
||||
| 字段 | 说明 | 必填 | 默认值 |
|
||||
| ------------- | ------------------------------------------------------------------------------------ | ---- | --------- |
|
||||
| `id` | 目标唯一标识,最长 30 字符,支持字母数字、下划线、连字符,不参与变量替换 | 是 | 无 |
|
||||
| `name` | 展示名称,最长 30 字符,支持变量替换,可省略或显式 null;前端展示时 null 回退到 `id` | 否 | 无 |
|
||||
| `description` | 目标描述,最长 500 字符,支持变量替换,可省略或显式 null,允许空字符串 | 否 | 无 |
|
||||
| `type` | 目标类型:`http`、`cmd`、`db`、`tcp`、`udp`、`dns`、`icmp`、`llm`、`ws`、`cpu` | 是 | 无 |
|
||||
| `group` | 分组名称 | 否 | `default` |
|
||||
| `interval` | 拨测间隔,最小 `10s` | 否 | `30s` |
|
||||
| `timeout` | 超时时间,必须小于等于 `interval` | 否 | `10s` |
|
||||
|
||||
## Checker 专属配置
|
||||
|
||||
每个 target 必须根据 `type` 配置对应的 checker 专属字段。详情见 [Checker 参考](checkers/README.md)。
|
||||
|
||||
## 校验规则
|
||||
|
||||
`expect` 字段按 checker 类型不同而变化。通用断言模型见 [校验规则](expectations.md)。
|
||||
109
docs/user/deployment.md
Normal file
109
docs/user/deployment.md
Normal file
@@ -0,0 +1,109 @@
|
||||
# 部署
|
||||
|
||||
本文档说明如何构建、运行、容器化和发布 DiAL。开发环境运行见 [README 快速开始](../../README.md#快速开始)。
|
||||
|
||||
## 生产构建和运行
|
||||
|
||||
```bash
|
||||
bun run build
|
||||
./dist/dial-server ./probes.yaml
|
||||
```
|
||||
|
||||
构建产物为独立可执行文件,只需要一个 YAML 配置文件即可运行。
|
||||
|
||||
启动后:
|
||||
|
||||
| 地址 | 行为 |
|
||||
| ------------------------------ | ------------------ |
|
||||
| `http://127.0.0.1:3000/` | 返回前端 Dashboard |
|
||||
| `http://127.0.0.1:3000/api/*` | 返回后端 API |
|
||||
| `http://127.0.0.1:3000/health` | 返回健康检查 |
|
||||
|
||||
## Docker 部署
|
||||
|
||||
DiAL 提供基于 Alpine 的多阶段镜像。构建阶段使用 Bun 生成 musl 目标单可执行文件,运行阶段只包含 `dial-server`、基础证书、`ping`、Bun musl executable 必需运行库、时区数据和容器运行目录。
|
||||
|
||||
```bash
|
||||
docker build -t dial:alpine .
|
||||
docker run --rm -p 3000:3000 -v dial-data:/data/dial dial:alpine
|
||||
```
|
||||
|
||||
容器默认读取 `/etc/dial/probes.yaml`,推荐将数据卷挂载到 `/data/dial`。
|
||||
|
||||
使用自定义配置文件:
|
||||
|
||||
```bash
|
||||
docker run --rm -p 3000:3000 \
|
||||
-v "$PWD/docker/probes.yaml:/etc/dial/probes.yaml:ro" \
|
||||
-v dial-data:/data/dial \
|
||||
dial:alpine
|
||||
```
|
||||
|
||||
容器专用示例配置位于 [`../../docker/probes.yaml`](../../docker/probes.yaml),默认监听 `0.0.0.0:3000`,并将 SQLite 数据和日志写入 `/data/dial`。
|
||||
|
||||
## ICMP 权限
|
||||
|
||||
如需在容器中运行 ICMP checker,除镜像内置 `iputils-ping` 外,还需要授予 `NET_RAW` capability:
|
||||
|
||||
```bash
|
||||
docker run --rm --cap-add=NET_RAW -p 3000:3000 -v dial-data:/data/dial dial:alpine
|
||||
```
|
||||
|
||||
## CMD checker 额外命令
|
||||
|
||||
官方镜像不内置 `bun`、`node`、`curl`、`dig`、`psql`、`mysql`、`redis-cli` 等 CMD checker 可能需要的额外命令。需要这些命令时请使用派生镜像自行安装:
|
||||
|
||||
```dockerfile
|
||||
FROM dial:alpine
|
||||
|
||||
USER root
|
||||
RUN apk add --no-cache curl bind-tools postgresql-client
|
||||
USER dial
|
||||
```
|
||||
|
||||
## 多架构镜像
|
||||
|
||||
```bash
|
||||
docker buildx build --platform linux/amd64,linux/arm64 -t dial:alpine .
|
||||
```
|
||||
|
||||
Dockerfile 通过 Docker 提供的 `TARGETARCH` 选择 Bun compile target。
|
||||
|
||||
| `TARGETARCH` | `BUN_TARGET` |
|
||||
| ------------ | ---------------------- |
|
||||
| `amd64` | `bun-linux-x64-musl` |
|
||||
| `arm64` | `bun-linux-arm64-musl` |
|
||||
|
||||
## 跨平台发布包
|
||||
|
||||
```bash
|
||||
bun run release
|
||||
bun run release --target linux-x64
|
||||
bun run release --target linux-x64,windows-x64,darwin-arm64
|
||||
```
|
||||
|
||||
支持的目标平台:
|
||||
|
||||
| CLI 参数 | Bun CompileTarget |
|
||||
| ------------------ | ---------------------- |
|
||||
| `linux-x64` | `bun-linux-x64` |
|
||||
| `linux-arm64` | `bun-linux-arm64` |
|
||||
| `linux-x64-musl` | `bun-linux-x64-musl` |
|
||||
| `linux-arm64-musl` | `bun-linux-arm64-musl` |
|
||||
| `windows-x64` | `bun-windows-x64` |
|
||||
| `darwin-x64` | `bun-darwin-x64` |
|
||||
| `darwin-arm64` | `bun-darwin-arm64` |
|
||||
|
||||
产出物结构:
|
||||
|
||||
```text
|
||||
dist/release/
|
||||
├── binaries/
|
||||
│ ├── dial-server-0.1.0-linux-x64
|
||||
│ └── dial-server-0.1.0-windows-x64.exe
|
||||
└── packages/
|
||||
├── dial-server_0.1.0_linux_x64.tar.gz
|
||||
└── dial-server_0.1.0_linux_x64.tar.gz.sha256
|
||||
```
|
||||
|
||||
压缩包内含可执行文件、`probes.example.yaml` 和 `LICENSE`,解压后可直接使用。
|
||||
161
docs/user/expectations.md
Normal file
161
docs/user/expectations.md
Normal file
@@ -0,0 +1,161 @@
|
||||
# 校验规则
|
||||
|
||||
本文档说明 `expect` 规则、状态判定、failure、observation 和各 checker 的快速失败顺序。
|
||||
|
||||
适用场景:编写 `expect`、理解 UP/DOWN、排查 mismatch/error、查看返回结果中的 `failure` 和 `observation`。
|
||||
|
||||
`expect` 描述拨测结果必须满足的条件。不同 checker 暴露不同字段,但共享三类基础断言模型:`ValueMatcher`、`ContentExpectations` 和 `KeyedExpectations`。
|
||||
|
||||
## 状态判定
|
||||
|
||||
DiAL 使用单层状态模型。
|
||||
|
||||
| 状态 | 含义 |
|
||||
| ------ | ---------------------------------------- |
|
||||
| `UP` | 拨测结果符合 `expect` 规则 |
|
||||
| `DOWN` | 拨测结果不符合 `expect` 规则,或执行失败 |
|
||||
|
||||
执行失败(网络错误、超时、进程崩溃)和 expect 不匹配都统一为 `DOWN`,通过 `failure.kind` 区分原因。
|
||||
|
||||
| `failure.kind` | 含义 |
|
||||
| -------------- | ---------------------------------------- |
|
||||
| `error` | 网络、超时、进程、协议解析或内部执行错误 |
|
||||
| `mismatch` | 拨测完成,但结果不满足 expect |
|
||||
|
||||
## API 结果字段
|
||||
|
||||
API 返回的检查结果包含 `detail` 和 `observation`。
|
||||
|
||||
| 字段 | 说明 |
|
||||
| ------------- | ------------------------------------------------------------ |
|
||||
| `detail` | 后端按 checker 类型从结构化 observation 动态生成的人可读摘要 |
|
||||
| `observation` | 保存该次检查的结构化观测数据 |
|
||||
| `failure` | 保存首个错误或不匹配原因 |
|
||||
| `matched` | 是否符合 expect |
|
||||
| `durationMs` | 本次检查耗时 |
|
||||
| `timestamp` | 本次检查时间 |
|
||||
|
||||
`detail` 不写入 SQLite。存储层仅持久化 `observation` JSON、`failure` JSON、匹配状态、耗时和时间戳。
|
||||
|
||||
## observation 示例
|
||||
|
||||
不同 checker 的 observation 字段不同,常见信息包括:
|
||||
|
||||
| Checker | observation 内容示例 |
|
||||
| ------- | ------------------------------------------------------------------ |
|
||||
| HTTP | 状态码、响应头、按需读取的 body 预览 |
|
||||
| Cmd | exit code、stdout/stderr 预览 |
|
||||
| TCP | 连接结果、banner 摘要 |
|
||||
| UDP | 响应内容、来源地址、响应大小 |
|
||||
| ICMP | 存活结果、丢包率、平均延迟、最大延迟 |
|
||||
| DNS | RCODE、记录值、TTL、flags、CNAME 链 |
|
||||
| LLM | HTTP 状态、模型输出、finish reason、token usage、流式首 token 时间 |
|
||||
| WS | 连接结果、连接耗时、握手头、消息内容、消息大小 |
|
||||
|
||||
Dashboard 基于存储的检查结果计算实时状态、可用率、耗时趋势、P95、状态条和故障段等指标。指标语义由后端应用层实现,SQLite 主要负责存储、筛选、排序、分页和基础聚合。
|
||||
|
||||
## ContentExpectations
|
||||
|
||||
`body`、`stdout`、`stderr`、`banner`、`response`、`output`、`result`、`message` 等返回内容字段均使用数组。
|
||||
|
||||
| 规则 | 说明 |
|
||||
| ---------- | ------------------------------------------------------ |
|
||||
| `contains` | 内容包含指定文本 |
|
||||
| `regex` | 正则匹配,启动期会拒绝存在 ReDoS 风险的模式 |
|
||||
| `json` | JSONPath 提取值比较,`path` 必填 |
|
||||
| `css` | CSS 选择器提取 HTML 元素,`selector` 必填,`attr` 可选 |
|
||||
| `xpath` | XPath 提取 XML/HTML 节点,`path` 必填 |
|
||||
|
||||
示例:
|
||||
|
||||
```yaml
|
||||
expect:
|
||||
body:
|
||||
- contains: "ok"
|
||||
- json:
|
||||
path: "$.status"
|
||||
equals: "ready"
|
||||
```
|
||||
|
||||
ContentExpectations 数组按顺序快速失败。数组项可以是直接 matcher,也可以是 `json`、`css`、`xpath` 提取器规则。一条规则不能混用直接 matcher 和 extractor,多个 extractor 也不能共存。Extractor 未配置 matcher 时等价于 `exists: true`。
|
||||
|
||||
## ValueMatcher
|
||||
|
||||
`ValueMatcher` 用于单个标量值、数字指标和字符串元数据。
|
||||
|
||||
| 字段 | 说明 |
|
||||
| ---------- | ------------------------------- |
|
||||
| `equals` | 精确匹配,支持 JSON 深度相等 |
|
||||
| `contains` | 字符串包含 |
|
||||
| `regex` | 正则匹配,固定使用无 flags 正则 |
|
||||
| `empty` | 判断是否为空 |
|
||||
| `exists` | 判断是否存在 |
|
||||
| `gte` | 大于等于 |
|
||||
| `lte` | 小于等于 |
|
||||
| `gt` | 大于 |
|
||||
| `lt` | 小于 |
|
||||
|
||||
一个 matcher 对象内多个字段为 AND 语义。`exists: false` 不能和其他 matcher 组合。
|
||||
|
||||
ValueMatcher expect 字段可直接写 string、number、boolean 或 null,等价于 `{ equals: value }`。数组和对象必须显式写成 `{ equals: ... }`。
|
||||
|
||||
```yaml
|
||||
expect:
|
||||
durationMs:
|
||||
lte: 5000
|
||||
finishReason: "stop"
|
||||
```
|
||||
|
||||
## KeyedExpectations
|
||||
|
||||
`headers`、DB `rows[]` 中的列值等动态键值对象使用 `KeyedExpectations`。每个键的值支持 `ValueMatcher` 的全部字段,字面量值自动等价于 `{ equals: value }`。
|
||||
|
||||
```yaml
|
||||
expect:
|
||||
headers:
|
||||
Content-Type:
|
||||
contains: "application/json"
|
||||
```
|
||||
|
||||
## 大小和时长格式
|
||||
|
||||
| 类型 | 示例 |
|
||||
| ---- | -------------------------------- |
|
||||
| 大小 | `4KB`、`10MB`、`1GB`、直接数字 |
|
||||
| 时长 | `500ms`、`30s`、`5m`、`2h`、`7d` |
|
||||
|
||||
`maxBodyBytes`、`maxOutputBytes`、`maxResponseBytes`、`maxBannerBytes` 等大小字段支持 `KB`、`MB`、`GB` 单位。
|
||||
|
||||
## 快速失败顺序
|
||||
|
||||
| Checker | 顺序 |
|
||||
| ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| HTTP | `status -> headers -> body -> durationMs` |
|
||||
| Cmd | `exitCode -> durationMs -> stdout -> stderr` |
|
||||
| DB | `durationMs -> rowCount -> rows -> result` |
|
||||
| TCP | `connected -> banner -> durationMs` |
|
||||
| UDP | `responded -> responseSize -> response -> sourceHost -> sourcePort -> durationMs` |
|
||||
| ICMP | `alive -> packetLossPercent -> avgLatencyMs -> maxLatencyMs -> durationMs` |
|
||||
| DNS system | `values -> valueCount -> durationMs` |
|
||||
| DNS server | `responded -> rcode -> values -> valueCount -> answerCount -> ttlMin -> ttlMax -> authoritative -> recursionAvailable -> truncated -> authenticatedData -> result -> durationMs` |
|
||||
| LLM http | `status -> headers -> output -> finishReason -> rawFinishReason -> usage -> durationMs` |
|
||||
| LLM stream | `status -> headers -> stream.completed -> stream.firstTokenMs -> output -> finishReason -> rawFinishReason -> usage -> durationMs` |
|
||||
| WS | `connected -> handshakeHeaders -> message -> connectTimeMs -> durationMs` |
|
||||
|
||||
## JSON Schema
|
||||
|
||||
仓库根目录导出 `probe-config.schema.json`。在 YAML 文件顶部添加以下注释可在编辑器中获得提示和校验:
|
||||
|
||||
```yaml
|
||||
# yaml-language-server: $schema=./probe-config.schema.json
|
||||
```
|
||||
|
||||
## 已移除字段
|
||||
|
||||
旧字段 `maxDurationMs`、`maxPacketLoss`、`maxAvgLatencyMs`、`maxMaxLatencyMs` 和旧正则字段 `match` 已移除,请分别改用 `durationMs`、ICMP matcher 字段和 `regex`。
|
||||
|
||||
非法配置会阻止启动并输出错误信息。除动态键值表(`headers`、`env`、`variables`)外,未知字段会导致启动失败,请使用 YAML 注释表达说明。
|
||||
|
||||
## 更新触发条件
|
||||
|
||||
修改 expect 断言模型、状态判定、failure 字段、observation 字段、快速失败顺序或已移除字段说明时,必须更新本文档。
|
||||
73
docs/user/troubleshooting.md
Normal file
73
docs/user/troubleshooting.md
Normal file
@@ -0,0 +1,73 @@
|
||||
# 故障排查
|
||||
|
||||
本文档记录常见运行问题和排查入口。
|
||||
|
||||
## 配置校验失败
|
||||
|
||||
DiAL 启动时会校验 YAML 配置。除动态键值表(`headers`、`env`、`variables`)外,未知字段会导致启动失败。
|
||||
|
||||
排查顺序:
|
||||
|
||||
1. 在 YAML 顶部添加 `# yaml-language-server: $schema=./probe-config.schema.json`。
|
||||
2. 对照 [配置文件](configuration.md) 检查顶层结构和通用字段。
|
||||
3. 对照 [Checker 参考](checkers/README.md) 检查 checker 专属字段。
|
||||
4. 对照 [校验规则](expectations.md) 检查 expect 写法。
|
||||
|
||||
## 变量无法解析
|
||||
|
||||
变量解析优先级为 `variables -> process.env -> 默认值`。如果三者均不存在,配置校验会失败。
|
||||
|
||||
常见修复:
|
||||
|
||||
| 问题 | 修复 |
|
||||
| -------------- | ----------------------------------- |
|
||||
| 环境变量未设置 | 设置环境变量或在 `variables` 中声明 |
|
||||
| 希望允许空值 | 使用 `${key\|}` |
|
||||
| 希望提供默认值 | 使用 `${key\|default}` |
|
||||
| 希望输出字面量 | 使用 `$${key}` |
|
||||
|
||||
## ICMP checker 无法运行
|
||||
|
||||
ICMP checker 依赖系统 `ping` 命令。
|
||||
|
||||
| 环境 | 处理 |
|
||||
| ------------------- | -------------------------------------- |
|
||||
| Alpine 或精简镜像 | 安装 `iputils-ping` |
|
||||
| Docker 容器 | 运行容器时增加 `--cap-add=NET_RAW` |
|
||||
| Windows/macOS/Linux | 确认系统 `ping` 可执行且输出格式受支持 |
|
||||
|
||||
Docker 示例:
|
||||
|
||||
```bash
|
||||
docker run --rm --cap-add=NET_RAW -p 3000:3000 -v dial-data:/data/dial dial:alpine
|
||||
```
|
||||
|
||||
## CMD checker 找不到命令
|
||||
|
||||
官方 Docker 镜像不内置 `bun`、`node`、`curl`、`dig`、`psql`、`mysql`、`redis-cli` 等额外命令。需要这些命令时请使用派生镜像安装。
|
||||
|
||||
```dockerfile
|
||||
FROM dial:alpine
|
||||
|
||||
USER root
|
||||
RUN apk add --no-cache curl bind-tools postgresql-client
|
||||
USER dial
|
||||
```
|
||||
|
||||
## Docker 数据或日志丢失
|
||||
|
||||
推荐将数据卷挂载到 `/data/dial`,并在配置中使用该目录作为 storage dataDir。
|
||||
|
||||
```bash
|
||||
docker run --rm -p 3000:3000 -v dial-data:/data/dial dial:alpine
|
||||
```
|
||||
|
||||
容器示例配置位于 [`../../docker/probes.yaml`](../../docker/probes.yaml)。
|
||||
|
||||
## HTTP 或 LLM 证书问题
|
||||
|
||||
HTTP 和 LLM checker 支持 `ignoreSSL`。该选项适合内网、自签名证书或测试环境;生产环境应优先修复证书链。
|
||||
|
||||
## 正则规则被拒绝
|
||||
|
||||
`regex` 启动期会执行 ReDoS 风险检测。被拒绝时应改写为更明确、回溯风险更低的表达式。
|
||||
@@ -1,8 +1,14 @@
|
||||
import js from "@eslint/js";
|
||||
import importPlugin from "eslint-plugin-import";
|
||||
import perfectionist from "eslint-plugin-perfectionist";
|
||||
import eslintPluginPrettierRecommended from "eslint-plugin-prettier/recommended";
|
||||
import reactHooks from "eslint-plugin-react-hooks";
|
||||
import reactRefresh from "eslint-plugin-react-refresh";
|
||||
import tseslint from "typescript-eslint";
|
||||
|
||||
const noDirectConsoleMessage =
|
||||
"后端运行时代码禁止直接使用 console.*;请通过注入的 Logger 实例输出日志,配置加载失败前使用 createConsoleFallback()。";
|
||||
|
||||
export default tseslint.config(
|
||||
{
|
||||
ignores: [
|
||||
@@ -14,16 +20,62 @@ export default tseslint.config(
|
||||
".opencode/**",
|
||||
".claude/**",
|
||||
".codex/**",
|
||||
".agents/**",
|
||||
"bun.lock",
|
||||
"data/**",
|
||||
],
|
||||
},
|
||||
js.configs.recommended,
|
||||
...tseslint.configs.recommended,
|
||||
...tseslint.configs.recommendedTypeChecked,
|
||||
...tseslint.configs.stylisticTypeChecked,
|
||||
importPlugin.flatConfigs.recommended,
|
||||
importPlugin.flatConfigs.typescript,
|
||||
perfectionist.configs["recommended-natural"],
|
||||
{
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
projectService: true,
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
},
|
||||
settings: {
|
||||
"import/resolver": { node: true, typescript: true },
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ["**/*.{ts,tsx}"],
|
||||
rules: {
|
||||
"@typescript-eslint/array-type": ["error", { default: "array-simple" }],
|
||||
"@typescript-eslint/consistent-type-assertions": ["error", { assertionStyle: "as" }],
|
||||
"@typescript-eslint/consistent-type-imports": ["error", { prefer: "type-imports" }],
|
||||
"@typescript-eslint/no-empty-function": ["error", { allow: ["private-constructors", "protected-constructors"] }],
|
||||
"@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
|
||||
"@typescript-eslint/only-throw-error": "error",
|
||||
"@typescript-eslint/prefer-nullish-coalescing": "error",
|
||||
"@typescript-eslint/prefer-optional-chain": "error",
|
||||
"import/no-unresolved": ["error", { ignore: ["^bun:"] }],
|
||||
"no-undef": "off",
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ["src/server/**/*.ts"],
|
||||
ignores: ["src/server/logger.ts"],
|
||||
rules: {
|
||||
"no-restricted-syntax": [
|
||||
"error",
|
||||
{
|
||||
message: noDirectConsoleMessage,
|
||||
selector: "MemberExpression[object.name='console']",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ["eslint.config.js"],
|
||||
rules: {
|
||||
"import/no-named-as-default": "off",
|
||||
"import/no-named-as-default-member": "off",
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ["src/web/**/*.{ts,tsx}"],
|
||||
plugins: {
|
||||
@@ -32,7 +84,6 @@ export default tseslint.config(
|
||||
},
|
||||
rules: {
|
||||
...reactHooks.configs.recommended.rules,
|
||||
"react-refresh/only-export-components": ["warn", { allowConstantExport: true }],
|
||||
"no-restricted-imports": [
|
||||
"error",
|
||||
{
|
||||
@@ -53,6 +104,8 @@ export default tseslint.config(
|
||||
],
|
||||
},
|
||||
],
|
||||
"react-refresh/only-export-components": ["warn", { allowConstantExport: true }],
|
||||
},
|
||||
},
|
||||
eslintPluginPrettierRecommended,
|
||||
);
|
||||
|
||||
21
opencode.json
Normal file
21
opencode.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"mcp": {
|
||||
"tdesign-mcp-server": {
|
||||
"enabled": true,
|
||||
"type": "local",
|
||||
"command": ["bunx", "tdesign-mcp-server@latest"]
|
||||
}
|
||||
},
|
||||
"permission": {
|
||||
"bash": {
|
||||
"npm *": "deny",
|
||||
"npx *": "deny",
|
||||
"pnpm *": "deny",
|
||||
"pnpx *": "deny"
|
||||
},
|
||||
"external_directory": {
|
||||
"/tmp/**": "allow"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
schema: spec-driven
|
||||
created: 2026-05-09
|
||||
@@ -1,121 +0,0 @@
|
||||
## Context
|
||||
|
||||
当前项目是 Bun + TypeScript 的最小工程,入口文件只有 `index.ts`,尚未形成前端、后端、共享类型、测试和构建的边界。目标是在保持 Bun 单文件部署优势的同时,引入完整的 Vite + React 前端开发体验。
|
||||
|
||||
业界成熟实践通常不是让后端参与前端 HMR,而是开发期让 Vite dev server 独立承载前端,使用 proxy 将 `/api/*` 转发给后端;生产期由前端构建生成静态资源,再由后端服务这些资源。Go/Rust 生态常用类似 `embed` 的方式把 Vite `dist/` 打入单个二进制,Bun 可通过 `bun build --compile` 与 embedded files/full-stack asset 能力实现同类目标。
|
||||
|
||||
```
|
||||
开发期
|
||||
|
||||
Browser
|
||||
|
|
||||
v
|
||||
Vite dev server :5173
|
||||
|-- React HMR
|
||||
|-- /api/* proxy
|
||||
|
|
||||
v
|
||||
Bun API server :3000
|
||||
|
||||
生产期
|
||||
|
||||
Browser
|
||||
|
|
||||
v
|
||||
gateway-checker executable
|
||||
|-- /api/* Bun API
|
||||
|-- /health 健康检查
|
||||
|-- /assets/* Vite 静态资源
|
||||
|-- /* React SPA fallback
|
||||
```
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
|
||||
- 建立 Vite + React + TypeScript 前端应用结构,保留开发期 HMR。
|
||||
- 建立 Bun 后端服务结构,统一承载 API、健康检查和生产前端资源。
|
||||
- 提供一个可运行 demo,前端页面通过 `/api/demo` 调用后端并展示响应。
|
||||
- 建立前后端共享类型边界,避免重复定义基础接口类型。
|
||||
- 建立生产构建链路,输出单个 Bun standalone executable。
|
||||
- 保持前端可拆离:前端只通过 HTTP `/api/*` 依赖后端,不直接 import 后端实现。
|
||||
- 更新 README,记录结构、命令、测试、构建和运行方式。
|
||||
|
||||
**Non-Goals:**
|
||||
|
||||
- 不引入 SSR、React Server Components、Next.js 或其他全栈框架。
|
||||
- 不引入数据库、认证、用户系统或业务功能。
|
||||
- 不要求一次性完成多平台发布矩阵,只定义可扩展的 target 机制。
|
||||
- 不把运行期配置、日志或可变数据嵌入 executable。
|
||||
- 不在开发期强制单端口访问;开发期可以使用 Vite 端口作为浏览器入口。
|
||||
|
||||
## Decisions
|
||||
|
||||
### Decision: 使用 Vite + React 作为前端开发框架
|
||||
|
||||
采用 Vite + React + TypeScript,开发期由 Vite 提供 HMR,生产期由 `vite build` 输出静态资源。React 适合后续构建复杂管理界面、状态页、图表和交互式检测视图。
|
||||
|
||||
替代方案:使用 Bun 原生 HTML imports。该方案更简单、依赖更少,但前端生态、插件、测试和组件体系弱于 Vite。
|
||||
|
||||
替代方案:使用 Next.js。该方案能力更强,但 SSR/路由/部署模型与“Bun 单 executable”目标存在额外摩擦。
|
||||
|
||||
### Decision: 开发期 Vite proxy `/api/*` 到 Bun 后端
|
||||
|
||||
浏览器开发入口默认使用 Vite dev server,前端请求统一使用相对路径 `/api/*`。Vite 负责把这些请求代理到 Bun 后端服务,从而保持同源开发体验,避免 CORS 和硬编码后端地址。
|
||||
|
||||
替代方案:Bun 后端反向代理 Vite dev server。该方案可以让开发期也统一一个端口,但会增加胶水代码,并且容易干扰 Vite HMR 行为。
|
||||
|
||||
### Decision: 生产期由 Bun 服务 Vite `dist/`
|
||||
|
||||
生产构建先执行 Vite build,再让 Bun 后端服务 `index.html`、`assets/*` 和其他静态资源。非 API、非静态资源路径 fallback 到 `index.html`,用于支持 React SPA 路由刷新。
|
||||
|
||||
替代方案:前端独立部署到 CDN。该方案扩展性更好,但不满足当前“一个可执行程序包含前后端”的目标。
|
||||
|
||||
### Decision: 单 executable 是发布形态,不是代码耦合方式
|
||||
|
||||
前端和后端在源码层保持清晰边界,只通过 HTTP API 和共享类型协作。打包层负责将 Vite 产物嵌入 Bun executable。这样未来若需要 CDN、独立前端部署或多客户端复用 API,不需要重写后端业务代码。
|
||||
|
||||
替代方案:后端源码直接 import 前端源码或前端模块。该方案短期简单,但会模糊运行时边界,增加后续拆离成本。
|
||||
|
||||
### Decision: API 路径统一保留在 `/api/*`
|
||||
|
||||
所有业务 API 使用 `/api/*` 前缀,健康检查使用 `/health`。demo API 使用 `/api/demo`,返回前端可展示的 JSON 响应。生产期路由优先级为 API、健康检查、静态资源、SPA fallback。未命中的 `/api/*` 必须返回 JSON 404,不能 fallback 到前端页面。
|
||||
|
||||
替代方案:API 与页面路径混排。该方案不利于 Vite proxy、生产 fallback 和未来前端独立部署。
|
||||
|
||||
### Decision: 运行配置使用环境变量或 CLI 参数
|
||||
|
||||
host、port、日志级别等运行期配置不嵌入 executable,优先从 CLI 参数或环境变量读取。executable 内只包含只读程序代码和前端静态资源。
|
||||
|
||||
替代方案:构建期写死配置。该方案部署简单,但不同环境需要重新构建,且不利于发布同一个二进制。
|
||||
|
||||
### Decision: demo 是验收基线而不是业务功能
|
||||
|
||||
demo 只证明前端开发、后端 API、生产静态服务和 executable 打包链路能跑通。页面应展示来自 `/api/demo` 的后端响应,并在 README 中记录开发期访问方式和 executable 运行后的验证方式。
|
||||
|
||||
替代方案:只搭建空白 React 页面和空 API。该方案能证明结构存在,但不能证明前后端开发和打包后联通链路真实可用。
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- [Risk] Bun standalone executable 与 Vite `dist/` 嵌入方式相对 Go `embed` 更年轻。→ Mitigation: 先实现最小静态资源嵌入和端到端构建测试,再扩展多平台构建。
|
||||
- [Risk] Vite hashed assets、SPA fallback 和 Bun 静态路由可能出现路径映射问题。→ Mitigation: 对 `/`, `/assets/*`, 前端路由刷新和 `/api/*` 404 编写测试或构建后验证。
|
||||
- [Risk] 依赖数量会明显增加。→ Mitigation: 初期只引入 Vite、React、React DOM 和必要类型,不引入 UI 组件库、状态管理或路由库,除非后续需求明确。
|
||||
- [Risk] 单 executable 会把前端资源大小计入二进制。→ Mitigation: 保留 Vite 产物压缩能力,后续可按需启用分离部署或 CDN。
|
||||
- [Risk] 开发期前端和后端是两个进程,启动命令更复杂。→ Mitigation: 提供 `dev:web`、`dev:server` 和 `dev` 聚合脚本,并在 README 中说明。
|
||||
|
||||
## Migration Plan
|
||||
|
||||
1. 保留当前最小入口语义,重构为新的 server 入口。
|
||||
2. 新增 web、server、shared 和 scripts 目录结构。
|
||||
3. 引入最小 Vite + React 依赖并配置开发代理。
|
||||
4. 实现 Bun API、健康检查和生产静态资源服务。
|
||||
5. 增加测试和构建验证。
|
||||
6. 更新 README 作为项目结构和命令的权威说明。
|
||||
|
||||
回滚策略:如果 Vite 集成阻塞,可以保留 Bun 后端结构,移除 web 目录和前端构建脚本,退回 Bun 原生 HTML imports 或后端-only 形态。
|
||||
|
||||
## Open Questions
|
||||
|
||||
- 前端是否需要路由库,例如 React Router,还是先保持单页面组件状态?
|
||||
- 是否需要 UI 组件库,例如 TDesign、shadcn/ui 或保持纯 CSS?
|
||||
- 生产 executable 首期目标平台是当前 macOS,还是同时需要 Linux x64/arm64?
|
||||
@@ -1,33 +0,0 @@
|
||||
## Why
|
||||
|
||||
当前项目只有 Bun + TypeScript 的最小入口,尚不能支撑完整的前后端服务开发。引入 Vite + React 开发体验,并保持 Bun 后端可打包为单个可执行程序,可以同时满足本地快速迭代、前后端同源集成和简单部署的目标。
|
||||
|
||||
## What Changes
|
||||
|
||||
- 新增基于 Vite + React + TypeScript 的前端应用开发能力,开发期保留 Vite HMR。
|
||||
- 新增 Bun 后端服务作为 API 与生产静态资源承载层,API 统一位于 `/api/*`。
|
||||
- 新增开发期前端代理后端 API 的同源调用约定,避免前端写死后端地址。
|
||||
- 新增生产期构建链路:先构建前端静态资源,再将后端与前端产物打包为单个 Bun standalone executable。
|
||||
- 新增可验收 demo:前端页面调用后端 API 并展示响应,开发期和 executable 运行期都能验证前后端联通。
|
||||
- 新增 SPA fallback 行为:生产环境非 API 前端路由返回前端入口页面。
|
||||
- 更新 README,记录项目结构、开发命令、测试命令、构建命令与部署方式。
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
- `frontend-development-workflow`: 约定 Vite + React 前端开发、API proxy、共享类型和本地开发命令。
|
||||
- `fullstack-app-runtime`: 约定 Bun 服务在运行期同时提供 API、健康检查、静态资源和 SPA fallback。
|
||||
- `single-executable-packaging`: 约定生产构建将 Vite 前端产物和 Bun 后端服务打包为单个可执行程序。
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
无。当前 `openspec/specs/` 为空,没有既有 capability 需要修改。
|
||||
|
||||
## Impact
|
||||
|
||||
- 代码结构将从单入口 `index.ts` 扩展为前端、后端、共享类型和构建脚本的模块化结构。
|
||||
- 依赖将新增 Vite、React、React DOM 及相关 TypeScript 类型;测试依赖按实现方案最小化引入。
|
||||
- 开发脚本将覆盖前端 dev server、后端 dev server、并行开发、测试和生产构建。
|
||||
- 生产产物将从直接运行 TypeScript 入口变为 `dist/` 下的平台相关 executable。
|
||||
- demo 验收将覆盖开发期联调和生产 executable 运行后的前端页面、API 与健康检查。
|
||||
- README 需要同步说明模块结构、API 路径约定、构建产物和运行参数。
|
||||
@@ -1,63 +0,0 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Vite React 开发服务器
|
||||
系统 SHALL 提供基于 Vite + React + TypeScript 的前端开发工作流,并支持热模块替换。
|
||||
|
||||
#### Scenario: 启动前端开发服务器
|
||||
- **WHEN** 开发者启动前端开发命令
|
||||
- **THEN** 前端 SHALL 由 Vite 提供服务,并启用 React 热模块替换
|
||||
|
||||
#### Scenario: 构建前端静态资源
|
||||
- **WHEN** 开发者运行前端生产构建命令
|
||||
- **THEN** 系统 SHALL 产出可由 Bun 后端服务的前端静态资源
|
||||
|
||||
### Requirement: 前端开发期 API 代理
|
||||
前端开发服务器 SHALL 在本地开发期间将 `/api/*` 请求代理到 Bun 后端服务。
|
||||
|
||||
#### Scenario: 前端开发期调用 API
|
||||
- **WHEN** 浏览器从 Vite 开发源请求 `/api/demo`
|
||||
- **THEN** Vite SHALL 将请求转发到 Bun 后端服务,且不需要浏览器 CORS 配置
|
||||
|
||||
#### Scenario: 开发期访问非 API 前端路由
|
||||
- **WHEN** 浏览器从 Vite 开发源请求非 API 前端路由
|
||||
- **THEN** Vite SHALL 将该请求作为前端应用流量处理,而不是转发到后端
|
||||
|
||||
### Requirement: 前端使用相对 API 路径
|
||||
除非有文档化的部署配置覆盖该行为,前端代码 MUST 通过相对 `/api/*` URL 调用后端 API。
|
||||
|
||||
#### Scenario: 前端获取后端数据
|
||||
- **WHEN** 前端代码调用后端 API
|
||||
- **THEN** 请求 URL 默认 MUST 使用相对 `/api/*` 路径
|
||||
|
||||
#### Scenario: 运行环境变化
|
||||
- **WHEN** host 或 port 在开发环境和生产环境之间变化
|
||||
- **THEN** 前端 API 调用 SHALL 无需修改源码即可继续工作
|
||||
|
||||
### Requirement: 端到端开发 demo
|
||||
项目 SHALL 提供一个可见的开发 demo,用于证明 React 前端可以通过 Vite 代理调用 Bun 后端。
|
||||
|
||||
#### Scenario: Demo 页面展示后端响应
|
||||
- **WHEN** 开发者启动文档化的开发命令并打开前端 URL
|
||||
- **THEN** 页面 SHALL 调用 `/api/demo` 并展示 Bun 后端返回的数据
|
||||
|
||||
#### Scenario: 开发期后端不可用
|
||||
- **WHEN** 前端 demo 无法访问 `/api/demo`
|
||||
- **THEN** 页面 SHALL 展示清晰的错误状态,而不是静默显示为成功
|
||||
|
||||
### Requirement: 集成开发命令
|
||||
项目 SHALL 提供一个文档化命令,用于在 demo 开发期间同时运行前端和后端。
|
||||
|
||||
#### Scenario: 启动全栈开发
|
||||
- **WHEN** 开发者运行文档化的全栈开发命令
|
||||
- **THEN** 系统 SHALL 启动 Vite 前端开发服务器和 `/api/demo` 所需的 Bun 后端服务器
|
||||
|
||||
### Requirement: 共享 TypeScript 契约
|
||||
项目 SHALL 为前端和后端共同使用的请求与响应类型提供共享 TypeScript 边界。
|
||||
|
||||
#### Scenario: 定义 API 响应结构
|
||||
- **WHEN** 前端和后端都需要某个 API 响应类型
|
||||
- **THEN** 该类型 SHALL 定义在 shared 模块中,而不是在两端重复定义
|
||||
|
||||
#### Scenario: 前端导入共享类型
|
||||
- **WHEN** 前端代码导入共享 API 类型
|
||||
- **THEN** 该导入 SHALL 不要求将后端运行时实现打包进前端
|
||||
@@ -1,67 +0,0 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Bun HTTP 运行时
|
||||
系统 SHALL 运行一个 Bun HTTP server,由单个进程提供后端 API、健康检查、生产静态资源和 SPA fallback 行为。
|
||||
|
||||
#### Scenario: 启动运行时服务器
|
||||
- **WHEN** server 进程成功启动
|
||||
- **THEN** 它 SHALL 监听配置的 host 和 port,并记录实际 server URL
|
||||
|
||||
#### Scenario: 提供运行时配置
|
||||
- **WHEN** 通过支持的运行时配置提供 host 或 port
|
||||
- **THEN** server SHALL 使用该值,且不需要重新构建
|
||||
|
||||
### Requirement: API 路由命名空间
|
||||
系统 MUST 将 `/api/*` 保留给后端 API 路由。
|
||||
|
||||
#### Scenario: API 路由匹配
|
||||
- **WHEN** 请求匹配已注册的 `/api/*` 路由
|
||||
- **THEN** Bun server SHALL 返回 API handler 的响应
|
||||
|
||||
#### Scenario: API 路由未命中
|
||||
- **WHEN** 请求访问未注册的 `/api/*` 路由
|
||||
- **THEN** Bun server MUST 返回 JSON 404 响应,而不是前端 HTML 文档
|
||||
|
||||
### Requirement: Demo API 端点
|
||||
系统 SHALL 暴露 `/api/demo` 作为稳定 demo 端点,用于证明前后端集成可用。
|
||||
|
||||
#### Scenario: Demo API 成功响应
|
||||
- **WHEN** 客户端请求 `/api/demo`
|
||||
- **THEN** Bun server SHALL 返回包含可读 message 和 runtime metadata 的 JSON 响应
|
||||
|
||||
#### Scenario: Demo API 内容类型
|
||||
- **WHEN** 客户端请求 `/api/demo`
|
||||
- **THEN** Bun server SHALL 返回 JSON content type 的响应
|
||||
|
||||
### Requirement: 健康检查端点
|
||||
系统 SHALL 在前端 SPA fallback 之外暴露健康检查端点。
|
||||
|
||||
#### Scenario: 健康检查成功
|
||||
- **WHEN** 客户端请求 `/health`
|
||||
- **THEN** Bun server SHALL 返回成功的、机器可读的健康检查响应
|
||||
|
||||
### Requirement: 生产静态资源服务
|
||||
系统 SHALL 在生产模式下由 Bun runtime 服务 Vite 生产资源。
|
||||
|
||||
#### Scenario: 请求构建后的资源
|
||||
- **WHEN** 客户端请求构建后的前端资源,例如 `/assets/app.js`
|
||||
- **THEN** Bun server SHALL 返回该资源并带有适当的 content type
|
||||
|
||||
#### Scenario: 请求前端根路径
|
||||
- **WHEN** 客户端请求 `/`
|
||||
- **THEN** Bun server SHALL 返回前端入口 HTML 文档
|
||||
|
||||
#### Scenario: 生产 demo 页面调用 API
|
||||
- **WHEN** 客户端从生产 Bun runtime 打开前端页面
|
||||
- **THEN** demo 页面 SHALL 能够从同源调用 `/api/demo` 并展示后端响应
|
||||
|
||||
### Requirement: SPA fallback 行为
|
||||
系统 SHALL 在生产环境中为非 API、非静态资源的前端路由返回前端入口 HTML 文档。
|
||||
|
||||
#### Scenario: 刷新前端路由
|
||||
- **WHEN** 客户端请求前端路由,例如 `/dashboard`
|
||||
- **THEN** Bun server SHALL 返回前端入口 HTML 文档
|
||||
|
||||
#### Scenario: 保留 API 错误语义
|
||||
- **WHEN** 客户端请求未知的 `/api/*` 路由
|
||||
- **THEN** Bun server MUST NOT 返回前端入口 HTML 文档
|
||||
@@ -1,49 +0,0 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 生产构建顺序
|
||||
生产构建 MUST 在编译 Bun 后端 executable 之前先构建 Vite 前端。
|
||||
|
||||
#### Scenario: 运行生产构建
|
||||
- **WHEN** 开发者运行生产构建命令
|
||||
- **THEN** 系统 MUST 在调用 Bun standalone executable 编译之前生成前端静态资源
|
||||
|
||||
#### Scenario: 前端构建失败
|
||||
- **WHEN** 前端生产构建失败
|
||||
- **THEN** 系统 MUST 停止生产构建,且不能输出 stale executable
|
||||
|
||||
### Requirement: 单 executable 输出
|
||||
生产构建 SHALL 输出一个 standalone executable,其中包含 Bun 后端、必要 server 依赖和构建后的前端资源。
|
||||
|
||||
#### Scenario: 在目标机器运行 executable
|
||||
- **WHEN** 生成的 executable 在兼容目标平台上运行
|
||||
- **THEN** 它 SHALL 启动全栈应用,且不要求目标机器安装 Node.js、Bun、Vite 或 `node_modules`
|
||||
|
||||
#### Scenario: 服务嵌入的前端
|
||||
- **WHEN** executable 收到前端根路径请求
|
||||
- **THEN** 它 SHALL 从 executable 内包含的资源服务前端,且不需要外部 `dist/` 目录
|
||||
|
||||
#### Scenario: 服务嵌入 demo API 和页面
|
||||
- **WHEN** 生成的 executable 启动,且浏览器打开前端根路径
|
||||
- **THEN** 页面 SHALL 展示同一个 executable 进程中 `/api/demo` 返回的数据
|
||||
|
||||
### Requirement: 外部运行时配置
|
||||
executable MUST 将环境相关运行时配置保留在嵌入的前端和 server bundle 之外。
|
||||
|
||||
#### Scenario: 修改监听端口
|
||||
- **WHEN** 操作者修改受支持的 port 配置
|
||||
- **THEN** 同一个 executable SHALL 在不重新构建的情况下监听新端口
|
||||
|
||||
#### Scenario: 缺少可选配置
|
||||
- **WHEN** 可选运行时配置被省略
|
||||
- **THEN** executable SHALL 使用文档化的默认值
|
||||
|
||||
### Requirement: 构建验证
|
||||
项目 SHALL 提供验证,证明生产 executable 可以服务 API、健康检查、静态资源和 SPA fallback 路由。
|
||||
|
||||
#### Scenario: 验证 executable 路由
|
||||
- **WHEN** 构建验证针对生成的 executable 运行
|
||||
- **THEN** 它 SHALL 检查 `/api/demo`、`/health`、前端根路径、静态资源和前端 fallback 请求
|
||||
|
||||
#### Scenario: 验证失败
|
||||
- **WHEN** 任一代表性生产路由检查失败
|
||||
- **THEN** 验证 SHALL 使构建或测试命令失败
|
||||
@@ -1,41 +0,0 @@
|
||||
## 1. 项目结构与依赖
|
||||
|
||||
- [x] 1.1 创建 `src/server`、`src/web`、`src/shared`、`scripts` 和测试目录结构
|
||||
- [x] 1.2 调整 `package.json` 脚本以覆盖前端开发、后端开发、并行开发、测试和生产构建
|
||||
- [x] 1.3 引入 Vite、React、React DOM 和必要 TypeScript 类型依赖
|
||||
- [x] 1.4 创建或更新 README 记录项目结构、开发规范和命令
|
||||
|
||||
## 2. 前端开发工作流
|
||||
|
||||
- [x] 2.1 创建 Vite + React + TypeScript 前端入口和基础页面
|
||||
- [x] 2.2 配置 Vite 开发服务器将 `/api/*` 代理到 Bun 后端
|
||||
- [x] 2.3 实现前端 demo 页面调用相对路径 `/api/demo` 并展示成功和失败状态
|
||||
- [x] 2.4 建立 `src/shared` 共享类型并确保前端不引入后端运行时实现
|
||||
- [x] 2.5 提供一个 documented fullstack dev command 同时启动 Vite 前端和 Bun 后端
|
||||
|
||||
## 3. Bun 后端运行时
|
||||
|
||||
- [x] 3.1 创建 Bun server 入口并支持 host 和 port 运行期配置
|
||||
- [x] 3.2 实现 `/health` 健康检查响应
|
||||
- [x] 3.3 实现 `/api/demo` JSON 路由并返回前端可展示的 message 和 runtime metadata
|
||||
- [x] 3.4 实现未命中 `/api/*` 路由返回 JSON 404 的行为
|
||||
- [x] 3.5 实现生产环境静态资源服务和 SPA fallback 行为
|
||||
|
||||
## 4. 单可执行程序构建
|
||||
|
||||
- [x] 4.1 创建生产构建脚本,确保先执行 Vite build 再执行 Bun compile
|
||||
- [x] 4.2 将 Vite `dist/` 产物嵌入 Bun executable,运行时不依赖外部 `dist/` 目录
|
||||
- [x] 4.3 配置 executable 输出路径和当前平台默认构建目标
|
||||
- [x] 4.4 确保 executable 运行不依赖本机 Node.js、Bun、Vite 或 `node_modules`
|
||||
|
||||
## 5. 测试与验证
|
||||
|
||||
- [x] 5.1 为 `/api/demo`、`/health`、API 404 和 SPA fallback 增加测试
|
||||
- [x] 5.2 为生产构建脚本增加失败中断或防止 stale executable 的验证
|
||||
- [x] 5.3 增加构建后 executable smoke test 覆盖 `/api/demo`、健康检查、静态资源、前端 fallback 和 demo 页面内容
|
||||
- [x] 5.4 运行完整测试和生产构建,确认所有任务满足 specs
|
||||
|
||||
## 6. 文档收尾
|
||||
|
||||
- [x] 6.1 更新 README 中的运行参数、构建产物、部署方式和已知限制
|
||||
- [x] 6.2 在 README 中记录前端可拆离原则、`/api/*` 路径约定和 demo 验证步骤
|
||||
@@ -1,2 +0,0 @@
|
||||
schema: spec-driven
|
||||
created: 2026-05-09
|
||||
@@ -1,52 +0,0 @@
|
||||
## Context
|
||||
|
||||
当前项目根目录存在三项冗余:
|
||||
|
||||
1. `index.ts` — 仅包含 `import "./src/server/dev.ts"`,与 `package.json` 的 `"start"` 脚本功能完全重复,无任何其他文件或脚本引用它
|
||||
2. `"module": "src/server/dev.ts"` — `private: true` 项目不会被发布,ESM 入口字段无消费者;且指向一个启动服务器的副作用文件本身就不合理
|
||||
3. `.build/` 目录 — 由 `scripts/build.ts` 在每次构建时生成,包含 `server-entry.ts` 和 `static-assets.ts` 两个中间文件。构建完成后这些文件残留在磁盘上,虽已被 `.gitignore` 忽略但不必要地占用空间
|
||||
|
||||
项目构建流程为:`vite build` → 生成 `.build/` 中间文件 → `Bun.build()` 编译为单可执行文件 → 输出到 `dist/gateway-checker`。
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- 移除无实际作用的文件和配置,减少项目结构噪音
|
||||
- 构建成功后自动清理中间产物,保持项目目录整洁
|
||||
- 构建失败时保留中间产物以便排查问题
|
||||
|
||||
**Non-Goals:**
|
||||
- 不改变构建产物本身(输出路径、可执行文件行为不变)
|
||||
- 不引入新的构建步骤或依赖
|
||||
- 不调整 RuntimeMode 或开发/生产模式的区分逻辑
|
||||
|
||||
## Decisions
|
||||
|
||||
### Decision 1: 直接删除 `index.ts`
|
||||
|
||||
**选择**:删除文件
|
||||
**备选**:保留但添加注释说明用途
|
||||
**理由**:无任何脚本、文件或构建流程引用它,保留只会增加困惑。`package.json` 的 `"start"` 脚本已直接指向 `src/server/dev.ts`。
|
||||
|
||||
### Decision 2: 移除 `"module"` 字段
|
||||
|
||||
**选择**:从 `package.json` 中删除 `"module"` 字段
|
||||
**备选**:改为 `"main"` 字段
|
||||
**理由**:`private: true` 意味着不会被 npm 发布,任何入口字段都没有消费者。改为 `"main"` 同样无意义,因为这不是一个库。完全移除最简洁。
|
||||
|
||||
### Decision 3: 构建成功后清理 `.build/`
|
||||
|
||||
**选择**:在 `Bun.build()` 成功后调用 `await rm(buildDir, { recursive: true, force: true })`
|
||||
**备选**:
|
||||
- 始终保留 `.build/`(当前行为)
|
||||
- 使用临时目录(`os.tmpdir()`)
|
||||
|
||||
**理由**:`build.ts` 开头已导入 `rm` 且已在构建开始时执行清理,只需在成功路径末尾复用同一行代码。构建失败时 `.build/` 自然保留,兼顾排查需求。不使用临时目录是因为 `Bun.build()` 的 `import ... with { type: "file" }` 需要相对路径引用 `dist/web/` 下的实际文件,临时目录增加路径复杂度。
|
||||
|
||||
具体修改位置:`scripts/build.ts` 第 53 行 `console.log(...)` 之后。
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- **[极低风险] 删除 `index.ts` 影响外部工具**:如果 CI/CD 或其他自动化流程通过 `bun index.ts` 启动,会中断。→ 检查确认无此类用法。`package.json` 中 `"start": "bun src/server/dev.ts"` 是标准入口。
|
||||
- **[极低风险] 移除 `module` 字段影响 IDE 解析**:某些 IDE 可能依赖 `module` 字段进行跳转。→ 实际指向 `dev.ts`,对代码导航无帮助。
|
||||
- **[低风险] 清理 `.build/` 后无法排查偶现构建问题**:→ 构建失败时 `.build/` 保留,只有成功时才清理,排查路径完整。
|
||||
@@ -1,25 +0,0 @@
|
||||
## Why
|
||||
|
||||
项目根目录存在冗余文件和无效配置:`index.ts` 与 `start` 脚本功能完全重复,`package.json` 的 `module` 字段在 `private` 项目中无实际作用,`.build/` 中间产物在构建成功后未清理导致磁盘残留。这些虽不影响运行,但增加了维护负担和项目结构的困惑。
|
||||
|
||||
## What Changes
|
||||
|
||||
- 删除根目录 `index.ts`,它是 `src/server/dev.ts` 的无意义包装,无任何脚本或文件引用它
|
||||
- 移除 `package.json` 中的 `"module": "src/server/dev.ts"` 字段,`private: true` 的应用项目不需要此字段,且指向副作用文件作为 ESM 入口本身就不合理
|
||||
- 在 `scripts/build.ts` 中,`Bun.build()` 成功后自动清理 `.build/` 目录,构建失败时保留以便排查
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
(无新增能力)
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
- `single-executable-packaging`: 构建流程新增成功后清理 `.build/` 中间产物目录的步骤
|
||||
|
||||
## Impact
|
||||
|
||||
- 删除文件:`index.ts`
|
||||
- 修改文件:`package.json`(移除 1 行)、`scripts/build.ts`(新增 1 行)
|
||||
- 不影响任何现有功能、API 或开发工作流
|
||||
@@ -1,24 +0,0 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: 单 executable 输出
|
||||
生产构建 SHALL 输出一个 standalone executable,其中包含 Bun 后端、必要 server 依赖和构建后的前端资源。构建成功后 SHALL 自动清理中间产物目录(`.build/`),构建失败时 SHALL 保留中间产物以便排查。
|
||||
|
||||
#### Scenario: 在目标机器运行 executable
|
||||
- **WHEN** 生成的 executable 在兼容目标平台上运行
|
||||
- **THEN** 它 SHALL 启动全栈应用,且不要求目标机器安装 Node.js、Bun、Vite 或 `node_modules`
|
||||
|
||||
#### Scenario: 服务嵌入的前端
|
||||
- **WHEN** executable 收到前端根路径请求
|
||||
- **THEN** 它 SHALL 从 executable 内包含的资源服务前端,且不需要外部 `dist/` 目录
|
||||
|
||||
#### Scenario: 服务嵌入 demo API 和页面
|
||||
- **WHEN** 生成的 executable 启动,且浏览器打开前端根路径
|
||||
- **THEN** 页面 SHALL 展示同一个 executable 进程中 `/api/demo` 返回的数据
|
||||
|
||||
#### Scenario: 构建成功后清理中间产物
|
||||
- **WHEN** 生产构建成功完成并输出 executable
|
||||
- **THEN** 系统 SHALL 自动删除 `.build/` 目录及其所有内容
|
||||
|
||||
#### Scenario: 构建失败时保留中间产物
|
||||
- **WHEN** 生产构建在任意步骤失败(前端构建、中间产物生成、Bun 编译)
|
||||
- **THEN** `.build/` 目录 SHALL 保留在磁盘上以供排查
|
||||
@@ -1,13 +0,0 @@
|
||||
## 1. 移除冗余文件和配置
|
||||
|
||||
- [x] 1.1 删除根目录 `index.ts` 文件
|
||||
- [x] 1.2 从 `package.json` 中移除 `"module": "src/server/dev.ts"` 字段
|
||||
|
||||
## 2. 构建后清理中间产物
|
||||
|
||||
- [x] 2.1 在 `scripts/build.ts` 的 `Bun.build()` 成功后添加清理 `.build/` 目录的代码
|
||||
|
||||
## 3. 验证
|
||||
|
||||
- [x] 3.1 运行 `bun run check` 确认类型检查、lint、格式化、测试全部通过
|
||||
- [x] 3.2 运行 `bun run verify` 确认完整构建和 smoke test 通过
|
||||
@@ -1,2 +0,0 @@
|
||||
schema: spec-driven
|
||||
created: 2026-05-09
|
||||
@@ -1,90 +0,0 @@
|
||||
## Context
|
||||
|
||||
当前项目是 Bun + TypeScript 的前后端一体化 demo:开发期由 Vite React 提供前端 HMR,Bun 提供 `/api/*` 和 `/health`;生产期先构建 Vite 静态资源,再通过 Bun file import 将资源和后端编译为单 executable。
|
||||
|
||||
现有能力已经通过 `typecheck`、单元测试和 executable smoke test 验证,但真实业务开发尚未开始。此变更聚焦平台基础设施硬化,目标是在业务 API、数据模型和前端业务页面扩展之前,先把开发联调、代码质量、格式一致性、HTTP 契约和生产验证闭环固化下来。
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
|
||||
- 引入 ESLint 审查代码质量、React Hooks 规则和前后端边界。
|
||||
- 引入 Prettier 统一代码风格,但不格式化 `openspec/`,避免影响 OpenSpec 文档和 tasks 一行一个任务的规则。
|
||||
- 提供快速 `check` 和完整 `verify` 两层验证命令。
|
||||
- 让开发期 Vite proxy 目标端口和 Bun server 监听端口保持一致。
|
||||
- 补齐 HTTP method、JSON 404/405、静态资源缓存和低风险安全头的运行时契约。
|
||||
- 增强生产 executable smoke test,确保验证的是当前源码构建出的生产产物。
|
||||
- 同步 README,使文档描述与脚本、构建中间产物和验证流程一致。
|
||||
|
||||
**Non-Goals:**
|
||||
|
||||
- 不开发 gateway checker 真实业务能力。
|
||||
- 不引入数据库、持久化、认证、React Router 或 UI 组件库。
|
||||
- 不新增 CI 配置;本次仅提供本地 `check` 和 `verify` 命令,CI 接入留给后续仓库托管策略。
|
||||
- 不引入 CSP;本次只加入低风险安全响应头,避免提前约束未来前端资源策略。
|
||||
- 不做大规模目录重构或业务框架抽象。
|
||||
|
||||
## Decisions
|
||||
|
||||
### ESLint 和 Prettier 分工
|
||||
|
||||
ESLint 只承担质量审查和边界约束,不承担缩进、换行、引号等格式职责。Prettier 专门负责代码风格,避免 ESLint stylistic 规则和格式化器重复工作。
|
||||
|
||||
备选方案是只引入 ESLint 并启用 stylistic 规则,但后续维护成本更高,且容易和编辑器格式化行为冲突。另一个备选方案是只引入 Prettier,但它无法检查 React Hooks、未处理 Promise 或前端误导入后端实现等质量问题。
|
||||
|
||||
本次采用的最小依赖集合为 `eslint`、`@eslint/js`、`typescript-eslint`、`eslint-plugin-react-hooks`、`eslint-plugin-react-refresh` 和 `prettier`。暂不引入 `eslint-config-prettier`,除非实现阶段引入会与 Prettier 冲突的 ESLint preset 或 stylistic 规则。
|
||||
|
||||
### 验证命令分层
|
||||
|
||||
新增 `check` 和 `verify` 两层命令:
|
||||
|
||||
```text
|
||||
check
|
||||
├─ typecheck
|
||||
├─ lint
|
||||
├─ format:check
|
||||
└─ test
|
||||
|
||||
verify
|
||||
├─ check
|
||||
├─ build
|
||||
└─ test:smoke
|
||||
```
|
||||
|
||||
`check` 面向日常开发,反馈快;`verify` 面向提交前或发布前验证,包含生产构建和 executable smoke test。备选方案是只提供 `verify`,但每次都构建 executable 会降低日常迭代速度。
|
||||
|
||||
### Prettier 忽略范围
|
||||
|
||||
Prettier SHALL 忽略 `openspec/`、`dist/`、`.build/`、`node_modules/`、`bun.lock` 和临时构建产物。`openspec/` 排除是显式决策,因为 OpenSpec tasks 要求一行一个任务,Markdown 自动折行可能破坏审阅体验和规则遵循。
|
||||
|
||||
### 开发期端口配置
|
||||
|
||||
文档化的全栈开发命令以 `PORT` 作为后端端口的唯一对外配置。Vite proxy 使用的 `BACKEND_PORT` 应由开发脚本从 `PORT` 派生,或者明确作为内部变量,避免用户只改 `BACKEND_PORT` 导致 proxy 与 server 分叉。直接运行 Bun server 或生产 executable 时仍可继续使用现有 CLI 参数覆盖 host 和 port。
|
||||
|
||||
### 运行配置校验
|
||||
|
||||
运行配置继续保持 CLI 参数优先于环境变量,缺省时使用 README 文档化默认值。端口配置必须拒绝非整数、小于 0 或大于 65535 的值,并通过单元测试覆盖默认值、优先级、非法输入和边界值,避免开发期和生产期配置行为分叉。
|
||||
|
||||
### HTTP method 和错误契约
|
||||
|
||||
现有 demo 端点按路径匹配,后续业务扩展前需要先固化 method 语义。`/health` 和 `/api/demo` 以 `GET` 为主,并支持 `HEAD` 返回相同状态和 headers 但无响应体;不支持的 method 返回 JSON 405,并带 `Allow` header。未知 `/api/*` 继续返回 JSON 404,不能落入前端 HTML fallback。
|
||||
|
||||
### 生产响应头策略
|
||||
|
||||
生产 HTML 使用 `Cache-Control: no-cache`,Vite hash 静态资源使用长缓存 `public, max-age=31536000, immutable`。所有生产 HTTP 响应增加低风险安全头,例如 `X-Content-Type-Options: nosniff` 和 `Referrer-Policy`。CSP 暂不纳入本次变更,避免后续业务页面接入外部资源时产生过早约束。
|
||||
|
||||
### 构建确定性
|
||||
|
||||
生成 `.build/static-assets.ts` 时,嵌入资源列表应按稳定顺序输出。这样可以减少重复构建时的无意义差异,也方便 smoke test 和后续审查定位问题。
|
||||
|
||||
### Smoke test 增强
|
||||
|
||||
`test:smoke` SHALL 针对当前构建出的 executable 验证生产行为,包括 `/health`、`/api/demo`、未知 API、根 HTML、SPA fallback、静态资源、未知静态资源、生产 runtime mode、缓存头和低风险安全头。`verify` 必须先执行 build 再 smoke,避免验证旧产物。
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- 新增 ESLint 和 Prettier 会增加开发依赖与初次配置成本 → 采用最小依赖集合,只启用与当前项目直接相关的规则。
|
||||
- 现有代码可能被 Prettier 产生格式化改动 → 本次作为平台硬化变更集中处理,后续业务变更减少格式噪音。
|
||||
- 405 和 HEAD 行为会让 HTTP handler 稍复杂 → 在业务 API 扩展前处理,避免未来每个端点重复补语义。
|
||||
- 安全头不包含 CSP,安全强度有限 → 先采用低风险头,CSP 在前端资源来源稳定后单独设计。
|
||||
- `verify` 包含构建和 smoke,运行更慢 → 保留快速 `check` 作为日常反馈通道。
|
||||
@@ -1,35 +0,0 @@
|
||||
## Why
|
||||
|
||||
当前项目已经具备 Bun 后端、Vite React 前端、生产静态资源嵌入和单 executable 打包链路,但仍处于 demo 基础设施阶段。真实业务开发开始前,需要先收紧前后端开发、运行时 HTTP 契约、代码质量门禁和生产验证闭环,避免后续业务变更建立在不稳定或不可重复验证的基础上。
|
||||
|
||||
## What Changes
|
||||
|
||||
- 增加 ESLint 作为代码质量、React Hooks 和前后端边界审查工具。
|
||||
- 增加 Prettier 作为代码风格格式化工具,并排除 `openspec/`、构建产物和依赖目录。
|
||||
- 增加快速 `check` 命令和完整 `verify` 命令,其中 `verify` SHALL 覆盖类型检查、lint、格式检查、单元测试、生产构建和 executable smoke test。
|
||||
- 明确开发期 Bun server 与 Vite proxy 的端口配置一致性,避免前端代理端口和后端监听端口分叉。
|
||||
- 补充运行配置校验要求,包括默认值、CLI 与环境变量优先级、无效端口拒绝和端口边界行为。
|
||||
- 强化 HTTP 运行时契约,包括 method 语义、JSON 404/405 错误、静态资源缓存策略和低风险安全响应头。
|
||||
- 强化单 executable 构建验证,包括确定性资源生成、生产模式验证、静态资源响应头、未知 API、未知 asset 和 SPA fallback 检查。
|
||||
- 修正 OpenSpec `tasks` artifact 规则键名,避免 CLI 状态命令产生无效规则警告。
|
||||
- 同步更新 README,说明质量门禁、验证命令、构建中间产物和运行配置边界。
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
- `code-quality-gates`: 定义 ESLint、Prettier、`check` 和 `verify` 的质量门禁行为要求。
|
||||
|
||||
### Modified Capabilities
|
||||
- `fullstack-app-runtime`: 补充运行配置校验、HTTP method、JSON 错误、静态资源缓存和低风险安全响应头等运行时契约。
|
||||
- `frontend-development-workflow`: 补充开发期 Bun server 与 Vite proxy 配置一致性的要求。
|
||||
- `single-executable-packaging`: 补充确定性构建、完整验证命令和 smoke 覆盖增强要求。
|
||||
|
||||
## Impact
|
||||
|
||||
- 影响 `package.json` scripts 和开发依赖,新增 lint、format、check、verify 相关命令。
|
||||
- 影响 ESLint、Prettier 配置文件和忽略规则。
|
||||
- 影响 `src/server/*` 的 HTTP method、错误响应、静态资源响应头和配置处理。
|
||||
- 影响 `scripts/build.ts`、`scripts/dev.ts`、`scripts/smoke.ts` 的构建、开发联调和验证逻辑。
|
||||
- 影响 `tests/`,需要补充配置解析、HTTP 语义、静态资源响应和验证行为相关测试。
|
||||
- 影响 `openspec/config.yaml`,修正 `tasks` artifact 规则键名。
|
||||
- 影响 `README.md`,需要同步开发命令、验证命令、构建流程和边界说明。
|
||||
@@ -1,53 +0,0 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: ESLint 代码质量门禁
|
||||
项目 SHALL 提供 ESLint 代码质量门禁,用于审查 TypeScript、React 前端、脚本和测试代码中的质量问题。
|
||||
|
||||
#### Scenario: 运行 lint 检查
|
||||
- **WHEN** 开发者运行文档化的 lint 命令
|
||||
- **THEN** 系统 SHALL 使用 ESLint 检查项目源码、脚本和测试代码,并在发现违规时以非零状态退出
|
||||
|
||||
#### Scenario: 检查 React Hooks 规则
|
||||
- **WHEN** 前端 React 代码违反 Hooks 调用规则
|
||||
- **THEN** lint 命令 MUST 失败并报告对应违规
|
||||
|
||||
#### Scenario: 保护前后端边界
|
||||
- **WHEN** `src/web` 前端代码导入 `src/server` 后端运行时实现
|
||||
- **THEN** lint 命令 MUST 失败并报告前后端边界违规
|
||||
|
||||
### Requirement: Prettier 代码格式门禁
|
||||
项目 SHALL 提供 Prettier 格式化和格式检查命令,用于统一代码风格。
|
||||
|
||||
#### Scenario: 检查代码格式
|
||||
- **WHEN** 开发者运行文档化的格式检查命令
|
||||
- **THEN** 系统 SHALL 使用 Prettier 检查受管理文件,并在发现未格式化文件时以非零状态退出
|
||||
|
||||
#### Scenario: 自动格式化代码
|
||||
- **WHEN** 开发者运行文档化的格式化命令
|
||||
- **THEN** 系统 SHALL 使用 Prettier 重写受管理文件的格式
|
||||
|
||||
#### Scenario: 排除 OpenSpec 文档和生成产物
|
||||
- **WHEN** Prettier 格式化或格式检查运行
|
||||
- **THEN** 系统 MUST 排除 `openspec/`、`dist/`、`.build/`、`node_modules/`、`bun.lock` 和临时构建产物
|
||||
|
||||
### Requirement: 快速检查命令
|
||||
项目 SHALL 提供快速 `check` 命令,用于日常开发期间验证代码质量和基础行为。
|
||||
|
||||
#### Scenario: 运行快速检查
|
||||
- **WHEN** 开发者运行 `bun run check`
|
||||
- **THEN** 系统 SHALL 依次执行类型检查、lint、格式检查和单元测试
|
||||
|
||||
#### Scenario: 快速检查失败
|
||||
- **WHEN** `check` 中任一子检查失败
|
||||
- **THEN** `check` MUST 以非零状态退出且不静默忽略失败
|
||||
|
||||
### Requirement: 完整验证命令
|
||||
项目 SHALL 提供完整 `verify` 命令,用于提交前或发布前验证当前源码、测试和生产 executable 行为。
|
||||
|
||||
#### Scenario: 运行完整验证
|
||||
- **WHEN** 开发者运行 `bun run verify`
|
||||
- **THEN** 系统 SHALL 先运行 `check`,再运行生产构建和 executable smoke test
|
||||
|
||||
#### Scenario: 完整验证失败
|
||||
- **WHEN** `verify` 中任一阶段失败
|
||||
- **THEN** `verify` MUST 以非零状态退出且不能继续声明验证成功
|
||||
@@ -1,23 +0,0 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 开发期后端端口一致性
|
||||
项目 SHALL 保证文档化的全栈开发命令中,Vite proxy 目标端口与 Bun 后端监听端口来自同一配置来源。
|
||||
|
||||
#### Scenario: 使用默认开发端口
|
||||
- **WHEN** 开发者未提供端口覆盖并运行文档化的全栈开发命令
|
||||
- **THEN** Bun 后端 SHALL 监听默认端口,且 Vite SHALL 将 `/api/*` 代理到同一端口
|
||||
|
||||
#### Scenario: 使用 PORT 覆盖开发端口
|
||||
- **WHEN** 开发者通过 `PORT` 覆盖后端端口并运行文档化的全栈开发命令
|
||||
- **THEN** Bun 后端 SHALL 监听该端口,且 Vite SHALL 将 `/api/*` 代理到同一端口
|
||||
|
||||
#### Scenario: 避免代理端口与后端端口分叉
|
||||
- **WHEN** 开发期脚本需要向 Vite 传递后端端口
|
||||
- **THEN** 该代理端口 MUST 从文档化的后端端口配置派生,而不是作为独立对外配置导致分叉
|
||||
|
||||
### Requirement: 开发质量命令文档化
|
||||
项目 SHALL 在前端开发工作流文档中说明日常检查和完整验证命令。
|
||||
|
||||
#### Scenario: 查阅开发命令
|
||||
- **WHEN** 开发者阅读 README 的开发或测试章节
|
||||
- **THEN** 文档 SHALL 说明 `check` 用于日常开发检查,`verify` 用于提交前或发布前完整验证
|
||||
@@ -1,76 +0,0 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: HTTP method 语义
|
||||
系统 SHALL 为运行时端点提供明确的 HTTP method 语义,避免不支持的 method 被错误地当作成功请求处理。
|
||||
|
||||
#### Scenario: GET 请求访问运行时端点
|
||||
- **WHEN** 客户端使用 `GET` 请求 `/health` 或 `/api/demo`
|
||||
- **THEN** Bun server SHALL 返回对应端点的成功响应
|
||||
|
||||
#### Scenario: HEAD 请求访问运行时端点
|
||||
- **WHEN** 客户端使用 `HEAD` 请求 `/health` 或 `/api/demo`
|
||||
- **THEN** Bun server SHALL 返回与 `GET` 相同的成功状态和 headers,但 MUST NOT 返回响应体
|
||||
|
||||
#### Scenario: 不支持的 method 访问运行时端点
|
||||
- **WHEN** 客户端使用不支持的 method 请求 `/health` 或 `/api/demo`
|
||||
- **THEN** Bun server MUST 返回 JSON 405 响应,并带有描述允许 method 的 `Allow` header
|
||||
|
||||
### Requirement: 运行配置校验
|
||||
系统 SHALL 对运行时 host 和 port 配置提供稳定、可测试的解析与校验行为。
|
||||
|
||||
#### Scenario: 使用默认运行配置
|
||||
- **WHEN** 未提供 host 或 port 覆盖
|
||||
- **THEN** server SHALL 使用 README 文档化的默认 host 和 port
|
||||
|
||||
#### Scenario: CLI 参数优先于环境变量
|
||||
- **WHEN** CLI 参数和环境变量同时提供同一项运行配置
|
||||
- **THEN** server SHALL 使用 CLI 参数中的值
|
||||
|
||||
#### Scenario: 拒绝无效端口
|
||||
- **WHEN** port 配置不是整数、小于 0 或大于 65535
|
||||
- **THEN** server MUST 拒绝启动并报告无效端口
|
||||
|
||||
#### Scenario: 接受端口边界值
|
||||
- **WHEN** port 配置为 0 或 65535
|
||||
- **THEN** server SHALL 将其作为有效端口配置处理
|
||||
|
||||
### Requirement: API 错误响应一致性
|
||||
系统 SHALL 为 API 命名空间内的错误返回机器可读 JSON 响应。
|
||||
|
||||
#### Scenario: 未知 API 路由
|
||||
- **WHEN** 客户端请求未知的 `/api/*` 路由
|
||||
- **THEN** Bun server MUST 返回包含 `error` 和 `status` 字段的 JSON 404 响应,而不是前端 HTML 文档
|
||||
|
||||
#### Scenario: API method 不允许
|
||||
- **WHEN** 客户端使用不支持的 method 请求已存在的 API 路由
|
||||
- **THEN** Bun server MUST 返回包含 `error` 和 `status` 字段的 JSON 405 响应
|
||||
|
||||
### Requirement: 生产缓存策略
|
||||
系统 SHALL 为生产静态资源和前端入口 HTML 使用明确的缓存策略。
|
||||
|
||||
#### Scenario: 请求前端入口 HTML
|
||||
- **WHEN** 生产 Bun server 返回前端入口 HTML 文档
|
||||
- **THEN** 响应 SHALL 使用 `Cache-Control: no-cache`
|
||||
|
||||
#### Scenario: 请求构建后的静态资源
|
||||
- **WHEN** 生产 Bun server 返回 Vite 构建后的静态资源
|
||||
- **THEN** 响应 SHALL 使用长缓存策略 `public, max-age=31536000, immutable`
|
||||
|
||||
#### Scenario: 请求未知静态资源
|
||||
- **WHEN** 客户端请求不存在的 `/assets/*` 资源或带文件扩展名的未知路径
|
||||
- **THEN** Bun server MUST 返回 404,且 MUST NOT 返回前端入口 HTML 文档
|
||||
|
||||
### Requirement: 低风险安全响应头
|
||||
系统 SHALL 在生产运行时响应中附加低风险安全响应头,提升基础安全性且不提前约束未来前端资源策略。
|
||||
|
||||
#### Scenario: 生产 HTML 响应包含安全头
|
||||
- **WHEN** 生产 Bun server 返回前端 HTML 文档
|
||||
- **THEN** 响应 SHALL 包含 `X-Content-Type-Options: nosniff` 和 `Referrer-Policy` headers
|
||||
|
||||
#### Scenario: 生产 JSON 响应包含安全头
|
||||
- **WHEN** 生产 Bun server 返回 `/health` 或 `/api/*` JSON 响应
|
||||
- **THEN** 响应 SHALL 包含 `X-Content-Type-Options: nosniff` 和 `Referrer-Policy` headers
|
||||
|
||||
#### Scenario: 生产静态资源响应包含安全头
|
||||
- **WHEN** 生产 Bun server 返回 Vite 构建后的静态资源
|
||||
- **THEN** 响应 SHALL 包含 `X-Content-Type-Options: nosniff` 和 `Referrer-Policy` headers
|
||||
@@ -1,33 +0,0 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 构建生成确定性
|
||||
生产构建 SHALL 以稳定顺序生成嵌入静态资源清单,减少重复构建产生无意义差异。
|
||||
|
||||
#### Scenario: 生成静态资源清单
|
||||
- **WHEN** 生产构建扫描 Vite 输出目录并生成嵌入资源模块
|
||||
- **THEN** 资源条目 SHALL 按稳定顺序输出
|
||||
|
||||
#### Scenario: 重复构建相同前端产物
|
||||
- **WHEN** Vite 输出内容未变化且生产构建重复运行
|
||||
- **THEN** 生成的嵌入资源模块 SHALL 保持语义一致且不依赖文件系统遍历顺序
|
||||
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: 构建验证
|
||||
项目 SHALL 提供验证,证明生产 executable 可以服务 API、健康检查、静态资源和 SPA fallback 路由,并且完整验证 MUST 针对当前源码重新构建后的 executable 运行。
|
||||
|
||||
#### Scenario: 验证 executable 路由
|
||||
- **WHEN** 构建验证针对生成的 executable 运行
|
||||
- **THEN** 它 SHALL 检查 `/api/demo`、`/health`、前端根路径、静态资源、未知 API、未知静态资源和前端 fallback 请求
|
||||
|
||||
#### Scenario: 验证生产模式和响应头
|
||||
- **WHEN** 构建验证针对生成的 executable 运行
|
||||
- **THEN** 它 SHALL 检查 demo 响应处于 production runtime mode,并验证代表性 HTML、JSON 和静态资源响应的缓存或低风险安全 headers
|
||||
|
||||
#### Scenario: 完整验证重新构建 executable
|
||||
- **WHEN** 开发者运行完整验证命令
|
||||
- **THEN** 系统 MUST 先基于当前源码执行生产构建,再对新生成的 executable 运行 smoke test
|
||||
|
||||
#### Scenario: 验证失败
|
||||
- **WHEN** 任一代表性生产路由、响应头、生产模式或构建阶段检查失败
|
||||
- **THEN** 验证 SHALL 使构建或测试命令失败
|
||||
@@ -1,34 +0,0 @@
|
||||
## 1. 质量门禁配置
|
||||
|
||||
- [x] 1.1 添加 `eslint`、`@eslint/js`、`typescript-eslint`、`eslint-plugin-react-hooks`、`eslint-plugin-react-refresh` 和 `prettier` 开发依赖并更新 lockfile
|
||||
- [x] 1.2 在 `package.json` 新增 `lint`、`format`、`format:check`、`check`、`verify` 脚本
|
||||
- [x] 1.3 配置 ESLint 检查 TypeScript、React、脚本和测试代码,并启用 React Hooks 规则
|
||||
- [x] 1.4 配置 ESLint 禁止 `src/web` 导入 `src/server` 后端运行时实现
|
||||
- [x] 1.5 配置 Prettier 和忽略规则,确保排除 `openspec/`、`dist/`、`.build/`、`node_modules/`、`bun.lock` 和临时构建产物
|
||||
|
||||
## 2. 开发期配置一致性
|
||||
|
||||
- [x] 2.1 调整全栈开发脚本,使 Vite proxy 端口从文档化的后端端口配置派生
|
||||
- [x] 2.2 调整或确认运行配置校验,覆盖默认值、CLI 优先级、无效端口和端口边界行为
|
||||
- [x] 2.3 补充运行配置测试,覆盖默认端口、`PORT` 覆盖、CLI 优先级、无效端口和端口边界
|
||||
|
||||
## 3. HTTP 运行时契约
|
||||
|
||||
- [x] 3.1 为 `/health` 和 `/api/demo` 实现 `GET` 与 `HEAD` 语义,并对不支持 method 返回 JSON 405 和 `Allow` header
|
||||
- [x] 3.2 统一 API 404 和 405 错误响应结构,确保包含 `error` 和 `status` 字段
|
||||
- [x] 3.3 为生产 HTML、JSON 和静态资源响应添加低风险安全 headers
|
||||
- [x] 3.4 明确生产 HTML、静态资源和未知静态资源的缓存与 404 行为
|
||||
- [x] 3.5 补充 HTTP handler 单元测试,覆盖 method、HEAD、JSON 错误、缓存 headers、安全 headers 和未知静态资源
|
||||
|
||||
## 4. 构建与 Smoke 验证
|
||||
|
||||
- [x] 4.1 调整生产构建脚本,按稳定顺序生成嵌入静态资源清单
|
||||
- [x] 4.2 增强 executable smoke test,验证 production runtime mode、未知 API、未知静态资源、SPA fallback、缓存 headers 和低风险安全 headers
|
||||
- [x] 4.3 确保 `verify` 先运行 `check`,再基于当前源码执行生产构建和 smoke test
|
||||
|
||||
## 5. 文档与最终验证
|
||||
|
||||
- [x] 5.1 更新 README,说明 `check`、`verify`、lint、format、构建中间产物、运行配置和验证边界
|
||||
- [x] 5.2 运行 `bun run check` 并修复发现的问题
|
||||
- [x] 5.3 运行 `bun run verify` 并修复发现的问题
|
||||
- [x] 5.4 修正 `openspec/config.yaml` 中 `tasks` artifact 规则键名并确认 OpenSpec CLI 不再告警
|
||||
@@ -1,2 +0,0 @@
|
||||
schema: spec-driven
|
||||
created: 2026-05-09
|
||||
@@ -1,130 +0,0 @@
|
||||
## Context
|
||||
|
||||
Gateway Checker 当前是一个 Bun + React 全栈脚手架,仅包含 demo 验证逻辑(`/api/demo` 端点 + 前端展示连接状态)。项目已有完整的开发、构建、打包、测试链路。需要将其转化为一个可用的 HTTP 拨测工具。
|
||||
|
||||
现有基础设施:
|
||||
- Bun 后端:路由框架(`createFetchHandler`)、服务启动(`startServer`)、运行时配置解析(`readRuntimeConfig`)
|
||||
- React 前端:Vite + React + TypeScript,开发期通过 Vite proxy 转发 `/api/*`
|
||||
- 构建:Vite 前端构建 + Bun 单 executable 打包
|
||||
- 测试:Bun test + smoke test
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- 提供完整的 HTTP 拨测能力:YAML 配置 → 定时并发拨测 → 结果持久化 → 可视化展示
|
||||
- 支持灵活的拨测配置:per-target interval、自定义 method/header/body、expect 校验
|
||||
- 前端 Dashboard 实时展示:总览统计、目标状态列表、历史记录、延迟趋势图
|
||||
- 保持现有项目架构风格和构建打包链路
|
||||
- 零外部运行时依赖新增(仅前端 recharts)
|
||||
|
||||
**Non-Goals:**
|
||||
- 不做告警通知(邮件/短信/Webhook),仅 Dashboard 展示
|
||||
- 不做数据自动清理/过期策略,保留全部历史记录
|
||||
- 不做 SSE/WebSocket 实时推送,用轮询即可
|
||||
- 不做拨测目标动态增删(需修改 YAML 后重启)
|
||||
- 不做认证/鉴权
|
||||
- 不做分布式/集群部署
|
||||
|
||||
## Decisions
|
||||
|
||||
### 1. 配置管理:YAML 统一配置 + 单 CLI 参数
|
||||
|
||||
**选择**:所有配置(server、数据目录、拨测默认值、目标列表)统一到 YAML 文件,CLI 只接受一个参数即配置文件路径。
|
||||
|
||||
**替代方案**:
|
||||
- CLI 参数 + 环境变量覆盖部分配置 → 配置分散,维护成本高
|
||||
- TOML 格式 → Bun 无内置支持,需引入依赖
|
||||
|
||||
**理由**:
|
||||
- 用户明确要求"配置统一到 YAML 文件"
|
||||
- `Bun.YAML.parse()` 内置支持,零依赖
|
||||
- 单参数 CLI 最简洁:`./gateway-checker ./probes.yaml`
|
||||
|
||||
### 2. 数据存储:SQLite(bun:sqlite)
|
||||
|
||||
**选择**:使用 Bun 内置 `bun:sqlite` 模块,WAL 模式运行。
|
||||
|
||||
**替代方案**:
|
||||
- JSONL 文件追加 → 聚合查询需全表扫描,趋势计算复杂
|
||||
- 外部 SQLite 库(better-sqlite3)→ bun:sqlite 已内置,无需引入
|
||||
|
||||
**理由**:
|
||||
- 趋势分析需要 `AVG(latency) GROUP BY hour` 等聚合查询,SQL 原生支持
|
||||
- bun:sqlite 是 Bun 内置模块,不违反"不引入新依赖"约束
|
||||
- WAL 模式支持并发读写
|
||||
- 单 `.db` 文件,便于管理
|
||||
|
||||
### 3. 调度模型:按 interval 分组 + 组内并发
|
||||
|
||||
**选择**:将所有 target 按其 interval 值分组,每组一个 `setInterval` timer,组内使用 `Promise.all` 并发拨测。
|
||||
|
||||
**替代方案**:
|
||||
- 全局统一 tick → 无法支持 per-target interval
|
||||
- 每个 target 独立 timer → 目标多时 timer 数量大,资源浪费
|
||||
- 使用调度队列(如 BullMQ)→ 过度设计
|
||||
|
||||
**理由**:
|
||||
- 支持 per-target interval,满足不同服务不同频率的需求
|
||||
- 相同 interval 的目标共享 timer,timer 数量 = 不同 interval 值的数量
|
||||
- 组内并发保证批量效率,组间隔离互不影响
|
||||
|
||||
### 4. 前端更新策略:轮询
|
||||
|
||||
**选择**:前端每 5-10 秒轮询 `/api/summary` 和 `/api/targets`。
|
||||
|
||||
**替代方案**:
|
||||
- SSE 服务端推送 → 实现复杂,拨测间隔 15-60s 级别无必要
|
||||
- WebSocket → 更复杂,过度设计
|
||||
|
||||
**理由**:
|
||||
- 拨测间隔本身是 15-60s,5s 轮询延迟完全可接受
|
||||
- 实现简单,无需维护长连接状态
|
||||
- Dashboard 面板按需加载趋势数据(展开详情时请求)
|
||||
|
||||
### 5. 趋势图:recharts
|
||||
|
||||
**选择**:引入 recharts 作为前端图表库。
|
||||
|
||||
**替代方案**:
|
||||
- 纯 SVG 手写 sparkline → 零依赖但代码量大,交互能力有限
|
||||
- Chart.js → 非 React 原生,需要 wrapper
|
||||
- D3 → 过于底层
|
||||
|
||||
**理由**:
|
||||
- 用户确认允许引入轻量图表库
|
||||
- recharts 是 React 原生图表库,与现有 React 技术栈一致
|
||||
- 支持折线图、迷你 Sparkline,满足需求
|
||||
- 社区活跃,文档完善
|
||||
|
||||
### 6. 目标状态判定模型
|
||||
|
||||
**选择**:两层判定——`success`(请求是否完成)+ `matched`(是否符合 expect 规则)。
|
||||
|
||||
```
|
||||
● UP = success ✓ && matched ✓
|
||||
● DOWN = !success || !matched
|
||||
```
|
||||
|
||||
**理由**:
|
||||
- 区分"网络不可达"和"返回了非预期状态码"两种故障场景
|
||||
- expect 规则可选,不配置时 matched 默认为 true
|
||||
- 前端可以根据 `success`/`matched` 分别展示不同故障原因
|
||||
|
||||
### 7. 数据库 Schema 设计
|
||||
|
||||
**targets 表**:从 YAML 同步初始化,运行时只读。
|
||||
**check_results 表**:只追加写入,索引 `(target_id, timestamp)` 加速历史查询。
|
||||
|
||||
**理由**:
|
||||
- targets 从 YAML 来,不提供运行时动态增删(符合 Non-Goals)
|
||||
- check_results 追加写入,无需更新/删除,简单可靠
|
||||
- 按时间范围查询是最高频操作,复合索引覆盖
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- **[YAML 格式错误导致启动失败]** → 解析时做完整校验,输出清晰错误信息(字段缺失、格式不对、值非法等),提前失败而非运行时出错
|
||||
- **[并发拨测对目标服务器压力]** → 每组内 Promise.all 并发,但同一 group 的 tick 间隔内不会重复拨测。如果用户配置了大量目标且 interval 很短,可能对目标产生压力,这是用户配置责任
|
||||
- **[SQLite 数据文件增长]** → 当前不清理,长期运行会增长。预留清理策略接口,后续可通过配置保留天数
|
||||
- **[recharts 包体积]** → recharts gzip 后约 70KB,会增加前端 bundle 大小。对于内部工具可接受
|
||||
- **[拨测请求超时阻塞]** → 使用 `AbortController` + `setTimeout` 实现超时,避免单个慢请求阻塞整组
|
||||
- **[进程重启后丢失 timer 状态]** → 拨测是幂等的(无状态定时任务),重启后立即开始新一轮即可,无需恢复状态
|
||||
@@ -1,36 +0,0 @@
|
||||
## Why
|
||||
|
||||
项目当前只有 demo 验证链路(`/api/demo` + 前端展示连接状态),缺少核心业务逻辑。需要一个 HTTP 拨测工具,通过 YAML 配置文件定义拨测目标(URL、method、header、body、期望条件等),后端按配置定时、并行批量拨测,结果持久化到本地 SQLite,前端 Dashboard 展示各目标实时状态、可用率、延迟趋势等。
|
||||
|
||||
## What Changes
|
||||
|
||||
- **清理 demo 样例代码**:移除 `/api/demo` 路由、`DemoResponse` 类型、前端 demo 展示逻辑,保留路由框架、服务启动、构建打包链路和 `/health` 端点
|
||||
- **新增 YAML 配置文件解析**:使用 Bun 内置 `Bun.YAML.parse()` 读取拨测规则文件,包含 server 配置、数据目录、全局默认值和拨测目标列表
|
||||
- **简化 CLI 参数**:只保留一个命令行参数——配置文件路径,所有配置统一到 YAML 文件
|
||||
- **新增 SQLite 数据存储**:使用 `bun:sqlite` 存储拨测目标(从 YAML 同步)和拨测结果(追加写入),支持索引查询
|
||||
- **新增拨测调度引擎**:按 target 的 interval 分组,每组独立 timer,组内 `Promise.all` 并发拨测,支持 expect 校验(状态码、响应体、延迟阈值)
|
||||
- **新增 REST API 层**:提供总览统计、目标列表含当前状态、历史记录、趋势聚合等接口
|
||||
- **新增前端 Dashboard**:使用 React 组件展示统计卡片、目标列表表格(含状态圆点和迷你趋势线)、可展开详情面板(含完整趋势图),通过轮询 5-10s 更新数据
|
||||
- **引入 recharts 依赖**:用于趋势图和迷你 Sparkline 可视化
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
- `probe-config`: YAML 配置文件格式定义、解析校验与 CLI 启动流程
|
||||
- `probe-engine`: 拨测调度引擎——按 interval 分组定时、并发拨测、expect 校验、结果存储
|
||||
- `probe-data-store`: SQLite 数据存储——targets 同步、results 追加、索引与聚合查询
|
||||
- `probe-api`: REST API 层——总览统计、目标列表含状态、历史记录、趋势聚合
|
||||
- `probe-dashboard`: React 前端 Dashboard——统计卡片、目标表格、详情面板、趋势图
|
||||
|
||||
### Modified Capabilities
|
||||
- `fullstack-app-runtime`: CLI 参数从 `--host/--port` 简化为单个配置文件路径参数;移除 `/api/demo` 路由;新增 `/api/*` 拨测相关 API 路由
|
||||
- `frontend-development-workflow`: 前端从 demo 展示页面替换为拨测 Dashboard;移除 `/api/demo` 相关代理场景
|
||||
|
||||
## Impact
|
||||
|
||||
- **代码变更**:`src/server/app.ts` 路由重写、`src/server/config.ts` 简化、`src/shared/api.ts` 类型重写、`src/web/` 前端全部重写
|
||||
- **新增模块**:`src/server/checker/` 目录(engine、fetcher、store、config-loader、types)
|
||||
- **新增依赖**:`recharts`(前端图表)
|
||||
- **无新增外部依赖**:YAML 解析使用 Bun 内置 `Bun.YAML`,SQLite 使用 Bun 内置 `bun:sqlite`
|
||||
- **构建打包**:现有 single executable 打包链路不变,YAML 配置文件为外部文件不嵌入 executable
|
||||
- **API 变更**:**BREAKING** 移除 `/api/demo`,新增 `/api/summary`、`/api/targets`、`/api/targets/:id/history`、`/api/targets/:id/trend`
|
||||
@@ -1,12 +0,0 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: 前端开发期 API 代理
|
||||
前端开发服务器 SHALL 在本地开发期间将 `/api/*` 请求代理到 Bun 后端服务。
|
||||
|
||||
#### Scenario: 前端开发期调用拨测 API
|
||||
- **WHEN** 浏览器从 Vite 开发源请求 `/api/summary`、`/api/targets` 等拨测 API
|
||||
- **THEN** Vite SHALL 将请求转发到 Bun 后端服务,且不需要浏览器 CORS 配置
|
||||
|
||||
#### Scenario: 开发期访问非 API 前端路由
|
||||
- **WHEN** 浏览器从 Vite 开发源请求非 API 前端路由
|
||||
- **THEN** Vite SHALL 将该请求作为前端应用流量处理,而不是转发到后端
|
||||
@@ -1,35 +0,0 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Bun HTTP 运行时
|
||||
系统 SHALL 运行一个 Bun HTTP server,由单个进程提供后端 API、健康检查、生产静态资源和 SPA fallback 行为。
|
||||
|
||||
#### Scenario: 启动运行时服务器
|
||||
- **WHEN** server 进程成功启动
|
||||
- **THEN** 它 SHALL 监听 YAML 配置文件中指定的 host 和 port,并记录实际 server URL
|
||||
|
||||
#### Scenario: 通过 YAML 配置提供运行时参数
|
||||
- **WHEN** 通过 YAML 配置文件提供 host、port、数据目录等参数
|
||||
- **THEN** server SHALL 使用该值,且不需要重新构建
|
||||
|
||||
#### Scenario: CLI 只接受配置文件路径
|
||||
- **WHEN** 用户通过命令行启动程序
|
||||
- **THEN** 系统 SHALL 只接受一个命令行参数作为 YAML 配置文件路径
|
||||
|
||||
#### Scenario: 提供拨测相关 API
|
||||
- **WHEN** server 启动完成
|
||||
- **THEN** 系统 SHALL 提供 `/api/summary`、`/api/targets`、`/api/targets/:id/history`、`/api/targets/:id/trend` 端点
|
||||
|
||||
### Requirement: HTTP method 语义
|
||||
系统 SHALL 为运行时端点提供明确的 HTTP method 语义,避免不支持的 method 被错误地当作成功请求处理。
|
||||
|
||||
#### Scenario: GET 请求访问运行时端点
|
||||
- **WHEN** 客户端使用 `GET` 请求 `/health` 或 `/api/*` 端点
|
||||
- **THEN** Bun server SHALL 返回对应端点的成功响应
|
||||
|
||||
#### Scenario: HEAD 请求访问运行时端点
|
||||
- **WHEN** 客户端使用 `HEAD` 请求 `/health` 或 `/api/*` 端点
|
||||
- **THEN** Bun server SHALL 返回与 `GET` 相同的成功状态和 headers,但 MUST NOT 返回响应体
|
||||
|
||||
#### Scenario: 不支持的 method 访问运行时端点
|
||||
- **WHEN** 客户端使用不支持的 method 请求 `/health` 或 `/api/*` 端点
|
||||
- **THEN** Bun server SHALL 返回 405 状态码和 Allow header
|
||||
@@ -1,59 +0,0 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 总览统计 API
|
||||
系统 SHALL 提供 `GET /api/summary` 端点,返回所有目标的总体统计信息。
|
||||
|
||||
#### Scenario: 获取总览统计
|
||||
- **WHEN** 客户端请求 `GET /api/summary`
|
||||
- **THEN** 系统 SHALL 返回 JSON 包含 total(总目标数)、up(正常数)、down(异常数)、avgLatencyMs(所有目标平均延迟)、lastCheckTime(最近一次拨测时间)
|
||||
|
||||
### Requirement: 目标列表 API
|
||||
系统 SHALL 提供 `GET /api/targets` 端点,返回所有目标及其最新状态和统计摘要。
|
||||
|
||||
#### Scenario: 获取目标列表
|
||||
- **WHEN** 客户端请求 `GET /api/targets`
|
||||
- **THEN** 系统 SHALL 返回 JSON 数组,每个元素包含目标基本信息、最近一次拨测结果(timestamp、success、statusCode、latencyMs、error、matched)和统计摘要(totalChecks、availability、avgLatencyMs、p99LatencyMs)
|
||||
|
||||
#### Scenario: 目标无历史记录
|
||||
- **WHEN** 某目标尚未执行过任何拨测
|
||||
- **THEN** 其 latestCheck 为 null,stats 中 totalChecks 为 0
|
||||
|
||||
### Requirement: 历史记录 API
|
||||
系统 SHALL 提供 `GET /api/targets/:id/history` 端点,返回指定目标的最近 N 条拨测记录。
|
||||
|
||||
#### Scenario: 获取最近历史记录
|
||||
- **WHEN** 客户端请求 `GET /api/targets/1/history?limit=20`
|
||||
- **THEN** 系统 SHALL 返回最多 20 条拨测记录,按时间倒序排列
|
||||
|
||||
#### Scenario: 使用默认 limit
|
||||
- **WHEN** 客户端请求 `GET /api/targets/1/history`(未指定 limit)
|
||||
- **THEN** 系统 SHALL 默认返回最近 20 条记录
|
||||
|
||||
### Requirement: 趋势聚合 API
|
||||
系统 SHALL 提供 `GET /api/targets/:id/trend` 端点,返回指定目标按小时聚合的趋势数据。
|
||||
|
||||
#### Scenario: 获取 24 小时趋势
|
||||
- **WHEN** 客户端请求 `GET /api/targets/1/trend?hours=24`
|
||||
- **THEN** 系统 SHALL 返回按小时分组的聚合数据,每个数据点包含 hour、avgLatencyMs、availability、totalChecks
|
||||
|
||||
#### Scenario: 使用默认时间范围
|
||||
- **WHEN** 客户端请求 `GET /api/targets/1/trend`(未指定 hours)
|
||||
- **THEN** 系统 SHALL 默认返回最近 24 小时的趋势数据
|
||||
|
||||
### Requirement: 保留健康检查端点
|
||||
系统 SHALL 保留 `GET /health` 端点,不受拨测功能影响。
|
||||
|
||||
#### Scenario: 访问健康检查
|
||||
- **WHEN** 客户端请求 `GET /health`
|
||||
- **THEN** 系统 SHALL 返回与之前格式一致的健康检查响应
|
||||
|
||||
### Requirement: API 错误处理
|
||||
系统 SHALL 对不存在的目标 ID 和无效参数返回适当的 HTTP 错误响应。
|
||||
|
||||
#### Scenario: 查询不存在的目标
|
||||
- **WHEN** 客户端请求 `GET /api/targets/999/history`
|
||||
- **THEN** 系统 SHALL 返回 404 状态码和错误信息
|
||||
|
||||
#### Scenario: 无效的 limit 参数
|
||||
- **WHEN** 客户端请求 `GET /api/targets/1/history?limit=abc`
|
||||
- **THEN** 系统 SHALL 返回 400 状态码和错误信息
|
||||
@@ -1,53 +0,0 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: YAML 配置文件格式
|
||||
系统 SHALL 支持通过 YAML 配置文件定义全部运行参数,包括 server 配置、数据目录、拨测默认值和拨测目标列表。
|
||||
|
||||
#### Scenario: 完整配置文件解析
|
||||
- **WHEN** 系统启动并读取包含 server、defaults、targets 的 YAML 配置文件
|
||||
- **THEN** 系统 SHALL 正确解析所有字段并用于初始化服务
|
||||
|
||||
#### Scenario: 最简配置文件解析
|
||||
- **WHEN** 系统读取只包含 targets 列表的 YAML 配置文件(省略 server 和 defaults)
|
||||
- **THEN** 系统 SHALL 使用内置默认值填充未指定的字段(host=127.0.0.1, port=3000, dir=./data, interval=30s, timeout=10s, method=GET)
|
||||
|
||||
#### Scenario: per-target 配置覆盖全局默认值
|
||||
- **WHEN** 某个 target 指定了 interval、timeout 或 method
|
||||
- **THEN** 该 target SHALL 使用其自身的值,不受 defaults 影响
|
||||
|
||||
### Requirement: CLI 参数
|
||||
系统 SHALL 通过单一命令行参数接受 YAML 配置文件路径。
|
||||
|
||||
#### Scenario: 指定配置文件启动
|
||||
- **WHEN** 用户执行 `./gateway-checker ./probes.yaml`
|
||||
- **THEN** 系统 SHALL 读取并解析指定路径的 YAML 文件作为配置
|
||||
|
||||
#### Scenario: 未提供配置文件路径
|
||||
- **WHEN** 用户启动程序时未提供任何命令行参数
|
||||
- **THEN** 系统 SHALL 以错误退出并提示需要指定配置文件路径
|
||||
|
||||
#### Scenario: 配置文件不存在
|
||||
- **WHEN** 用户指定的配置文件路径不存在
|
||||
- **THEN** 系统 SHALL 以错误退出并提示文件不存在
|
||||
|
||||
### Requirement: 配置校验
|
||||
系统 SHALL 在启动时对 YAML 配置进行完整校验,校验失败时以非零状态退出并输出清晰的错误信息。
|
||||
|
||||
#### Scenario: target 缺少必填字段
|
||||
- **WHEN** YAML 中某个 target 缺少 name 或 url 字段
|
||||
- **THEN** 系统 SHALL 以错误退出,提示哪个 target 缺少哪个字段
|
||||
|
||||
#### Scenario: target name 重复
|
||||
- **WHEN** YAML 中存在两个 name 相同的 target
|
||||
- **THEN** 系统 SHALL 以错误退出,提示重复的 name
|
||||
|
||||
#### Scenario: interval 格式非法
|
||||
- **WHEN** interval 或 timeout 值不是有效的时长格式(如 `30s`、`5m`)
|
||||
- **THEN** 系统 SHALL 以错误退出并提示格式错误
|
||||
|
||||
### Requirement: YAML 配置使用 Bun 内置解析
|
||||
系统 SHALL 使用 Bun 内置的 `Bun.YAML.parse()` 解析配置文件,不引入外部 YAML 解析库。
|
||||
|
||||
#### Scenario: 解析 YAML 内容
|
||||
- **WHEN** 系统读取 YAML 文件内容
|
||||
- **THEN** 系统 SHALL 调用 `Bun.YAML.parse()` 将内容解析为配置对象
|
||||
@@ -1,69 +0,0 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 总览统计卡片
|
||||
Dashboard SHALL 在页面顶部展示总览统计卡片,包含总目标数、正常数、异常数和平均延迟。
|
||||
|
||||
#### Scenario: 展示统计卡片
|
||||
- **WHEN** 用户打开 Dashboard 页面
|
||||
- **THEN** 页面顶部 SHALL 显示 4 个统计卡片:全部目标数、正常目标数、异常目标数、所有目标平均延迟
|
||||
|
||||
#### Scenario: 统计数据自动刷新
|
||||
- **WHEN** 页面处于打开状态
|
||||
- **THEN** 统计卡片 SHALL 每 5-10 秒自动刷新数据
|
||||
|
||||
### Requirement: 目标列表表格
|
||||
Dashboard SHALL 展示所有拨测目标的列表表格,包含名称、URL、当前状态、最新延迟和迷你趋势线。
|
||||
|
||||
#### Scenario: 展示目标列表
|
||||
- **WHEN** 用户打开 Dashboard 页面
|
||||
- **THEN** 页面 SHALL 显示表格,每行包含目标名称、URL、状态指示圆点(● UP / ● DOWN)、最新延迟值、迷你 Sparkline 趋势线
|
||||
|
||||
#### Scenario: 状态指示圆点
|
||||
- **WHEN** 目标最近一次拨测 success=true 且 matched=true
|
||||
- **THEN** 状态圆点 SHALL 显示为绿色(UP)
|
||||
- **WHEN** 目标最近一次拨测 success=false 或 matched=false
|
||||
- **THEN** 状态圆点 SHALL 显示为红色(DOWN)
|
||||
|
||||
### Requirement: 可展开的目标详情面板
|
||||
Dashboard SHALL 支持在目标列表中展开某行,显示该目标的详细状态、统计摘要、趋势图和最近历史记录。
|
||||
|
||||
#### Scenario: 展开目标详情
|
||||
- **WHEN** 用户点击目标列表中的某一行
|
||||
- **THEN** 该行下方 SHALL 展开详情面板,包含:可用率百分比、平均延迟、P99 延迟、24 小时延迟趋势折线图、最近 5-10 条拨测记录列表
|
||||
|
||||
#### Scenario: 收起目标详情
|
||||
- **WHEN** 用户再次点击已展开的目标行
|
||||
- **THEN** 详情面板 SHALL 收起
|
||||
|
||||
#### Scenario: 趋势图按需加载
|
||||
- **WHEN** 用户展开某个目标的详情面板
|
||||
- **THEN** 系统 SHALL 此时请求该目标的趋势数据,而非页面加载时预加载所有目标的趋势数据
|
||||
|
||||
### Requirement: 历史记录展示
|
||||
Dashboard SHALL 在目标详情面板中展示最近的拨测记录,包含时间、状态码、延迟和成功/失败标记。
|
||||
|
||||
#### Scenario: 展示历史记录
|
||||
- **WHEN** 用户展开目标详情面板
|
||||
- **THEN** 面板 SHALL 显示最近拨测记录列表,每条包含时间戳、HTTP 状态码(或错误信息)、延迟毫秒数、成功/失败图标
|
||||
|
||||
### Requirement: 趋势图可视化
|
||||
Dashboard SHALL 使用 recharts 库渲染趋势图,包括目标列表中的迷你 Sparkline 和详情面板中的完整折线图。
|
||||
|
||||
#### Scenario: 表格行内迷你趋势线
|
||||
- **WHEN** 目标列表表格渲染
|
||||
- **THEN** 每行 SHALL 包含一个基于 recharts 的迷你折线图,展示最近的延迟趋势
|
||||
|
||||
#### Scenario: 详情面板完整趋势图
|
||||
- **WHEN** 用户展开目标详情面板
|
||||
- **THEN** 面板 SHALL 展示基于 recharts 的完整折线图,X 轴为时间(小时),Y 轴为平均延迟,并标注可用率
|
||||
|
||||
### Requirement: 页面加载与错误状态
|
||||
Dashboard SHALL 正确处理加载状态和 API 错误。
|
||||
|
||||
#### Scenario: 首次加载
|
||||
- **WHEN** 页面首次加载且数据尚未返回
|
||||
- **THEN** 页面 SHALL 显示加载状态指示
|
||||
|
||||
#### Scenario: API 请求失败
|
||||
- **WHEN** 前端轮询 API 请求失败
|
||||
- **THEN** 页面 SHALL 显示错误提示,并在下一次轮询周期自动重试
|
||||
@@ -1,56 +0,0 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: SQLite 数据库初始化
|
||||
系统 SHALL 使用 Bun 内置 `bun:sqlite` 模块在配置的数据目录下创建 SQLite 数据库文件,并以 WAL 模式运行。
|
||||
|
||||
#### Scenario: 首次启动创建数据库
|
||||
- **WHEN** 指定的数据目录下不存在数据库文件
|
||||
- **THEN** 系统 SHALL 创建数据库文件并初始化 targets 和 check_results 表
|
||||
|
||||
#### Scenario: 数据目录不存在
|
||||
- **WHEN** 配置的数据目录路径不存在
|
||||
- **THEN** 系统 SHALL 自动创建该目录
|
||||
|
||||
#### Scenario: 数据库已存在时启动
|
||||
- **WHEN** 数据库文件已存在
|
||||
- **THEN** 系统 SHALL 直接打开数据库,不重新建表
|
||||
|
||||
### Requirement: targets 表同步
|
||||
系统 SHALL 在启动时将 YAML 配置中的目标列表同步到 SQLite targets 表。
|
||||
|
||||
#### Scenario: 首次同步目标
|
||||
- **WHEN** 数据库为空且 YAML 中定义了 N 个目标
|
||||
- **THEN** 系统 SHALL 将所有目标插入 targets 表
|
||||
|
||||
#### Scenario: 配置变更后重新同步
|
||||
- **WHEN** YAML 配置发生变更(新增、删除或修改目标)后重启
|
||||
- **THEN** 系统 SHALL 根据 name 字段匹配:新增的插入、删除的移除、修改的更新
|
||||
|
||||
### Requirement: check_results 表追加写入
|
||||
系统 SHALL 将每次拨测结果追加写入 check_results 表,不更新或删除已有记录。
|
||||
|
||||
#### Scenario: 写入拨测结果
|
||||
- **WHEN** 一次拨测完成
|
||||
- **THEN** 系统 SHALL 插入一条包含 target_id、timestamp、success、status_code、latency_ms、error、matched 的记录
|
||||
|
||||
### Requirement: 时间范围查询索引
|
||||
系统 SHALL 在 check_results 表上创建 (target_id, timestamp) 复合索引,加速按目标和时间范围的查询。
|
||||
|
||||
#### Scenario: 查询某目标的历史记录
|
||||
- **WHEN** 查询指定 target_id 的最近 N 条记录
|
||||
- **THEN** 系统 SHALL 使用索引快速定位,无需全表扫描
|
||||
|
||||
### Requirement: 聚合查询支持
|
||||
数据存储 SHALL 支持按时间段聚合查询,用于计算可用率、平均延迟、P99 延迟等统计指标。
|
||||
|
||||
#### Scenario: 计算目标可用率
|
||||
- **WHEN** 查询某目标在指定时间范围内的可用率
|
||||
- **THEN** 系统 SHALL 返回 UP (success=true AND matched=true) 的记录数占总记录数的百分比
|
||||
|
||||
#### Scenario: 计算目标平均延迟
|
||||
- **WHEN** 查询某目标在指定时间范围内的平均延迟
|
||||
- **THEN** 系统 SHALL 返回 latency_ms 的平均值(仅计算 success=true 的记录)
|
||||
|
||||
#### Scenario: 按小时聚合趋势数据
|
||||
- **WHEN** 查询某目标在指定时间范围内的趋势数据
|
||||
- **THEN** 系统 SHALL 返回按小时分组的聚合数据,包括每小时的平均延迟和可用率
|
||||
@@ -1,83 +0,0 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 按 interval 分组调度
|
||||
系统 SHALL 将拨测目标按 interval 值分组,每组使用独立的定时器进行调度。
|
||||
|
||||
#### Scenario: 相同 interval 的目标共享定时器
|
||||
- **WHEN** 多个 target 配置了相同的 interval(如 30s)
|
||||
- **THEN** 系统 SHALL 使用同一个 `setInterval` 定时器,每次 tick 并发拨测所有该组目标
|
||||
|
||||
#### Scenario: 不同 interval 的目标各自调度
|
||||
- **WHEN** target A 配置 15s interval,target B 配置 30s interval
|
||||
- **THEN** 系统 SHALL 创建两个独立定时器,分别按各自频率调度
|
||||
|
||||
### Requirement: 组内并发拨测
|
||||
系统 SHALL 在每次调度 tick 时,使用 `Promise.all` 并发执行同组内所有目标的拨测。
|
||||
|
||||
#### Scenario: 同组目标并发执行
|
||||
- **WHEN** 调度器触发一次 tick,该组有 3 个目标
|
||||
- **THEN** 系统 SHALL 同时发起 3 个 HTTP 请求,而非顺序执行
|
||||
|
||||
#### Scenario: 单个目标失败不影响同组其他目标
|
||||
- **WHEN** 同组中某个目标的拨测请求超时或失败
|
||||
- **THEN** 其他目标的拨测 SHALL 正常完成并记录结果
|
||||
|
||||
### Requirement: HTTP 拨测执行
|
||||
系统 SHALL 对每个目标执行 HTTP 请求,支持 GET、POST、PUT、DELETE、PATCH、HEAD 方法,并携带配置的 headers 和 body。
|
||||
|
||||
#### Scenario: 执行 GET 请求
|
||||
- **WHEN** 目标配置 method 为 GET
|
||||
- **THEN** 系统 SHALL 发送 GET 请求到目标 URL
|
||||
|
||||
#### Scenario: 执行 POST 请求带 body
|
||||
- **WHEN** 目标配置 method 为 POST 且指定了 body 和 Content-Type header
|
||||
- **THEN** 系统 SHALL 发送带指定 body 的 POST 请求
|
||||
|
||||
#### Scenario: 携带自定义 headers
|
||||
- **WHEN** 目标配置了 headers(如 Authorization)
|
||||
- **THEN** 系统 SHALL 在请求中包含所有配置的 headers
|
||||
|
||||
### Requirement: 请求超时控制
|
||||
系统 SHALL 对每次拨测请求实施超时控制,超时时间使用目标配置的 timeout 值。
|
||||
|
||||
#### Scenario: 请求超时
|
||||
- **WHEN** 拨测请求在 timeout 时间内未收到响应
|
||||
- **THEN** 系统 SHALL 中止该请求,记录为失败并标注超时错误
|
||||
|
||||
#### Scenario: 请求在超时前完成
|
||||
- **WHEN** 拨测请求在 timeout 时间内收到响应
|
||||
- **THEN** 系统 SHALL 正常记录响应结果
|
||||
|
||||
### Requirement: expect 校验
|
||||
系统 SHALL 在拨测完成后根据目标的 expect 配置校验响应,校验结果记入 check result。
|
||||
|
||||
#### Scenario: 校验状态码
|
||||
- **WHEN** 目标配置了 `expect.status: [200, 201]`
|
||||
- **THEN** 系统 SHALL 检查响应状态码是否在列表中,将匹配结果记录到 matched 字段
|
||||
|
||||
#### Scenario: 校验响应体包含
|
||||
- **WHEN** 目标配置了 `expect.bodyContains: "healthy"`
|
||||
- **THEN** 系统 SHALL 检查响应体是否包含该文本,将匹配结果记录到 matched 字段
|
||||
|
||||
#### Scenario: 校验延迟阈值
|
||||
- **WHEN** 目标配置了 `expect.maxLatencyMs: 3000`
|
||||
- **THEN** 系统 SHALL 检查实际延迟是否超过阈值,将匹配结果记录到 matched 字段
|
||||
|
||||
#### Scenario: 无 expect 配置
|
||||
- **WHEN** 目标未配置任何 expect 规则
|
||||
- **THEN** 系统 SHALL 将 matched 字段设为 true
|
||||
|
||||
#### Scenario: 多条 expect 规则
|
||||
- **WHEN** 目标同时配置了 status、bodyContains 和 maxLatencyMs
|
||||
- **THEN** 系统 SHALL 所有规则全部通过时 matched 为 true,任一不通过则为 false
|
||||
|
||||
### Requirement: 拨测结果记录
|
||||
系统 SHALL 在每次拨测完成后,将结果写入 SQLite 数据存储,包含 target_id、timestamp、success、status_code、latency_ms、error、matched 字段。
|
||||
|
||||
#### Scenario: 成功拨测结果记录
|
||||
- **WHEN** 拨测请求成功完成(收到 HTTP 响应)
|
||||
- **THEN** 系统 SHALL 记录 success=true、status_code、latency_ms、matched
|
||||
|
||||
#### Scenario: 失败拨测结果记录
|
||||
- **WHEN** 拨测请求失败(网络错误、超时等)
|
||||
- **THEN** 系统 SHALL 记录 success=false、error 信息,status_code 和 latency_ms 为 null
|
||||
@@ -1,62 +0,0 @@
|
||||
## 1. 项目准备与依赖
|
||||
|
||||
- [x] 1.1 清理 demo 代码:移除 /api/demo 路由、DemoResponse 类型、前端 demo 展示逻辑
|
||||
- [x] 1.2 安装 recharts 依赖
|
||||
- [x] 1.3 创建 src/server/checker/ 目录结构和类型定义文件 types.ts
|
||||
- [x] 1.4 创建示例 YAML 配置文件 probes.example.yaml
|
||||
|
||||
## 2. 配置解析层
|
||||
|
||||
- [x] 2.1 实现 YAML 配置类型定义(ProbeConfig、TargetConfig、ExpectConfig 等)
|
||||
- [x] 2.2 实现 config-loader.ts:读取文件 + Bun.YAML.parse + 配置校验(必填字段、name 唯一性、interval 格式、port 范围)
|
||||
- [x] 2.3 重写 src/server/config.ts:CLI 只接受配置文件路径参数,从 YAML 读取 host/port/dataDir
|
||||
- [x] 2.4 为配置解析和校验编写完整测试
|
||||
|
||||
## 3. 数据存储层
|
||||
|
||||
- [x] 3.1 实现 store.ts:SQLite 初始化(建表、WAL 模式、复合索引)、数据目录自动创建
|
||||
- [x] 3.2 实现 targets 表同步逻辑(根据 name 匹配:新增插入、删除移除、修改更新)
|
||||
- [x] 3.3 实现 check_results 追加写入方法
|
||||
- [x] 3.4 实现查询方法:按 target+时间范围查询、按小时聚合趋势、计算可用率/平均延迟/P99
|
||||
- [x] 3.5 为数据存储层编写完整测试(初始化、同步、写入、查询、聚合)
|
||||
|
||||
## 4. 拨测引擎
|
||||
|
||||
- [x] 4.1 实现 fetcher.ts:HTTP 请求执行(method/header/body)+ AbortController 超时控制
|
||||
- [x] 4.2 实现 expect 校验逻辑(status 列表匹配、bodyContains、maxLatencyMs)
|
||||
- [x] 4.3 实现 engine.ts:按 interval 分组 → setInterval → Promise.all 并发拨测 → 结果写入 store
|
||||
- [x] 4.4 为 fetcher 和 expect 校验编写完整测试(使用 mock HTTP server)
|
||||
- [x] 4.5 为调度引擎编写完整测试(分组逻辑、并发执行、单目标失败隔离)
|
||||
|
||||
## 5. API 路由层
|
||||
|
||||
- [x] 5.1 定义 src/shared/api.ts 响应类型(SummaryResponse、TargetStatus、CheckResult、TrendPoint)
|
||||
- [x] 5.2 重写 src/server/app.ts:注册新 API 路由(/api/summary、/api/targets、/api/targets/:id/history、/api/targets/:id/trend),保留 /health
|
||||
- [x] 5.3 实现 API 错误处理(目标不存在返回 404、参数无效返回 400)
|
||||
- [x] 5.4 为 API 路由编写完整测试(各端点正常响应、边界情况、错误处理)
|
||||
|
||||
## 6. 前端 Dashboard
|
||||
|
||||
- [x] 6.1 创建前端组件目录结构 src/web/components/ 和 src/web/hooks/
|
||||
- [x] 6.2 实现 hooks:useSummary(轮询 /api/summary)、useTargets(轮询 /api/targets)、useTrend(按需加载趋势数据)
|
||||
- [x] 6.3 实现 StatusDot 组件(绿色 UP / 红色 DOWN 圆点)
|
||||
- [x] 6.4 实现 SummaryCards 组件(4 个统计卡片)
|
||||
- [x] 6.5 实现 SparklineChart 组件(recharts 迷你折线图)
|
||||
- [x] 6.6 实现 TrendChart 组件(recharts 完整折线图,含时间轴和双 Y 轴)
|
||||
- [x] 6.7 实现 TargetRow 组件(表格行:名称、URL、状态、延迟、Sparkline,可展开)
|
||||
- [x] 6.8 实现 TargetDetail 组件(展开面板:统计摘要、趋势图、历史记录列表)
|
||||
- [x] 6.9 实现 TargetTable 组件(组合 TargetRow 和 TargetDetail)
|
||||
- [x] 6.10 重写 App.tsx:组合 SummaryCards + TargetTable,处理加载和错误状态
|
||||
- [x] 6.11 重写 styles.css:Dashboard 布局样式(卡片、表格、详情面板、响应式)
|
||||
- [x] 6.12 更新 vite.config.ts 代理配置确保 /api/* 转发
|
||||
|
||||
## 7. 集成与启动流程
|
||||
|
||||
- [x] 7.1 重写 src/server/dev.ts 和 src/server/server.ts:启动流程为 读取配置 → 初始化 store → 同步 targets → 启动 engine → 启动 HTTP server
|
||||
- [x] 7.2 更新构建脚本确保 recharts 正确打包进 executable
|
||||
- [x] 7.3 更新 README.md:新的 CLI 用法、YAML 配置说明、API 端点文档、项目结构变更
|
||||
|
||||
## 8. 端到端验证
|
||||
|
||||
- [x] 8.1 更新 smoke test 脚本适配新的 API 端点和前端路由
|
||||
- [x] 8.2 手动验证完整流程:YAML 配置 → 启动 → 拨测执行 → Dashboard 展示
|
||||
@@ -1,2 +0,0 @@
|
||||
schema: spec-driven
|
||||
created: 2026-05-09
|
||||
@@ -1,255 +0,0 @@
|
||||
## Context
|
||||
|
||||
当前实现的配置、执行、存储、API 和 Dashboard 都以 HTTP 请求为中心:`target.url` 是必填字段,执行器直接 `fetch(url)`,结果存储包含 `status_code` 与 `latency_ms`,前端展示 URL、method 和 HTTP 状态码。这种模型无法承载本地命令等非 HTTP checker,也让 `expect` 只能表达 HTTP response 的 status/header/body。
|
||||
|
||||
项目尚未上线,不需要兼容旧 YAML、旧数据库 schema 或旧 API 契约,因此本次设计选择直接建立 typed target 与领域专用 expect,而不是添加兼容分支。目标是让 HTTP 变成 runner 的一种实现,同时新增 command runner,并为未来其他 checker 类型保留清晰扩展点。
|
||||
|
||||
```text
|
||||
YAML target
|
||||
│
|
||||
▼
|
||||
ResolvedTarget(type)
|
||||
│
|
||||
▼
|
||||
ProbeEngine + concurrency limit
|
||||
│
|
||||
├─ http runner
|
||||
│ └─ HTTP expect pipeline
|
||||
│
|
||||
└─ command runner
|
||||
└─ command expect pipeline
|
||||
│
|
||||
▼
|
||||
CheckResult(success, matched, durationMs, statusDetail, failure)
|
||||
│
|
||||
▼
|
||||
SQLite + API + Dashboard
|
||||
```
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
|
||||
- 使用 `target.type` 建模不同 checker 类型,v1 支持 `http` 与 `command`。
|
||||
- 将 HTTP 配置放入 `target.http`,将命令配置放入 `target.command`,移除顶层 HTTP 字段。
|
||||
- 为各 checker 类型定义领域专用 expect 名称,HTTP 使用 `status`、`headers`、`body`,command 使用 `exitCode`、`stdout`、`stderr`。
|
||||
- 为不同 checker 类型提供默认成功语义:HTTP 默认 `status: [200]`,command 默认 `exitCode: [0]`。
|
||||
- 将可排序内容检查表达为数组,保证 `body`、`stdout`、`stderr` 按配置顺序执行。
|
||||
- 在 runner 和 expect pipeline 层共同实现快速失败,避免 status/header 已失败时仍读取或解析 body。
|
||||
- 使用 `durationMs` 表达 checker 执行耗时,替代 HTTP-only 的 `latencyMs`。
|
||||
- 引入结构化失败信息并入库,区分执行错误和 expect 不匹配,耗时阈值字段统一为 `maxDurationMs`。
|
||||
- 引入全局并发限制和 100MB 默认读取上限,避免 HTTP body 或 command 输出造成资源失控。
|
||||
|
||||
**Non-Goals:**
|
||||
|
||||
- 不兼容旧的顶层 `url`、`method`、`headers`、`body` 配置。
|
||||
- 不做旧 SQLite schema 迁移;实现阶段可以按新 schema 初始化和测试。
|
||||
- 不支持 shell 字符串命令;command v1 仅支持 `exec + args`。
|
||||
- 不持久化完整 HTTP body、stdout 或 stderr,只持久化结构化失败摘要。
|
||||
- 不引入新的解析或执行依赖。
|
||||
- 不在本次实现告警通知、认证鉴权或动态增删目标。
|
||||
|
||||
## Decisions
|
||||
|
||||
### 1. 使用判别联合建模 Target
|
||||
|
||||
配置和解析后的目标都使用 `type` 判别:
|
||||
|
||||
```yaml
|
||||
targets:
|
||||
- name: "HTTP 健康检查"
|
||||
type: http
|
||||
http:
|
||||
url: "https://example.com/health"
|
||||
method: GET
|
||||
|
||||
- name: "Nginx 进程检查"
|
||||
type: command
|
||||
command:
|
||||
exec: "pgrep"
|
||||
args: ["nginx"]
|
||||
```
|
||||
|
||||
理由:HTTP 与 command 的领域字段差异明显,强行把 URL、exec、status、exitCode 抽成统一字段会降低语义清晰度。判别联合可以让 TypeScript 在执行器选择、配置校验和 expect 校验中获得更明确的类型约束。
|
||||
|
||||
替代方案:保留顶层 `url` 并通过字段存在性推断 HTTP。该方案兼容性更好,但会继续让 HTTP 成为隐式默认类型,不符合当前无兼容包袱下的最佳模型。
|
||||
|
||||
### 2. defaults 分为通用和领域分组
|
||||
|
||||
建议配置形态:
|
||||
|
||||
```yaml
|
||||
runtime:
|
||||
maxConcurrentChecks: 20
|
||||
|
||||
defaults:
|
||||
interval: "30s"
|
||||
timeout: "10s"
|
||||
http:
|
||||
method: GET
|
||||
maxBodyBytes: "100MB"
|
||||
command:
|
||||
cwd: "."
|
||||
maxOutputBytes: "100MB"
|
||||
```
|
||||
|
||||
通用默认值只覆盖所有 checker 都共享的调度与超时字段,领域默认值只覆盖对应 target type。target 自身配置优先级高于 defaults。
|
||||
|
||||
替代方案:继续使用 `defaults.method`、`defaults.headers` 等 HTTP 字段。该方案会在 command target 中产生无意义字段,因此不采用。
|
||||
|
||||
### 3. 默认 expect 是逻辑默认值
|
||||
|
||||
当用户未显式配置对应状态类 expect 时,runner 在校验阶段应用领域默认值,而不是把默认值写回用户配置。
|
||||
|
||||
HTTP 默认:`status: [200]`。
|
||||
|
||||
Command 默认:`exitCode: [0]`。
|
||||
|
||||
示例:
|
||||
|
||||
```yaml
|
||||
expect:
|
||||
body:
|
||||
- contains: "ok"
|
||||
```
|
||||
|
||||
该 HTTP target 仍然先检查 `status == 200`,再检查 body。这样用户只写内容检查时不会把 HTTP 500 错误响应误判为 UP。
|
||||
|
||||
替代方案:只有完全不写 `expect` 时才应用默认值。该方案会让“只写 body”绕过 status 检查,不符合默认成功语义,因此不采用。
|
||||
|
||||
### 4. Expect pipeline 使用固定阶段顺序和有序规则数组
|
||||
|
||||
HTTP 顺序:
|
||||
|
||||
```text
|
||||
status -> duration -> headers -> body[0] -> body[1] -> ...
|
||||
```
|
||||
|
||||
Command 顺序:
|
||||
|
||||
```text
|
||||
exitCode -> duration -> stdout[0] -> stdout[1] -> ... -> stderr[0] -> stderr[1] -> ...
|
||||
```
|
||||
|
||||
`body`、`stdout`、`stderr` 使用数组表达配置顺序:
|
||||
|
||||
```yaml
|
||||
expect:
|
||||
body:
|
||||
- contains: "healthy"
|
||||
- json:
|
||||
path: "$.status"
|
||||
equals: "ok"
|
||||
- regex: '"version":"\\d+\\.\\d+"'
|
||||
```
|
||||
|
||||
理由:对象字段天然更像无序集合,不适合表达用户指定的检查顺序。数组规则可以直接生成 `path`,例如 `expect.body[1].json($.status)`,方便失败定位。
|
||||
|
||||
替代方案:保留对象结构并约定 contains/regex/json/css/xpath 固定顺序。该方案无法满足“按配置文件中的配置顺序依次检查”的要求,因此不采用。
|
||||
|
||||
### 5. 复用通用值操作符,但保持领域 expect 名称
|
||||
|
||||
保留并扩展现有操作符:`equals`、`contains`、`match`、`empty`、`exists`、`gte`、`lte`、`gt`、`lt`。这些操作符可用于 HTTP header、HTTP body 提取值、command stdout/stderr 文本等。
|
||||
|
||||
领域名称保持专用:HTTP 使用 `status`,command 使用 `exitCode`;HTTP body 可使用 `json/css/xpath`,command 输出只使用文本规则和通用操作符。
|
||||
|
||||
替代方案:把所有值统一抽象成 `status`、`metadata`、`payload`。该方案过度泛化,会让 YAML 对使用者不直观,因此不采用。
|
||||
|
||||
### 6. Runner 负责按需产生 Observation
|
||||
|
||||
HTTP runner 不应总是读取完整 response body。它先发起请求并取得 status、headers 和 duration,再运行 status/duration/headers 阶段;只有配置中存在 body 规则且前置阶段通过时,才读取 body,并受 `maxBodyBytes` 限制。
|
||||
|
||||
Command runner 需要执行命令并收集 exitCode、duration、stdout、stderr。stdout 和 stderr 合计受 `maxOutputBytes` 限制,默认 `100MB`。命令超时或输出超限时,runner 产生 `success=false` 和 `failure.kind=error`。
|
||||
|
||||
替代方案:runner 总是完整产生所有字段,再交给 expect。该方案实现简单,但无法真正快速失败,也无法避免不必要的资源读取,因此不采用。
|
||||
|
||||
### 7. Command 执行不经过 shell
|
||||
|
||||
command target 使用 `exec + args`,实现阶段优先使用 Bun 可用的子进程 API,并禁止默认 shell 展开。
|
||||
|
||||
```yaml
|
||||
command:
|
||||
exec: "pgrep"
|
||||
args: ["nginx"]
|
||||
cwd: "."
|
||||
env:
|
||||
LANG: "C"
|
||||
```
|
||||
|
||||
`cwd` 相对配置文件所在目录解析。`env` 默认继承当前进程环境并允许覆盖指定键。v1 不支持 stdin,避免命令阻塞。
|
||||
|
||||
替代方案:允许 `shell: "pgrep nginx | wc -l"`。该方案更灵活,但引入转义、注入和跨平台 shell 差异,不适合作为第一版默认能力。
|
||||
|
||||
### 8. 全局并发限制由 ProbeEngine 统一执行
|
||||
|
||||
`runtime.maxConcurrentChecks` 默认 20。调度仍按 interval 分组触发,但每个目标进入全局并发池后再执行,避免同一 tick 或多个 tick 同时启动过多 HTTP 请求和本地进程。
|
||||
|
||||
理由:command target 可能启动本地进程,继续无限 `Promise.allSettled` 会有资源风险。全局限制比按组限制更容易理解,也能覆盖不同 interval 组同时触发的情况。
|
||||
|
||||
替代方案:为 HTTP 和 command 分别设置并发上限。该方案更精细,但增加配置复杂度,当前需求只要求全局默认值。
|
||||
|
||||
### 9. CheckResult 使用结构化 failure
|
||||
|
||||
结果模型区分 runner 执行失败和 expect 不匹配:
|
||||
|
||||
```ts
|
||||
interface CheckFailure {
|
||||
kind: "error" | "mismatch";
|
||||
phase: "status" | "duration" | "headers" | "body" | "exitCode" | "stdout" | "stderr";
|
||||
path: string;
|
||||
expected?: unknown;
|
||||
actual?: unknown;
|
||||
message: string;
|
||||
}
|
||||
```
|
||||
|
||||
`success=false` 表示 runner 未能正常产生可校验结果,例如网络错误、超时、命令启动失败、输出超限。`matched=false` 表示 runner 执行成功但 expect 不匹配。`failure` 字段存储首个失败原因,实际值需要截断,避免超长内容或敏感内容进入数据库和 API。
|
||||
|
||||
替代方案:继续只存 `error` 字符串。该方案无法区分执行失败与规则不匹配,也不能准确定位失败 path,因此不采用。
|
||||
|
||||
### 10. 存储、API、Dashboard 改为 checker 通用语义
|
||||
|
||||
SQLite schema 建议从 HTTP-only 字段调整为:
|
||||
|
||||
```text
|
||||
targets:
|
||||
id, name, type, target, config, interval_ms, timeout_ms, expect
|
||||
|
||||
check_results:
|
||||
id, target_id, timestamp, success, matched, duration_ms, status_detail, failure
|
||||
```
|
||||
|
||||
`target` 是用于展示和搜索的目标摘要,例如 HTTP URL 或 command 命令行摘要;`config` 持久化解析后的领域配置 JSON;`status_detail` 存储领域状态摘要,例如 `HTTP 200` 或 `exitCode=1`。
|
||||
|
||||
API 共享类型使用 `durationMs`、`statusDetail`、`failure`,Dashboard 表格展示“类型、目标、状态、耗时、最近失败原因、趋势”。HTTP 详情可显示 status code,command 详情可显示 exit code,但列表层不使用 HTTP-only 列名。
|
||||
|
||||
替代方案:继续保留 `url`、`method`、`status_code`、`latency_ms` 并为 command 填空。该方案会把领域语义混在一起,后续扩展成本高,因此不采用。
|
||||
|
||||
### 11. Size 字符串解析
|
||||
|
||||
新增 size 解析支持 `B`、`KB`、`MB`、`GB`,默认 `100MB` 等于 `104857600` bytes。HTTP `maxBodyBytes` 限制单次 body 读取,command `maxOutputBytes` 限制 stdout 和 stderr 合计读取。
|
||||
|
||||
理由:YAML 直接写字节数可读性差,二进制单位更适合内存和 buffer 限制。
|
||||
|
||||
替代方案:复用 duration 解析或只接受 number。前者语义不匹配,后者配置可读性差。
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- [Risk] `maxConcurrentChecks=20` 且单次读取上限为 `100MB` 时理论内存峰值较高 → [Mitigation] 提供全局并发限制和 per-target/per-default 读取上限,文档明确资源上限由用户配置共同决定。
|
||||
- [Risk] 结构化失败信息可能包含敏感响应片段或命令输出 → [Mitigation] 只存首个失败原因,`actual` 做长度截断,默认不持久化完整 body/stdout/stderr。
|
||||
- [Risk] command checker 允许执行本地命令,有误配置或高开销命令风险 → [Mitigation] 不支持 shell,强制 timeout,限制输出大小,使用全局并发限制。
|
||||
- [Risk] 不兼容旧配置会导致现有样例和测试全部失效 → [Mitigation] 项目未上线,实施时同步更新 README、示例配置、单元测试和 smoke test。
|
||||
- [Risk] SQLite schema 重建会丢失旧数据 → [Mitigation] 当前无上线数据,不做迁移;若后续需要升级已部署实例,应另起兼容迁移 change。
|
||||
|
||||
## Migration Plan
|
||||
|
||||
- 更新类型定义、配置解析和 README 示例,先让新 YAML 契约成为唯一入口。
|
||||
- 重构存储 schema 和共享 API 类型,再更新 Dashboard 使用新字段。
|
||||
- 引入 expect 规则数组和结构化 failure,迁移 HTTP runner 到新 pipeline。
|
||||
- 添加 command runner,并接入 ProbeEngine 的 runner 选择与全局并发限制。
|
||||
- 更新测试覆盖配置、HTTP expect、command expect、存储、API、Dashboard 和 smoke test。
|
||||
- 运行 `bun run check` 和 `bun run verify`,确保完整质量门禁通过。
|
||||
|
||||
## Open Questions
|
||||
|
||||
无。当前讨论已确认默认 HTTP status 使用 `[200]`、默认并发限制使用全局配置、HTTP body 与 command 输出默认上限均为 `100MB`。
|
||||
@@ -1,39 +0,0 @@
|
||||
## Why
|
||||
|
||||
当前系统以 HTTP 请求作为唯一 checker 形态,`target`、`expect`、存储、API 和 Dashboard 都围绕 URL、HTTP method、status code 与 response body 建模,无法自然表达本地命令检查等非 HTTP 场景。项目尚未上线,没有兼容性约束,适合一次性重构为面向多种 checker 类型的清晰模型。
|
||||
|
||||
## What Changes
|
||||
|
||||
- **BREAKING**: 移除顶层 `target.url`、`target.method`、`target.headers`、`target.body` 配置形态,改为 `target.type` 判别不同 checker 类型,并将领域字段放入 `http` 或 `command` 分组。
|
||||
- **BREAKING**: `expect.body` 从对象分组改为有序规则数组,按配置顺序执行并快速失败。
|
||||
- 引入 `http` target 类型,支持 HTTP URL、method、headers、body、最大 body 读取字节数和 HTTP 专用 expect。
|
||||
- 引入 `command` target 类型,支持本地命令 `exec + args`、`cwd`、`env`、最大输出读取字节数和 command 专用 expect。
|
||||
- 为不同 checker 类型提供领域默认成功语义:HTTP 默认 `expect.status: [200]`,command 默认 `expect.exitCode: [0]`。
|
||||
- 引入全局并发限制 `runtime.maxConcurrentChecks`,默认值为 20。
|
||||
- 引入 size 配置解析,支持 `B`、`KB`、`MB`、`GB`,HTTP `maxBodyBytes` 和 command `maxOutputBytes` 默认均为 `100MB`。
|
||||
- 调整 expect 执行管线:先执行状态类检查,再执行耗时检查,再执行元数据或内容检查;同一内容字段内部按数组顺序检查,任一失败立即返回结构化失败信息。
|
||||
- 将 check result 的失败信息结构化入库,区分 runner 执行错误与 expect 不匹配,便于后续追查。
|
||||
- 将 DB、API、Dashboard 从 HTTP-only 字段命名调整为通用 checker 展示模型,同时保留 HTTP 和 command 的领域专用细节。
|
||||
- 同步更新 README、示例配置和测试,覆盖 typed target、默认 expect、快速失败、输出限制、失败信息和 Dashboard 展示。
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
- `command-checker`: 定义本地命令 checker 的配置、执行、安全边界、默认成功语义、输出限制和 expect 校验。
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
- `probe-config`: YAML 配置从 HTTP-only target 改为 typed target,新增 runtime 并发限制、HTTP/command 默认配置和 size 字符串解析。
|
||||
- `probe-engine`: 调度引擎从固定 HTTP fetch 改为按 target type 选择 runner,并在全局并发限制下执行检查。
|
||||
- `expect-body-checkers`: HTTP body expect 改为有序规则数组,通用值操作符可复用于 stdout/stderr/header/body 等不同字段。
|
||||
- `probe-data-store`: targets 和 check_results schema 从 HTTP-only 字段改为 checker 通用字段,并持久化结构化失败信息。
|
||||
- `probe-api`: API 响应从 URL/method/statusCode/latencyMs 为中心改为 type/target/durationMs/statusDetail/failure 等通用 checker 契约。
|
||||
- `probe-dashboard`: Dashboard 从 HTTP 拨测视图调整为 checker 通用视图,展示类型、目标、耗时、最近失败原因和领域状态详情。
|
||||
|
||||
## Impact
|
||||
|
||||
- 影响后端类型定义、配置加载校验、调度执行、HTTP runner、command runner、expect 校验模块、SQLite schema、聚合查询和 API 映射。
|
||||
- 影响前后端共享 API 类型和 Dashboard 表格、详情、历史记录、趋势图展示字段。
|
||||
- 影响 README、`probes.example.yaml`、单元测试和 smoke test 配置样例。
|
||||
- 不引入新依赖,优先复用 Bun、TypeScript、现有 cheerio/xpath 和 SQLite 能力。
|
||||
@@ -1,78 +0,0 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: command target 配置
|
||||
系统 SHALL 支持 `type: command` 的 target 配置,通过 `command.exec` 和 `command.args` 描述本地命令,并使用 command 专用字段配置工作目录、环境变量和输出限制。
|
||||
|
||||
#### Scenario: 解析 command target
|
||||
- **WHEN** YAML 中 target 配置 `type: command`、`command.exec: "pgrep"` 和 `command.args: ["nginx"]`
|
||||
- **THEN** 系统 SHALL 将其解析为 command checker,并保留 exec、args、cwd、env、maxOutputBytes、interval、timeout 和 expect 配置
|
||||
|
||||
#### Scenario: command target 缺少 exec
|
||||
- **WHEN** YAML 中 target 配置 `type: command` 但缺少 `command.exec`
|
||||
- **THEN** 系统 SHALL 以配置错误退出,并提示该 target 缺少 command.exec 字段
|
||||
|
||||
#### Scenario: cwd 相对配置文件目录解析
|
||||
- **WHEN** command target 配置 `command.cwd: "scripts"` 且配置文件位于 `/opt/checker/probes.yaml`
|
||||
- **THEN** 系统 SHALL 将 cwd 解析为 `/opt/checker/scripts`
|
||||
|
||||
#### Scenario: command 不使用 shell
|
||||
- **WHEN** command target 配置 `exec` 和 `args`
|
||||
- **THEN** 系统 MUST 直接执行该程序和参数,不通过 shell 解释整段命令字符串
|
||||
|
||||
#### Scenario: env 默认继承并允许覆盖
|
||||
- **WHEN** command target 配置 `command.env: {LANG: "C"}` 且当前进程环境包含 `PATH`
|
||||
- **THEN** 系统 SHALL 继承当前进程的全部环境变量,并将 `LANG` 覆盖为 `"C"`
|
||||
|
||||
#### Scenario: 不支持 stdin
|
||||
- **WHEN** command target 配置并执行命令
|
||||
- **THEN** 系统 MUST NOT 向子进程 stdin 写入数据,避免命令因等待输入而阻塞
|
||||
|
||||
### Requirement: command checker 执行
|
||||
系统 SHALL 按 command target 配置执行本地命令,记录执行耗时、退出码、stdout 和 stderr,并在执行失败时产生结构化错误信息。
|
||||
|
||||
#### Scenario: 命令正常退出
|
||||
- **WHEN** command target 执行的进程正常退出且 exit code 为 0
|
||||
- **THEN** 系统 SHALL 记录 `success=true`、`durationMs`、`statusDetail="exitCode=0"`,并进入 expect 校验
|
||||
|
||||
#### Scenario: 命令非零退出
|
||||
- **WHEN** command target 执行的进程正常退出但 exit code 为 1
|
||||
- **THEN** 系统 SHALL 记录 `success=true` 和 `statusDetail="exitCode=1"`,并由 expect.exitCode 决定 matched 结果
|
||||
|
||||
#### Scenario: 命令启动失败
|
||||
- **WHEN** command target 的 exec 不存在或无法启动
|
||||
- **THEN** 系统 SHALL 记录 `success=false`、`matched=false`,并在 failure 中写入 kind=`error`、phase=`exitCode` 和可读错误信息
|
||||
|
||||
#### Scenario: 命令超时
|
||||
- **WHEN** command target 在 timeout 时间内未结束
|
||||
- **THEN** 系统 MUST 终止该子进程,记录 `success=false`、`matched=false`,并在 failure 中写入命令超时信息
|
||||
|
||||
#### Scenario: 命令输出超限
|
||||
- **WHEN** command target 的 stdout 和 stderr 合计输出超过 `maxOutputBytes`
|
||||
- **THEN** 系统 MUST 停止收集输出并终止该检查,记录 `success=false`、`matched=false`,并在 failure 中写入输出超限信息
|
||||
|
||||
### Requirement: command expect 校验
|
||||
系统 SHALL 支持 command 专用 expect,包括 `exitCode`、`stdout` 和 `stderr`,并按 exitCode、duration、stdout、stderr 的阶段顺序快速失败。
|
||||
|
||||
#### Scenario: 默认 exitCode 成功语义
|
||||
- **WHEN** command target 未显式配置 `expect.exitCode`
|
||||
- **THEN** 系统 SHALL 使用默认 `expect.exitCode: [0]` 进行校验
|
||||
|
||||
#### Scenario: 显式 exitCode 校验
|
||||
- **WHEN** command target 配置 `expect.exitCode: [0, 2]` 且实际 exit code 为 2
|
||||
- **THEN** 系统 SHALL 判定 exitCode 阶段通过,并继续后续 expect 阶段
|
||||
|
||||
#### Scenario: exitCode 不匹配快速失败
|
||||
- **WHEN** command target 配置 `expect.exitCode: [0]` 且实际 exit code 为 1
|
||||
- **THEN** 系统 SHALL 立即返回 `matched=false`,并在 failure 中写入 phase=`exitCode`、path=`expect.exitCode`、expected 和 actual
|
||||
|
||||
#### Scenario: stdout 按配置顺序校验
|
||||
- **WHEN** command target 配置 `expect.stdout` 为两个规则,第一条通过且第二条失败
|
||||
- **THEN** 系统 SHALL 先执行第一条 stdout 规则,再执行第二条,并将 failure.path 指向失败的 `expect.stdout[1]`
|
||||
|
||||
#### Scenario: stderr 校验为空
|
||||
- **WHEN** command target 配置 `expect.stderr: [{empty: true}]` 且实际 stderr 为空字符串
|
||||
- **THEN** 系统 SHALL 判定 stderr 阶段通过
|
||||
|
||||
#### Scenario: stdout 失败后不检查 stderr
|
||||
- **WHEN** command target 同时配置 stdout 和 stderr 规则,且 stdout 规则失败
|
||||
- **THEN** 系统 SHALL 快速失败并 MUST NOT 继续执行 stderr 规则
|
||||
@@ -1,126 +0,0 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: 响应体多种校验方法
|
||||
系统 SHALL 支持对 HTTP 响应体进行五种可组合的校验方法:contains(子串)、regex(正则)、json(JSONPath)、css(CSS 选择器)、xpath(XPath)。这些方法 MUST 配置在 `expect.body` 有序数组中。
|
||||
|
||||
#### Scenario: contains 子串匹配
|
||||
- **WHEN** HTTP target 配置 `expect.body: [{contains: "healthy"}]`,且响应体包含 `"healthy"`
|
||||
- **THEN** 系统 SHALL 判定该 body 规则通过
|
||||
|
||||
#### Scenario: contains 不匹配
|
||||
- **WHEN** HTTP target 配置 `expect.body: [{contains: "healthy"}]`,且响应体不包含该文本
|
||||
- **THEN** 系统 SHALL 判定 matched 为 false,并记录该规则的 failure.path
|
||||
|
||||
#### Scenario: regex 正则匹配
|
||||
- **WHEN** HTTP target 配置 `expect.body: [{regex: '"status"\\s*:\\s*"ok"'}]`,且响应体匹配该正则
|
||||
- **THEN** 系统 SHALL 判定该 body 规则通过
|
||||
|
||||
#### Scenario: regex 不匹配
|
||||
- **WHEN** HTTP target 配置 regex body 规则,且响应体不匹配该正则
|
||||
- **THEN** 系统 SHALL 判定 matched 为 false,并记录该规则的 failure.path
|
||||
|
||||
#### Scenario: json JSONPath 等值匹配
|
||||
- **WHEN** HTTP target 配置 `expect.body: [{json: {path: "$.status", equals: "ok"}}]`,且响应 JSON 中 `$.status` 值为 `"ok"`
|
||||
- **THEN** 系统 SHALL 判定该 body 规则通过
|
||||
|
||||
#### Scenario: json JSONPath 值不匹配
|
||||
- **WHEN** HTTP target 配置 json body 规则,且提取值不符合期望
|
||||
- **THEN** 系统 SHALL 判定 matched 为 false,并记录包含 JSONPath 的 failure.path
|
||||
|
||||
#### Scenario: json 解析失败
|
||||
- **WHEN** HTTP target 配置了 json body 规则但响应体不是合法 JSON
|
||||
- **THEN** 系统 SHALL 判定 matched 为 false
|
||||
|
||||
#### Scenario: css 选择器匹配
|
||||
- **WHEN** HTTP target 配置 `expect.body: [{css: {selector: "div#health", equals: "OK"}}]`,且 HTML 中存在 `div#health` 元素文本为 `"OK"`
|
||||
- **THEN** 系统 SHALL 判定该 body 规则通过
|
||||
|
||||
#### Scenario: css 选择器匹配属性值
|
||||
- **WHEN** HTTP target 配置 css 规则带 `attr: "content"` 用于提取属性,且属性值匹配期望
|
||||
- **THEN** 系统 SHALL 判定该 body 规则通过
|
||||
|
||||
#### Scenario: css 选择器无匹配元素
|
||||
- **WHEN** HTTP target 配置了 css 选择器但 HTML 中无匹配元素
|
||||
- **THEN** 系统 SHALL 判定 matched 为 false
|
||||
|
||||
#### Scenario: xpath 表达式匹配
|
||||
- **WHEN** HTTP target 配置 `expect.body: [{xpath: {path: "/root/status/text()", equals: "ok"}}]`,且 XML 中 `/root/status` 节点文本为 `"ok"`
|
||||
- **THEN** 系统 SHALL 判定该 body 规则通过
|
||||
|
||||
#### Scenario: xpath 表达式无匹配节点
|
||||
- **WHEN** HTTP target 配置了 xpath 表达式但 XML 中无匹配节点
|
||||
- **THEN** 系统 SHALL 判定 matched 为 false
|
||||
|
||||
### Requirement: 多种 body 校验方法 AND 组合
|
||||
系统 SHALL 支持在 `expect.body` 数组中同时配置多种 body 校验方法,所有方法均通过时 matched 方为 true。
|
||||
|
||||
#### Scenario: 多种方法全部通过
|
||||
- **WHEN** HTTP target 的 `expect.body` 数组依次配置 contains、json、regex,且全部通过
|
||||
- **THEN** 系统 SHALL 判定 matched 为 true
|
||||
|
||||
#### Scenario: 多种方法任一失败
|
||||
- **WHEN** HTTP target 的 `expect.body` 数组第一条 contains 不通过,后续还有 json 规则
|
||||
- **THEN** 系统 SHALL 判定 matched 为 false,且不再检查后续 json 规则
|
||||
|
||||
### Requirement: 操作符系统
|
||||
系统 SHALL 支持对提取值和文本值使用以下操作符进行比较:equals(默认等值)、contains(子串包含)、match(正则匹配)、empty(空值判断)、exists(存在性判断)、gte/lte/gt/lt(数值比较)。
|
||||
|
||||
#### Scenario: 标量值隐式 equals
|
||||
- **WHEN** 配置的期望值为标量(字符串/数字/布尔/null),如 `equals: "ok"`
|
||||
- **THEN** 系统 SHALL 使用 equals 操作符,对实际值做严格相等比较
|
||||
|
||||
#### Scenario: 显式 contains 操作符
|
||||
- **WHEN** 配置 `{contains: "success"}`,且实际值包含 `"success"`
|
||||
- **THEN** 系统 SHALL 判定该规则通过
|
||||
|
||||
#### Scenario: 显式 match 操作符
|
||||
- **WHEN** 配置 `{match: '\\d+\\.\\d+\\.\\d+'}`,且实际值匹配该正则
|
||||
- **THEN** 系统 SHALL 判定该规则通过
|
||||
|
||||
#### Scenario: empty 操作符判断为空
|
||||
- **WHEN** 配置 `{empty: true}`,且实际值为空数组 `[]`
|
||||
- **THEN** 系统 SHALL 判定该规则通过
|
||||
|
||||
#### Scenario: empty 操作符判断非空
|
||||
- **WHEN** 配置 `{empty: false}`,且实际值为 `[1, 2]`
|
||||
- **THEN** 系统 SHALL 判定该规则通过
|
||||
|
||||
#### Scenario: exists 操作符判断存在
|
||||
- **WHEN** 配置 `{exists: false}`,且实际值不存在
|
||||
- **THEN** 系统 SHALL 判定该规则通过
|
||||
|
||||
#### Scenario: gte 数值比较
|
||||
- **WHEN** 配置 `{gte: 10}`,且实际值为 `15`(数字)
|
||||
- **THEN** 系统 SHALL 判定该规则通过
|
||||
|
||||
#### Scenario: gt/lt 数值比较
|
||||
- **WHEN** 配置 `{gt: 0, lt: 1000}`,且实际值为 `500`
|
||||
- **THEN** 系统 SHALL 对同一字段进行多操作符复合比较,全部通过则该规则通过
|
||||
|
||||
### Requirement: 响应头校验
|
||||
系统 SHALL 支持通过 `expect.headers` 配置对 HTTP 响应头进行键值规则校验,header 名称匹配 MUST 不区分大小写。
|
||||
|
||||
#### Scenario: 响应头匹配
|
||||
- **WHEN** HTTP target 配置 `expect.headers: {"Content-Type": {contains: "application/json"}}`,且响应包含该 header 且值匹配
|
||||
- **THEN** 系统 SHALL 判定 headers 阶段通过
|
||||
|
||||
#### Scenario: 响应头不匹配
|
||||
- **WHEN** HTTP target 配置 `expect.headers: {"Content-Type": {equals: "application/json"}}`,且响应 header 值为 `"text/html"`
|
||||
- **THEN** 系统 SHALL 判定 matched 为 false
|
||||
|
||||
#### Scenario: 响应头缺失
|
||||
- **WHEN** HTTP target 配置了某个 header 但响应中不存在该 header
|
||||
- **THEN** 系统 SHALL 判定 matched 为 false
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 结构化 expect 失败信息
|
||||
系统 SHALL 在任一 expect 规则失败时生成结构化 failure,用于标识失败阶段、规则路径、期望值、实际值和可读错误信息。
|
||||
|
||||
#### Scenario: body 规则失败信息
|
||||
- **WHEN** HTTP target 的 `expect.body[1].json` 规则失败
|
||||
- **THEN** failure SHALL 包含 kind=`mismatch`、phase=`body`、path 指向 `expect.body[1]`,并包含 message
|
||||
|
||||
#### Scenario: actual 值截断
|
||||
- **WHEN** 失败规则的实际值超过系统允许记录的摘要长度
|
||||
- **THEN** 系统 MUST 截断 actual 摘要,而不是持久化完整响应体或命令输出
|
||||
@@ -1,54 +0,0 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: 总览统计 API
|
||||
系统 SHALL 提供 `GET /api/summary` 端点,返回所有目标的总体统计信息。
|
||||
|
||||
#### Scenario: 获取总览统计
|
||||
- **WHEN** 客户端请求 `GET /api/summary`
|
||||
- **THEN** 系统 SHALL 返回 JSON 包含 total(总目标数)、up(正常数)、down(异常数)、avgDurationMs(所有目标平均耗时)、lastCheckTime(最近一次检查时间)
|
||||
|
||||
### Requirement: 目标列表 API
|
||||
系统 SHALL 提供 `GET /api/targets` 端点,返回所有 typed target 及其最新状态和统计摘要。
|
||||
|
||||
#### Scenario: 获取目标列表
|
||||
- **WHEN** 客户端请求 `GET /api/targets`
|
||||
- **THEN** 系统 SHALL 返回 JSON 数组,每个元素包含目标基本信息(id、name、type、target、interval)、最近一次检查结果(timestamp、success、matched、durationMs、statusDetail、failure)和统计摘要(totalChecks、availability、avgDurationMs、p99DurationMs)
|
||||
|
||||
#### Scenario: 目标无历史记录
|
||||
- **WHEN** 某目标尚未执行过任何检查
|
||||
- **THEN** 其 latestCheck 为 null,stats 中 totalChecks 为 0
|
||||
|
||||
### Requirement: 历史记录 API
|
||||
系统 SHALL 提供 `GET /api/targets/:id/history` 端点,返回指定目标的最近 N 条检查记录。
|
||||
|
||||
#### Scenario: 获取最近历史记录
|
||||
- **WHEN** 客户端请求 `GET /api/targets/1/history?limit=20`
|
||||
- **THEN** 系统 SHALL 返回最多 20 条检查记录,按时间倒序排列,且每条包含 success、matched、durationMs、statusDetail 和 failure
|
||||
|
||||
#### Scenario: 使用默认 limit
|
||||
- **WHEN** 客户端请求 `GET /api/targets/1/history`(未指定 limit)
|
||||
- **THEN** 系统 SHALL 默认返回最近 20 条记录
|
||||
|
||||
### Requirement: 趋势聚合 API
|
||||
系统 SHALL 提供 `GET /api/targets/:id/trend` 端点,返回指定目标按小时聚合的趋势数据。
|
||||
|
||||
#### Scenario: 获取 24 小时趋势
|
||||
- **WHEN** 客户端请求 `GET /api/targets/1/trend?hours=24`
|
||||
- **THEN** 系统 SHALL 返回按小时分组的聚合数据,每个数据点包含 hour、avgDurationMs、availability、totalChecks
|
||||
|
||||
#### Scenario: 使用默认时间范围
|
||||
- **WHEN** 客户端请求 `GET /api/targets/1/trend`(未指定 hours)
|
||||
- **THEN** 系统 SHALL 默认返回最近 24 小时的趋势数据
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 失败信息 API 契约
|
||||
系统 SHALL 通过 API 返回结构化 failure 信息,供 Dashboard 展示和后续排查。
|
||||
|
||||
#### Scenario: 返回 expect 不匹配信息
|
||||
- **WHEN** 最近一次检查结果包含 failure.kind=`mismatch`
|
||||
- **THEN** `/api/targets` 和 `/api/targets/:id/history` SHALL 返回该 failure 的 kind、phase、path、expected、actual、message 字段
|
||||
|
||||
#### Scenario: 无失败信息
|
||||
- **WHEN** 检查结果 success=true 且 matched=true
|
||||
- **THEN** API SHALL 返回 failure 为 null
|
||||
@@ -1,102 +0,0 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: YAML 配置文件格式
|
||||
系统 SHALL 支持通过 YAML 配置文件定义全部运行参数,包括 server 配置、runtime 配置、checker 默认值和 typed target 列表。target MUST 使用 `type` 字段声明 checker 类型,HTTP 领域字段 MUST 放在 `http` 分组,command 领域字段 MUST 放在 `command` 分组。
|
||||
|
||||
#### Scenario: 完整配置文件解析
|
||||
- **WHEN** 系统启动并读取包含 server、runtime、defaults、targets 的 YAML 配置文件
|
||||
- **THEN** 系统 SHALL 正确解析所有字段并用于初始化服务、调度引擎和对应 checker runner
|
||||
|
||||
#### Scenario: 最简 HTTP 配置文件解析
|
||||
- **WHEN** 系统读取只包含一个 `type: http` target 和 `http.url` 的 YAML 配置文件(省略 server、runtime、defaults 和 expect)
|
||||
- **THEN** 系统 SHALL 使用内置默认值填充未指定的字段(host=127.0.0.1, port=3000, dir=./data, interval=30s, timeout=10s, runtime.maxConcurrentChecks=20, http.method=GET, http.maxBodyBytes=100MB)
|
||||
|
||||
#### Scenario: 最简 command 配置文件解析
|
||||
- **WHEN** 系统读取只包含一个 `type: command` target 和 `command.exec` 的 YAML 配置文件
|
||||
- **THEN** 系统 SHALL 使用内置默认值填充未指定的字段(interval=30s, timeout=10s, command.cwd 为配置文件所在目录, command.maxOutputBytes=100MB)
|
||||
|
||||
#### Scenario: per-target 配置覆盖全局默认值
|
||||
- **WHEN** 某个 target 指定 interval、timeout 或对应领域分组中的默认字段
|
||||
- **THEN** 该 target SHALL 使用其自身的值,不受 defaults 中对应字段影响
|
||||
|
||||
### Requirement: 配置校验
|
||||
系统 SHALL 在启动时对 YAML 配置进行完整校验,校验失败时以非零状态退出并输出清晰的错误信息。
|
||||
|
||||
#### Scenario: target 缺少必填字段
|
||||
- **WHEN** YAML 中某个 target 缺少 name 或 type 字段
|
||||
- **THEN** 系统 SHALL 以错误退出,提示哪个 target 缺少哪个字段
|
||||
|
||||
#### Scenario: HTTP target 缺少 url
|
||||
- **WHEN** YAML 中某个 target 配置 `type: http` 但缺少 `http.url`
|
||||
- **THEN** 系统 SHALL 以错误退出,提示该 target 缺少 http.url 字段
|
||||
|
||||
#### Scenario: command target 缺少 exec
|
||||
- **WHEN** YAML 中某个 target 配置 `type: command` 但缺少 `command.exec`
|
||||
- **THEN** 系统 SHALL 以错误退出,提示该 target 缺少 command.exec 字段
|
||||
|
||||
#### Scenario: target type 非法
|
||||
- **WHEN** YAML 中某个 target 的 type 不是 `http` 或 `command`
|
||||
- **THEN** 系统 SHALL 以错误退出,提示不支持的 target type
|
||||
|
||||
#### Scenario: target name 重复
|
||||
- **WHEN** YAML 中存在两个 name 相同的 target
|
||||
- **THEN** 系统 SHALL 以错误退出,提示重复的 name
|
||||
|
||||
#### Scenario: interval 格式非法
|
||||
- **WHEN** interval 或 timeout 值不是有效的时长格式(如 `30s`、`5m`、`500ms`)
|
||||
- **THEN** 系统 SHALL 以错误退出并提示格式错误
|
||||
|
||||
#### Scenario: maxConcurrentChecks 非法
|
||||
- **WHEN** runtime.maxConcurrentChecks 不是正整数
|
||||
- **THEN** 系统 SHALL 以错误退出并提示 runtime.maxConcurrentChecks 格式错误
|
||||
|
||||
#### Scenario: size 格式非法
|
||||
- **WHEN** maxBodyBytes 或 maxOutputBytes 值不是有效的 size 格式
|
||||
- **THEN** 系统 SHALL 以错误退出并提示支持 B、KB、MB、GB 格式
|
||||
|
||||
### Requirement: expect 配置增强
|
||||
系统 SHALL 支持 typed target 的领域专用 expect 配置,包括 HTTP 的 `status`、`headers`、`body` 和 command 的 `exitCode`、`stdout`、`stderr`。内容类 expect MUST 使用数组表达配置顺序。
|
||||
|
||||
#### Scenario: 解析 HTTP expect 配置
|
||||
- **WHEN** YAML 配置文件中 HTTP target 的 expect 包含 status、headers、body 规则数组及内部方法
|
||||
- **THEN** 系统 SHALL 正确解析并存储为 HTTP target 的 expect 字段
|
||||
|
||||
#### Scenario: 解析 command expect 配置
|
||||
- **WHEN** YAML 配置文件中 command target 的 expect 包含 exitCode、stdout 和 stderr 规则数组
|
||||
- **THEN** 系统 SHALL 正确解析并存储为 command target 的 expect 字段
|
||||
|
||||
#### Scenario: 解析 body 有序规则数组
|
||||
- **WHEN** YAML 中 HTTP target 配置 `expect.body` 为 contains、json、regex 三个数组项
|
||||
- **THEN** 系统 SHALL 保留数组顺序,供执行阶段按配置顺序快速失败
|
||||
|
||||
#### Scenario: 不配置 HTTP status
|
||||
- **WHEN** HTTP target 未配置 `expect.status`
|
||||
- **THEN** 系统 SHALL 在执行 expect 时使用默认 `status: [200]` 语义
|
||||
|
||||
#### Scenario: 不配置 command exitCode
|
||||
- **WHEN** command target 未配置 `expect.exitCode`
|
||||
- **THEN** 系统 SHALL 在执行 expect 时使用默认 `exitCode: [0]` 语义
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: size 配置解析
|
||||
系统 SHALL 支持使用单位字符串配置读取上限,单位包括 `B`、`KB`、`MB` 和 `GB`。
|
||||
|
||||
#### Scenario: 解析 MB
|
||||
- **WHEN** YAML 中配置 `maxBodyBytes: "100MB"`
|
||||
- **THEN** 系统 SHALL 将其解析为 104857600 bytes
|
||||
|
||||
#### Scenario: 解析 KB
|
||||
- **WHEN** YAML 中配置 `maxOutputBytes: "512KB"`
|
||||
- **THEN** 系统 SHALL 将其解析为 524288 bytes
|
||||
|
||||
### Requirement: runtime 并发配置
|
||||
系统 SHALL 支持 `runtime.maxConcurrentChecks` 配置全局最大并发检查数。
|
||||
|
||||
#### Scenario: 使用默认并发限制
|
||||
- **WHEN** YAML 中未配置 runtime.maxConcurrentChecks
|
||||
- **THEN** 系统 SHALL 使用默认值 20
|
||||
|
||||
#### Scenario: 配置并发限制
|
||||
- **WHEN** YAML 中配置 `runtime.maxConcurrentChecks: 5`
|
||||
- **THEN** 系统 SHALL 将全局最大并发检查数设置为 5
|
||||
@@ -1,71 +0,0 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: 总览统计卡片
|
||||
Dashboard SHALL 在页面顶部展示总览统计卡片,包含总目标数、正常数、异常数和平均耗时。
|
||||
|
||||
#### Scenario: 展示统计卡片
|
||||
- **WHEN** 用户打开 Dashboard 页面
|
||||
- **THEN** 页面顶部 SHALL 显示 4 个统计卡片:全部目标数、正常目标数、异常目标数、所有目标平均耗时
|
||||
|
||||
#### Scenario: 统计数据自动刷新
|
||||
- **WHEN** 页面处于打开状态
|
||||
- **THEN** 统计卡片 SHALL 每 5-10 秒自动刷新数据
|
||||
|
||||
### Requirement: 目标列表表格
|
||||
Dashboard SHALL 展示所有 checker target 的列表表格,包含名称、类型、目标摘要、当前状态、最新耗时、最近失败原因和迷你趋势线。
|
||||
|
||||
#### Scenario: 展示目标列表
|
||||
- **WHEN** 用户打开 Dashboard 页面
|
||||
- **THEN** 页面 SHALL 显示表格,每行包含目标名称、类型、目标摘要、状态指示圆点(UP / DOWN)、最新耗时值、最近失败原因摘要、迷你 Sparkline 趋势线
|
||||
|
||||
#### Scenario: 状态指示圆点
|
||||
- **WHEN** 目标最近一次检查 success=true 且 matched=true
|
||||
- **THEN** 状态圆点 SHALL 显示为绿色(UP)
|
||||
- **WHEN** 目标最近一次检查 success=false 或 matched=false
|
||||
- **THEN** 状态圆点 SHALL 显示为红色(DOWN)
|
||||
|
||||
### Requirement: 可展开的目标详情面板
|
||||
Dashboard SHALL 支持在目标列表中展开某行,显示该目标的详细状态、统计摘要、趋势图和最近历史记录。
|
||||
|
||||
#### Scenario: 展开目标详情
|
||||
- **WHEN** 用户点击目标列表中的某一行
|
||||
- **THEN** 该行下方 SHALL 展开详情面板,包含:可用率百分比、平均耗时、P99 耗时、24 小时耗时趋势折线图、最近 5-10 条检查记录列表、领域状态详情和失败信息
|
||||
|
||||
#### Scenario: 收起目标详情
|
||||
- **WHEN** 用户再次点击已展开的目标行
|
||||
- **THEN** 详情面板 SHALL 收起
|
||||
|
||||
#### Scenario: 趋势图按需加载
|
||||
- **WHEN** 用户展开某个目标的详情面板
|
||||
- **THEN** 系统 SHALL 此时请求该目标的趋势数据,而非页面加载时预加载所有目标的趋势数据
|
||||
|
||||
### Requirement: 历史记录展示
|
||||
Dashboard SHALL 在目标详情面板中展示最近的检查记录,包含时间、领域状态详情、耗时、成功/失败标记和失败信息。
|
||||
|
||||
#### Scenario: 展示历史记录
|
||||
- **WHEN** 用户展开目标详情面板
|
||||
- **THEN** 面板 SHALL 显示最近检查记录列表,每条包含时间戳、statusDetail(如 HTTP 200 或 exitCode=1)、耗时毫秒数、UP/DOWN 标记和 failure.message(如存在)
|
||||
|
||||
### Requirement: 趋势图可视化
|
||||
Dashboard SHALL 使用 recharts 库渲染趋势图,包括目标列表中的迷你 Sparkline 和详情面板中的完整折线图。
|
||||
|
||||
#### Scenario: 表格行内迷你趋势线
|
||||
- **WHEN** 目标列表表格渲染
|
||||
- **THEN** 每行 SHALL 包含一个基于 recharts 的迷你折线图,展示最近的耗时趋势
|
||||
|
||||
#### Scenario: 详情面板完整趋势图
|
||||
- **WHEN** 用户展开目标详情面板
|
||||
- **THEN** 面板 SHALL 展示基于 recharts 的完整折线图,X 轴为时间(小时),Y 轴为平均耗时,并标注可用率
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: checker 类型展示
|
||||
Dashboard SHALL 在列表和详情中明确展示 target 的 checker 类型。
|
||||
|
||||
#### Scenario: 展示 HTTP 类型
|
||||
- **WHEN** 目标 type 为 `http`
|
||||
- **THEN** Dashboard SHALL 在类型列显示 HTTP,并将目标摘要显示为 URL
|
||||
|
||||
#### Scenario: 展示 command 类型
|
||||
- **WHEN** 目标 type 为 `command`
|
||||
- **THEN** Dashboard SHALL 在类型列显示 Command,并将目标摘要显示为命令摘要
|
||||
@@ -1,74 +0,0 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: SQLite 数据库初始化
|
||||
系统 SHALL 使用 Bun 内置 `bun:sqlite` 模块在配置的数据目录下创建 SQLite 数据库文件,并以 WAL 模式运行。数据库 schema MUST 支持 typed checker target 和结构化检查结果。
|
||||
|
||||
#### Scenario: 首次启动创建数据库
|
||||
- **WHEN** 指定的数据目录下不存在数据库文件
|
||||
- **THEN** 系统 SHALL 创建数据库文件并初始化包含 type、target、config、duration_ms、status_detail、failure 等字段的 targets 和 check_results 表
|
||||
|
||||
#### Scenario: 数据目录不存在
|
||||
- **WHEN** 配置的数据目录路径不存在
|
||||
- **THEN** 系统 SHALL 自动创建该目录
|
||||
|
||||
#### Scenario: 数据库已存在时启动
|
||||
- **WHEN** 数据库文件已存在
|
||||
- **THEN** 系统 SHALL 直接打开数据库,不重新建表
|
||||
|
||||
### Requirement: targets 表同步
|
||||
系统 SHALL 在启动时将 YAML 配置中的目标列表同步到 SQLite targets 表,并持久化 target 类型、展示摘要、领域配置、调度配置和 expect 配置。
|
||||
|
||||
#### Scenario: 首次同步目标
|
||||
- **WHEN** 数据库为空且 YAML 中定义了 N 个 typed target
|
||||
- **THEN** 系统 SHALL 将所有目标插入 targets 表,包含 name、type、target、config、interval_ms、timeout_ms 和 expect
|
||||
|
||||
#### Scenario: 配置变更后重新同步
|
||||
- **WHEN** YAML 配置发生变更(新增、删除或修改目标)后重启
|
||||
- **THEN** 系统 SHALL 根据 name 字段匹配:新增的插入、删除的移除、修改的更新
|
||||
|
||||
### Requirement: check_results 表追加写入
|
||||
系统 SHALL 将每次检查结果追加写入 check_results 表,不更新或删除已有记录。
|
||||
|
||||
#### Scenario: 写入检查结果
|
||||
- **WHEN** 一次 checker 执行完成
|
||||
- **THEN** 系统 SHALL 插入一条包含 target_id、timestamp、success、matched、duration_ms、status_detail、failure 的记录
|
||||
|
||||
#### Scenario: 写入结构化失败信息
|
||||
- **WHEN** checker 执行失败或 expect 不匹配
|
||||
- **THEN** 系统 SHALL 将首个失败原因序列化写入 failure 字段
|
||||
|
||||
### Requirement: 聚合查询支持
|
||||
数据存储 SHALL 支持按时间段聚合查询,用于计算可用率、平均耗时、P99 耗时等统计指标。
|
||||
|
||||
#### Scenario: 计算目标可用率
|
||||
- **WHEN** 查询某目标在指定时间范围内的可用率
|
||||
- **THEN** 系统 SHALL 返回 UP (success=true AND matched=true) 的记录数占总记录数的百分比
|
||||
|
||||
#### Scenario: 计算目标平均耗时
|
||||
- **WHEN** 查询某目标在指定时间范围内的平均耗时
|
||||
- **THEN** 系统 SHALL 返回 duration_ms 的平均值(仅计算 success=true 的记录)
|
||||
|
||||
#### Scenario: 按小时聚合趋势数据
|
||||
- **WHEN** 查询某目标在指定时间范围内的趋势数据
|
||||
- **THEN** 系统 SHALL 返回按小时分组的聚合数据,包括每小时的平均耗时和可用率
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 目标展示摘要持久化
|
||||
数据存储 SHALL 为每个 target 持久化一个领域无关的展示摘要字段 `target`。
|
||||
|
||||
#### Scenario: HTTP target 展示摘要
|
||||
- **WHEN** 同步 HTTP target
|
||||
- **THEN** targets.target SHALL 存储该 target 的 URL
|
||||
|
||||
#### Scenario: command target 展示摘要
|
||||
- **WHEN** 同步 command target
|
||||
+- **THEN** targets.target SHALL 存储由 exec 和 args 组成的命令摘要
|
||||
|
||||
#### Scenario: HTTP target config 序列化
|
||||
- **WHEN** 同步 HTTP target
|
||||
- **THEN** targets.config SHALL 存储 JSON,包含 url、method、headers、body、maxBodyBytes
|
||||
|
||||
#### Scenario: command target config 序列化
|
||||
- **WHEN** 同步 command target
|
||||
- **THEN** targets.config SHALL 存储 JSON,包含 exec、args、cwd、env、maxOutputBytes
|
||||
@@ -1,128 +0,0 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: 组内并发拨测
|
||||
系统 SHALL 在每次调度 tick 时并发执行同组内目标的检查,但实际同时运行的检查数 MUST 受全局 `runtime.maxConcurrentChecks` 限制。
|
||||
|
||||
#### Scenario: 同组目标并发执行
|
||||
- **WHEN** 调度器触发一次 tick,该组有 3 个目标,且全局并发余量至少为 3
|
||||
- **THEN** 系统 SHALL 同时执行 3 个 checker,而非顺序执行
|
||||
|
||||
#### Scenario: 单个目标失败不影响同组其他目标
|
||||
- **WHEN** 同组中某个目标的检查请求超时或失败
|
||||
- **THEN** 其他目标的检查 SHALL 正常完成并记录结果
|
||||
|
||||
#### Scenario: 全局并发限制生效
|
||||
- **WHEN** 调度器同时触发 10 个目标且 runtime.maxConcurrentChecks 为 3
|
||||
- **THEN** 系统 MUST 同时最多运行 3 个检查,其余检查等待并发槽位释放
|
||||
|
||||
### Requirement: HTTP 拨测执行
|
||||
系统 SHALL 对 `type: http` 的目标执行 HTTP 请求,支持 GET、POST、PUT、DELETE、PATCH、HEAD 方法,并携带 `http.headers` 和 `http.body`。
|
||||
|
||||
#### Scenario: 执行 GET 请求
|
||||
- **WHEN** HTTP target 配置 http.method 为 GET
|
||||
- **THEN** 系统 SHALL 发送 GET 请求到 http.url
|
||||
|
||||
#### Scenario: 执行 POST 请求带 body
|
||||
- **WHEN** HTTP target 配置 http.method 为 POST 且指定了 http.body 和 Content-Type header
|
||||
- **THEN** 系统 SHALL 发送带指定 body 的 POST 请求
|
||||
|
||||
#### Scenario: 携带自定义 headers
|
||||
- **WHEN** HTTP target 配置了 http.headers(如 Authorization)
|
||||
- **THEN** 系统 SHALL 在请求中包含所有配置的 headers
|
||||
|
||||
#### Scenario: HTTP body 读取上限
|
||||
- **WHEN** HTTP response body 超过该 target 的 maxBodyBytes
|
||||
- **THEN** 系统 MUST 停止读取并记录 `success=false`、`matched=false` 和结构化输出超限错误
|
||||
|
||||
### Requirement: 请求超时控制
|
||||
系统 SHALL 对每次 checker 执行实施超时控制,超时时间使用目标配置的 timeout 值。
|
||||
|
||||
#### Scenario: HTTP 请求超时
|
||||
- **WHEN** HTTP 请求在 timeout 时间内未收到响应
|
||||
- **THEN** 系统 SHALL 中止该请求,记录为失败并标注超时错误
|
||||
|
||||
#### Scenario: command 执行超时
|
||||
- **WHEN** command 进程在 timeout 时间内未退出
|
||||
- **THEN** 系统 MUST 终止该子进程,记录为失败并标注超时错误
|
||||
|
||||
#### Scenario: 请求在超时前完成
|
||||
- **WHEN** checker 在超时前完成执行
|
||||
- **THEN** 系统 SHALL 正常记录执行结果并进入 expect 校验
|
||||
|
||||
### Requirement: expect 校验
|
||||
系统 SHALL 在 checker 执行完成后根据目标类型的 expect 配置校验观测结果,校验结果和首个失败原因记入 check result。
|
||||
|
||||
#### Scenario: HTTP 默认状态码
|
||||
- **WHEN** HTTP target 未配置 `expect.status`
|
||||
- **THEN** 系统 SHALL 按默认 `status: [200]` 校验响应状态码
|
||||
|
||||
#### Scenario: 校验 HTTP 状态码
|
||||
- **WHEN** HTTP target 配置了 `expect.status: [200, 201]`
|
||||
- **THEN** 系统 SHALL 检查响应状态码是否在列表中,将匹配结果记录到 matched 字段
|
||||
|
||||
#### Scenario: 校验 HTTP 响应头
|
||||
- **WHEN** HTTP target 配置了 `expect.headers: {"Content-Type": {contains: "application/json"}}`
|
||||
- **THEN** 系统 SHALL 检查响应头是否符合指定规则,全部匹配时继续后续阶段
|
||||
|
||||
#### Scenario: 校验 HTTP 响应体
|
||||
- **WHEN** HTTP target 配置了有序 `expect.body` 规则数组
|
||||
- **THEN** 系统 SHALL 按数组顺序执行 body 规则,任一失败立即记录 failure 并停止后续规则
|
||||
|
||||
#### Scenario: command 默认 exitCode
|
||||
- **WHEN** command target 未配置 `expect.exitCode`
|
||||
- **THEN** 系统 SHALL 按默认 `exitCode: [0]` 校验命令退出码
|
||||
|
||||
#### Scenario: 校验 command stdout
|
||||
- **WHEN** command target 配置了有序 `expect.stdout` 规则数组
|
||||
- **THEN** 系统 SHALL 按数组顺序执行 stdout 规则,任一失败立即记录 failure 并停止后续规则
|
||||
|
||||
#### Scenario: 校验耗时阈值
|
||||
- **WHEN** 目标配置了 `expect.maxDurationMs`
|
||||
- **THEN** 系统 SHALL 检查实际 durationMs 是否超过阈值,将匹配结果记录到 matched 字段
|
||||
|
||||
#### Scenario: 多条 expect 规则
|
||||
- **WHEN** 目标同时配置状态、duration、元数据和内容规则
|
||||
- **THEN** 系统 SHALL 所有规则全部通过时 matched 为 true,任一不通过则为 false 并记录首个失败原因
|
||||
|
||||
### Requirement: Body 校验按需解析
|
||||
系统 SHALL 仅在 HTTP target 配置了 body 校验且 status、duration、headers 阶段均通过时才读取并解析响应体,避免不必要的读取和解析开销。
|
||||
|
||||
#### Scenario: status 失败时不读取 body
|
||||
- **WHEN** HTTP target 的 status 阶段不匹配
|
||||
- **THEN** 系统 SHALL 立即返回 matched=false,且 MUST NOT 读取 response body
|
||||
|
||||
#### Scenario: 仅配置 contains 时不解析 JSON
|
||||
- **WHEN** HTTP target 仅配置 body contains 规则而未配置 json/css/xpath 规则
|
||||
- **THEN** 系统 SHALL 不执行 JSON.parse 或 HTML/XML 解析
|
||||
|
||||
#### Scenario: 配置 json 时解析 JSON 失败
|
||||
- **WHEN** HTTP target 配置了 body json 规则但响应体不是合法 JSON
|
||||
- **THEN** 系统 SHALL 判定 matched 为 false,并记录 json 规则对应的 failure.path
|
||||
|
||||
### Requirement: 拨测结果记录
|
||||
系统 SHALL 在每次 checker 完成后,将结果写入 SQLite 数据存储,包含 target_id、timestamp、success、matched、duration_ms、status_detail、failure 字段。
|
||||
|
||||
#### Scenario: 成功检查结果记录
|
||||
- **WHEN** checker 成功执行且 expect 全部匹配
|
||||
- **THEN** 系统 SHALL 记录 success=true、matched=true、duration_ms、status_detail,failure 为 null
|
||||
|
||||
#### Scenario: 执行失败结果记录
|
||||
- **WHEN** checker 执行失败(网络错误、超时、命令启动失败、输出超限等)
|
||||
- **THEN** 系统 SHALL 记录 success=false、matched=false、failure.kind="error" 和具体错误信息
|
||||
|
||||
#### Scenario: expect 不匹配结果记录
|
||||
- **WHEN** checker 执行成功但 expect 不匹配
|
||||
- **THEN** 系统 SHALL 记录 success=true、matched=false、failure.kind="mismatch" 和具体不匹配信息
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: runner 选择
|
||||
系统 SHALL 根据 target.type 选择对应 runner 执行检查。
|
||||
|
||||
#### Scenario: 选择 HTTP runner
|
||||
- **WHEN** target.type 为 `http`
|
||||
- **THEN** 系统 SHALL 使用 HTTP runner 执行该目标
|
||||
|
||||
#### Scenario: 选择 command runner
|
||||
- **WHEN** target.type 为 `command`
|
||||
- **THEN** 系统 SHALL 使用 command runner 执行该目标
|
||||
@@ -1,50 +0,0 @@
|
||||
## 1. 类型与配置契约
|
||||
|
||||
- [x] 1.1 重构 checker 类型定义为 `http` 与 `command` 判别联合,并新增 `CheckFailure`、`durationMs`、`statusDetail` 等结果字段,将 `maxLatencyMs` 重命名为 `maxDurationMs`
|
||||
- [x] 1.2 更新 YAML 配置类型,新增 `runtime.maxConcurrentChecks`、`defaults.http`、`defaults.command` 和 typed target 配置
|
||||
- [x] 1.3 实现 size 解析工具,支持 `B`、`KB`、`MB`、`GB` 并覆盖 `100MB=104857600` 的测试
|
||||
- [x] 1.4 重构 config-loader 校验逻辑,移除顶层 HTTP 字段支持并校验 type、http.url、command.exec、并发和 size 格式;ResolvedConfig 需携带配置文件目录,用于 command cwd 相对路径解析
|
||||
- [x] 1.5 更新配置解析测试,覆盖最简 HTTP、最简 command、per-target 覆盖、默认值、非法 type、缺失字段和非法 size
|
||||
|
||||
## 2. Expect 与失败信息
|
||||
|
||||
- [x] 2.1 抽取通用值操作符,使 equals、contains、match、empty、exists、gte、lte、gt、lt 可复用于 header、body、stdout 和 stderr
|
||||
- [x] 2.2 将 HTTP `expect.body` 重构为有序规则数组,并支持 contains、regex、json、css、xpath 规则
|
||||
- [x] 2.3 实现 HTTP expect pipeline,按 status、duration、headers、body[] 顺序执行并应用默认 `status: [200]`
|
||||
- [x] 2.4 实现 command expect pipeline,按 exitCode、duration、stdout[]、stderr[] 顺序执行并应用默认 `exitCode: [0]`
|
||||
- [x] 2.5 实现结构化 failure 生成与 actual 摘要截断,区分 `error` 和 `mismatch`
|
||||
- [x] 2.6 将 expect 相关文件(body、http、command、failure)移入 `checker/expect/` 子目录,统一导入路径并更新测试文件引用
|
||||
- [x] 2.7 更新 expect 单元测试,覆盖规则顺序、快速失败、默认 status、默认 exitCode、失败 path 和 actual 截断
|
||||
|
||||
## 3. Runner 与调度引擎
|
||||
|
||||
- [x] 3.1 将现有 fetcher 拆分或重命名为 HTTP runner,并改为读取 status、duration、headers 后再按需读取 body
|
||||
- [x] 3.2 在 HTTP runner 中实现 maxBodyBytes 限制、超时处理、statusDetail 和结构化执行错误
|
||||
- [x] 3.3 新增 command runner,使用 `exec + args` 执行本地命令且不经过 shell
|
||||
- [x] 3.4 在 command runner 中实现 cwd 相对配置文件目录解析、env 覆盖、timeout kill 和 maxOutputBytes 合计限制
|
||||
- [x] 3.5 重构 ProbeEngine 按 target.type 选择 runner,并引入全局 maxConcurrentChecks 并发池
|
||||
- [x] 3.6 更新 runner 和 engine 测试,覆盖 HTTP 快速失败不读 body、command 非零退出、启动失败、超时、输出超限和并发限制
|
||||
|
||||
## 4. 存储与 API
|
||||
|
||||
- [x] 4.1 重建 SQLite schema,使用 targets 的 type、target、config 字段和 check_results 的 duration_ms、status_detail、failure 字段
|
||||
- [x] 4.2 更新目标同步逻辑,持久化 HTTP URL 摘要和 command 命令摘要
|
||||
- [x] 4.3 更新检查结果写入和聚合查询,使用 duration_ms 计算平均耗时、P99 耗时和趋势数据
|
||||
- [x] 4.4 更新 shared API 类型,将 avgLatencyMs、p99LatencyMs、latencyMs、statusCode 替换为 avgDurationMs、p99DurationMs、durationMs、statusDetail 和 failure
|
||||
- [x] 4.5 更新 API handler 映射逻辑,返回 type、target、durationMs、statusDetail、failure 和新的统计字段
|
||||
- [x] 4.6 更新 store 和 API 测试,覆盖结构化 failure 入库、目标摘要、summary、targets、history 和 trend 响应
|
||||
|
||||
## 5. Dashboard 与文档
|
||||
|
||||
- [x] 5.1 更新 Dashboard 总览卡片、目标表格和详情面板,将 URL/方法/延迟改为类型、目标、耗时和失败原因展示
|
||||
- [x] 5.2 更新趋势图和 Sparkline 数据字段,从 latency 切换为 duration
|
||||
- [x] 5.3 更新前端类型引用和组件测试或相关断言,覆盖 HTTP 与 command target 展示
|
||||
- [x] 5.4 更新 README 的项目说明、配置说明、目标状态判定、API 字段和已知限制
|
||||
- [x] 5.5 更新 `probes.example.yaml`,提供 HTTP 与 command typed target 示例以及 100MB 默认说明
|
||||
- [x] 5.6 更新 smoke test 配置和断言,确保生产 executable 可使用新配置启动并服务 API 与 Dashboard
|
||||
|
||||
## 6. 质量验证
|
||||
|
||||
- [x] 6.1 运行 `bun run check`,修复类型检查、lint、格式检查和单元测试问题
|
||||
- [x] 6.2 运行 `bun run verify`,修复生产构建和 smoke test 问题
|
||||
- [x] 6.3 复查 OpenSpec change 与实现一致性,确认所有任务完成且 README、测试和示例同步更新
|
||||
@@ -1,2 +0,0 @@
|
||||
schema: spec-driven
|
||||
created: 2026-05-09
|
||||
@@ -1,112 +0,0 @@
|
||||
## 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 会打包进二进制,对最终产物影响有限
|
||||
@@ -1,39 +0,0 @@
|
||||
## Why
|
||||
|
||||
当前 expect 规则仅有 `status`、`bodyContains`、`maxLatencyMs` 三条,无法满足 API 网关拨测中对 JSON 返回值字段校验、HTML 页面内容校验、响应头校验等常见需求。body 校验能力单薄(仅子串匹配),需要增强为多种可组合的校验方法,覆盖主流响应格式。
|
||||
|
||||
## What Changes
|
||||
|
||||
- 新增 `headers` 规则,支持按响应头键值对校验
|
||||
- 重构 body 校验:将独立的 `bodyContains` 移至 `body` 分组下,新增五种 body 校验方法:
|
||||
- `contains`:子串匹配(从原 `bodyContains` 迁移)
|
||||
- `regex`:正则表达式全文匹配
|
||||
- `json`:JSONPath 提取值后比较
|
||||
- `css`:CSS 选择器提取 HTML 元素文本/属性后比较
|
||||
- `xpath`:XPath 提取 XML/HTML 节点后比较
|
||||
- body 五种方法可任意组合,AND 串联
|
||||
- 新增操作符系统:`equals`(默认)、`contains`、`match`(正则)、`empty`、`exists`、`gte`、`lte`、`gt`、`lt`
|
||||
- 新增依赖:`cheerio`(CSS 选择器)、`xpath` + `@xmldom/xmldom`(XPath 引擎)
|
||||
- **BREAKING**:`expect.bodyContains` 迁移至 `expect.body.contains`
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
- `expect-body-checkers`:body 响应校验方法集(contains/regex/json/css/xpath)及操作符系统
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
- `probe-config`:expect 配置 schema 变更,新增 headers/body 分组,bodyContains 迁移
|
||||
- `probe-engine`:checkExpect 函数扩展,支持新的 body 校验方法和操作符
|
||||
|
||||
## Impact
|
||||
|
||||
- 类型定义:`src/server/checker/types.ts`(ExpectConfig/BodyExpectConfig/ExpectOperator)
|
||||
- 配置加载:`src/server/checker/config-loader.ts`(解析新的 expect 结构)
|
||||
- 拨测执行:`src/server/checker/fetcher.ts`(checkExpect 扩展)
|
||||
- 数据存储:`src/server/checker/store.ts`(expect JSON 序列化兼容)
|
||||
- 前端展示:状态判定逻辑不变(matched 字段语义不变)
|
||||
- 配置文件:`probes.example.yaml`(更新示例)
|
||||
- README.md:更新配置文档
|
||||
- 依赖:`package.json` 新增 cheerio、xpath、@xmldom/xmldom
|
||||
@@ -1,113 +0,0 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 响应体多种校验方法
|
||||
系统 SHALL 支持对 HTTP 响应体进行五种可组合的校验方法:contains(子串)、regex(正则)、json(JSONPath)、css(CSS 选择器)、xpath(XPath),配置在 `expect.body` 分组下。
|
||||
|
||||
#### Scenario: contains 子串匹配
|
||||
- **WHEN** 目标配置 `expect.body.contains: "healthy"`,且响应体包含 `"healthy"`
|
||||
- **THEN** 系统 SHALL 判定 matched 为 true
|
||||
|
||||
#### Scenario: contains 不匹配
|
||||
- **WHEN** 目标配置 `expect.body.contains: "healthy"`,且响应体不包含该文本
|
||||
- **THEN** 系统 SHALL 判定 matched 为 false
|
||||
|
||||
#### Scenario: regex 正则匹配
|
||||
- **WHEN** 目标配置 `expect.body.regex: '"status"\\s*:\\s*"ok"'`,且响应体匹配该正则
|
||||
- **THEN** 系统 SHALL 判定 matched 为 true
|
||||
|
||||
#### Scenario: regex 不匹配
|
||||
- **WHEN** 目标配置 `expect.body.regex: '"status"\\s*:\\s*"ok"'`,且响应体不匹配该正则
|
||||
- **THEN** 系统 SHALL 判定 matched 为 false
|
||||
|
||||
#### Scenario: json JSONPath 等值匹配
|
||||
- **WHEN** 目标配置 `expect.body.json: {"$.status": "ok"}`,且响应 JSON 中 `$.status` 值为 `"ok"`
|
||||
- **THEN** 系统 SHALL 判定 matched 为 true
|
||||
|
||||
#### Scenario: json JSONPath 值不匹配
|
||||
- **WHEN** 目标配置 `expect.body.json: {"$.status": "ok"}`,且响应 JSON 中 `$.status` 值为 `"error"`
|
||||
- **THEN** 系统 SHALL 判定 matched 为 false
|
||||
|
||||
#### Scenario: json 解析失败
|
||||
- **WHEN** 目标配置了 `expect.body.json` 但响应体不是合法 JSON
|
||||
- **THEN** 系统 SHALL 判定 matched 为 false
|
||||
|
||||
#### Scenario: css 选择器匹配
|
||||
- **WHEN** 目标配置 `expect.body.css: {"div#health": "OK"}`,且 HTML 中存在 `div#health` 元素文本为 `"OK"`
|
||||
- **THEN** 系统 SHALL 判定 matched 为 true
|
||||
|
||||
#### Scenario: css 选择器匹配属性值
|
||||
- **WHEN** 目标配置 css 规则带 `attr: "content"` 用于提取属性,且属性值匹配期望
|
||||
- **THEN** 系统 SHALL 判定 matched 为 true
|
||||
|
||||
#### Scenario: css 选择器无匹配元素
|
||||
- **WHEN** 目标配置了 css 选择器但 HTML 中无匹配元素
|
||||
- **THEN** 系统 SHALL 判定 matched 为 false
|
||||
|
||||
#### Scenario: xpath 表达式匹配
|
||||
- **WHEN** 目标配置 `expect.body.xpath: {"/root/status/text()": "ok"}`,且 XML 中 `/root/status` 节点文本为 `"ok"`
|
||||
- **THEN** 系统 SHALL 判定 matched 为 true
|
||||
|
||||
#### Scenario: xpath 表达式无匹配节点
|
||||
- **WHEN** 目标配置了 xpath 表达式但 XML 中无匹配节点
|
||||
- **THEN** 系统 SHALL 判定 matched 为 false
|
||||
|
||||
### Requirement: 多种 body 校验方法 AND 组合
|
||||
系统 SHALL 支持同时配置多种 body 校验方法,所有方法均通过时 matched 方为 true。
|
||||
|
||||
#### Scenario: 多种方法全部通过
|
||||
- **WHEN** 目标同时配置 `body.contains`、`body.json`、`body.regex`,且全部通过
|
||||
- **THEN** 系统 SHALL 判定 matched 为 true
|
||||
|
||||
#### Scenario: 多种方法任一失败
|
||||
- **WHEN** 目标同时配置 `body.contains` 和 `body.json`,且 `body.contains` 不通过
|
||||
- **THEN** 系统 SHALL 判定 matched 为 false,且不再检查 `body.json`
|
||||
|
||||
### Requirement: 操作符系统
|
||||
系统 SHALL 支持对 body 校验的提取值使用以下操作符进行比较:equals(默认等值)、contains(子串包含)、match(正则匹配)、empty(空值判断)、exists(存在性判断)、gte/lte/gt/lt(数值比较)。
|
||||
|
||||
#### Scenario: 标量值隐式 equals
|
||||
- **WHEN** jsonPath 配置的期望值为标量(字符串/数字/布尔/null),如 `$.status: ok`
|
||||
- **THEN** 系统 SHALL 使用 equals 操作符,对提取值做严格相等比较
|
||||
|
||||
#### Scenario: 显式 contains 操作符
|
||||
- **WHEN** 配置 `$.message: {contains: "success"}`,且提取值包含 `"success"`
|
||||
- **THEN** 系统 SHALL 判定 matched 为 true
|
||||
|
||||
#### Scenario: 显式 match 操作符
|
||||
- **WHEN** 配置 `$.version: {match: '\\d+\\.\\d+\\.\\d+'}`,且提取值匹配该正则
|
||||
- **THEN** 系统 SHALL 判定 matched 为 true
|
||||
|
||||
#### Scenario: empty 操作符判断为空
|
||||
- **WHEN** 配置 `$.items: {empty: true}`,且提取值为空数组 `[]`
|
||||
- **THEN** 系统 SHALL 判定 matched 为 true
|
||||
|
||||
#### Scenario: empty 操作符判断非空
|
||||
- **WHEN** 配置 `$.items: {empty: false}`,且提取值为 `[1, 2]`
|
||||
- **THEN** 系统 SHALL 判定 matched 为 true
|
||||
|
||||
#### Scenario: exists 操作符判断存在
|
||||
- **WHEN** 配置 `$.error: {exists: false}`,且 JSON 中不存在 `error` 字段
|
||||
- **THEN** 系统 SHALL 判定 matched 为 true
|
||||
|
||||
#### Scenario: gte 数值比较
|
||||
- **WHEN** 配置 `$.count: {gte: 10}`,且提取值为 `15`(数字)
|
||||
- **THEN** 系统 SHALL 判定 matched 为 true
|
||||
|
||||
#### Scenario: gt/lt 数值比较
|
||||
- **WHEN** 配置 `$.latency: {gt: 0, lt: 1000}`,且提取值为 `500`
|
||||
- **THEN** 系统 SHALL 对同一字段进行多操作符复合比较,全部通过则 matched 为 true
|
||||
|
||||
### Requirement: 响应头校验
|
||||
系统 SHALL 支持通过 `expect.headers` 配置对响应头进行键值对校验。
|
||||
|
||||
#### Scenario: 响应头匹配
|
||||
- **WHEN** 目标配置 `expect.headers: {"Content-Type": "application/json"}`,且响应包含该 header 且值匹配
|
||||
- **THEN** 系统 SHALL 判定 matched 为 true
|
||||
|
||||
#### Scenario: 响应头不匹配
|
||||
- **WHEN** 目标配置 `expect.headers: {"Content-Type": "application/json"}`,且响应 header 值为 `"text/html"`
|
||||
- **THEN** 系统 SHALL 判定 matched 为 false
|
||||
|
||||
#### Scenario: 响应头缺失
|
||||
- **WHEN** 目标配置了某个 header 但响应中不存在该 header
|
||||
- **THEN** 系统 SHALL 判定 matched 为 false
|
||||
@@ -1,21 +0,0 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: expect 配置增强
|
||||
系统 SHALL 支持增强的 expect 配置格式,包括 `headers` 响应头校验和 `body` 分组下的多种校验方法(contains、regex、json、css、xpath)。
|
||||
|
||||
#### Scenario: 解析增强的 expect 配置
|
||||
- **WHEN** YAML 配置文件中 target 的 expect 包含 headers、body 分组及内部方法
|
||||
- **THEN** 系统 SHALL 正确解析并存储为 ResolvedTarget 的 expect 字段
|
||||
|
||||
#### Scenario: 解析仅含 body.contains 的最简配置
|
||||
- **WHEN** YAML 中 target 配置 `expect.body.contains: "healthy"`
|
||||
- **THEN** 系统 SHALL 正确解析,功能等价于旧版 `expect.bodyContains`
|
||||
|
||||
#### Scenario: 不配置 expect
|
||||
- **WHEN** target 未配置任何 expect 规则
|
||||
- **THEN** 系统 SHALL 正常处理,expect 字段为 undefined
|
||||
|
||||
#### Scenario: 旧版 bodyContains 字段不再支持
|
||||
- **WHEN** YAML 中使用 `expect.bodyContains: "xxx"` 格式
|
||||
- **THEN** 该字段 SHALL 被忽略(系统仅识别 `expect.body.contains`)
|
||||
- **Migration**: 将配置文件中 `expect.bodyContains: "xxx"` 改为 `expect.body.contains: "xxx"`
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user