docs: 修正 DEVELOPMENT.md 与实际代码的差异并精简 tcp 示例
This commit is contained in:
487
DEVELOPMENT.md
487
DEVELOPMENT.md
@@ -20,22 +20,22 @@
|
|||||||
```text
|
```text
|
||||||
src/
|
src/
|
||||||
server/
|
server/
|
||||||
app.ts Bun HTTP 路由入口(路由分发 + API 汇聚)
|
app.ts Bun HTTP 路由入口(路由分发 + API 汇聚、StaticAssets 接口定义)
|
||||||
config.ts CLI 参数解析
|
config.ts CLI 参数解析(仅提取配置文件路径)
|
||||||
dev.ts 生产/开发启动入口
|
dev.ts 开发模式启动入口
|
||||||
server.ts HTTP server 启动工厂
|
server.ts HTTP server 启动工厂(接收 StartServerOptions)
|
||||||
helpers.ts 共享响应格式化工具(jsonResponse、createHeaders 等)
|
helpers.ts 共享响应格式化工具(见下方函数清单)
|
||||||
middleware.ts API 参数校验中间件(guardGetHead、validateTargetId 等)
|
middleware.ts API 参数校验中间件(guardGetHead、validateTargetId、validateTimeRange、validatePagination)
|
||||||
static.ts 静态资源服务与 SPA fallback
|
static.ts 静态资源服务与 SPA fallback
|
||||||
routes/ API 路由 handler(按端点拆分)
|
routes/ API 路由 handler(按端点拆分,签名因端点而异)
|
||||||
health.ts GET /health
|
health.ts GET /health(无 store 参数)
|
||||||
summary.ts GET /api/summary
|
summary.ts GET /api/summary
|
||||||
targets.ts GET /api/targets
|
targets.ts GET /api/targets
|
||||||
history.ts GET /api/targets/:id/history
|
history.ts GET /api/targets/:id/history
|
||||||
trend.ts GET /api/targets/:id/trend
|
trend.ts GET /api/targets/:id/trend
|
||||||
checker/
|
checker/
|
||||||
types.ts 基础类型定义(ResolvedTargetBase、RawTargetConfig、DefaultsConfig 等 base interface)
|
types.ts 基础类型定义(ResolvedTargetBase、RawTargetConfig、DefaultsConfig、CheckResult 等基础 interface)
|
||||||
config-loader.ts YAML 配置解析、契约校验、语义校验与运行期解析
|
config-loader.ts YAML 配置解析、契约校验、语义校验与运行期解析(输出 ResolvedConfig)
|
||||||
schema/ TypeBox + Ajv 配置契约、schema fragments、issue 渲染和 schema 导出入口
|
schema/ TypeBox + Ajv 配置契约、schema fragments、issue 渲染和 schema 导出入口
|
||||||
builder.ts 全量 JSON Schema 组装(遍历 registry 生成)
|
builder.ts 全量 JSON Schema 组装(遍历 registry 生成)
|
||||||
fragments.ts 共享 TypeBox schema 片段(duration、size、operator 等)
|
fragments.ts 共享 TypeBox schema 片段(duration、size、operator 等)
|
||||||
@@ -43,48 +43,46 @@ src/
|
|||||||
issues.ts 校验问题类型与渲染
|
issues.ts 校验问题类型与渲染
|
||||||
types.ts schema 层类型
|
types.ts schema 层类型
|
||||||
export.ts JSON Schema 文件导出
|
export.ts JSON Schema 文件导出
|
||||||
store.ts SQLite 数据存储
|
store.ts SQLite 数据存储(含 syncTargets、prune 等生命周期方法)
|
||||||
engine.ts 调度引擎(按 interval 分组的 es-toolkit groupBy + Semaphore 并发控制)
|
engine.ts 调度引擎(按 interval 分组的 es-toolkit groupBy + Semaphore 并发控制 + 数据清理)
|
||||||
utils.ts 共享工具函数(parseSize、parseDuration)
|
utils.ts 共享工具函数(parseSize、parseDuration)
|
||||||
expect/ 共享 expect 断言基础设施(跨 checker 复用)
|
expect/ 共享 expect 断言基础设施(跨 checker 复用)
|
||||||
types.ts ExpectResult 等共享断言类型
|
types.ts ExpectResult 共享断言类型
|
||||||
failure.ts 失败信息构造(errorFailure、mismatchFailure)
|
failure.ts 失败信息构造(errorFailure、mismatchFailure、truncateActual)
|
||||||
operator.ts 操作符系统(applyOperator、checkExpectValue、evaluateJsonPath)
|
operator.ts 操作符系统(applyOperator、evaluateJsonPath)
|
||||||
duration.ts 耗时断言(checkDuration)
|
duration.ts 耗时断言(checkDuration)
|
||||||
validate-operator.ts 操作符语义校验(validateOperatorObject、isJsonValue)
|
validate-operator.ts 操作符语义校验(validateOperatorObject、isJsonValue、isPlainRecord)
|
||||||
runner/ Checker 统一抽象与注册机制
|
runner/ Checker 统一抽象与注册机制
|
||||||
types.ts CheckerDefinition、CheckerContext、CheckerSchemas、ResolveContext
|
types.ts CheckerDefinition、CheckerContext、CheckerSchemas、ResolveContext
|
||||||
registry.ts CheckerRegistry 注册中心
|
registry.ts CheckerRegistry 注册中心
|
||||||
index.ts 注册入口(显式数组 + 循环注册)
|
index.ts 注册入口(显式数组 + 循环注册)
|
||||||
http/ HTTP Checker(自包含模块)
|
http/ HTTP Checker(自包含模块,含 types/schema/execute/expect/validate/body)
|
||||||
index.ts 模块入口(re-export HttpChecker)
|
command/ Command Checker(自包含模块,含 types/schema/execute/expect/validate/text)
|
||||||
types.ts HTTP 专属类型(ResolvedHttpTarget、HttpTargetConfig、HttpExpectConfig 等)
|
|
||||||
schema.ts HTTP TypeBox 契约(defaults、target.http、expect)
|
|
||||||
execute.ts HttpChecker 类(resolve/execute/serialize/validate)
|
|
||||||
expect.ts HTTP 专用断言(checkStatus、checkHeaders)
|
|
||||||
validate.ts HTTP 语义校验(URL、body rules、header operators 等)
|
|
||||||
body.ts Body 规则断言(JSONPath/XPath/CSS/contains/regex)
|
|
||||||
command/ Command Checker(自包含模块)
|
|
||||||
index.ts 模块入口(re-export CommandChecker)
|
|
||||||
types.ts Command 专属类型(ResolvedCommandTarget、CommandTargetConfig、CommandExpectConfig 等)
|
|
||||||
schema.ts Command TypeBox 契约(defaults、target.command、expect)
|
|
||||||
execute.ts CommandChecker 类(resolve/execute/serialize/validate)
|
|
||||||
expect.ts Command 专用断言(checkExitCode)
|
|
||||||
validate.ts Command 语义校验(text rules 等)
|
|
||||||
text.ts 文本规则断言(checkTextRules)
|
|
||||||
shared/
|
shared/
|
||||||
api.ts 前后端共享 TypeScript 类型
|
api.ts 前后端共享 TypeScript 类型
|
||||||
web/ Vite + React 前端 Dashboard
|
web/ Vite + React 前端 Dashboard
|
||||||
components/ UI 组件(表格、分组、Drawer、状态条等)
|
app.tsx 根组件(编排全局状态与布局)
|
||||||
constants/ 常量定义(列配置、类型映射、排序/筛选/颜色阈值函数)
|
main.tsx 入口(QueryClient 挂载 + ErrorBoundary + ReactQueryDevtools)
|
||||||
hooks/ TanStack Query 数据层(useTargetDetail 集成轮询/条件查询)
|
styles.css 全局样式与自定义 CSS 变量
|
||||||
|
components/ UI 组件(见下方组件清单)
|
||||||
|
constants/ 常量与纯函数
|
||||||
|
target-type-display.ts 类型名称映射
|
||||||
|
target-table-columns.tsx 表格列定义
|
||||||
|
target-table-filters.ts 表格筛选器
|
||||||
|
target-table-sorters.ts 表格排序器
|
||||||
|
color-threshold.ts 可用率颜色阈值函数
|
||||||
|
hooks/ TanStack Query 数据层
|
||||||
|
useTargetDetail.ts 集成轮询/条件查询的组合 hook
|
||||||
utils/ 前端工具函数
|
utils/ 前端工具函数
|
||||||
|
time.ts 时间处理(subtractHours)
|
||||||
scripts/ 开发、构建、schema 生成和 smoke test 脚本
|
scripts/ 开发、构建、schema 生成和 smoke test 脚本
|
||||||
tests/ Bun test 测试
|
tests/ Bun test 测试(结构镜像 src 目录)
|
||||||
openspec/ OpenSpec 变更与规格文档
|
openspec/ OpenSpec 变更与规格文档
|
||||||
probe-config.schema.json 用户配置 JSON Schema 导出物
|
probe-config.schema.json 用户配置 JSON Schema 导出物(用于 IDE 自动补全和校验)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> **说明**:`runner/http/` 和 `runner/command/` 的完整文件结构见 [1.7.1 架构总览](#171-架构总览) 中的标准文件表。
|
||||||
|
|
||||||
## 前后端边界
|
## 前后端边界
|
||||||
|
|
||||||
前端只通过 HTTP 调用后端,API 路径为 `/api/*`。共享类型放在 `src/shared`,前端不得 import `src/server` 的运行时实现。
|
前端只通过 HTTP 调用后端,API 路径为 `/api/*`。共享类型放在 `src/shared`,前端不得 import `src/server` 的运行时实现。
|
||||||
@@ -97,13 +95,17 @@ probe-config.schema.json 用户配置 JSON Schema 导出物
|
|||||||
|
|
||||||
```
|
```
|
||||||
启动流程:
|
启动流程:
|
||||||
dev.ts → readRuntimeConfig(cli args) → loadConfig(yaml)
|
dev.ts → readRuntimeConfig(cli args, 仅提取 configPath)
|
||||||
→ ProbeStore(db) → ProbeEngine(store, targets) → startServer(store)
|
→ loadConfig(yaml) → ResolvedConfig{ host, port, dataDir, maxConcurrentChecks, retentionMs, targets }
|
||||||
|
→ ProbeStore(db) → store.syncTargets(targets)
|
||||||
|
→ ProbeEngine(store, targets, maxConcurrentChecks, retentionMs)
|
||||||
|
→ startServer({ config, mode: "development", store })
|
||||||
|
|
||||||
运行时:
|
运行时:
|
||||||
定时器(tick) → ProbeEngine.probeGroup()
|
定时器(tick) → ProbeEngine.probeGroup()
|
||||||
→ HTTP: fetcher.ts / Command: command-runner.ts
|
→ checkerRegistry.get(target.type).execute()
|
||||||
→ runner/*/expect.ts 校验 → store.insertCheckResult()
|
→ runner/*/expect.ts 校验 → engine.writeResult() → store.insertCheckResult()
|
||||||
|
数据清理: 定时 prune(retentionMs),每小时执行一次
|
||||||
|
|
||||||
HTTP 请求:
|
HTTP 请求:
|
||||||
Request → app.ts(路由分发) → routes/*.ts(handler)
|
Request → app.ts(路由分发) → routes/*.ts(handler)
|
||||||
@@ -126,19 +128,29 @@ HTTP 请求:
|
|||||||
|
|
||||||
### 1.3 API 路由开发
|
### 1.3 API 路由开发
|
||||||
|
|
||||||
路由文件位于 `src/server/routes/`,每个端点一个文件。handler 函数签名统一为:
|
路由文件位于 `src/server/routes/`,每个端点一个文件。handler 函数签名因端点而异:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
export function handleXxx(params, store: ProbeStore, method: string, mode: RuntimeMode): Response;
|
// 无 store 的路由(健康检查不依赖数据库)
|
||||||
|
export function handleHealth(method: string, mode: RuntimeMode): Response;
|
||||||
|
|
||||||
|
// 仅有 store 的路由
|
||||||
|
export function handleSummary(store: ProbeStore, method: string, mode: RuntimeMode): Response;
|
||||||
|
export function handleTargets(store: ProbeStore, method: string, mode: RuntimeMode): Response;
|
||||||
|
|
||||||
|
// 带 target ID 和查询参数的路由
|
||||||
|
export function handleHistory(idStr: string, url: URL, method: string, store: ProbeStore, mode: RuntimeMode): Response;
|
||||||
|
export function handleTrend(idStr: string, url: URL, method: string, store: ProbeStore, mode: RuntimeMode): Response;
|
||||||
```
|
```
|
||||||
|
|
||||||
**请求处理流程**:
|
**请求处理流程**:
|
||||||
|
|
||||||
1. `app.ts` 的 `createFetchHandler` 作为总入口,根据 URL pattern 匹配路由
|
1. `app.ts` 的 `createFetchHandler` 作为总入口,根据 URL pattern 匹配路由
|
||||||
2. API 路由统一经过 `guardGetHead` 做方法检查(仅允许 GET/HEAD)
|
2. `/health` 路由独立处理,不经过 `guardGetHead`(使用 `helpers.ts` 的 `allowsGetHead` 自行校验方法)
|
||||||
3. 各 handler 内部通过 `middleware.ts` 提供的 `validateTargetId`、`validateTimeRange`、`validatePagination` 做参数校验
|
3. `/api/*` 路由统一经过 `guardGetHead` 做方法检查(仅允许 GET/HEAD),返回 `null` 表示通过
|
||||||
4. 校验函数返回 `Response` 表示校验失败(直接返回),返回数据对象表示通过
|
4. 各 handler 内部通过 `middleware.ts` 提供的 `validateTargetId`、`validateTimeRange`、`validatePagination` 做参数校验
|
||||||
5. 业务逻辑通过 `store` 查询数据,用 `helpers.ts` 的 `jsonResponse`、`mapCheckResult`、`formatDuration` 等格式化输出
|
5. 校验函数返回 `Response` 实例表示校验失败(直接返回),返回数据对象表示通过
|
||||||
|
6. 业务逻辑通过 `store` 查询数据,用 `helpers.ts` 的 `jsonResponse`、`mapCheckResult`、`formatDuration` 等格式化输出
|
||||||
|
|
||||||
**新增路由步骤**:
|
**新增路由步骤**:
|
||||||
|
|
||||||
@@ -149,7 +161,15 @@ export function handleXxx(params, store: ProbeStore, method: string, mode: Runti
|
|||||||
|
|
||||||
### 1.4 共享工具
|
### 1.4 共享工具
|
||||||
|
|
||||||
- **`helpers.ts`**:跨路由共用的响应工具函数(`jsonResponse`、`createHeaders`、`createApiError`、`mapCheckResult`、`formatDuration`、`createHealthResponse`)
|
- **`helpers.ts`**:跨路由共用的响应工具函数
|
||||||
|
- `allowsGetHead(method)` — 判断是否为 GET/HEAD 方法
|
||||||
|
- `createApiError(error, status)` — 构造 API 错误体
|
||||||
|
- `createHeaders(mode, init)` — 创建响应 Headers(生产模式附加安全头)
|
||||||
|
- `createHealthResponse()` — 构造健康检查响应
|
||||||
|
- `formatDuration(ms)` — 毫秒转为可读时长字符串
|
||||||
|
- `jsonResponse(body, options)` — JSON 响应构造(自动处理 HEAD 空体)
|
||||||
|
- `mapCheckResult(row)` — 数据库行转 API CheckResult
|
||||||
|
- `methodNotAllowedResponse(allow, mode)` — 构造 405 响应
|
||||||
- **`middleware.ts`**:API 参数校验函数(`guardGetHead`、`validateTargetId`、`validateTimeRange`、`validatePagination`)
|
- **`middleware.ts`**:API 参数校验函数(`guardGetHead`、`validateTargetId`、`validateTimeRange`、`validatePagination`)
|
||||||
- **`static.ts`**:生产模式下的静态资源服务与 SPA fallback
|
- **`static.ts`**:生产模式下的静态资源服务与 SPA fallback
|
||||||
|
|
||||||
@@ -172,6 +192,18 @@ export function handleXxx(params, store: ProbeStore, method: string, mode: Runti
|
|||||||
|
|
||||||
`config-loader.ts` 只负责 YAML 解析、契约调度、公共语义校验和最终运行期解析;checker 专属规则必须下沉到对应 checker 的 `schema.ts` 和 `validate.ts`。
|
`config-loader.ts` 只负责 YAML 解析、契约调度、公共语义校验和最终运行期解析;checker 专属规则必须下沉到对应 checker 的 `schema.ts` 和 `validate.ts`。
|
||||||
|
|
||||||
|
`ResolvedConfig` 包含以下字段:
|
||||||
|
|
||||||
|
| 字段 | 来源 | 默认值 |
|
||||||
|
| --------------------- | ----------------------------- | ----------- |
|
||||||
|
| `configDir` | 配置文件所在目录 | — |
|
||||||
|
| `dataDir` | `server.dataDir` | `./data` |
|
||||||
|
| `host` | `server.host` | `127.0.0.1` |
|
||||||
|
| `port` | `server.port` | `3000` |
|
||||||
|
| `maxConcurrentChecks` | `runtime.maxConcurrentChecks` | `20` |
|
||||||
|
| `retentionMs` | `runtime.retention` | `7d` |
|
||||||
|
| `targets` | `targets[]` 经 resolve 后 | — |
|
||||||
|
|
||||||
契约层使用 `src/server/checker/schema/` 中的 TypeBox fragments 生成 JSON Schema,并用 Ajv 执行启动期校验。Ajv 必须保持严格拒绝模式:`allErrors: true`、不启用类型强制转换、不注入默认值、不自动删除未知字段。
|
契约层使用 `src/server/checker/schema/` 中的 TypeBox fragments 生成 JSON Schema,并用 Ajv 执行启动期校验。Ajv 必须保持严格拒绝模式:`allErrors: true`、不启用类型强制转换、不注入默认值、不自动删除未知字段。
|
||||||
|
|
||||||
默认对象策略是 `additionalProperties: false`。只有明确声明的动态键值表可以开放任意键名,例如 `http.headers`、`defaults.http.headers`、`expect.headers`、`command.env`。
|
默认对象策略是 `additionalProperties: false`。只有明确声明的动态键值表可以开放任意键名,例如 `http.headers`、`defaults.http.headers`、`expect.headers`、`command.env`。
|
||||||
@@ -217,72 +249,18 @@ checkerRegistry(单例)
|
|||||||
|
|
||||||
#### 1.7.2 步骤一:创建 Checker 目录与类型
|
#### 1.7.2 步骤一:创建 Checker 目录与类型
|
||||||
|
|
||||||
在 `src/server/checker/runner/tcp/types.ts` 中定义 checker 专属类型:
|
在 `src/server/checker/runner/tcp/types.ts` 中定义 checker 专属类型(参考 `http/types.ts`、`command/types.ts`):
|
||||||
|
|
||||||
```typescript
|
- `XxxTargetConfig` — YAML 原始配置类型
|
||||||
import type { ResolvedTargetBase } from "../../types";
|
- `XxxExpectConfig` — expect 字段类型
|
||||||
|
- `XxxDefaultsConfig` — defaults 专属字段类型
|
||||||
export interface TcpTargetConfig {
|
- `ResolvedXxxTarget extends ResolvedTargetBase` — resolve 后的完整类型,含 `type: "xxx"` 字面量
|
||||||
host: string;
|
|
||||||
port: number;
|
|
||||||
connectTimeout?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TcpExpectConfig {
|
|
||||||
connected?: boolean;
|
|
||||||
maxDurationMs?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TcpDefaultsConfig {
|
|
||||||
connectTimeout?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ResolvedTcpTarget extends ResolvedTargetBase {
|
|
||||||
expect?: TcpExpectConfig;
|
|
||||||
tcp: {
|
|
||||||
connectTimeout: number;
|
|
||||||
host: string;
|
|
||||||
port: number;
|
|
||||||
};
|
|
||||||
type: "tcp";
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**注意**:不需要修改顶层 `checker/types.ts`。base interface 使用 index signature(`[key: string]: unknown`),checker 专属类型通过 `extends ResolvedTargetBase` 自动兼容。
|
**注意**:不需要修改顶层 `checker/types.ts`。base interface 使用 index signature(`[key: string]: unknown`),checker 专属类型通过 `extends ResolvedTargetBase` 自动兼容。
|
||||||
|
|
||||||
#### 1.7.3 步骤二:创建 TypeBox 契约 Schema
|
#### 1.7.3 步骤二:创建 TypeBox 契约 Schema
|
||||||
|
|
||||||
在 `src/server/checker/runner/tcp/schema.ts` 中定义三部分 schema:
|
在 `src/server/checker/runner/tcp/schema.ts` 中定义 `CheckerSchemas`(config / defaults / expect 三部分)。参考 `http/schema.ts`、`command/schema.ts`,使用 `schema/fragments.ts` 中的共享片段。
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { Type } from "@sinclair/typebox";
|
|
||||||
import type { CheckerSchemas } from "../types";
|
|
||||||
|
|
||||||
export const tcpCheckerSchemas: CheckerSchemas = {
|
|
||||||
config: Type.Object(
|
|
||||||
{
|
|
||||||
host: Type.String(),
|
|
||||||
port: Type.Integer({ maximum: 65535, minimum: 0 }),
|
|
||||||
connectTimeout: Type.Optional(Type.Integer({ minimum: 100 })),
|
|
||||||
},
|
|
||||||
{ additionalProperties: false },
|
|
||||||
),
|
|
||||||
|
|
||||||
defaults: Type.Object(
|
|
||||||
{
|
|
||||||
connectTimeout: Type.Optional(Type.Integer({ minimum: 100 })),
|
|
||||||
},
|
|
||||||
{ additionalProperties: false },
|
|
||||||
),
|
|
||||||
|
|
||||||
expect: Type.Object(
|
|
||||||
{
|
|
||||||
connected: Type.Optional(Type.Boolean()),
|
|
||||||
},
|
|
||||||
{ additionalProperties: false },
|
|
||||||
),
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
**可复用的共享 fragments**(来自 `schema/fragments.ts`):
|
**可复用的共享 fragments**(来自 `schema/fragments.ts`):
|
||||||
|
|
||||||
@@ -301,29 +279,10 @@ export const tcpCheckerSchemas: CheckerSchemas = {
|
|||||||
|
|
||||||
#### 1.7.4 步骤三:实现语义校验
|
#### 1.7.4 步骤三:实现语义校验
|
||||||
|
|
||||||
在 `src/server/checker/runner/tcp/validate.ts` 中实现 JSON Schema 无法表达的语义规则:
|
在 `src/server/checker/runner/tcp/validate.ts` 中实现 JSON Schema 无法表达的语义规则(参考 `http/validate.ts`、`command/validate.ts`)。函数签名统一为:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import type { ConfigValidationIssue } from "../../schema/issues";
|
export function validateTcpConfig(input: CheckerValidationInput): ConfigValidationIssue[];
|
||||||
import type { CheckerValidationInput } from "../types";
|
|
||||||
|
|
||||||
import { issue } from "../../schema/issues";
|
|
||||||
|
|
||||||
export function validateTcpConfig(input: CheckerValidationInput): ConfigValidationIssue[] {
|
|
||||||
const issues: ConfigValidationIssue[] = [];
|
|
||||||
|
|
||||||
for (const target of input.targets) {
|
|
||||||
if (target.type !== "tcp") continue;
|
|
||||||
const name = target.name;
|
|
||||||
const tcp = target["tcp"] as { host?: string } | undefined;
|
|
||||||
|
|
||||||
if (tcp && typeof tcp.host === "string" && tcp.host.trim() === "") {
|
|
||||||
issues.push(issue("invalid-value", "tcp.host", "host 不能为空字符串", name));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return issues;
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**共享校验工具**(`expect/validate-operator.ts`):
|
**共享校验工具**(`expect/validate-operator.ts`):
|
||||||
@@ -335,89 +294,18 @@ export function validateTcpConfig(input: CheckerValidationInput): ConfigValidati
|
|||||||
|
|
||||||
#### 1.7.5 步骤四:实现 Checker 类
|
#### 1.7.5 步骤四:实现 Checker 类
|
||||||
|
|
||||||
在 `src/server/checker/runner/tcp/execute.ts` 中实现 `CheckerDefinition` 接口的全部成员:
|
在 `src/server/checker/runner/tcp/execute.ts` 中实现 `CheckerDefinition` 接口的全部成员(参考 `http/execute.ts`、`command/execute.ts`):
|
||||||
|
|
||||||
```typescript
|
```
|
||||||
import type { CheckResult, RawTargetConfig, ResolvedTargetBase } from "../../types";
|
TcpChecker implements Checker
|
||||||
import type { Checker, CheckerContext, CheckerValidationInput, ResolveContext } from "../types";
|
readonly configKey ← "tcp"(对应 YAML 中的 target.tcp 字段)
|
||||||
|
readonly type ← "tcp"
|
||||||
|
readonly schemas ← tcpCheckerSchemas
|
||||||
|
|
||||||
import { checkDuration } from "../../expect/duration";
|
validate(input) ← 调用 validateTcpConfig(input)
|
||||||
import { errorFailure } from "../../expect/failure";
|
resolve(target, ctx)← 默认值合并 + 解析,返回 satisfies ResolvedTcpTarget
|
||||||
import { tcpCheckerSchemas } from "./schema";
|
execute(target, ctx)← 执行检查,返回 CheckResult
|
||||||
import type { ResolvedTcpTarget, TcpExpectConfig, TcpTargetConfig } from "./types";
|
serialize(target) ← 返回 { config, target } 用于 DB 持久化
|
||||||
import { validateTcpConfig } from "./validate";
|
|
||||||
|
|
||||||
export class TcpChecker implements Checker {
|
|
||||||
readonly configKey = "tcp";
|
|
||||||
readonly type = "tcp";
|
|
||||||
readonly schemas = tcpCheckerSchemas;
|
|
||||||
|
|
||||||
validate(input: CheckerValidationInput) {
|
|
||||||
return validateTcpConfig(input);
|
|
||||||
}
|
|
||||||
|
|
||||||
resolve(target: RawTargetConfig, context: ResolveContext): ResolvedTargetBase {
|
|
||||||
const t = target as RawTargetConfig & { tcp: TcpTargetConfig; type: "tcp" };
|
|
||||||
const defaults = context.defaults["tcp"] as { connectTimeout?: number } | undefined;
|
|
||||||
|
|
||||||
return {
|
|
||||||
expect: target.expect as TcpExpectConfig | undefined,
|
|
||||||
group: target.group ?? "default",
|
|
||||||
intervalMs: context.defaultIntervalMs,
|
|
||||||
name: t.name,
|
|
||||||
tcp: {
|
|
||||||
connectTimeout: t.tcp.connectTimeout ?? defaults?.connectTimeout ?? 3000,
|
|
||||||
host: t.tcp.host,
|
|
||||||
port: t.tcp.port,
|
|
||||||
},
|
|
||||||
timeoutMs: context.defaultTimeoutMs,
|
|
||||||
type: "tcp",
|
|
||||||
} satisfies ResolvedTcpTarget;
|
|
||||||
}
|
|
||||||
|
|
||||||
async execute(target: ResolvedTargetBase, ctx: CheckerContext): Promise<CheckResult> {
|
|
||||||
const t = target as ResolvedTcpTarget;
|
|
||||||
const timestamp = new Date().toISOString();
|
|
||||||
const start = performance.now();
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 执行 TCP 连接检查...
|
|
||||||
|
|
||||||
const durationMs = Math.round(performance.now() - start);
|
|
||||||
const durationResult = checkDuration(durationMs, t.expect?.maxDurationMs);
|
|
||||||
if (!durationResult.matched) {
|
|
||||||
return {
|
|
||||||
durationMs,
|
|
||||||
failure: durationResult.failure,
|
|
||||||
matched: false,
|
|
||||||
statusDetail: "TCP connected",
|
|
||||||
targetName: t.name,
|
|
||||||
timestamp,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return { durationMs, failure: null, matched: true, statusDetail: "TCP connected", targetName: t.name, timestamp };
|
|
||||||
} catch (error) {
|
|
||||||
const durationMs = Math.round(performance.now() - start);
|
|
||||||
return {
|
|
||||||
durationMs,
|
|
||||||
failure: errorFailure("connection", "connection", String(error)),
|
|
||||||
matched: false,
|
|
||||||
statusDetail: null,
|
|
||||||
targetName: t.name,
|
|
||||||
timestamp,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
serialize(target: ResolvedTargetBase): { config: string; target: string } {
|
|
||||||
const t = target as ResolvedTcpTarget;
|
|
||||||
return {
|
|
||||||
config: JSON.stringify({ connectTimeout: t.tcp.connectTimeout, host: t.tcp.host, port: t.tcp.port }),
|
|
||||||
target: `${t.tcp.host}:${t.tcp.port}`,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**`resolve()` 规范**:
|
**`resolve()` 规范**:
|
||||||
@@ -437,45 +325,23 @@ export class TcpChecker implements Checker {
|
|||||||
|
|
||||||
**可用的共享断言工具**(`checker/expect/`):
|
**可用的共享断言工具**(`checker/expect/`):
|
||||||
|
|
||||||
| 模块 | 函数 | 用途 |
|
| 模块 | 函数 | 用途 |
|
||||||
| ---------------------- | ----------------------------------------------------- | ---------------------- |
|
| ---------------------- | ----------------------------------------------------- | ------------------------------------- |
|
||||||
| `failure.ts` | `errorFailure(phase, path, msg)` | 构造错误类型 failure |
|
| `failure.ts` | `errorFailure(phase, path, msg)` | 构造错误类型 failure |
|
||||||
| `failure.ts` | `mismatchFailure(phase, path, expected, actual, msg)` | 构造不匹配类型 failure |
|
| `failure.ts` | `mismatchFailure(phase, path, expected, actual, msg)` | 构造不匹配类型 failure |
|
||||||
| `duration.ts` | `checkDuration(ms, maxMs?)` | 耗时断言 |
|
| `failure.ts` | `truncateActual(value, maxLen?)` | 截断过长的 actual 值(默认 200 字符) |
|
||||||
| `operator.ts` | `applyOperator(actual, operator)` | 执行单个操作符比较 |
|
| `duration.ts` | `checkDuration(ms, maxMs?)` | 耗时断言 |
|
||||||
| `operator.ts` | `evaluateJsonPath(json, path)` | JSONPath 提取 |
|
| `operator.ts` | `applyOperator(actual, operator)` | 执行单个操作符比较 |
|
||||||
| `validate-operator.ts` | `validateOperatorObject(ops, path, name)` | 操作符语义校验 |
|
| `operator.ts` | `evaluateJsonPath(json, path)` | JSONPath 提取 |
|
||||||
|
| `validate-operator.ts` | `validateOperatorObject(ops, path, name)` | 操作符语义校验 |
|
||||||
|
|
||||||
**Checker 专属断言**(如需要)放在同目录的 `expect.ts` 中,参考 `http/expect.ts`(checkStatus、checkHeaders)和 `command/expect.ts`(checkExitCode)。
|
**Checker 专属断言**(如需要)放在同目录的 `expect.ts` 中,参考 `http/expect.ts`(checkStatus、checkHeaders)和 `command/expect.ts`(checkExitCode)。
|
||||||
|
|
||||||
#### 1.7.6 步骤五:创建模块入口并注册
|
#### 1.7.6 步骤五:创建模块入口并注册
|
||||||
|
|
||||||
创建 `src/server/checker/runner/tcp/index.ts`:
|
创建 `src/server/checker/runner/tcp/index.ts`(re-export Checker 类)。
|
||||||
|
|
||||||
```typescript
|
在 `src/server/checker/runner/index.ts` 中添加一行导入和一个数组元素(参考现有 HttpChecker/CommandChecker)。
|
||||||
export { TcpChecker } from "./execute";
|
|
||||||
```
|
|
||||||
|
|
||||||
在 `src/server/checker/runner/index.ts` 中添加一行导入和一个数组元素:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { CommandChecker } from "./command";
|
|
||||||
import { HttpChecker } from "./http";
|
|
||||||
import { TcpChecker } from "./tcp"; // ← 新增
|
|
||||||
import { CheckerRegistry } from "./registry";
|
|
||||||
|
|
||||||
const checkers = [new HttpChecker(), new CommandChecker(), new TcpChecker()]; // ← 新增
|
|
||||||
|
|
||||||
export function createDefaultCheckerRegistry(): CheckerRegistry {
|
|
||||||
const registry = new CheckerRegistry();
|
|
||||||
for (const checker of checkers) {
|
|
||||||
registry.register(checker);
|
|
||||||
}
|
|
||||||
return registry;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const checkerRegistry = createDefaultCheckerRegistry();
|
|
||||||
```
|
|
||||||
|
|
||||||
注册后,以下管线会通过 registry 自动委托,**无需新增 type 分支**:
|
注册后,以下管线会通过 registry 自动委托,**无需新增 type 分支**:
|
||||||
|
|
||||||
@@ -544,6 +410,21 @@ export const checkerRegistry = createDefaultCheckerRegistry();
|
|||||||
|
|
||||||
基于 `bun:sqlite`,WAL 模式运行,数据库文件位于配置的 `dataDir` 下。
|
基于 `bun:sqlite`,WAL 模式运行,数据库文件位于配置的 `dataDir` 下。
|
||||||
|
|
||||||
|
**核心方法**:
|
||||||
|
|
||||||
|
| 方法 | 用途 |
|
||||||
|
| ---------------------- | ---------------------------------------------------------------- |
|
||||||
|
| `syncTargets(targets)` | 启动期同步 targets(基于 name 做 upsert + delete 事务) |
|
||||||
|
| `insertCheckResult()` | 写入单条检查结果 |
|
||||||
|
| `getTargets()` | 查询全部 targets(default 分组优先排序) |
|
||||||
|
| `getLatestChecksMap()` | 批量获取每个 target 的最新检查结果(单次 SQL 聚合) |
|
||||||
|
| `getAllTargetStats()` | 批量获取每个 target 的可用率统计(GROUP BY 聚合) |
|
||||||
|
| `getSummary()` | 获取总览统计(基于 `getLatestChecksMap` 内存计算 up/down/total) |
|
||||||
|
| `getTrend()` | 获取按小时聚合的趋势数据 |
|
||||||
|
| `getHistory()` | 分页查询历史记录 |
|
||||||
|
| `getRecentSamples()` | 获取最近 N 条采样数据(用于状态条渲染) |
|
||||||
|
| `prune(retentionMs)` | 按 retention 策略清理过期数据(由 engine 定时调用) |
|
||||||
|
|
||||||
**Statement 使用规范**:
|
**Statement 使用规范**:
|
||||||
|
|
||||||
| 场景 | 方式 | 原因 |
|
| 场景 | 方式 | 原因 |
|
||||||
@@ -566,11 +447,12 @@ export const checkerRegistry = createDefaultCheckerRegistry();
|
|||||||
### 1.9 拨测引擎
|
### 1.9 拨测引擎
|
||||||
|
|
||||||
- **调度**:`ProbeEngine` 用 `es-toolkit/groupBy` 按 interval 分组,每组独立 `setInterval` 定时触发
|
- **调度**:`ProbeEngine` 用 `es-toolkit/groupBy` 按 interval 分组,每组独立 `setInterval` 定时触发
|
||||||
- **并发控制**:`es-toolkit/Semaphore` 限制全局最大并发数(`maxConcurrentChecks`),`acquire()` 阻塞等待
|
- **并发控制**:`es-toolkit/Semaphore` 限制全局最大并发数(`maxConcurrentChecks`,默认 20),`acquire()` 阻塞等待
|
||||||
- **Runner 选择**:`engine.runCheck()` 通过 `checkerRegistry.get(target.type)` 获取 checker,并调用 `checker.execute(target, { signal })`
|
- **Runner 选择**:`engine.runCheck()` 通过 `checkerRegistry.get(target.type)` 获取 checker,并调用 `checker.execute(target, { signal })`
|
||||||
- **超时控制**:`ProbeEngine` 为每次检查创建 `AbortController` 并按 `target.timeoutMs` 触发 abort;checker 必须使用 `CheckerContext.signal` 感知超时,HTTP 将 signal 传给 `fetch()`,Command 在 signal abort 时 `proc.kill()`
|
- **超时控制**:`ProbeEngine` 为每次检查创建 `AbortController` 并按 `target.timeoutMs` 触发 abort;checker 必须使用 `CheckerContext.signal` 感知超时,HTTP 将 signal 传给 `fetch()`,Command 在 signal abort 时 `proc.kill()`
|
||||||
- **结果写入**:检查结果通过 `store.insertCheckResult()` 写入 SQLite,engine 通过 `targetNameToId` 缓存 name→id 映射
|
- **结果写入**:检查结果通过 `store.insertCheckResult()` 写入 SQLite,engine 通过 `targetNameToId` 缓存 name→id 映射
|
||||||
- **生命周期**:`start()`/`stop()` 管理定时器,`stop()` 清理所有 `setInterval`
|
- **数据清理**:当 `retentionMs > 0` 时,engine 启动时立即执行一次 `store.prune()`,之后每小时定时执行,按 `timestamp` 清理过期数据
|
||||||
|
- **生命周期**:`start()`/`stop()` 管理定时器(含调度定时器和清理定时器),`stop()` 清理所有 `setInterval`
|
||||||
|
|
||||||
### 1.10 expect 断言系统
|
### 1.10 expect 断言系统
|
||||||
|
|
||||||
@@ -615,7 +497,12 @@ CommandChecker.execute → 收集观测(exitCode/stdout/stderr/durationMs)
|
|||||||
|
|
||||||
### 1.12 测试规范
|
### 1.12 测试规范
|
||||||
|
|
||||||
- 测试文件与源文件对应:`tests/server/checker/runner/shared/body.test.ts` ↔ `src/server/checker/runner/shared/body.ts`
|
- 测试目录 `tests/` 镜像 `src/` 目录结构,但共享模块的测试集中放在 `tests/server/checker/runner/shared/` 下
|
||||||
|
- `tests/server/checker/runner/shared/failure.test.ts` ↔ `src/server/checker/expect/failure.ts`
|
||||||
|
- `tests/server/checker/runner/shared/duration.test.ts` ↔ `src/server/checker/expect/duration.ts`
|
||||||
|
- `tests/server/checker/runner/shared/operator.test.ts` ↔ `src/server/checker/expect/operator.ts`
|
||||||
|
- `tests/server/checker/runner/shared/body.test.ts` ↔ `src/server/checker/runner/http/body.ts`
|
||||||
|
- `tests/server/checker/runner/shared/text.test.ts` ↔ `src/server/checker/runner/command/text.ts`
|
||||||
- 使用 `bun:test` 框架(`describe`/`test`/`expect`),测试数据库用临时目录 + `tmpdir()`
|
- 使用 `bun:test` 框架(`describe`/`test`/`expect`),测试数据库用临时目录 + `tmpdir()`
|
||||||
- 新增 store 方法必须编写单元测试;新增 API 端点必须在 `app.test.ts` 中添加集成测试
|
- 新增 store 方法必须编写单元测试;新增 API 端点必须在 `app.test.ts` 中添加集成测试
|
||||||
- 测试后清理:`afterAll` 中 `store.close()` + `rm(tempDir, { recursive: true })`
|
- 测试后清理:`afterAll` 中 `store.close()` + `rm(tempDir, { recursive: true })`
|
||||||
@@ -626,15 +513,15 @@ CommandChecker.execute → 收集观测(exitCode/stdout/stderr/durationMs)
|
|||||||
|
|
||||||
### 2.1 技术栈概览
|
### 2.1 技术栈概览
|
||||||
|
|
||||||
| 层面 | 技术 | 用途 |
|
| 层面 | 技术 | 用途 |
|
||||||
| ------ | ----------------------------------- | ---------------------------- |
|
| ------ | --------------------------------------------------- | ---------------------------- |
|
||||||
| 框架 | React 19 | UI 组件开发 |
|
| 框架 | React 19 | UI 组件开发 |
|
||||||
| 构建 | Vite 8 | 开发服务与生产构建 |
|
| 构建 | Vite 8 | 开发服务与生产构建 |
|
||||||
| 语言 | TypeScript 6 | 类型安全 |
|
| 语言 | TypeScript 6 | 类型安全 |
|
||||||
| UI 库 | TDesign React + tdesign-icons-react | UI 组件与图标 |
|
| UI 库 | TDesign React + tdesign-icons-react | UI 组件与图标 |
|
||||||
| 数据层 | TanStack Query (React Query) | 服务端状态管理与自动轮询 |
|
| 数据层 | TanStack Query (React Query) + React Query Devtools | 服务端状态管理与自动轮询 |
|
||||||
| 图表 | Recharts | 拨测趋势折线图与状态环状图 |
|
| 图表 | Recharts | 拨测趋势折线图与状态环状图 |
|
||||||
| 路由 | 无(单页面 Dashboard) | 仅需 Drawer/Tab 做页面内导航 |
|
| 路由 | 无(单页面 Dashboard) | 仅需 Drawer/Tab 做页面内导航 |
|
||||||
|
|
||||||
**不引入的依赖**:React Router(单页面场景不需要)、状态管理库(TanStack Query 即服务端状态层,组件内用 `useState` 足够)
|
**不引入的依赖**:React Router(单页面场景不需要)、状态管理库(TanStack Query 即服务端状态层,组件内用 `useState` 足够)
|
||||||
|
|
||||||
@@ -642,18 +529,21 @@ CommandChecker.execute → 收集观测(exitCode/stdout/stderr/durationMs)
|
|||||||
|
|
||||||
```
|
```
|
||||||
main.tsx
|
main.tsx
|
||||||
└── QueryClientProvider(TanStack Query 全局挂载)
|
└── StrictMode
|
||||||
└── App(根组件)
|
└── ErrorBoundary(React 错误边界)
|
||||||
├── SummaryCards(总览统计卡片)
|
└── QueryClientProvider(TanStack Query 全局挂载)
|
||||||
│ └── useSummary() ─── GET /api/summary(8s 轮询)
|
├── App(根组件)
|
||||||
└── TargetBoard(目标列表)
|
│ ├── SummaryCards(总览统计卡片)
|
||||||
├── useTargets() ─── GET /api/targets(8s 轮询)
|
│ │ └── useSummary() ─── GET /api/summary(8s 轮询)
|
||||||
└── TargetGroup[](按 group 字段分组)
|
│ └── TargetBoard(目标列表)
|
||||||
└── PrimaryTable ← TARGET_TABLE_COLUMNS(列定义:排序/筛选/渲染)
|
│ ├── useTargets() ─── GET /api/targets(8s 轮询)
|
||||||
└── TargetDetailDrawer(目标详情抽屉)
|
│ └── TargetGroup[](按 group 字段分组)
|
||||||
└── useTargetDetail() ── 按需发起 trend + history 查询
|
│ └── PrimaryTable ← TARGET_TABLE_COLUMNS(列定义:排序/筛选/渲染)
|
||||||
├── Tab: 概览 → Statistic + TrendChart + StatusDonut + Descriptions
|
│ └── TargetDetailDrawer(目标详情抽屉)
|
||||||
└── Tab: 记录 → PrimaryTable(分页历史记录)
|
│ └── useTargetDetail() ── 按需发起 trend + history 查询
|
||||||
|
│ ├── Tab: 概览 → Statistic + TrendChart + StatusDonut + Descriptions
|
||||||
|
│ └── Tab: 记录 → PrimaryTable(分页历史记录)
|
||||||
|
└── ReactQueryDevtools(开发工具,仅开发环境)
|
||||||
```
|
```
|
||||||
|
|
||||||
**数据层架构**:
|
**数据层架构**:
|
||||||
@@ -759,23 +649,24 @@ export function TargetGroup({ name, targets, onTargetClick }: TargetGroupProps)
|
|||||||
|
|
||||||
- **展示组件**(`components/`):纯渲染逻辑,通过 props 接收数据,通过回调返回事件
|
- **展示组件**(`components/`):纯渲染逻辑,通过 props 接收数据,通过回调返回事件
|
||||||
- **容器逻辑**放在 hooks 中,组件只做数据消费
|
- **容器逻辑**放在 hooks 中,组件只做数据消费
|
||||||
- **常量数据**(列定义、排序器、筛选器)放在 `constants/`,不放在组件内部
|
- **常量数据**(列定义、排序器、筛选器、颜色阈值)放在 `constants/`,不放在组件内部
|
||||||
- **工具函数**(时间处理等)放在 `utils/`,保持纯函数无副作用
|
- **工具函数**(时间处理等)放在 `utils/`,保持纯函数无副作用
|
||||||
|
|
||||||
#### 现有组件清单
|
#### 现有组件清单
|
||||||
|
|
||||||
| 组件 | 文件 | 用途 |
|
| 组件 | 文件 | 用途 |
|
||||||
| -------------------- | ----------------------------------- | ---------------------------------- |
|
| -------------------- | ----------------------------------- | ----------------------------------------- |
|
||||||
| `App` | `app.tsx` | 根组件,编排全局状态与布局 |
|
| `App` | `app.tsx` | 根组件,编排全局状态与布局 |
|
||||||
| `SummaryCards` | `components/SummaryCards.tsx` | 总览统计卡片(全部/正常/异常) |
|
| `ErrorBoundary` | `components/ErrorBoundary.tsx` | React 错误边界,捕获渲染异常并展示降级 UI |
|
||||||
| `TargetBoard` | `components/TargetBoard.tsx` | 按分组渲染目标表格列表 |
|
| `SummaryCards` | `components/SummaryCards.tsx` | 总览统计卡片(全部/正常/异常) |
|
||||||
| `TargetGroup` | `components/TargetGroup.tsx` | 单个分组标题 + PrimaryTable |
|
| `TargetBoard` | `components/TargetBoard.tsx` | 按分组渲染目标表格列表 |
|
||||||
| `TargetDetailDrawer` | `components/TargetDetailDrawer.tsx` | 目标详情抽屉(概览/记录 Tab) |
|
| `TargetGroup` | `components/TargetGroup.tsx` | 单个分组标题 + PrimaryTable |
|
||||||
| `TrendChart` | `components/TrendChart.tsx` | Recharts 双轴折线图(耗时/可用率) |
|
| `TargetDetailDrawer` | `components/TargetDetailDrawer.tsx` | 目标详情抽屉(概览/记录 Tab) |
|
||||||
| `StatusDonut` | `components/StatusDonut.tsx` | Recharts 环状图(UP/DOWN 分布) |
|
| `TrendChart` | `components/TrendChart.tsx` | Recharts 双轴折线图(耗时/可用率) |
|
||||||
| `StatusDot` | `components/StatusDot.tsx` | 圆形状态指示点(绿/红) |
|
| `StatusDonut` | `components/StatusDonut.tsx` | Recharts 环状图(UP/DOWN 分布) |
|
||||||
| `StatusBar` | `components/StatusBar.tsx` | 最近采样状态条(多色块) |
|
| `StatusDot` | `components/StatusDot.tsx` | 圆形状态指示点(绿/红) |
|
||||||
| `GroupHeader` | `components/GroupHeader.tsx` | 分组标题(名称 + 统计) |
|
| `StatusBar` | `components/StatusBar.tsx` | 最近采样状态条(多色块) |
|
||||||
|
| `GroupHeader` | `components/GroupHeader.tsx` | 分组标题(名称 + 统计) |
|
||||||
|
|
||||||
### 2.5 新增功能开发步骤
|
### 2.5 新增功能开发步骤
|
||||||
|
|
||||||
@@ -817,7 +708,7 @@ export function TargetGroup({ name, targets, onTargetClick }: TargetGroupProps)
|
|||||||
### 2.7 前端测试规范
|
### 2.7 前端测试规范
|
||||||
|
|
||||||
- 测试目录:`tests/web/`,结构对应 `src/web/`
|
- 测试目录:`tests/web/`,结构对应 `src/web/`
|
||||||
- 重点测试 **constants/** 中的纯函数(排序器、筛选器、颜色阈值等)
|
- 重点测试 **constants/** 中的纯函数(排序器、筛选器、颜色阈值、类型映射等)
|
||||||
- 使用 `bun:test` 框架
|
- 使用 `bun:test` 框架
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -858,9 +749,10 @@ bun run dev:web
|
|||||||
|
|
||||||
#### 开发期代理
|
#### 开发期代理
|
||||||
|
|
||||||
Vite 配置了开发代理(`vite.config.ts`):
|
Vite 配置了开发代理(`vite.config.ts`)和代码分割策略:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
|
// 开发代理
|
||||||
server: {
|
server: {
|
||||||
proxy: {
|
proxy: {
|
||||||
"/api": {
|
"/api": {
|
||||||
@@ -869,6 +761,11 @@ server: {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 生产代码分割(rolldownOptions.output.codeSplitting.groups)
|
||||||
|
// vendor-react: react/react-dom/scheduler
|
||||||
|
// vendor-tdesign: tdesign
|
||||||
|
// vendor-chart: recharts/d3-*
|
||||||
```
|
```
|
||||||
|
|
||||||
前端访问 `/api/*` 时,Vite 开发服务器自动转发到后端 `http://127.0.0.1:${backendPort}`,无需 CORS 配置。
|
前端访问 `/api/*` 时,Vite 开发服务器自动转发到后端 `http://127.0.0.1:${backendPort}`,无需 CORS 配置。
|
||||||
@@ -879,7 +776,7 @@ server: {
|
|||||||
|
|
||||||
#### 生产期集成
|
#### 生产期集成
|
||||||
|
|
||||||
生产可执行文件是单体应用:前端静态资源嵌入 binary,后端同时提供 API 和静态文件服务。
|
生产可执行文件是单体应用:前端静态资源嵌入 binary(通过 `StaticAssets` 接口:`files: Record<string, Blob>` + `indexHtml: Blob`),后端同时提供 API 和静态文件服务。
|
||||||
|
|
||||||
```
|
```
|
||||||
./dist/dial-server probes.yaml
|
./dist/dial-server probes.yaml
|
||||||
@@ -1006,16 +903,17 @@ bun run test:smoke
|
|||||||
|
|
||||||
### 3.8 项目配置文件
|
### 3.8 项目配置文件
|
||||||
|
|
||||||
| 文件 | 用途 |
|
| 文件 | 用途 |
|
||||||
| --------------------- | ---------------------------------------------- |
|
| ---------------------- | ---------------------------------------------- |
|
||||||
| `package.json` | 项目信息、脚本、依赖声明 |
|
| `package.json` | 项目信息、脚本、依赖声明 |
|
||||||
| `tsconfig.json` | TypeScript 配置(ESNext 模块、严格模式) |
|
| `tsconfig.json` | TypeScript 配置(ESNext 模块、严格模式) |
|
||||||
| `vite.config.ts` | Vite 开发代理与构建配置 |
|
| `vite.config.ts` | Vite 开发代理与构建配置(含代码分割策略) |
|
||||||
| `eslint.config.js` | ESLint 规则(含前端不得 import server 的检查) |
|
| `eslint.config.js` | ESLint 规则(含前端不得 import server 的检查) |
|
||||||
| `.prettierrc.json` | Prettier 格式化规则(`printWidth: 120`) |
|
| `commitlint.config.js` | commitlint 提交信息格式校验 |
|
||||||
| `.prettierignore` | Prettier 排除路径 |
|
| `.prettierrc.json` | Prettier 格式化规则(`printWidth: 120`) |
|
||||||
| `probes.example.yaml` | 配置文件示例 |
|
| `.prettierignore` | Prettier 排除路径 |
|
||||||
| `opencode.json` | OpenCode 工具配置(TDesign MCP server) |
|
| `probes.example.yaml` | 配置文件示例 |
|
||||||
|
| `opencode.json` | OpenCode 工具配置(TDesign MCP server) |
|
||||||
|
|
||||||
### 3.9 依赖管理
|
### 3.9 依赖管理
|
||||||
|
|
||||||
@@ -1083,6 +981,7 @@ bun run check # 一键运行 schema:check + typecheck + lint + test
|
|||||||
| `noUnusedParameters` | false | 保留关闭(路由 handler 统一签名需要,如 `handleXxx(store, method, mode)`) |
|
| `noUnusedParameters` | false | 保留关闭(路由 handler 统一签名需要,如 `handleXxx(store, method, mode)`) |
|
||||||
| `noPropertyAccessFromIndexSignature` | true | 禁止通过点号访问索引签名属性,强制使用括号语法 |
|
| `noPropertyAccessFromIndexSignature` | true | 禁止通过点号访问索引签名属性,强制使用括号语法 |
|
||||||
| `noUncheckedIndexedAccess` | true | 数组/Map 访问必须运行时真值检查 |
|
| `noUncheckedIndexedAccess` | true | 数组/Map 访问必须运行时真值检查 |
|
||||||
|
| `noImplicitOverride` | true | 子类覆盖父类方法时必须显式使用 `override` 关键字 |
|
||||||
| `verbatimModuleSyntax` | true | 强制 `import type` 纯类型导入 |
|
| `verbatimModuleSyntax` | true | 强制 `import type` 纯类型导入 |
|
||||||
|
|
||||||
### Git Hooks
|
### Git Hooks
|
||||||
@@ -1107,4 +1006,4 @@ bun run verify # 完整验证(check + 构建 + smoke test)
|
|||||||
|
|
||||||
## 已知限制
|
## 已知限制
|
||||||
|
|
||||||
当前不做告警通知、数据自动清理、拨测目标动态增删、认证鉴权和分布式部署。Command 类型拨测不支持 Windows 环境。
|
当前不做告警通知、拨测目标动态增删、认证鉴权和分布式部署。Command 类型拨测不支持 Windows 环境。
|
||||||
|
|||||||
Reference in New Issue
Block a user