File size: 9,915 Bytes
d9ffd67 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 | ---
title: "平台端点"
description: "World Monitor 平台基础设施端点完整参考:涵盖引导启动、健康检查、版本探测、缓存清理与用户偏好设置等 RPC——这些是每个仪表盘客户端、桌面应用、命令行工具、SDK 与 MCP 服务器在启动握手、运行时同步、遥测上报与退出清理阶段都会调用的底层管道 API。"
---
这些端点不属于任何领域 RPC 服务 —— 它们位于 API 表面的根层,负责处理平台层面的关注点。
## 引导
### `GET /api/bootstrap`
为仪表盘提供单次往返的初始数据加载。在单次响应中返回**所有已注册引导的 Redis 缓存键**,从其 seed 封装中解包出来。
- **认证**:浏览器 `wm-session` cookie、`X-WorldMonitor-Key` 或 `X-Api-Key` 别名。用户签发的密钥会针对当前 API 访问权限进行验证。
- **匿名天气**:`?keys=weatherAlerts` 仅在**未发送任何 API 密钥头时**公开。如果你附加 `X-WorldMonitor-Key` / `X-Api-Key`,即使是天气请求也会被完全验证 —— 格式错误的密钥返回 `401`,没有当前 API 访问权限的密钥返回 `403`。始终发送密钥的调用方必须发送有效的、有权限的密钥(或省略该头以使用匿名天气路径)。该 URL 为 `no-store`,这正是该约定在边缘层与源站同样成立的原因:没有任何缓存持有它,因此无效密钥永远不会被已预热的匿名缓存条目应答。
- **公开天气**:`?keys=weatherAlerts&public=1` 是经 CDN 缓存的天气读取。与 `?tier=fast&public=1` 一样,它对所有调用方返回同一份共享种子负载,并忽略你附加的任何凭据 —— CDN 命中先于鉴权发生,正是该标记让响应可以被安全缓存。高频匿名读取请优先使用它;需要校验自己的密钥时使用不带标记的 URL。
- **服务器到服务器**:直接用 `X-WorldMonitor-Key: wm_...` 调用 `https://api.worldmonitor.app/api/bootstrap`。此端点没有单独的网关主机、令牌交换、激活步骤或 IP 白名单要求。
- **缓存**:只有显式标记的 `?...&public=1` URL 可被共享缓存,因为只有它们对每个调用方返回相同结果。`?tier=fast&public=1` / `?tier=slow&public=1` 使用浏览器 `max-age=60` / `max-age=300` 和 CDN `s-maxage=600` / `s-maxage=7200`。单键公开 URL:on-demand 键(`?keys=<onDemandName>&public=1`)在未声明自有配置时继承 slow 配置 —— 浏览器 `max-age=300`、CDN `s-maxage=7200`;目前仅 `chinaDecisionSignals` 声明了自有配置(浏览器 `max-age=60`、CDN `s-maxage=900`);`?keys=weatherAlerts&public=1` 使用 `Cache-Control: public, s-maxage=600, stale-while-revalidate=120, stale-if-error=900` 并配合 fast 层 CDN 屏蔽。其余所有形态 —— 密钥认证、会话认证、未标记的 `?tier=...` URL,以及匿名 `?keys=weatherAlerts` 路径 —— 均使用 `Cache-Control: no-store` 且不发出 CDN 缓存头。这一区分是有意为之:CDN 命中先于鉴权发生,因此答案取决于凭据的 URL 绝不能可缓存。
- **速率限制**:此端点上的用户 API 密钥验证具有 fail-closed 的固定每 IP 60 秒 600 次预验证上限,与默认的 API 滑动窗口限流器分开。
- **结构**:`{ "data": { "earthquakes": ..., "outages": ..., "marketQuotes": ... }, "missing": [] }` —— 约 40+ 个已解包的已 seed 领域负载嵌套在 `data` 之下,外加一个 `missing` 列表用于 Redis 中不存在的缓存键。
在页面初始加载时使用此端点,可避免 40 个并行的 RPC 调用。
## 版本
### `GET /api/version`
返回 `koala73/worldmonitor` 的最新 **GitHub Release**。桌面应用使用此端点检测是否有更新的发布版本,并提示用户升级。它**不是**当前部署的 Vercel commit。
```json
{
"version": "2.6.7",
"tag": "v2.6.7",
"url": "https://github.com/koala73/worldmonitor/releases/tag/v2.6.7",
"prerelease": false
}
```
缓存策略为 `public, s-maxage=300, stale-while-revalidate=60, stale-if-error=3600`。当 GitHub API 不可达时,返回 `502 { "error": "upstream" }` 或 `502 { "error": "fetch_failed" }`。
## 缓存清理
### `POST /api/cache-purge`
内部端点。通过显式列表或 glob 模式使 Redis 缓存键失效。
- **认证**:`Authorization: Bearer $RELAY_SHARED_SECRET`(使用 timing-safe 比较)。其他任何凭据返回 `401`。
- **请求体**(`keys` / `patterns` 至少需要其一):
```json
{
"keys": ["market:stocks-bootstrap:v1", "infra:outages:v1"],
"patterns": ["market:sectors:*"],
"dryRun": false
}
```
- **限制**:最多 20 个显式键,最多 3 个模式(每个必须以 `*` 结尾,裸 `*` 会被拒绝),总共最多 200 次删除,每个模式最多 5 次 SCAN 迭代。
- **安全**:前缀为 `rl:` / `__` 的键始终被跳过;会匹配 `military:bases:*`、`conflict:iran-events:*`、`conflict:ucdp-events:*`(持久 seed)的模式会被跳过。
- **非生产环境**:在 preview / development 部署上,键会自动加上 `{env}:{git-sha}:` 前缀,因此清理操作不会影响生产数据。
- **响应**:
```json
{ "matched": 4, "deleted": 4, "keys": ["..."], "dryRun": false, "truncated": false }
```
## 健康检查
### `GET /api/health`
针对**所有已注册 seed 键**的聚合新鲜度报告。在 JSON `status` 字段中返回 `HEALTHY`、`WARNING`、`DEGRADED`、`UNHEALTHY` 或 `REDIS_DOWN`。
除 `REDIS_DOWN` 外的所有状态均返回 HTTP 200。`REDIS_DOWN` 返回 HTTP 503,因为 Redis 不可达且端点无法评估 seed 状态。响应不被缓存(`private, no-store, max-age=0` 加上 `CDN-Cache-Control: no-store`)。
通过 UptimeRobot / Better Stack 并使用 `?compact=1` 进行监控 —— 任何非 `HEALTHY` 状态都应触发告警。完整的详细视图需要 operator/enterprise API 密钥,因为它包含规范的缓存键名和新鲜度阈值。
```json
{
"status": "HEALTHY",
"checkedAt": "2026-04-19T12:00:00Z",
"summary": {
"total": 194,
"ok": 180,
"warn": 5,
"onDemandWarn": 9,
"staleContent": 0,
"crit": 0
},
"checks": {
"marketQuotes": { "status": "OK", "records": 78, "seedAgeMin": 12 },
"earthquakes": { "status": "OK", "records": 142, "seedAgeMin": 8 }
}
}
```
### `GET /api/seed-health`
针对由 Railway cron 驱动的 seeder 的并行注册表,具有各自的节奏阈值。与 `/api/health` 不同 —— 当节奏变化时两者都必须更新。参见[健康检查端点](/zh/health-endpoints)。
### `POST /api/seed-contract-probe`
内部探针,用于验证每个 seed 生产者的封装结构是否与其消费者匹配。如果任何消费者读取了生产者不再输出的字段,则返回违规。
## 用户偏好设置
### `GET /api/user-prefs`
### `POST /api/user-prefs`
每用户仪表盘偏好设置(布局、开关、过滤器)。需要 Clerk bearer 凭据。由 Convex 提供支持。
```json
{
"layout": "classic",
"enabledLayers": ["conflict", "aviation", "maritime"],
"defaultCountry": "US"
}
```
- **幂等性**:`POST /api/user-prefs` 上支持可选的 `Idempotency-Key`。使用相同键重试并附带相同请求体会重放原始偏好设置响应,而非再次应用更新。
## API key 缓存失效
### `POST /api/invalidate-user-api-key-cache`
在订阅变更后(Dodo webhook → Convex → 此端点)使用户的权益(entitlement)缓存失效。内部端点 —— 需要 `RELAY_SHARED_SECRET`。
## 地理工具
### `GET /api/geo?iso2=US`
返回国家元数据:质心、bbox、首都、ISO 代码。
### `GET /api/reverse-geocode?lat=40.7&lon=-74.0`
使用内置坐标数据集将 lat/lon 反向地理编码为最近的国家 + 城市。
### `GET /api/data/city-coords?q=Tokyo`
城市名 → 坐标查询。
## 实用工具
### `GET /api/download?platform=<id>&variant=<id>`
重定向到 `koala73/worldmonitor` 最新 GitHub release 中匹配的资产。成功时返回 `302` 指向资产 URL,任何失败时(未知平台、无匹配、GitHub 错误)返回 `302` 指向 [releases/latest](https://github.com/koala73/worldmonitor/releases/latest)。
**`platform`**(必填,精确字符串):
| 值 | 匹配 |
|-------|---------|
| `windows-exe` | `*_x64-setup.exe` |
| `windows-msi` | `*_x64_en-US.msi` |
| `macos-arm64` | `*_aarch64.dmg` |
| `macos-x64` | `*_x64.dmg`(排除 `*setup*`) |
| `linux-appimage` | `*_amd64.AppImage` |
| `linux-appimage-arm64` | `*_aarch64.AppImage` |
**`variant`**(可选):
| 值 | 将资产名称过滤为 |
|-------|-----------------------|
| `full` / `world` | `worldmonitor` |
| `tech` | `techmonitor` |
| `finance` | `financemonitor` |
将 302 缓存 5 分钟(`s-maxage=300`、`stale-while-revalidate=60`、`stale-if-error=600`)。
### `POST /api/leads/v1/submit-contact`
公开的企业联系表单。通过 Turnstile 验证,按 IP 限流。属于 `LeadsService` 的一部分。
### `POST /api/leads/v1/register-interest`
捕获邮箱用于 Pro 候补名单注册。写入 Convex 并发送确认邮件。属于 `LeadsService` 的一部分。
浏览器调用方必须通过 Turnstile。使用 `source: "desktop-settings"` 的桌面调用方仅当请求使用共享的桌面密钥进行身份验证时,才能绕过 Turnstile:
- `X-WorldMonitor-Desktop-Timestamp`:Unix epoch 毫秒,与服务器时间相差不超过 5 分钟。
- `X-WorldMonitor-Desktop-Signature`:`sha256=<hex HMAC-SHA256>`。
HMAC 输入为 `<timestamp>\n<canonical JSON>`,其中 canonical JSON 按顺序包含 `email`、`source`、`appVersion`、`referredBy`、`website` 和 `turnstileToken`。在桌面 sidecar 和云 API 上都要配置 `WM_DESKTOP_SHARED_SECRET`。在滚动推出期间,`WM_DESKTOP_AUTH_ALLOW_LEGACY=true` 仅在云 API 未配置 `WM_DESKTOP_SHARED_SECRET` 时接受未签名的旧版桌面请求。一旦设置了云密钥,桌面请求在缺少有效签名时将拒绝访问(fail closed),且仍受更严格的桌面限流约束。
|