# Backend API 规范(v1) 本文列出 Backend 的全部对外接口。通用规则见 [api-guidelines.md](api-guidelines.md);OpenAPI 3.1 文档在运行时由 `/openapi/v1.json` 提供,构建时同时生成快照 `backend/openapi/v1.json`。两者冲突时,以 OpenAPI 为准。 ## 1. 分层 ```text /api/public/* 只读。站点设为公开时匿名可访问,设为私有时需要登录或只读令牌; public_visible = false 的 Agent 对匿名访客隐藏 /api/admin/* 管理。需要 Cookie 会话(含 CSRF 校验)或 admin 作用域的 API 令牌 /api/agent/v1/* 节点专用:注册(使用加入令牌)和 RPC(使用签名认证) /healthz /metrics /openapi/v1.json /install.sh /install.ps1 ``` 资源总览: ```text /api/public ├─ overview 首屏快照:所有线路的在线状态、最新指标、评分 ├─ live SSE 实时推送 ├─ lines/{line_id} 线路详情(线路 = Agent 与 Probe 的一对,id 形如 agt_…~prb_…) ├─ lines/{line_id}/series 历史曲线(指标与评分) ├─ compare 多条线路对比 ├─ tests/{test_id} 测试结果 └─ catalog/{kinds|plans|metrics|models} 目录(由代码注册表生成) /api/admin ├─ auth/{login|logout|me} ├─ agents agents/{id} agents/{id}/{approve|disable|enable|takeover} ├─ join-tokens ├─ probes probes/{id} probes/{id}/{rotate|revoke} Probe 身份(后端生成、吊销、删除) ├─ tasks tasks/{id} 线路模板、针对单个 Agent 的覆盖、目标任务 ├─ lines/{line_id}/tests 在某条线路(某个 Probe)上发起按需测试;tests/{id}/cancel ├─ raw/chunks raw/chunks/{id} 原始数据检索与下载 ├─ releases 应用版本、灰度、批准(Fleet.Server.Releases) ├─ events analysis/recompute api-tokens settings users ``` ## 2. Public API > 展示数据的查询规则(时间段、步长、分层、指标分组、列式响应、实时跟随、缓存)以 [query.md](query.md) 为准。本节只列出端点。 ### 2.1 首屏排行 `GET /api/public/overview?range=5m&preset=udp,tcp,scores&sort=score.mc&order=desc` - 返回每条线路在该时间段内的精确汇总,以及在线状态、会话信息(源地址变化次数)、最近一次测试的摘要; - `range` 默认 `5m`,也可以写 `from/to`; - 汇总字段的格式见 [query.md](query.md) 第 6 节。 ### 2.2 实时推送(SSE) `GET /api/public/live?topics=nodes,overview:15m:udp,series:agt_…:2s:udp,tcp,test:tst_…`(`text/event-stream`) | 事件 | 订阅主题 | 频率 | 内容 | | --- | --- | --- | --- | | `node` | `nodes` | 上下线时 | `{kind, id, online, at}` | | `overview` | `overview:{range}:{preset}` | 约每 10 s | 每条线路的最新汇总 | | `series` | `series:{line_id}:{step}:{preset}`(step ≤ 1m) | 约每 10 s | 新增的桶(列式)和最新的汇总 | | `host` | `hosts` | Agent 每次上报(约 2 s) | `{agent_id, online, host}`:Agent 主机资源占用的最新快照(连接时先推一份当前的) | | `test` | `test:{test_id}` | 状态变化时 | `{test_id, status, phase, phases}` | - 每 15 秒发送一行注释作为心跳;断线后浏览器会自动重连,服务端先推送当前快照,再推送增量,不补发历史。 - 单个连接最多订阅 32 个主题;每个客户端最多 4 个 SSE 连接。 ### 2.3 时序、对比、分布 | 端点 | 说明 | | --- | --- | | `GET /api/public/lines/{line_id}/series?range=15m&preset=udp,tcp` | 单条线路的时序(列式),附带整个时间段的精确汇总、缺口和事件 | | `GET /api/public/compare?lines=a~p1,b~p1,c~p2&metric=loss.down&range=1h` | 多条线路的时间轴对齐对比 | | `GET /api/public/lines/{line_id}/distribution?metric=rtt&range=15m` | 该时间段内的直方图和 CDF | | `GET /api/public/lines/{line_id}/heatmap?metric=rtt&range=6h` | 时间 × 延迟桶的热力图 | 参数、响应示例和限额见 [query.md](query.md)。 线路详情 `GET /api/public/lines/{line_id}` 带 `data_since`:这条线路最早的数据是什么时候(没有数据是 null),前端据此只提供「有数据」的时间范围。**展示端的响应里没有 Probe 的版本和来源地址**(那些只在管理端)。 ### 2.3.1 Agent 的主机资源占用 | 端点 | 内容 | | --- | --- | | `GET /api/public/hosts` | 每个对外可见的 Agent 的最新快照:`{agent_id, name, online, stale, last_seen_at, host}`;超过 15 s 没有更新 `stale` 为真(离线,或者 Agent 关了上报) | | `GET /api/public/agents/{agent_id}/host?range=1h` | 历史曲线(列式,按步长取平均;`range` 从 1 小时到 30 天):`cpu`、`mem`、`disk`(百分比)、`rx_bps`、`tx_bps` | `host` 只有汇总的数字:CPU 占用、内存与根分区的已用和总量、网卡累计收发字节与瞬时网速、系统运行时长。**没有 CPU 型号、主机名、网卡名,也没有任何 IP**——Agent 不采集这些,公开接口当然也不会返回;有测试断言响应里不出现这类字段。实时更新走 SSE 的 `hosts` 主题,历史每个 Agent 每分钟存一个样本(`retention.host_days`,默认 7 天)。 ### 2.4 测试结果与目录 - `GET /api/public/tests/{test_id}`:包括计划、参数、状态、阶段,`metrics` 与 `scores`,`series`(例如码率–丢包曲线),以及置信说明 `caveats`。 - `GET /api/public/catalog/kinds`:每个 Kind 的积木组合与 `ParamSpec`(默认值和上下限)。 - `GET /api/public/catalog/plans`、`/metrics`(id、单位、标签、说明、`better`、阈值、来源 Kind、版本)、`/presets`(指标分组)、`/models`(评分维度与所需指标)。 ### 2.5 跨域访问 默认前端和 Backend 同源(Cookie 会话)。前端在别的来源时(比如本机跑前端、连线上的 Backend),Backend 设 `Monitor__CorsOrigins`(逗号分隔的来源,或 `*`;默认空 = 不允许)。只对 `/api/*` 生效,响应带 `Access-Control-Allow-Origin`,预检请求(`OPTIONS`)返回 204,允许的头是 `Authorization, Content-Type, X-CSRF-Token`。**不带凭据**,Cookie 不会跨域,所以跨域的调用只能用 `Authorization: Bearer `:展示端用只读令牌(站点私有时需要),管理端用 admin 范围的令牌(`/api/admin/auth/me` 对令牌同样可用)。SSE 在跨域时前端用 fetch 读流,因为 `EventSource` 带不了请求头。 ## 3. Admin API | 方法 | 路径 | 说明 | | --- | --- | --- | | POST | `/api/admin/auth/login` | `{username, password}` → 设置 `HttpOnly; Secure; SameSite=Strict` 的会话 Cookie,并返回 `csrf_token` | | POST | `/api/admin/auth/logout` | | | GET | `/api/admin/auth/me` | | | GET | `/api/admin/agents` | 全部 Agent(包括 pending/disabled)、版本、本地硬上限、心跳计数、任务同步状态 | | PATCH | `/api/admin/agents/{id}` | `name`、`labels`、`public_host`、`public_visible` | | POST | `/api/admin/agents/{id}/approve` `/disable` `/enable` `/takeover` | 见 [enrollment.md](enrollment.md) | | DELETE | `/api/admin/agents/{id}` | 删除;历史数据按保留策略自然过期 | | GET/POST | `/api/admin/join-tokens` | 创建时返回令牌明文和安装命令(仅此一次) | | DELETE | `/api/admin/join-tokens/{id}` | 吊销 | | GET/POST | `/api/admin/probes` | 列出所有 Probe(含它现在连着哪些 Agent:版本、远端 IP、起始时间);创建 Probe 身份,返回 `probe.json`(含私钥,仅此一次) | | GET/PATCH/DELETE | `/api/admin/probes/{id}` | 详情、改名、删除(历史数据按保留策略自然过期) | | POST | `/api/admin/probes/{id}/rotate` | 换新密钥(id 不变):旧密钥立即失效,所有 Agent 断开它的会话;返回新的 `probe.json`(仅此一次) | | POST | `/api/admin/probes/{id}/revoke` | 吊销:公钥从所有 Agent 的名单里移除,Agent 立即断开它;记录和历史保留 | | POST | `/api/admin/agents/{id}/probes/{probe_id}/disconnect` | 让这个 Agent 断开这个 Probe 的会话(Probe 会重连,除非已吊销);没有会话时返回 409 `probe_offline` | | GET/POST | `/api/admin/tasks` | 列表和创建:line 模板(不带 `agent_id`)、单个 Agent 的覆盖(带 `agent_id` + `base_task_id`)、target 任务 | | GET/PATCH/DELETE | `/api/admin/tasks/{id}` | 参数按 Kind 的 `ParamSpec` 校验,并检查带宽预算 | | POST | `/api/admin/lines/{line_id}/tests` | `{plan, params?}` → **202** + `Location: /api/public/tests/{id}`;Probe 离线时返回 409 `probe_offline`,节点繁忙时返回 409 `test_busy` | | POST | `/api/admin/tests/{id}/cancel` | | | GET | `/api/admin/raw/chunks?agent_id=&kind=&from=&to=&test_id=` | 原始块元数据 | | GET | `/api/admin/raw/chunks/{chunk_id}` | 下载原始块(`application/vnd.monitor.rawchunk`);过期返回 410 `raw_expired` | | GET | `/api/admin/events?agent_id=&kind=&from=&to=` | 事件 | | GET | `/api/admin/analysis/recompute` | 指标重算进度 | | GET/POST/DELETE | `/api/admin/api-tokens` | 作用域为 `read` 或 `admin`;明文只显示一次 | | GET | `/api/admin/releases` | 各应用、各通道的当前版本,以及节点的版本分布(由 `Fleet.Server.Releases` 提供) | | POST | `/api/admin/releases/{app}/{channel}/rollout` | `{version, batches:[{labels?, count?}], soak_minutes}`:分批灰度;只能选择已经签名的清单 | | POST | `/api/admin/releases/{app}/approve` | 对 `policy = notify` 的节点批准更新 `{version, agents?}` | | GET/PATCH | `/api/admin/settings` | 站点公开或私有、保留期等 | | GET/POST/PATCH | `/api/admin/users` | 首次启动时通过命令行或环境变量创建初始管理员 | 任务的请求体示例: ```json { "scope": "line", "kind": "tcp_echo", "schedule": "continuous", "params": { "rate_hz": 50, "tcp_info_ms": 20, "port": 7444 } } ``` ## 4. Agent API | 方法 | 路径 | 说明 | | --- | --- | --- | | POST | `/api/agent/v1/enroll` | 由 `MapFleetEnrollment` 提供(使用加入令牌认证),见 [fleet/link.md](fleet/link.md) 第 3 节 | | GET | `/api/agent/v1/rpc` | 由 `MapFleetLink` 提供,升级为 WebSocket;Monitor 的方法见 [protocol.md](protocol.md) | | GET | `/fleet/releases/{app}/{channel}/latest.json`、`/fleet/releases/{app}/{version}/…` | 由 `MapFleetReleases` 提供的公开发布源(清单和制品),供 `fleet-updater` 轮询;也可以改用 GitHub Releases | ## 5. 运维端点 | 路径 | 认证 | 说明 | | --- | --- | --- | | `GET /healthz` | 无 | SQLite 与原始数据目录都可写 | | `GET /metrics` | admin 令牌 | Prometheus 格式:在线数、块入库速率、分析队列长度、重算进度、磁盘占用、SSE 连接数 | | `GET /openapi/v1.json` | 无 | | | `GET /install.sh` `/install.ps1` | 无 | 安装脚本(只下载二进制并写入配置) | ## 6. 错误码(节选,完整列表见共享的 `ErrorCode`) | HTTP | code | | --- | --- | | 400 | `validation_failed`、`budget_exceeded` | | 401 | `unauthorized`、`csrf_failed` | | 403 | `forbidden`(令牌作用域不足)、`site_private` | | 404 | `agent_not_found`、`task_not_found`、`test_not_found`、`chunk_not_found`、`unknown_kind`、`unknown_plan` | | 409 | `name_conflict`、`probe_offline`、`test_busy`、`agent_not_active` | | 410 | `raw_expired`、`join_token_expired` | | 422 | `range_too_large`、`invalid_step`、`unknown_metric`、`unknown_preset`、`too_many_metrics` | | 429 | `too_many_requests`、`too_many_streams` | | 503 | `storage_unavailable` |