# OIP-Map-Tianditu **Repository Path**: orginone/OIP-Map-Tianditu ## Basic Information - **Project Name**: OIP-Map-Tianditu - **Description**: OIP-Map-Tianditu天地图地图插件 - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-10-02 - **Last Updated**: 2026-10-05 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # OIP-Map-Tianditu > OIP **天地图(Tianditu)地图渲染接入插件** —— 地图族 5 个插件中的第一个。 > 对应设计方案:`OIP-插件-地图与文件格式渲染方案-2026-10-02.md`(§10 裁决 / §11 格式族)。 ## 1. 它是什么,不是什么 **是**:一个 `type: business`、`pre_auth: false` 的**形态 A(前端资产型)**插件。 它的交付物是**浏览器里跑的组件**,服务端只承担三件事: | 职责 | 说明 | | --- | --- | | 声明 | 清单里声明 `ui.providers[]`(能力族 `map.render` / 键 `tianditu`)与 `config[]`(含 `expose`) | | 资产交付 | 读取 `assets/renderer.mjs`、算 SRI,**经底座资产通道**交给浏览器(不自报 CDN) | | 平台代理(可选) | 瓦片 / 地理编码 / 逆地理转发 + 缓存;`proxy_only` 时连 `tk` 都不下发浏览器 | **不是**: - **不持有地理事实**:`XMapData` 是宿主协议面,本插件只是消费者; - **不做坐标转换**:GCJ-02 / BD-09 / WGS-84 的转换属宿主 **`geo`** 服务。 5 家各写一遍必然漂移,而写坏的坐标落库后要**回滚已提交数据**(§10.3); - **不自己加载重库**:three / monaco / … 属宿主 **`viewers`** 服务; - **不自己发资产**:资产分发(含 SRI 校验与撤销)属底座 **`assets`** 服务。 ## 2. 为什么它是"独立插件"而不是"地图统一插件" `(family, key)` 展开即**注册键** `renderer.map.tianditu` —— 每键独立、天然满足交换性。 把 5 家塞进一个插件,等于在插件内部再写一个 `switch`(正是今天 `LocationPicker/index.tsx` 那个**被注释掉的 switch** 的形态),并同时退化四件事: 安装粒度(只想要天地图却带 4 家共 ~120 KB SDK)、卸载粒度、依赖门控 (`ak`/`tk`/`key+referer` 各自不同)、合规审计(无法单独禁用某一家)。 **「5 个插件」≠「5 个仓库」**:插件是**装配单元**,仓库是**交付单元**。 地图属形态 A(服务端近零职责),一个仓产出 5 份清单 + 5 个 ESM bundle 完全可行。 ## 3. 端点 | 方法 | 路径 | 说明 | | --- | --- | --- | | GET/POST | `/oip/v1/tianditu/methods` | 发现面(**只列可服务的能力**) | | GET/POST | `/oip/v1/tianditu/client_config` | 前端可见配置(**只回 `expose: client` 的项**) | | GET/POST | `/oip/v1/tianditu/tk_check` | `tk` 自检(**服务端独有**:浏览器做不到) | | GET/POST | `/oip/v1/tianditu/tile` | 取瓦片(JSON 面,`encoding: base64`) | | GET/POST | `/oip/v1/tianditu/tile/:layer/:z/:x/:y` | 取瓦片(**原生二进制**面) | | GET/POST | `/oip/v1/tianditu/geocode` | 地理编码(地名 → 坐标) | | GET/POST | `/oip/v1/tianditu/reverse` | 逆地理(坐标 → 地址) | | GET/POST | `/oip/v1/tianditu/asset_meta` | 资产元信息(**服务间**,需入站令牌) | | GET/POST | `/oip/v1/tianditu/asset_body` | 资产字节(**服务间**,需入站令牌) | | GET | `/healthz` · `/readyz` | 探针(豁免接缝判定) | ⚠️ **每条路由都注册 `GET` 与 `POST`**:业务插件经前门**只有** `POST /oip/v1/rest/request {module, action, params}` 一条已认证通道,且转发**恒用 POST**。 只注册 GET 的动作经前门必然 405 —— 而网关把它统一报成 `4001`,看起来像"参数没传对"。 ⚠️ **`module` 是 `route_prefixes` 的子串**(`tianditu`),**不是插件名** `OIP-Map-Tianditu`。 ## 4. 启动顺序(两次上报**不是冗余**) ```text 配置加载 → 日志 → 状态装配(含资产索引) → 上报清单 ①(await;必须先于配置下发) → 拉取下发配置(必须先于挂载) → 用生效配置**重新装配**状态 → 上报清单 ②(让 provider 反映生效后的配置) → 本地挂载(登记接缝)→ 心跳 + 清扫 → 服务 ``` 两条约束方向相反,必须同时满足: 1. 底座配置读面以「插件已挂载且 ACTIVE」为前提 ⇒ **先上报再拉配置** (反序 100% 得到 `4041`,症状是"下发从未生效"); 2. 但 `ui.providers[]` 是**按当前配置**构造的(凭据不齐 ⇒ 不下发)。 只做 ① 会得到一份**按文件配置**构造的清单:运维在前端表单里填了 `tk`、下发也成功了, 而清单仍是"不可服务" ⇒ **地图入口永远不出现**,而日志里一切正常。 注册幂等(同名覆盖),第二次上报没有副作用。 ## 5. 配置 见 `config.example.yaml`(逐项注释)。要点: - `tianditu.tk` 是**唯一凭据**。留空是**合法形态**(装了但没配): `/tile` 等一律 5031,且**清单不声明 provider**(前端不会出现地图入口); - `proxy.proxy_only = true` ⇒ **不再下发 `tk`**。这是 「`expose: client` 是**允许面上界**、不是承诺」的落地点:判定权在插件,底座只强制上界; - `asset.path` MUST 是插件目录内的**相对路径**(它最终由底座按插件名取字节, 越界即等于一个任意文件读取原语)。 环境变量: | 变量 | 作用 | | --- | --- | | `OIP_TIANDITU_CONFIG` | 配置文件路径(缺省 `./config.yaml`) | | `OIP_TIANDITU_INTERNAL_TOKEN` | 服务间**入站**令牌。⚠️ 未配置 ⇒ 资产面**一律 4011**(fail-closed) | | `RUST_LOG` | 覆盖 `log.level` | ## 6. 已知缺口(**显式记录,不用变通绕过**) 1. **前门没有通用的字节透传面**。瓦片是字节流,而已认证面是 JSON 信封 ⇒ 今天只能把字节 base64 进信封(体积 +33%)。这与资产通道是**同一类缺口**; MUST NOT 用"插件自己对外暴露地址"来绕过——那会让浏览器直连插件, 等于绕开前门的鉴权与审计。 2. **前端资产尚未构建**。本仓交付 `assets/renderer.mjs` 的**契约**与**通道** (见 `assets/README.md`),文件缺失时清单不下发 provider(fail-closed)。 把现有硬编码的 `TiandituLocationPicker` / `TiandituMapView` 移植进该资产, 是替换硬编码的**下一步**。 3. **`/readyz` 的探针与单测是互补的两件事**:单测用 `wiremock` 保证**请求构造正确**, 真机连通性由 `tk_check` 保证**平台真的认这个 key**。两者 MUST NOT 互相替代。 ## 7. 门禁 ```bash cargo fmt --check cargo clippy --all-targets -- -D warnings cargo test ``` ⚠️ **只认 `test` / `--all-targets` 的结论**:`cargo check` 与 `cargo build` **不编译 `#[cfg(test)]`**,给结构体加必填字段后生产侧编过、测试侧可能全红。 ## 8. 部署 ```bash cp config.example.yaml config.yaml # 至少填:goinone.base_url / public_base_url / tianditu.tk cargo run --release ``` 联调端口 **8459**(登录族 8452–8456、支付族 8457–8458 之后的第一个空位)。 ⚠️ 插件进程的 **cwd 就是 `config.yaml` 所在目录**(`log.dir` 与 `asset.path` 都相对它解析)。 cwd 不存在时进程会以更底层的 IO 错误启动失败,**不是**"配置缺失"这种可诊断的错。