release: bump version to 0.49.0
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -23,7 +23,10 @@
|
||||
|
||||
- [快速开始](/home/ray/dev/linkong/planet/docs/technical/zh/quickstart.md):从零启动 Planet 的最短路径
|
||||
- [Planet 使用手册](/home/ray/dev/linkong/planet/docs/technical/zh/manual.md):控制台、`planet.sh`、Earth 和 Docs 的完整使用手册
|
||||
- [Earth 位置候选采集使用手册](/home/ray/dev/linkong/planet/docs/technical/zh/location-pipeline-user.md):在 Earth 上为算力中心和 BGP 观测站采集、预览坐标候选
|
||||
- [数据源、采集器设置与连接验证](/home/ray/dev/linkong/planet/docs/technical/zh/datasource-collector-settings-connectivity.md):数据源目录、采集器设置、连接验证、BarentsWatch 凭证链路
|
||||
- [通用位置估算管线开发说明](/home/ray/dev/linkong/planet/docs/technical/zh/location-pipeline-development.md):后端 location resolver / pipeline 的接口、注册表和扩展方式
|
||||
- [Docs Gatekeeper 开发说明](/home/ray/dev/linkong/planet/docs/technical/zh/docs-gatekeeper-development.md):后端 Docs 目录、正文读取和 Gatekeeper 权限组实现
|
||||
- [Earth 可交互图标接入](/home/ray/dev/linkong/planet/docs/technical/zh/earth-interactable-usage.md):Earth 地表可交互图标 `Interactable` 的接口、生命周期和接入示例
|
||||
- [Earth 工具栏与浮层协同](/home/ray/dev/linkong/planet/docs/technical/zh/earth-toolbar-overlay-coordination.md):工具栏按钮与搜索 / 设置 / 新闻 / 图层浮层之间的关闭矩阵和接入规则
|
||||
|
||||
|
||||
@@ -88,6 +88,10 @@ async def run(self, db):
|
||||
| BarentsWatch AIS | vessel | 船只位置、航速、航向、MMSI 等 AIS 数据 | 依采集器配置 |
|
||||
| AISStream Vessels | vessel_ais | AIS WebSocket 实时流,写入原始观测层并由聚合接口展示 | 依采集器配置 |
|
||||
|
||||
AIS 船只类采集器和其它 `CollectedData` 采集器的落库路径不同。BarentsWatch、AISStream 和自定义 `vessel_ais` 源都会进入 AIS 原始观测层,随后由聚合服务合并成 Earth 船只图层使用的 GeoJSON 和详情数据。这样做可以保留来源、传输方式、字段冲突和观测时间,避免某个实时源直接覆盖最终展示表。
|
||||
|
||||
TOP500 和 Epoch AI 算力数据的公开源不总是提供可用经纬度。Earth 统一算力中心接口在主地图启动链路中只使用源数据自带坐标或 `compute_center_locations` 维表坐标;缺少坐标的记录会进入 `unresolved`,不会通过本地注册表、国家质心或猜测城市自动渲染。用户手动采集候选时,后端会用源字段调用 ROR 组织注册 API 和 Nominatim/OpenStreetMap 在线搜索;候选经前端保存后写入 `compute_center_locations`,后续地图刷新再从维表渲染。
|
||||
|
||||
## 四、数据格式 (统一存储到 CollectedData 表)
|
||||
|
||||
```python
|
||||
@@ -242,8 +246,16 @@ backend/app/services/collectors/
|
||||
├── vessel_ais.py # BarentsWatch AIS 船只采集器
|
||||
└── aisstream.py # AISStream WebSocket 船只采集器
|
||||
|
||||
backend/app/services/
|
||||
├── custom_datasource_runtime.py # 自定义 REST / WebSocket 映射运行时
|
||||
├── datasource_mapping.py # 确定性字段映射与目标写入
|
||||
├── vessel_ais_aggregation.py # AIS 原始观测写入与聚合读取
|
||||
├── vessel_aggregation_strategy.py # 多源字段选择、freshness fallback 和冲突记录
|
||||
└── vessel_enrichment.py # 船舶资料富化缓存
|
||||
|
||||
backend/app/models/
|
||||
└── collected_data.py # 统一数据模型
|
||||
├── collected_data.py # 统一数据模型
|
||||
└── vessel_enrichment.py # 船舶富化结果缓存
|
||||
```
|
||||
|
||||
## 九、凭证型采集器
|
||||
@@ -253,7 +265,7 @@ backend/app/models/
|
||||
| 采集器 | credential provider | 凭证来源 |
|
||||
| --- | --- | --- |
|
||||
| `barentswatch_vessels` | `barentswatch` | 控制台采集器设置、环境变量、`~/.zshrc` |
|
||||
| `aisstream_vessels` | `aisstream` | 控制台采集器设置、环境变量 |
|
||||
| `aisstream_vessels` | `aisstream` | 控制台采集器设置、环境变量、`~/.zshrc`(连接验证可读;正式采集建议保存到采集器设置或注入后端环境) |
|
||||
| `spacetrack_tle` | `spacetrack` | 环境变量、`~/.zshrc` |
|
||||
|
||||
### BarentsWatch AIS
|
||||
@@ -292,6 +304,50 @@ export BARRENTSWATCH_CLIENT_SECRET="..."
|
||||
|
||||
连接验证会先请求 `https://id.barentswatch.no/connect/token` 获取 `scope=ais` 的 access token,再用 `Authorization: Bearer <token>` 请求 AIS endpoint。
|
||||
|
||||
### AISStream 实时船舶
|
||||
|
||||
AISStream 使用 `wss://stream.aisstream.io/v0/stream` WebSocket endpoint。默认运行方式是长连接实时采集,而不是传统 REST collector 的“请求一次、进度到 100%、完成”模型。
|
||||
|
||||
运行时配置:
|
||||
|
||||
- `api_key`:优先从 `DataSourceConfig.auth_config.api_key` 或 `config.api_key` 读取;也可由后端进程环境变量 `AISSTREAM_API_KEY` 提供。
|
||||
- `bounding_boxes`:AISStream 订阅范围,默认示例为全球 `[[[-90, -180], [90, 180]]]`,生产或演示建议先缩小区域。
|
||||
- `message_types`:默认 `PositionReport` 和 `ShipStaticData`。
|
||||
- `streaming_enabled`:默认启用长连接;关闭后回退到批次式 `fetch -> transform -> save`。
|
||||
- `streaming_max_messages`:测试用上限,非 0 时收到指定消息数后停止。
|
||||
- `reconnect_delay_seconds`、`receive_timeout_seconds`:控制断线重连和空闲等待。
|
||||
|
||||
状态语义:
|
||||
|
||||
- `connecting`:正在连接 AISStream。
|
||||
- `streaming`:持续接收实时消息,`records_processed` 表示已见消息数,通常没有固定总量和百分比。
|
||||
- `reconnecting`:上游断开或网络异常,采集器记录 `AISSourceHealth` 后等待重连。
|
||||
- `stopped` / `cancelled`:任务被测试上限或用户停止。
|
||||
|
||||
AISStream 连接验证会通过 `datasource_connectivity.py` 读取保存的采集器配置、环境变量和 `~/.zshrc` 中的 `AISSTREAM_API_KEY`。正式采集时,最稳妥的方式是把 API Key 保存到“设置 -> 采集器设置 -> AISStream 实时船舶”;如果只放在 `~/.zshrc`,需要确认后端进程实际继承到了该环境变量。
|
||||
|
||||
### AIS 原始观测与聚合
|
||||
|
||||
AIS 观测写入后不会直接替换最终船只记录,而是先保存为 raw observation:
|
||||
|
||||
- `source` 记录来源,例如 `barentswatch_vessels`、`aisstream_vessels` 或自定义源名称。
|
||||
- `delivery_mode` 表达实时性,`realtime_stream` 优先于 `polling`。
|
||||
- `transport` 记录 `websocket` 或 `http`。
|
||||
- 位置、速度、航向等动态字段会按 freshness 和来源优先级选择。
|
||||
- 静态字段优先保留非空值;冲突候选会记录到详情接口,便于排查多源差异。
|
||||
|
||||
Earth 使用的接口仍是:
|
||||
|
||||
```http
|
||||
GET /api/v1/visualization/geo/vessels
|
||||
GET /api/v1/visualization/vessels/{mmsi}
|
||||
GET /api/v1/visualization/vessels/{mmsi}/track
|
||||
GET /api/v1/visualization/vessels/{mmsi}/conflicts
|
||||
GET /api/v1/visualization/vessels/aggregation/diagnostics
|
||||
```
|
||||
|
||||
`/geo/vessels` 会合并 raw observation 聚合结果和 legacy BarentsWatch latest position 结果,避免只接入 AISStream 后把历史 BarentsWatch 船只遮蔽掉。
|
||||
|
||||
## 十、采集器设置与连接验证
|
||||
|
||||
控制台的“采集器设置”页提供所有内置采集器的 endpoint、请求头、超时、重试和凭证配置。连接验证不是只看前端按钮状态,而是由后端计算 checksum:
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
当前行为:
|
||||
|
||||
- `collector_credentials` tab 展示为“采集器设置”。
|
||||
- 下拉框列出所有内置采集器。
|
||||
- 下拉框列出内置采集器,并支持维护合并到内置数据的自定义补充源。
|
||||
- 下拉框右侧只有一个插头图标按钮,用于健康检查。
|
||||
- 下拉框下方用状态标签展示:
|
||||
- `需要凭证` / `无需凭证`
|
||||
@@ -72,6 +72,8 @@
|
||||
- 是否覆盖 endpoint
|
||||
- 需要凭证的采集器把凭证卡片放在基础配置上方。
|
||||
- 不需要凭证的采集器只显示基础配置。
|
||||
- AISStream 采集器使用 WebSocket 语义,状态会显示为连接中、实时接收、重连或停止,不使用固定百分比表达完成度。
|
||||
- 自定义源入口放在采集器设置内,不在数据源目录里重复提供编辑入口;数据源目录只保留总览、运行和只读抽屉。
|
||||
|
||||
连接按钮使用内联 Tabler 风格插头图标,来源语义对应 `plug-connected`,避免继续使用刷新图标表达连接动作。
|
||||
|
||||
@@ -286,6 +288,100 @@ AISStream 使用 WebSocket 实时流,采集器只写入 `ais_raw_observations`
|
||||
- 船型通常来自低频 `ShipStaticData.Type`;后端会把 AIS 数字类型码映射为 Cargo / Tanker / Passenger / Fishing / Military。
|
||||
- 如果某艘船尚未收到静态消息,聚合结果的船型仍可能是 `Other`,后续由 v5 船舶资料 enrichment 补齐。
|
||||
|
||||
连接验证会读取保存配置、环境变量和 `~/.zshrc` 中的 `AISSTREAM_API_KEY`。正式采集时,推荐把 API Key 保存到采集器设置;如果只写在 `~/.zshrc`,需要确认后端进程实际继承了该变量,否则连接验证可能可用但 collector 运行时拿不到 key。
|
||||
|
||||
## 自定义 REST / WebSocket 映射运行时
|
||||
|
||||
文件:
|
||||
|
||||
- [custom_datasource_runtime.py](/home/ray/dev/linkong/planet/backend/app/services/custom_datasource_runtime.py)
|
||||
- [datasource_mapping.py](/home/ray/dev/linkong/planet/backend/app/services/datasource_mapping.py)
|
||||
|
||||
自定义源现在不是独立的新数据孤岛,而是作为内置数据源的补充源写入目标 schema。当前最完整的目标是 `vessel_ais`:自定义 REST 或 WebSocket 源经过确定性 mapping 后写入 AIS raw observations,再通过 `vessels` WebSocket channel 推送给 Earth。
|
||||
|
||||
### 配置语义
|
||||
|
||||
关键字段:
|
||||
|
||||
- `source_type`:`rest` / `http` / `websocket` / `ws`。
|
||||
- `endpoint`:REST 使用 `http(s)://`,WebSocket 使用 `ws(s)://`。
|
||||
- `auth_type`:`none`、`bearer`、`api_key`、`basic`。
|
||||
- `headers`:静态请求头。
|
||||
- `auth_config`:token、API key、basic 用户名密码,API key 支持 header 或 query。
|
||||
- `config.target_schema`:例如 `vessel_ais`。
|
||||
- `config.delivery_mode`:REST 默认 `polling`,WebSocket 默认 `realtime_stream`。
|
||||
- `config.merge_target_source`:记录该自定义源补充哪个内置数据,例如 `barentswatch_vessels`。
|
||||
|
||||
REST runner 支持:
|
||||
|
||||
- `GET` / `POST`
|
||||
- query params
|
||||
- JSON body
|
||||
- headers 和 auth 注入
|
||||
- active mapping 写入目标 schema
|
||||
|
||||
WebSocket runner 支持:
|
||||
|
||||
- endpoint 格式校验
|
||||
- headers 和 auth 注入
|
||||
- 可选 `ws_subscribe_message`
|
||||
- `ws_message_path` / `ws_items_path` 提取消息主体或数组
|
||||
- 断线重连
|
||||
- `debug_max_messages` 调试上限
|
||||
- 后台 stream start / stop / status
|
||||
|
||||
相关 API:
|
||||
|
||||
```http
|
||||
POST /api/v1/datasources/custom/sample
|
||||
GET /api/v1/datasources/target-schemas
|
||||
POST /api/v1/datasources/{config_id}/run-mapped
|
||||
POST /api/v1/datasources/{config_id}/stop-mapped
|
||||
GET /api/v1/datasources/{config_id}/mapped-status
|
||||
DELETE /api/v1/datasources/configs/{config_id}?delete_mappings=true&delete_source_data=true
|
||||
```
|
||||
|
||||
`run-mapped?background=true` 只对 WebSocket 源有意义,会启动后台 stream。REST 源仍是一次性采集。
|
||||
|
||||
### 删除与数据清理
|
||||
|
||||
删除自定义源时有三种层级:
|
||||
|
||||
- 只删除配置:保留 mapping 和历史数据。
|
||||
- 删除配置和 mapping:同时删除该配置的 mapping 模板。
|
||||
- 删除配置、mapping 和该源数据:删除该源写入的 `collected_data`、`ais_raw_observations` 和 `ais_source_health`。
|
||||
|
||||
如果删除的是 `vessel_ais` 自定义源数据,后端会向 `vessels` channel 广播 `reload_required`,提示 Earth 重新拉取船只聚合结果。legacy `vessel_position` 不按自定义源直接删除,因为它没有可靠的 source 归因。
|
||||
|
||||
### 本地 AIS mock WebSocket
|
||||
|
||||
文件:
|
||||
|
||||
- [mock-ais-ws-server.ts](/home/ray/dev/linkong/planet/scripts/mock-ais-ws-server.ts)
|
||||
|
||||
运行方式:
|
||||
|
||||
```bash
|
||||
bun run mock:ais-ws
|
||||
```
|
||||
|
||||
mock 服务持续发送 AIS-like JSON,用于验证“WebSocket 自定义源 -> mapping -> AIS raw observation -> `vessels` channel -> Earth 船只 upsert”链路。典型配置:
|
||||
|
||||
```json
|
||||
{
|
||||
"source_type": "websocket",
|
||||
"endpoint": "ws://localhost:8787",
|
||||
"config": {
|
||||
"target_schema": "vessel_ais",
|
||||
"delivery_mode": "realtime_stream",
|
||||
"merge_target_source": "barentswatch_vessels",
|
||||
"ws_message_path": "$.data",
|
||||
"ws_items_path": "$.vessels[*]",
|
||||
"ws_reconnect": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 凭证教程
|
||||
|
||||
文件:
|
||||
@@ -347,6 +443,7 @@ BarentsWatch `client_secret` 保存时有特殊处理:
|
||||
当前已经支持的凭证 provider:
|
||||
|
||||
- `barentswatch`
|
||||
- `aisstream`
|
||||
- `spacetrack`
|
||||
|
||||
其他 `requires_credentials=true` 的采集器如果还没有 provider,会返回“凭证链路尚未接入”,前端显示 `不可用`。
|
||||
|
||||
116
docs/technical/zh/docs-gatekeeper-development.md
Normal file
116
docs/technical/zh/docs-gatekeeper-development.md
Normal file
@@ -0,0 +1,116 @@
|
||||
# Docs Gatekeeper 开发说明
|
||||
|
||||
Docs Gatekeeper 把 `/docs` 从“前端构建时打包所有 Markdown”改成“后端按权限返回目录和正文”。它的目标是让公开使用手册、用户文档、开发文档和管理/运维文档在同一个 Docs 页面内可检索,但正文读取必须经过服务端白名单和用户权限检查。
|
||||
|
||||
用户侧说明见 [Planet 使用手册](/home/ray/dev/linkong/planet/docs/technical/zh/manual.md) 的 Docs 章节。
|
||||
|
||||
## 鉴权模型
|
||||
|
||||
Docs 使用两层权限:
|
||||
|
||||
- `users.role`:保留给控制台系统权限。
|
||||
- `users.gatekeeper_groups`:Docs 内容权限组。
|
||||
|
||||
权限组:
|
||||
|
||||
| 组 | 用途 |
|
||||
| --- | --- |
|
||||
| `docs_user` | 用户操作类文档 |
|
||||
| `docs_developer` | Earth、前端、后端、采集器和 AI Provider 开发文档 |
|
||||
| `docs_admin` | 服务控制、运维、环境变量和敏感操作文档 |
|
||||
|
||||
继承规则:
|
||||
|
||||
- 未登录用户只能读 `public`。
|
||||
- `docs_developer` 隐含 `docs_user`。
|
||||
- `docs_admin` 隐含 `docs_developer` 和 `docs_user`。
|
||||
- `admin` 和 `super_admin` 默认拥有全部 Docs 权限。
|
||||
|
||||
## 后端入口
|
||||
|
||||
文件:
|
||||
|
||||
- [docs.py](/home/ray/dev/linkong/planet/backend/app/api/v1/docs.py)
|
||||
- [docs_gatekeeper.py](/home/ray/dev/linkong/planet/backend/app/services/docs_gatekeeper.py)
|
||||
- [user.py](/home/ray/dev/linkong/planet/backend/app/models/user.py)
|
||||
- [users.py](/home/ray/dev/linkong/planet/backend/app/api/v1/users.py)
|
||||
|
||||
API:
|
||||
|
||||
```http
|
||||
GET /api/v1/docs/catalog
|
||||
GET /api/v1/docs/{lang}/{slug}
|
||||
```
|
||||
|
||||
`catalog` 只返回当前用户可见文档。正文接口会先校验语言、slug 和文件是否在 metadata 白名单里,再判断权限:
|
||||
|
||||
- 未登录访问受保护文档:`401`。
|
||||
- 已登录但权限不足:`403`。
|
||||
- 未知语言、未知 slug 或文件不存在:`404`。
|
||||
|
||||
正文文件只能来自 `docs/technical/{zh,en}/` 下的白名单文件,不能通过路径拼接读取任意文件。
|
||||
|
||||
## Metadata 来源
|
||||
|
||||
当前服务端 metadata 维护在 [docs_gatekeeper.py](/home/ray/dev/linkong/planet/backend/app/services/docs_gatekeeper.py):
|
||||
|
||||
```python
|
||||
DocsMetadata(
|
||||
"manual.md",
|
||||
"manual",
|
||||
"public",
|
||||
"Manual",
|
||||
2,
|
||||
"Planet 使用手册",
|
||||
"Planet Manual",
|
||||
)
|
||||
```
|
||||
|
||||
新增公开文档时,需要同步:
|
||||
|
||||
- 新增中英文 Markdown 文件。
|
||||
- 在服务端 `DOCS_METADATA` 添加 filename、slug、access、group、order、标题。
|
||||
- 在前端 [docs-content.ts](/home/ray/dev/linkong/planet/frontend/src/pages/Docs/docs-content.ts) 添加同名 metadata,保持导航标题和排序一致。
|
||||
- 如果需要从 README 发现,更新 `docs/technical/zh/README.md` 和 `docs/technical/en/README.md`。
|
||||
|
||||
## 用户管理
|
||||
|
||||
`users` 表新增 `gatekeeper_groups JSONB DEFAULT '[]'`。启动时 [session.py](/home/ray/dev/linkong/planet/backend/app/db/session.py) 会用 `ALTER TABLE ... ADD COLUMN IF NOT EXISTS` 补列,适配已有本地数据库。
|
||||
|
||||
用户 API 负责:
|
||||
|
||||
- 创建用户时写入 `gatekeeper_groups`。
|
||||
- 更新用户时校验组名只能是 `docs_user`、`docs_developer`、`docs_admin`。
|
||||
- 只有 `super_admin` 能修改 Gatekeeper 权限组。
|
||||
|
||||
前端 [Users.tsx](/home/ray/dev/linkong/planet/frontend/src/pages/Users/Users.tsx) 展示权限组标签,并在编辑表单中提供多选框。非 `super_admin` 打开的表单会禁用该字段,并在提交前移除 `gatekeeper_groups`。
|
||||
|
||||
## 前端 Docs 加载
|
||||
|
||||
文件:
|
||||
|
||||
- [Docs.tsx](/home/ray/dev/linkong/planet/frontend/src/pages/Docs/Docs.tsx)
|
||||
- [docs-content.ts](/home/ray/dev/linkong/planet/frontend/src/pages/Docs/docs-content.ts)
|
||||
- [docs-search.ts](/home/ray/dev/linkong/planet/frontend/src/pages/Docs/docs-search.ts)
|
||||
|
||||
关键变化:
|
||||
|
||||
- 移除 `import.meta.glob(...?raw)` 作为正文来源。
|
||||
- 页面加载时请求 `/api/v1/docs/catalog` 构建当前可见目录。
|
||||
- 打开正文时请求 `/api/v1/docs/{lang}/{slug}`。
|
||||
- 搜索只索引当前用户可见文档,并按需从后端读取 Markdown。
|
||||
- `401` 显示登录提示,`403` 显示权限提示,`404` 显示文档不可用。
|
||||
|
||||
## 测试覆盖
|
||||
|
||||
相关测试:
|
||||
|
||||
- [test_docs_gatekeeper.py](/home/ray/dev/linkong/planet/backend/tests/test_docs_gatekeeper.py)
|
||||
|
||||
测试应覆盖:
|
||||
|
||||
- 匿名用户只能看到 public 文档。
|
||||
- 受保护正文的 `401` / `403`。
|
||||
- `docs_developer` 可读开发文档但不能读管理文档。
|
||||
- `admin` 和 `super_admin` 可读管理文档。
|
||||
- 未知 slug、未知语言和路径穿越字符串不能读取文件。
|
||||
@@ -303,7 +303,9 @@ AISStream 的 `PositionReport` 常带实时位置和 `MetaData.ShipName`,但
|
||||
|
||||
登陆点是当前明确保留的例外:它曾接入 `Interactable`,但 pin 类 SVG 在地球边缘会被 `THREE.Points` 的深度测试裁切成碎片;关闭 depthTest 又会破坏背面遮挡语义。因此登陆点退回 `cables.js` 内的专用 `THREE.Sprite` 路径,并改为 canvas 生成的黄色扁平球纹理。它的 `altitudeOffset` 和 `renderOrder` 与海缆线一致,避免漂在海缆之上;Sprite 本体关闭 `depthTest` 保持球完整,背面可见性由 `isFacingCamera()` 的球体遮挡判断控制。
|
||||
|
||||
图标资源可以继续用 canvas draw,也可以放到 `frontend/public/earth/assets/icons/` 后由 `Interactable` 预加载。asset 路径不会在每帧读取;图层加载阶段通过 `preloadAssets()` 只加载一次 SVG / 图片,之后按 `icon source + state + bucket + color` 生成 `CanvasTexture` 并复用。当前算力中心已经从 `assets/icons/compute-supercomputer.svg`、`assets/icons/compute-gpu-cluster.svg` 和备用 `assets/icons/compute-hdd-network.svg` 读取图标,再在 canvas 上叠加估算位置的 `?` badge。
|
||||
图标资源可以继续用 canvas draw,也可以放到 `frontend/public/earth/assets/icons/` 后由 `Interactable` 预加载。asset 路径不会在每帧读取;图层加载阶段通过 `preloadAssets()` 只加载一次 SVG / 图片,之后按 `icon source + state + bucket + color` 生成 `CanvasTexture` 并复用。当前算力中心已经从 `assets/icons/compute-supercomputer.svg`、`assets/icons/compute-gpu-cluster.svg` 和备用 `assets/icons/compute-hdd-network.svg` 读取图标,再在 canvas 上叠加未确认位置的 `?` badge。算力中心后端在启动链路只渲染源数据自带坐标或 `compute_center_locations` 维表坐标;手动候选采集会调用 ROR 和 Nominatim/OpenStreetMap,并在 GeoJSON 或候选响应中返回位置精度、置信度、来源说明和核验时间;前端详情卡展示这些字段。
|
||||
|
||||
算力中心图层行左上角的通知气泡显示 GeoJSON `unresolved` 数量。这个数字表示“完全没有可信坐标、不能渲染到地球上”的记录,不等同于地图上带 `?` 的已定位待确认点。点击气泡会在图层面板右侧打开固定信息卡,信息卡内容区内部滚动,不随鼠标 hover 消失。列表中的单条 `采集` 只展示候选;顶部 `一键采用` 会按当前列表顺序逐条采集、保存最高置信候选,成功一条就移除一条、重新编号,并通过 `earth:compute-center-unresolved-count-change` 同步气泡数量。批量结束后再触发 `earth:compute-center-location-saved` 刷新真实图层。
|
||||
|
||||
asset 图标大小由 `Interactable` 的 `icon.fitSize` 控制。SVG / 图片文件应尽量保持原始 viewBox 和路径,不要为了在地球上显示成 60x60 而手写 `transform`;`drawAssetIcon()` 会把资源等比 contain 到指定尺寸并居中绘制到 atlas canvas。
|
||||
|
||||
@@ -352,6 +354,10 @@ const scale = THREE.MathUtils.clamp(
|
||||
- 按钮:`#toggle-terrain`
|
||||
- 状态节点:`#terrain-status`
|
||||
|
||||
地形不是默认可见图层时,启动期不会立即阻塞加载地形瓦片。`controls.js` 会在图层可见性恢复完成后才调度 `scheduleTerrainPrefetch()`,并且只在高清材质可用、地形尚未 ready、预取未开始时执行。预取使用 `setTimeout` + `requestIdleCallback`,避免和首屏云图、高清材质、图层启动队列抢主线程。
|
||||
|
||||
地形瓦片请求也不再逐个散发大量单 tile 请求。`terrain.js` 会把需要的 Terrarium tile 去重后按 `TERRAIN_CONFIG.batchRequestSize` 分批请求 `/api/v1/visualization/terrain/terrarium/batch`;后端用 LRU 内存缓存、批次去重和并发限制代理 S3 Terrarium tile。单 tile endpoint 仍保留给回退路径和浏览器缓存语义。
|
||||
|
||||
以后别的异步图层也可以沿用这套约定。
|
||||
|
||||
## 当前设置持久化
|
||||
|
||||
@@ -155,6 +155,7 @@
|
||||
- 渲染 `/docs` 的 Markdown 正文
|
||||
- 支持标题、列表、引用、代码块、表格和基础行内格式
|
||||
- 代码块和表格内部复用 `Scrollbar`,避免横向内容撑爆文档页
|
||||
- Docs 正文由后端 `/api/v1/docs/...` 按 Gatekeeper 权限返回;前端只渲染当前用户可见内容
|
||||
|
||||
当前约束:
|
||||
|
||||
@@ -190,9 +191,10 @@
|
||||
|
||||
- token
|
||||
- 当前用户
|
||||
- Gatekeeper 权限组
|
||||
- 登录/退出
|
||||
|
||||
`App.tsx` 用它判断是否进入登录页。
|
||||
`App.tsx` 用它判断是否进入登录页。`/docs` 仍是公开路由,但目录和正文由后端按 token 决定;未登录时只返回公开文档。
|
||||
|
||||
### 2. 业务数据网关
|
||||
|
||||
|
||||
200
docs/technical/zh/location-pipeline-development.md
Normal file
200
docs/technical/zh/location-pipeline-development.md
Normal file
@@ -0,0 +1,200 @@
|
||||
# 通用位置估算管线开发说明
|
||||
|
||||
`backend/app/services/location/` 是所有“给定一条记录,决定它的 lat/lon”业务的共享抽象。算力中心、BGP 观测站、BGP 事件目前都跑在这条管线上。未来需要位置估算的实体,例如卫星地面站、用户认领点位、IXP 设施,也应接入这里,而不是各自再写地理解析逻辑。
|
||||
|
||||
用户侧流程见 [Earth 位置候选采集使用手册](/home/ray/dev/linkong/planet/docs/technical/zh/location-pipeline-user.md)。
|
||||
|
||||
## 设计目标
|
||||
|
||||
历史上算力中心有自己的 4 层链路,BGP 观测站使用写死字典,BGP 事件继承 collector。三套实现互不复用,新算法也没有稳定挂入点。
|
||||
|
||||
重构后的原则:
|
||||
|
||||
- 共享 `LocationResolver` 协议和 `LocationPipeline` 编排器。
|
||||
- 各领域只负责构造 `LocationQuery` 和选择 resolver 顺序。
|
||||
- 新算法通过新增 resolver 类接入,不改 ingestion、API 和前端 envelope。
|
||||
- 只有达到城市级或更高精度的位置能渲染到 Earth。
|
||||
- 本地 JSON registry 不作为算力中心或 BGP 观测站的运行时候选来源;持久事实写入数据库维表。
|
||||
|
||||
## 核心接口
|
||||
|
||||
```python
|
||||
@dataclass(frozen=True)
|
||||
class LocationQuery:
|
||||
name: str | None
|
||||
aliases: tuple[str, ...]
|
||||
city: str | None
|
||||
country: str | None
|
||||
region: str | None
|
||||
source_latitude: float | None
|
||||
source_longitude: float | None
|
||||
extra: Mapping[str, Any]
|
||||
```
|
||||
|
||||
```python
|
||||
@dataclass(frozen=True)
|
||||
class LocationCandidate:
|
||||
latitude: float
|
||||
longitude: float
|
||||
display_name: str
|
||||
precision: str
|
||||
confidence: float
|
||||
source: str
|
||||
needs_confirmation: bool
|
||||
matched_fields: tuple[str, ...]
|
||||
suggested_registry_entry: dict | None
|
||||
```
|
||||
|
||||
```python
|
||||
class LocationResolver(Protocol):
|
||||
name: str
|
||||
def resolve(self, query: LocationQuery) -> ResolverOutput: ...
|
||||
```
|
||||
|
||||
`LocationPipeline.collect_candidates()` 返回排序后的候选和 `attempted_queries`;`resolve_best()` 返回最佳候选及诊断信息。默认排序按 source rank、precision rank、confidence,且对同 source 和同坐标候选去重。
|
||||
|
||||
## 内置 resolver
|
||||
|
||||
| Resolver | 文件 | 职责 |
|
||||
| --- | --- | --- |
|
||||
| `SourceCoordinatesResolver` | `resolvers/source_coordinates.py` | 源记录已有 lat/lon 时直接产出 `precision="precise"` |
|
||||
| `RegistryResolver` | `resolvers/registry.py` | 遗留通用 resolver;当前算力中心和 BGP 运行时链路不使用它生成候选 |
|
||||
| `NominatimResolver` | `resolvers/nominatim.py` | 按领域 query plan 调 Nominatim,带 LRU 缓存和速率限制 |
|
||||
| `InheritFromAnotherEntityResolver` | `resolvers/inherit.py` | 把外部实体的已解析位置包装为候选 |
|
||||
|
||||
`RegistryResolver` 仍保留给后续可能的受控导入场景,但它不应被重新接入算力中心或 BGP 作为“硬编码 hint”候选源。过去仅凭 `operator`、`city` 等通用字段匹配 registry 容易把多个实体落到同一个点,这是这次下线 registry 候选链路的主要原因。
|
||||
|
||||
## 当前领域管线
|
||||
|
||||
### 算力中心
|
||||
|
||||
入口文件:
|
||||
|
||||
- [compute_center_locations.py](/home/ray/dev/linkong/planet/backend/app/services/compute_center_locations.py)
|
||||
|
||||
管线顺序:
|
||||
|
||||
```python
|
||||
SourceCoordinatesResolver()
|
||||
StoredComputeCenterLocationResolver()
|
||||
```
|
||||
|
||||
主地图启动链路只做“源坐标优先,其次数据库维表坐标”。数据库表为 `compute_center_locations`,唯一键是 `(source, source_id)`,用于保存人工确认或从源记录真实坐标迁入的位置。`init_db()` 只幂等迁入源记录里已有的真实经纬度,不迁入旧硬编码 hint,不在启动期批量调用 ROR、Nominatim 或 LLM。
|
||||
|
||||
手动候选采集链路和渲染链路分开。`collect_location_candidates()` 使用源字段构造 ROR 和 Nominatim/OpenStreetMap 查询,但不会把 `compute_center_locations` 当前坐标当候选返回。用户在前端确认某个候选后,通过保存接口写入维表;之后地图刷新时由 `StoredComputeCenterLocationResolver` 渲染。
|
||||
|
||||
`resolve_compute_center_location()`、`resolve_compute_center_location_full()` 和 `collect_location_candidates()` 保留为领域 API。`visualization.py` 只消费领域 API,不再持有坐标提示常量、国家质心兜底或 Nominatim 细节。
|
||||
|
||||
GeoJSON 输出只包含 `RENDERABLE_PRECISIONS` 内的位置。未解析记录进入 `unresolved`,并带上 `failure_reason`、`attempted_queries`、`source_id`、`record_id` 等诊断字段。
|
||||
|
||||
### BGP 观测站
|
||||
|
||||
入口文件:
|
||||
|
||||
- [bgp_collector_locations.py](/home/ray/dev/linkong/planet/backend/app/services/bgp_collector_locations.py)
|
||||
- [bgp_collector_location.py](/home/ray/dev/linkong/planet/backend/app/models/bgp_collector_location.py)
|
||||
|
||||
管线顺序:
|
||||
|
||||
```python
|
||||
SourceCoordinatesResolver()
|
||||
StoredCollectorLocationResolver()
|
||||
NominatimResolver(_bgp_collector_query_plan)
|
||||
```
|
||||
|
||||
23 个 RIPE RIS collector 坐标从旧表迁入 `bgp_collector_locations` 维表,默认 `source=legacy_seed`、`needs_confirmation=true`。旧字典仍由 DB-backed cache 维护,保证下游接口兼容;手动候选采集不会把这份维表坐标当作候选,只用它补齐 site/city/country 查询上下文。
|
||||
|
||||
### BGP 事件
|
||||
|
||||
入口文件:
|
||||
|
||||
- [bgp_event_locations.py](/home/ray/dev/linkong/planet/backend/app/services/bgp_event_locations.py)
|
||||
|
||||
管线顺序:
|
||||
|
||||
```python
|
||||
SourceCoordinatesResolver()
|
||||
InheritFromAnotherEntityResolver(_inherit_from_owning_collector)
|
||||
```
|
||||
|
||||
事件继承使用所属 collector 的严格查找,不跑完整 collector registry 模糊匹配。后续 ASN 设施、PrefixGeo 或 PeeringDB resolver 可以挂在继承 resolver 之后。
|
||||
|
||||
## API envelope
|
||||
|
||||
```http
|
||||
POST /api/v1/visualization/compute-centers/{source_id}/collect-location
|
||||
POST /api/v1/visualization/compute-centers/{source_id}/location
|
||||
POST /api/v1/bgp/collectors/{collector_id}/collect-location
|
||||
```
|
||||
|
||||
`collect-location` 返回统一 envelope:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"candidates": [],
|
||||
"best_candidate": {},
|
||||
"attempted_queries": [],
|
||||
"context": {}
|
||||
}
|
||||
```
|
||||
|
||||
`POST /api/v1/visualization/compute-centers/{source_id}/location` 把前端选中的候选 upsert 到 `compute_center_locations`。人工保存默认 `needs_confirmation=false`、`verification_status="verified"` 并写入 `verified_at`;如果后续接入自动暂存,也可以显式传 `needs_confirmation=true`。
|
||||
|
||||
前端 [info-card.js](/home/ray/dev/linkong/planet/frontend/public/earth/js/info-card.js) 使用通用候选列表和预览事件渲染对象详情卡。算力中心图层按钮左上角会显示 `unresolved` 数量;点击角标打开待定位列表。列表中的 `采集` 只拉候选,`一键采用` 会逐条调用候选采集接口,选择最高置信且有有效经纬度的候选保存。保存成功一条就从列表移除并重新编号,同时通过 `earth:compute-center-unresolved-count-change` 同步角标;批量结束后再触发 `earth:compute-center-location-saved` 刷新真实图层。
|
||||
|
||||
如果剩余记录没有任何 city-level 候选,批量采用不会伪造坐标。前端会保留这些记录并展示后端返回的 `failure_reason` 和已尝试查询。
|
||||
|
||||
## 新增 resolver
|
||||
|
||||
resolver 只需要实现 `name` 和 `resolve()`,返回 `ResolverOutput`。
|
||||
|
||||
```python
|
||||
class PeeringDBFacilityResolver:
|
||||
name = "peeringdb_facility"
|
||||
|
||||
def __init__(self, client):
|
||||
self._client = client
|
||||
|
||||
def resolve(self, query):
|
||||
asn = query.extra.get("origin_asn")
|
||||
if not asn:
|
||||
return ResolverOutput()
|
||||
return ResolverOutput(candidates=tuple(
|
||||
LocationCandidate(
|
||||
latitude=f.latitude,
|
||||
longitude=f.longitude,
|
||||
display_name=f.name,
|
||||
precision="site",
|
||||
confidence=0.78,
|
||||
query=f"peeringdb::{asn}",
|
||||
source=self.name,
|
||||
source_note=f"PeeringDB facility for AS{asn}",
|
||||
matched_fields=("origin_asn",),
|
||||
needs_confirmation=False,
|
||||
city=f.city,
|
||||
country=f.country,
|
||||
)
|
||||
for f in self._client.facilities_for_asn(asn)
|
||||
))
|
||||
```
|
||||
|
||||
挂入:
|
||||
|
||||
```python
|
||||
BGP_EVENT_PIPELINE = LocationPipeline([
|
||||
SourceCoordinatesResolver(),
|
||||
InheritFromAnotherEntityResolver(source_lookup=...),
|
||||
PeeringDBFacilityResolver(client=peeringdb_client),
|
||||
])
|
||||
```
|
||||
|
||||
## 测试覆盖
|
||||
|
||||
相关测试:
|
||||
|
||||
- [test_location_pipeline.py](/home/ray/dev/linkong/planet/backend/tests/test_location_pipeline.py)
|
||||
- [test_bgp_collector_locations.py](/home/ray/dev/linkong/planet/backend/tests/test_bgp_collector_locations.py)
|
||||
- [test_visualization_compute_centers.py](/home/ray/dev/linkong/planet/backend/tests/test_visualization_compute_centers.py)
|
||||
|
||||
测试重点包括 resolver 可插拔性、注册表 alias 约束、BGP collector 兼容字典、算力中心公共 API 兼容、不可渲染位置进入 `unresolved`。
|
||||
127
docs/technical/zh/location-pipeline-user.md
Normal file
127
docs/technical/zh/location-pipeline-user.md
Normal file
@@ -0,0 +1,127 @@
|
||||
# Earth 位置候选采集使用手册
|
||||
|
||||
位置候选采集用于给 Earth 上的算力中心和 BGP 观测站补齐或核验经纬度。它不会要求用户手工输入坐标,而是把源数据、开放组织注册 API 和在线地理编码结果整理成候选列表,供用户预览和后续认领。
|
||||
|
||||
## 适用对象
|
||||
|
||||
当前支持:
|
||||
|
||||
- 算力中心:TOP500 超算、Epoch AI GPU 集群。
|
||||
- BGP 观测站:RIPE RIS `rrcXX` collector。
|
||||
|
||||
BGP 事件的位置默认继承所属 collector。事件本身暂不提供单独按钮;后续 ASN 设施、Prefix 地理位置或 PeeringDB 算法接入后会继续走同一条管线。
|
||||
|
||||
## 用户能看到什么
|
||||
|
||||
在 Earth 上点击算力中心或 BGP 观测站后,详情卡会展示位置相关字段:
|
||||
|
||||
| 字段 | 含义 |
|
||||
| --- | --- |
|
||||
| 位置精度 | `精确坐标`、`站点级位置`、`城市级位置` 或 `位置未确认` |
|
||||
| 位置来源 | 源数据坐标、ROR 组织注册 API、Nominatim 在线搜索,或已存储的 BGP collector 维表位置 |
|
||||
| 位置置信度 | 后端 resolver 给出的相对置信度百分比 |
|
||||
| 核验状态 | 已确认、估算位置或在线检索结果待确认 |
|
||||
| 解析依据 | 为什么选择这个位置,例如匹配了哪个站点或城市 |
|
||||
| 匹配的位置名称 | 开放来源、在线结果或已存储 collector 位置中的规范名称 |
|
||||
| 位置核验时间 | 已确认位置的核验日期,在线候选通常为空 |
|
||||
|
||||
算力中心 GeoJSON 不再渲染国家质心、未知位置或 `[0, 0]` 占位点。无法达到城市级精度的数据会进入接口的 `unresolved` 列表,并在图层开关左上角显示待定位数量。点击这个通知气泡会打开待定位列表。
|
||||
|
||||
地图上带 `?` 的算力中心不是 `unresolved`。它们已经有坐标,只是 `needs_confirmation=true` 或来自在线地理编码,仍需人工核验。真正 `unresolved` 的记录没有可信经纬度,因此不会出现在地球上。
|
||||
|
||||
## 自动采集候选
|
||||
|
||||
1. 打开 `http://localhost:3000/earth`。
|
||||
2. 打开 `算力中心` 或 `BGP 观测` 图层。
|
||||
3. 点击目标对象打开详情卡。
|
||||
4. 点击 `自动采集坐标候选` 或 `重新自动采集坐标`。
|
||||
5. 等待详情卡列出最多 5 个候选位置。
|
||||
6. 点击候选行里的 `预览`,Earth 会飞到该候选经纬度附近。
|
||||
|
||||
候选列表会显示:
|
||||
|
||||
- 候选名称。
|
||||
- 精度:精确、站点或城市。
|
||||
- 来源 resolver。
|
||||
- 置信度。
|
||||
- 经纬度。
|
||||
|
||||
点击候选行里的 `保存` 会把所选候选写入算力中心位置维表。保存成功后,算力中心图层会刷新;如果该记录原本在待定位列表中,待定位数量也会减少。
|
||||
|
||||
## 待定位列表和一键采用
|
||||
|
||||
算力中心图层按钮左上角的通知气泡显示当前 `unresolved` 数量。点击后会在图层面板右侧打开固定列表:
|
||||
|
||||
1. 列表只包含没有可信经纬度的算力中心。
|
||||
2. 单条 `采集` 会调用候选接口,并展示最多 5 个候选供预览和保存。
|
||||
3. 顶部 `一键采用` 会从上到下逐条采集候选,选择置信度最高且有有效经纬度的候选保存。
|
||||
4. 成功保存一条后,该行会立即从列表中移除,下面的序号自动上移,通知气泡数量同步减少。
|
||||
5. 批量结束后,前端会刷新算力中心图层,确保 UI 和后端真实状态一致。
|
||||
|
||||
如果某条记录没有任何可保存候选,系统不会用国家中心点、厂商总部或硬编码 hint 伪造位置。该记录会留在列表中,并显示后端返回的失败原因和已尝试查询,等待人工补充更可靠的地址或坐标证据。
|
||||
|
||||
## 后端接口
|
||||
|
||||
前端按钮调用的接口如下:
|
||||
|
||||
```http
|
||||
POST /api/v1/visualization/compute-centers/{source_id}/collect-location
|
||||
POST /api/v1/visualization/compute-centers/{source_id}/location
|
||||
POST /api/v1/bgp/collectors/{collector_id}/collect-location
|
||||
```
|
||||
|
||||
两个 `collect-location` 接口返回相同结构:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"candidates": [],
|
||||
"best_candidate": {},
|
||||
"attempted_queries": [],
|
||||
"context": {}
|
||||
}
|
||||
```
|
||||
|
||||
当没有候选达到城市级精度时,`success` 为 `false`,响应会包含 `failure_reason` 和已尝试的查询文本,便于判断是源数据字段不足、开放来源缺项,还是在线地理编码没有命中。
|
||||
|
||||
## 数据维护建议
|
||||
|
||||
算力中心和 BGP 观测站都不再维护本地候选注册表。算力中心的人工确认位置保存在 `compute_center_locations` 数据库维表中,唯一键是 `(source, source_id)`;BGP 观测站的当前位置保存在 `bgp_collector_locations` 数据库维表中,旧 RIPE RIS 城市级坐标只作为初始化 seed 写入,默认仍需人工核验。
|
||||
|
||||
维护算力中心时优先补齐:
|
||||
|
||||
- `source` / `source_id`:例如 `top500` + `top500_50`。
|
||||
- `name` / `operator` / `site`。
|
||||
- `city` / `country`。
|
||||
- `latitude` / `longitude`。
|
||||
- `precision`:`precise`、`site` 或 `city`。
|
||||
- `confidence`:0 到 1 的置信度。
|
||||
- `location_source` / `source_url` / `source_note` / `raw_payload`:证据来源。
|
||||
- `needs_confirmation` / `verification_status` / `verified_at`:人工核验状态和日期。
|
||||
|
||||
维护 BGP 观测站时优先补齐:
|
||||
|
||||
- `collector_id`:例如 `rrc12`。
|
||||
- `site` / `operator`:站点和运营方。
|
||||
- `city` / `country` / `region`。
|
||||
- `latitude` / `longitude`。
|
||||
- `precision`:`precise`、`site` 或 `city`。
|
||||
- `confidence`:0 到 1 的置信度。
|
||||
- `source` / `source_url` / `raw_payload`:证据来源。
|
||||
- `verification_status` / `verified_at`:人工核验状态和日期。
|
||||
|
||||
如果只是知道城市,不知道设施坐标,应使用城市级精度,不要填一个看似精确但无法核验的点位。
|
||||
|
||||
## 常见问题
|
||||
|
||||
### 为什么有些算力中心不显示在 Earth 上
|
||||
|
||||
Earth 只渲染达到城市级或更高精度的坐标。源数据没有坐标、已验证位置没有命中、在线搜索也没有城市级结果时,记录会进入 `unresolved`,避免在地图上出现误导性的国家中心点或 `[0, 0]`。
|
||||
|
||||
### 为什么在线搜索结果显示“待确认”
|
||||
|
||||
Nominatim/OpenStreetMap 结果来自在线地理编码,可能匹配到同名城市、机构或园区。它可以用于快速定位和预览,但在写入已验证位置前应人工确认。
|
||||
|
||||
### 为什么 BGP 事件没有全部落到 Amsterdam
|
||||
|
||||
旧逻辑中,事件可能因为 `operator="RIPE NCC"` 这种通用字段误匹配到 `rrc00`。当前 BGP 事件继承只按所属 collector 在 DB-backed cache 中严格查找,不再用 registry 模糊匹配。
|
||||
@@ -5,7 +5,7 @@
|
||||
- `planet.sh`:本地启动、停止、重启、健康检查和日志入口
|
||||
- Earth:公开 3D 地球态势页面
|
||||
- 控制台:登录后的管理后台
|
||||
- Docs:公开开发文档与使用手册
|
||||
- Docs:后端 Gatekeeper 受控的文档站,基础使用文档公开,开发/运维文档按权限组开放
|
||||
|
||||
快速启动路径见 [快速开始](/home/ray/dev/linkong/planet/docs/technical/zh/quickstart.md)。
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
| 名称 | 地址 | 是否需要登录 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| Earth | `http://localhost:3000/earth` | 否 | 3D 地球、图层、BGP、卫星、海缆、新闻态势 |
|
||||
| Docs | `http://localhost:3000/docs` | 否 | 开发文档、技术说明、使用手册 |
|
||||
| Docs | `http://localhost:3000/docs` | 部分需要 | 使用手册公开;开发、后端、运维文档按 Gatekeeper 权限组开放 |
|
||||
| 控制台 | `http://localhost:3000/admin` | 是 | 数据、配置、告警、日志和专题观测 |
|
||||
| AI Playground | `http://localhost:3000/playground` | 是 | AI Provider 状态和调试 |
|
||||
| 后端 API 文档 | `http://localhost:8000/docs` | 视接口而定 | FastAPI / OpenAPI 文档 |
|
||||
@@ -198,7 +198,26 @@ AI Provider 镜像只在代码、Dockerfile、Compose 配置或相关 Python 依
|
||||
- 手机或平板演示 Earth
|
||||
- 局域网其他机器访问同一个开发实例
|
||||
|
||||
启动后注意检查防火墙和 WSL 网络转发。
|
||||
`--allow-lan` 只负责让前端和后端监听 `0.0.0.0`。如果服务运行在 WSL 中,Windows 本机通常可以通过 `localhost` 访问,但手机或其他电脑访问 `http://<Windows局域网IP>:3000` 还依赖 Windows 端口转发和防火墙放行。
|
||||
|
||||
推荐按顺序判断:
|
||||
|
||||
```bash
|
||||
# 在 WSL 或运行 Planet 的 shell 中
|
||||
curl http://localhost:3000
|
||||
curl http://localhost:8000/health
|
||||
ss -ltnp | grep -E ':3000|:8000'
|
||||
```
|
||||
|
||||
如果这里能看到 `0.0.0.0:3000` 和 `0.0.0.0:8000`,但局域网 IP 访问失败,请在管理员 PowerShell 中配置:
|
||||
|
||||
```powershell
|
||||
netsh interface portproxy add v4tov4 listenaddress=0.0.0.0 listenport=3000 connectaddress=127.0.0.1 connectport=3000
|
||||
netsh interface portproxy add v4tov4 listenaddress=0.0.0.0 listenport=8000 connectaddress=127.0.0.1 connectport=8000
|
||||
|
||||
New-NetFirewallRule -DisplayName "WSL Planet 3000" -Direction Inbound -Action Allow -Protocol TCP -LocalPort 3000
|
||||
New-NetFirewallRule -DisplayName "WSL Planet 8000" -Direction Inbound -Action Allow -Protocol TCP -LocalPort 8000
|
||||
```
|
||||
|
||||
## Earth
|
||||
|
||||
@@ -290,6 +309,12 @@ Earth 搜索支持查找当前地球对象,例如:
|
||||
|
||||
搜索结果可以用于快速定位对象,并打开对应详情。
|
||||
|
||||
### 位置候选采集
|
||||
|
||||
算力中心和 BGP 观测站详情卡支持自动采集坐标候选。点击对象后,使用详情卡中的 `自动采集坐标候选` 或 `重新自动采集坐标` 按钮,后端会从源坐标、开放组织注册 API 和在线地理编码中整理候选位置。BGP 观测站的已存储位置只用于补齐查询上下文,不会作为候选直接返回。
|
||||
|
||||
候选可以直接在 Earth 上预览。算力中心候选点击 `保存` 后会写入 `compute_center_locations` 维表,并立即刷新图层。算力中心图层左上角的通知气泡显示无法渲染的待定位数量;点击后可查看列表,单条采集候选,或用 `一键采用` 从上到下保存最高置信候选。没有可用候选的记录会留在列表中,不会被国家中心点或硬编码 hint 伪造位置。详细流程见 [Earth 位置候选采集使用手册](/home/ray/dev/linkong/planet/docs/technical/zh/location-pipeline-user.md)。
|
||||
|
||||
### 设置
|
||||
|
||||
设置面板包含:
|
||||
@@ -536,33 +561,41 @@ BarentsWatch AIS 支持从以下位置读取凭证:
|
||||
|
||||
## Docs
|
||||
|
||||
公开文档站入口:
|
||||
文档站入口:
|
||||
|
||||
```text
|
||||
http://localhost:3000/docs
|
||||
```
|
||||
|
||||
当前公开内容来自:
|
||||
Docs 正文由后端 API 按权限读取,不再把全部 Markdown 直接打进前端构建产物。当前文档源文件仍位于:
|
||||
|
||||
```text
|
||||
docs/technical/zh/*.md
|
||||
docs/technical/en/*.md
|
||||
```
|
||||
|
||||
未登录访客默认只能看到 `public` 文档,例如首页、快速开始和使用手册。登录用户如果被分配 Gatekeeper 权限组,可以看到更多技术文档:
|
||||
|
||||
- `docs_user`:用户操作类文档。
|
||||
- `docs_developer`:Earth、前端、后端、采集器和 AI Provider 等开发文档。
|
||||
- `docs_admin`:服务控制、运维、环境变量和敏感操作文档。
|
||||
|
||||
`admin` 默认拥有管理文档权限,`super_admin` 拥有全部 Docs 权限。Gatekeeper 权限组在控制台“用户管理”中配置。
|
||||
|
||||
Docs 支持:
|
||||
|
||||
- 分类导航
|
||||
- Markdown 渲染
|
||||
- 表格和代码块
|
||||
- 文档内目录
|
||||
- 本地搜索
|
||||
- 对当前可见文档搜索
|
||||
- technical 文档之间的内部链接跳转
|
||||
|
||||
如果新增 technical 文档,应同步检查:
|
||||
|
||||
- 是否有清晰的一级标题
|
||||
- 是否需要加入 `/docs` 的人工分类和排序
|
||||
- 是否包含不适合公开展示的信息
|
||||
- 是否需要加入后端 Docs metadata 的人工分类和排序
|
||||
- 应归入 `public`、`docs_user`、`docs_developer` 还是 `docs_admin`
|
||||
|
||||
## 开发命令约定
|
||||
|
||||
@@ -633,6 +666,7 @@ source ~/.zshrc && bun run build
|
||||
- [控制台前端结构](/home/ray/dev/linkong/planet/docs/technical/zh/frontend-admin-frontend-context.md)
|
||||
- [Earth 前端结构](/home/ray/dev/linkong/planet/docs/technical/zh/earth-frontend-context.md)
|
||||
- [Earth 图层样式属性索引](/home/ray/dev/linkong/planet/docs/technical/zh/earth-layer-style-reference.md)
|
||||
- [Earth 位置候选采集使用手册](/home/ray/dev/linkong/planet/docs/technical/zh/location-pipeline-user.md)
|
||||
- [系统服务控制](/home/ray/dev/linkong/planet/docs/technical/zh/backend-system-service-control.md)
|
||||
- [数据采集系统](/home/ray/dev/linkong/planet/docs/technical/zh/backend-collectors.md)
|
||||
- [数据源、采集器设置与连接验证](/home/ray/dev/linkong/planet/docs/technical/zh/datasource-collector-settings-connectivity.md)
|
||||
|
||||
@@ -150,6 +150,13 @@ wait_for_port_release() {
|
||||
}
|
||||
```
|
||||
|
||||
当前启动前端时还有一层预清理重试:
|
||||
|
||||
- `PORT_PRESTART_RETRIES`:默认 3 次。
|
||||
- `PORT_PRESTART_RETRY_INTERVAL`:默认 2 秒。
|
||||
|
||||
`kill_port_if_requested()` 只在当前环境能找到监听 PID 时主动杀进程;如果没有 PID 但端口暂时不可绑定,它会记录诊断并把最终确认交给服务启动流程。`start_frontend_with_retry()` 也只在发现监听 PID 时进入预清理重试,避免在宿主机或外部 network namespace 尚未释放端口时做无意义的“空杀重试”。这意味着第一次重启时看到“未发现监听进程但端口仍不可绑定”通常是外部环境仍在释放端口;脚本不会再把这种情况当成立刻失败的本地进程清理问题。
|
||||
|
||||
## 问题三:端口检测用 Python
|
||||
|
||||
### 原因
|
||||
|
||||
@@ -30,6 +30,16 @@ AI Provider 的个人配置也可以放在 `~/.zshrc`。`planet.sh` 会读取简
|
||||
./planet.sh restart -a
|
||||
```
|
||||
|
||||
AISStream、BarentsWatch 等采集器凭证也可以先写在 `~/.zshrc` 里供连接验证读取,例如:
|
||||
|
||||
```bash
|
||||
export AISSTREAM_API_KEY="..."
|
||||
export BARENTSWATCH_CLIENT_ID="..."
|
||||
export BARENTSWATCH_CLIENT_SECRET="..."
|
||||
```
|
||||
|
||||
正式采集更推荐在控制台 `设置 -> 采集器设置` 保存凭证,尤其是 AISStream 这类长连接 WebSocket collector。这样连接验证、后端采集任务和 Earth 实时船只聚合会使用同一份配置。
|
||||
|
||||
## 1. 启动服务
|
||||
|
||||
在仓库根目录执行:
|
||||
@@ -44,7 +54,7 @@ AI Provider 的个人配置也可以放在 `~/.zshrc`。`planet.sh` 会读取简
|
||||
| --- | --- | --- |
|
||||
| Earth | `http://localhost:3000/earth` | 公开 3D Earth 可视化页面 |
|
||||
| 控制台 | `http://localhost:3000/admin` | 登录后的管理后台 |
|
||||
| 文档站 | `http://localhost:3000/docs` | 公开开发文档和使用手册 |
|
||||
| 文档站 | `http://localhost:3000/docs` | 使用手册公开;开发/运维文档按 Gatekeeper 权限组开放 |
|
||||
| AI Playground | `http://localhost:3000/playground` | 登录后的 AI 调试入口 |
|
||||
| 后端 API 文档 | `http://localhost:8000/docs` | FastAPI / OpenAPI 接口文档 |
|
||||
|
||||
@@ -64,6 +74,8 @@ AI Provider 的个人配置也可以放在 `~/.zshrc`。`planet.sh` 会读取简
|
||||
|
||||
按提示输入用户名、密码和角色。
|
||||
|
||||
如果需要阅读开发或运维文档,用 `super_admin` 登录控制台后,在“用户管理”里给目标用户分配 Gatekeeper 权限组:`docs_developer` 用于开发文档,`docs_admin` 用于服务控制和运维文档。
|
||||
|
||||
## 3. 打开 Earth
|
||||
|
||||
访问:
|
||||
@@ -79,6 +91,7 @@ Earth 是公开页面,不需要登录。
|
||||
- 地球正常显示
|
||||
- 右侧图层控制可打开/关闭图层
|
||||
- 搜索可以查找海缆、卫星、算力中心、BGP 事件
|
||||
- 算力中心和 BGP 观测站详情卡可以自动采集并预览坐标候选;算力中心待定位气泡可以打开列表并保存候选
|
||||
- 鼠标拖动、滚轮缩放和缩放百分比提示正常工作
|
||||
- 设置面板可以切换巡航模式、日夜模式、卫星显示风格
|
||||
|
||||
@@ -176,6 +189,14 @@ http://localhost:3000/admin
|
||||
|
||||
这会让前端和后端监听局域网可访问地址。
|
||||
|
||||
注意:`--allow-lan` 只负责让 Planet 服务监听 `0.0.0.0`,不等于自动把 WSL 服务暴露到 Windows 局域网 IP。常见情况是:
|
||||
|
||||
- WSL 内 `localhost:3000` / `localhost:8000` 能访问
|
||||
- Windows 本机 `localhost:3000` / `localhost:8000` 能访问
|
||||
- 但手机或其他电脑访问 `http://<Windows局域网IP>:3000` 失败
|
||||
|
||||
这通常说明 Windows 端还缺少端口转发或防火墙放行。
|
||||
|
||||
如果访问失败,先在运行 Planet 的 shell 中检查:
|
||||
|
||||
```bash
|
||||
@@ -184,6 +205,16 @@ curl http://localhost:8000/health
|
||||
ss -ltnp | grep -E ':3000|:8000'
|
||||
```
|
||||
|
||||
如果确认 WSL 中已监听 `0.0.0.0:3000` 和 `0.0.0.0:8000`,但局域网 IP 仍不能访问,请在管理员 PowerShell 中配置 Windows 端转发和防火墙:
|
||||
|
||||
```powershell
|
||||
netsh interface portproxy add v4tov4 listenaddress=0.0.0.0 listenport=3000 connectaddress=127.0.0.1 connectport=3000
|
||||
netsh interface portproxy add v4tov4 listenaddress=0.0.0.0 listenport=8000 connectaddress=127.0.0.1 connectport=8000
|
||||
|
||||
New-NetFirewallRule -DisplayName "WSL Planet 3000" -Direction Inbound -Action Allow -Protocol TCP -LocalPort 3000
|
||||
New-NetFirewallRule -DisplayName "WSL Planet 8000" -Direction Inbound -Action Allow -Protocol TCP -LocalPort 8000
|
||||
```
|
||||
|
||||
## 9. 停止服务
|
||||
|
||||
```bash
|
||||
|
||||
Reference in New Issue
Block a user