Files
MoShiro/docs/api/api.md
T
justme c9beeee8e3 docs: describe the unified Agent topology and link controls
Align architecture, protocol, API, storage, security and deployment documentation with default mutual measurements and the Agent link board.
2026-10-21 11:55:55 +08:00

27 KiB
Raw Blame History

Backend API 规范(v1)

本文列出 Backend 的全部对外接口。通用规则见本文第 7 节「设计约束」;OpenAPI 3.1 文档在运行时由 /openapi/v1.json 提供,构建时同时生成快照 backend/openapi/v1.json。两者冲突时,以 OpenAPI 为准。

1. 分层与资源

IPv4 / IPv6

  • PATCH /api/admin/agents/{id} 新增可选 ipv6_host:IPv6 地址或域名;空字符串关闭 IPv6,缺省或 null 不修改。原 public_host 用于 IPv4。
  • GET /api/public/overview 新增可选 ip_version=ipv4|ipv6,缺省 IPv4。SSE /api/public/live 的同名参数选择 overview 主题的地址族;series 主题仍按线路 id 定位。
  • IPv4 线路 id 保持 listener~dialer,IPv6 为 agent~dialer~ipv6。series、distribution、heatmap、compare 和管理端测试端点共用这个标识。测试记录与评分叠加独立。
  • 线路、时序、测试和连接 DTO 新增 ip_version;管理端 Agent DTO 新增 ipv6_host、ipv6_supported;线路详情新增 ipv6_available(是否配置了 IPv6 地址)。公开 DTO 不包含任一种 IP 地址。
  • API v1 采用新增字段和可选参数,现有 IPv4 请求及历史链接继续有效。前端类型从 OpenAPI 重新生成。
/api/public/*     只读。站点设为公开时匿名可访问,设为私有时需要登录或只读令牌;
                  public_visible = false 的 Agent 对匿名访客隐藏
/api/admin/*      管理。需要 Cookie 会话(含 CSRF 校验)或带有对应能力的 API 令牌;每个端点声明自己需要的一项能力(OpenAPI 里的 `x-moshiro-capability`),完整目录见 `GET /api/admin/capabilities`,设计见 [access.md](../security/access.md)
/api/agent/v1/*   节点专用:注册(使用加入令牌)和 RPC(使用签名认证)
/api/release/offer  CI 登记签名发布清单(凭签名,无需令牌)
/healthz  /metrics  /openapi/v1.json  /install/*

资源总览:

/api/public
├─ overview                          首屏快照:所有线路的在线状态、最新指标、评分
├─ live                              SSE 实时推送
├─ lines/{line_id}                   线路详情(线路 = Agent 的一对,id 形如 agt_…~agt_…)
├─ lines/{line_id}/series            历史曲线(指标与评分)
├─ nodes   about   ipv6              节点的展示信息(系统、国家、运营商,开启后带版本和 tag);后端的版本信息;IPv6 配置与采集诊断(不含地址)
├─ compare                           多条线路对比
├─ tests/{test_id}                   测试结果
└─ catalog/{kinds|plans|metrics|models}   目录(由代码注册表生成)

/api/admin
├─ auth/{setup|login|logout|me|passkey|passkeys}   首次认领、登录、passkey
├─ trust                             操作员集合(信任根)
├─ audit                             管理操作审计日志
├─ agents  agents/{id}  agents/{id}/{approve|disable|enable|takeover}
├─ join-tokens
├─ links  links/{dialer_id}/{agent_id}[/retry]   线路开关:哪些(拨号方,Agent)连成线路(自动连接 + 手动选择),立即重试
├─ tasks  tasks/{id}                 线路模板、针对单个 Agent 的覆盖、目标任务
├─ lines/{line_id}/tests             在某条线路(某个 拨号方)上发起按需测试、列出近期测试;tests/{id}/cancel
├─ raw/chunks  raw/chunks/{id}       原始数据检索与下载
├─ build-info                        后端和每个节点的版本、tag、提交、构建时间、更新状态
├─ releases  release-key  nodes/{id}/update   CI 签名的发布清单、可信发布密钥;请求节点更新
├─ backend/update  backend/update/live   Backend 自身的更新确认与通知
├─ nodes/{id}/logs                   节点近期运行日志
├─ events   analysis/recompute   api-tokens   settings   users

2. Public API

展示数据的查询规则(时间段、步长、分层、指标分组、列式响应、实时跟随、缓存)以 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 第 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。

线路详情 GET /api/public/lines/{line_id} 带 data_since:这条线路最早的数据是什么时候(没有数据是 null),前端据此只提供「有数据」的时间范围。展示端的响应里没有 拨号方 的版本和来源地址(那些只在管理端)。

2.3.1 Agent 的主机资源占用

端点 内容
GET /api/public/hosts 每个对外可见的 Agent 和没被吊销的 拨号方 的最新快照:{id, name, online, stale, last_seen_at, host};超过 15 s 没有更新 stale 为真(离线,或者节点关了上报)
GET /api/public/hosts/{node_id}?range=1h Agent 或 拨号方 的历史曲线(列式,按步长取平均;range 从 1 小时到 30 天):cpu、mem、disk(百分比)、rx_bps、tx_bps

host 只有汇总的数字:CPU 占用、内存与根分区的已用和总量、网卡累计收发字节与瞬时网速、系统运行时长。没有 CPU 型号、主机名、网卡名,也没有任何 IP——Agent 不采集这些,公开接口当然也不会返回;有测试断言响应里不出现这类字段。实时更新走 SSE 的 hosts 主题,历史每个 Agent 每分钟存一个样本(retention.host_days,默认 7 天)。

2.3.2 节点的展示信息

GET /api/public/nodes:看得见的 Agent 和没被吊销的 拨号方 的展示信息,{id, kind, os, os_name, arch, country, isp_key, isp_name, isp_manual}——系统(linux / windows / macos)、「Debian 12」这样的系统名、架构(x64 / arm64…)、国家代码(CN)、运营商类别(mobile / telecom / unicom / broadnet / other)和名字。没有 IP 地址、城市、ASN、经纬度,有测试保证:公开接口的整个响应里不出现 IP 形状的字符串,也没有 ip、city、asn 这些字段。管理员在设置里打开 site.show_build 之后,每项还有 version 和 tag(节点自己上报的程序版本和 tag);关着时这两个字段是 null。

GET /api/public/about:{show_build, backend}。show_build 关着时 backend 是 null;打开后 backend 是 {version, tag, built_at}(后端自己的版本、tag、构建时间,不含提交号)。

  • 系统和架构是节点自报的:Agent 都在各自连面板时的 fleet.hello 里自报。只有粗粒度的名字,没有主机名、内核版本、CPU 型号。
  • 国家和运营商由 Backend 用节点的来源地址向 IP 归属服务查一次(MoShiro__GeoLookupUrl,缺省 https://ip.007995.xyz,留空就不查):拨号方 用 Agent 看到的来源 IP,Agent 用它的公开地址。Agent 不访问任何外部服务。只查公网地址(回环、内网、CGNAT、链路本地一律不发出去);同一个地址一周内只查一次,失败 10 分钟后再试;查到的只留国家代码和运营商,地址只存在 Backend 内部的 node_meta.geo_host,不对外。

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 设 MoShiro__CorsOrigins(逗号分隔的来源,或 *;默认空 = 不允许)。只对 /api/* 生效,响应带 Access-Control-Allow-Origin,预检请求(OPTIONS)返回 204,允许的头是 Authorization, Content-Type, X-CSRF-Token。不带凭据,Cookie 不会跨域,所以跨域的调用只能用 Authorization: Bearer <API 令牌>:展示端用只读令牌(站点私有时需要),管理端用 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、ipv6_host、public_visible;public_host_auto / ipv6_host_auto 为 true 时撤销手填、回到自动识别。手填就是手动,Agent 上报的地址不再覆盖;DTO 的 public_host_auto / ipv6_host_auto 表示当前是不是自动
POST /api/admin/agents/{id}/approve /disable /enable /takeover 见 enrollment.md
DELETE /api/admin/agents/{id} 删除;历史数据按保留策略自然过期
GET/POST /api/admin/join-tokens 创建时返回令牌明文和安装命令(仅此一次);role 统一为 agent(默认)
DELETE /api/admin/join-tokens/{id} 吊销
POST /api/admin/agents/{id}/dialers/{dialer_id}/disconnect 让这个 Agent 断开这个 拨号方 的会话(拨号方 会重连,除非已吊销);没有会话时返回 409 dialer_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};拨号方 离线时返回 409 dialer_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.moshiro.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 创建时勾选能力(capabilities,只能是创建者自己拥有能力的子集,写蕴含读)和可选的有效期(expires_in_days);明文只显示一次
POST /api/admin/auth/passkey/options、/api/admin/auth/passkey/login 匿名(限流)。passkey 登录:先取挑战,再提交断言;响应同密码登录
GET/POST/DELETE /api/admin/auth/passkeys、/auth/passkeys/options、/auth/passkeys/{id} 已登录用户管理自己的 passkey:列出、取注册挑战、注册、删除(API key 不能用)
GET /api/admin/capabilities 能力目录:分组、哪些改变信任、哪些是预留、依赖关系和预设(只读、运维、接入、CI 发布、全部)
GET/PATCH /api/admin/settings 站点公开或私有、是否对访客显示构建信息(site.show_build)、保留期、节点定时重启(fleet.restart_enabled、fleet.restart_hour、fleet.restart_minute、fleet.restart_utc_offset_minutes,默认开、04:30、UTC+8;改完立即通知在线节点)等
GET /api/admin/build-info 后端和每个 Agent 的版本、tag、提交、构建时间、系统,以及节点宿主机更新器报告的更新状态(管理端「系统 → 版本」)
GET /api/admin/releases 每个组件最新的一份签名发布清单
GET/PUT/DELETE /api/admin/release-key 可信资产来源:GET 看官方发布密钥和(如果有)自有发布公钥的指纹;PUT {public_key} 另外信任自己的发布公钥(P-256 的 PEM,拒绝私钥);DELETE 停用它,只有它签过的已登记清单会被丢掉。官方密钥始终信任。PUT/DELETE 需要 update.confirm,写审计
POST /api/release/offer CI 登记发布清单的入口,不需要令牌:{manifest, signature} 的签名必须能用 Backend 内嵌的官方发布公钥或已开启的自有发布公钥验过(ECDSA P-256 / SHA-256,DER),否则 403;seq 必须比已登记的新,否则 409。登记不依赖面板里的任何凭证,清库、重装、还没有 passkey 时照常工作
GET /api/admin/links 每一对(拨号方,Agent)的线路开关:linked(最终结果)、override(手动设置,null 跟随自动连接)、connected、connection_state(disabled、dialer_unavailable、listener_unavailable、waiting_report、control_connecting、control_down、task_connecting、task_retrying、connected,含义见 architecture.md 第 4.3 节),以及 auto_link
PUT /api/admin/links/{dialer_id}/{agent_id} {enabled: true|false|null}:手动强制连或不连,null 取消手动设置;改完立即重新下发
POST /api/admin/nodes/{id}/update 请求一个在线的 Agent 或 拨号方 更新到它收到的清单(节点不在线返回 409)
GET/POST/PATCH /api/admin/users、/api/admin/users/{id} 用户列表、创建、修改密码
GET/POST /api/admin/auth/setup 首次认领:GET 返回面板是否尚无用户;POST 凭后端日志中的认领码创建第一个管理员,响应同密码登录,已有用户后关闭
GET/POST /api/admin/trust 操作员集合(信任根):GET 返回创世指纹、版本、与已注册 passkey 是否一致;POST 提交 passkey 签名的下一版集合
GET /api/admin/audit 管理操作审计日志,按时间倒序
GET /api/admin/lines/{line_id}/tests 该线路的近期测试
POST /api/admin/links/{dialer_id}/{agent_id}/retry 启用该线路并立即让 拨号方 重试连接
GET /api/admin/nodes/{id}/logs 节点近期运行日志(经心跳上传)
GET/POST /api/admin/backend/update、GET /backend/update/live Backend 自身的更新状态、管理员确认更新,以及 SSE 通知

任务的请求体示例:

{ "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 文档的「注册」一节
POST /api/agent/v1/enroll/trust 返回操作员集合链和加入令牌的授权,节点据此验证后再注册
GET /api/agent/v1/rpc 由 MapFleetLink 提供,升级为 WebSocket;MoShiro 的方法见 protocol.md

5. 运维端点

路径 认证 说明
GET /healthz 无 SQLite 与原始数据目录都可写
GET /metrics admin 令牌 Prometheus 格式:在线数、块入库速率、分析队列长度、重算进度、磁盘占用、SSE 连接数
GET /openapi/v1.json 无
GET /install(也接受 /install.sh)、GET /install.ps1、GET /install/openwrt.sh 无 静态安装脚本,原样返回、不替换任何值,所以哈希在一个版本内固定;角色、面板地址、代码和属主走环境变量
GET /install/compose/{file}、GET /install/quadlet/{file} 无 静态的 compose 文件与 Quadlet 单元(agent、backend),所有人拿到的字节相同
GET /install/manifest?code=…、GET /install/manifest.sig?code=… 无(凭一次性代码) 该角色已签名的发布清单(原样)和签名,安装脚本据此验签、按 digest 拉镜像;代码不能用就是 404

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、dialer_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

7. 设计约束

本文是所有 Backend REST 端点都必须遵守的规则;具体端点见上面第 1–6 节。规则参考了 Microsoft Azure REST API Guidelines 和 RFC 9457,并根据本项目做了取舍。内部协议(拨号方↔Agent、Agent↔Backend)的兼容规则见 protocol.md 第 4 节。

7.1 契约生成链路

Minimal API 端点(TypedResults 返回类型 + 显式元数据)
   │ dotnet build(Microsoft.Extensions.ApiDescription.Server)
   ▼
backend/openapi/v1.json(OpenAPI 3.1,提交到仓库)
   │ CI:git diff --exit-code,未提交的变更会导致失败
   ▼
web:openapi-typescript 生成类型,调用方的类型错误会直接暴露
  • 端点必须声明精确的返回类型,例如 Results<Ok<AgentDto>, NotFound<ProblemDetails>>,不能返回 IResult 或匿名对象,否则 OpenAPI 文档会不准确。
  • 每个端点都必须设置 WithName("agents_list")(即 operationId,snake_case、资源_动作)、WithSummary、WithTags。
  • 请求和响应 DTO 放在 MoShiro.Backend.Api/Contracts/,并注册到 Backend 的 JSON 源生成上下文。不要直接暴露 MoShiro.Protocol 中的线协议类型,因为 REST 与线协议的演进节奏不同。
  • 结构强类型,指标弱类型:资源结构(Agent、任务、测试等)必须是强类型 DTO;而指标值和评分统一使用字典 metrics: { "<metric_id>": number }、scores: { "<model_id>": number },键的含义、单位、版本由 /api/public/catalog/* 给出。这样新增指标或模型不需要改 OpenAPI,也不需要改前端类型。

7.2 版本与兼容性

  • 当前版本为 v1,路径前缀为 /api/public、/api/admin、/api/agent/v1。出现破坏性变更时,新增 /api/v2/public、/api/v2/admin 等路径。v1 内只允许非破坏性变更:
非破坏性变更(v1 内允许) 破坏性变更(需要新建 v2)
新增端点 删除或重命名端点、字段
响应中新增字段 修改字段类型、单位或语义
新增可选的请求字段或查询参数 可选字段改为必填
枚举新增值 删除枚举值
放宽校验 收紧校验、修改默认值
新增错误码 修改已有错误码对应的 HTTP 状态
  • 客户端必须忽略未知字段,也必须能处理未知的枚举值。
  • 需要 v2 时,v1 和 v2 至少并存一个发布周期;OpenAPI 文档按版本分文件(v1.json、v2.json)。

7.3 URL 与方法

  • 按调用方分层:/api/public(只读展示)、/api/admin(管理)、/api/agent(节点)。同一个资源在不同层中可以有不同的 DTO,例如 public 层不暴露 pubkey、local_limits。
  • 路径全部小写,资源名用复数名词,多个单词用 - 连接:/api/admin/join-tokens。
  • 嵌套最多一层;子资源一旦有全局唯一 ID,就提供顶层路径(/agents/{dialer_id})。
  • 查询参数和 JSON 字段使用 snake_case。
操作 方法 成功状态 说明
列表 GET 集合 200 分页
读取 GET 单个 200
创建 POST 集合 201 + Location 响应体返回完整资源
部分更新 PATCH 200 application/merge-patch+json(RFC 7396)
删除 DELETE 204 资源不存在时同样返回 204(幂等)
非 CRUD 动作 POST /{resource}/{id}/{verb} 200 或 204 例如 revoke、rotate-token
  • GET 请求不能产生副作用。PUT 不使用:配置类资源都通过 PATCH 修改。

7.4 数据表示

类别 规则 示例
时间点 RFC 3339 UTC,以 Z 结尾,精确到毫秒。唯一例外:列式时序响应中的时间轴数组 t 使用 Unix 毫秒(见 query.md) 2026-09-27T10:00:00.000Z
时长 整数 + 单位后缀 interval_ms: 1000
延迟 带小数的毫秒值(指标字典中的单位见目录) rtt_p95_ms: 58.93
百分比 0–100 的数字 + _pct loss_pct: 0.5
计数 整数,不使用浮点数 attempts: 600
ID 带类型前缀的 ULID 字符串 agt_01J9Z...
枚举 snake_case 字符串 "udp_echo"
缺失值 字段始终存在;未知值写 null,不省略字段 "last_seen_at": null
布尔值 使用 is_/has_ 前缀,或者用意义明确的形容词 online、enabled

7.5 错误

所有 4xx/5xx 响应都使用 application/problem+json(RFC 9457),并附加扩展字段:

{
  "type": "about:blank",
  "title": "Validation failed",
  "status": 400,
  "code": "validation_failed",
  "detail": "interval_ms must be >= 200",
  "trace_id": "00-4bf92f...-01",
  "errors": { "interval_ms": ["must be >= 200"] }
}
  • code 取自共享的 ErrorCode 枚举,是客户端做判断的唯一依据;title/detail 只供人阅读,内容可能变化。
  • errors 只在 400 validation_failed 中出现,键名是 JSON 字段路径。
  • 5xx 响应不能泄露异常信息或调用栈,只提供 trace_id,用于在日志中定位问题。
状态码 使用场景
400 请求格式错误或校验失败
401 未认证
404 资源不存在
403 已认证但作用域不足
409 名称冲突、状态冲突(例如 dialer_offline)
410 资源曾经存在但已过保留期(例如 raw_expired)
412 If-Match 不匹配
413 请求体超过上限
422 语义上无法满足(例如 range_too_large)
429 超过限流,需要带 Retry-After
503 存储不可用,或正在关闭

7.6 集合与查询

  • 分页由服务端驱动:?limit=(默认 50,最大 500)和 ?cursor=(不透明字符串);响应格式为 { "items": [...], "next_cursor": string | null }。排序必须稳定(按 ID 或时间 + ID)。
  • 时间范围参数统一使用 from/to,区间为左闭右开 [from, to)。缺省时 to = now,from = to − 1h。
  • 所有查询都有上限(点数、时间跨度、结果条数);超出时返回 422 并在 detail 中给出可行的替代方案,不能悄悄截断。

7.7 并发控制

  • 配置类资源(Agent、任务)在响应中返回 ETag,PATCH/DELETE 支持 If-Match,不匹配时返回 412。没有带 If-Match 的请求按“最后写入者获胜”处理。

7.8 安全

  • 只提供 HTTPS。认证方式有两种:
    • 浏览器使用 Cookie 会话:HttpOnly; Secure; SameSite=Strict;所有非 GET 请求都必须带 X-CSRF-Token 请求头;
    • 脚本使用 Authorization: Bearer <api_token>,作用域为 read 或 admin。
  • 凭据不能放在 URL 或查询参数中;SSE 依靠 Cookie 或同源令牌认证。
  • 登录接口需要限流(例如每个 IP 每分钟 10 次),失败时统一返回 401,不区分“用户不存在”和“密码错误”。
  • public 层根据站点设置和 public_visible 过滤数据:被隐藏的资源对匿名访客一律返回 404,而不是 403。
  • 令牌明文只在创建或轮换的响应中出现一次;这类响应带 Cache-Control: no-store。
  • 请求体上限为 64 KiB;查询接口有超时(默认 10 秒)。

7.9 实时推送(SSE)

  • 实时数据使用 text/event-stream。event 名称就是数据类型(line、node、point、test);data 是单行 JSON,遵守第 7.4 节的数据表示规则。
  • 每 15 秒发送一行注释作为心跳。重连后服务端先推送当前快照,再推送增量,不补发历史。
  • 订阅的主题数和连接数都有上限,超出时返回 429 too_many_streams。
  • 推送的内容必须能由对应的 REST 查询重建:SSE 只负责“更快地获得变化”,不承载独有的数据。

7.10 新增端点检查清单

  1. 符合第 7.3 节的路径与方法规则,并有 operationId、summary 和 tag;
  2. 返回类型精确(TypedResults),错误使用 problem+json,错误码来自 ErrorCode;
  3. DTO 符合第 7.4 节的数据表示规则,并注册到 JSON 源生成上下文;
  4. 列表支持分页,查询有上限;
  5. 选择正确的层(public / admin / agent),public 层只暴露可以公开的字段;
  6. 重新生成并提交 openapi/v1.json,在 PR 中说明是否属于破坏性变更;
  7. 测试覆盖:成功路径、校验失败、404、鉴权失败、隐藏资源;
  8. 同步更新本文上面的端点说明。