Files
map-asset-gateway/docs/API.md
T
Harmon 46d83145d9 Keep the admin surface local while serving tiles on the LAN
Binding to 0.0.0.0 used to demand credentials because the admin console and
the tile routes shared one listener. That forced a choice between keeping the
console off the network and letting other machines fetch tiles.

Admin routes are now loopback-only by default: the service can bind 0.0.0.0 so
/tiles, /vectors, the catalog and the probes reach the LAN, while /admin and
/api/v1/admin answer 403 to anything arriving from another host. Credentials
are only required when ADMIN_ALLOW_REMOTE=true actually opens the console up,
which is the case worth guarding.

Tests cover both sides: a remote admin request is refused, a remote tile
request still reaches the token check.
2026-10-16 10:41:32 +08:00

350 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# API 调用文档
面向调用方(前端、下游系统、脚本)。服务默认监听 `http://127.0.0.1:8910`,
局域网开放方式见文末。
---
## 1. 认证
### 1.1 管理面(`/api/v1/admin/*`、`/admin/*`)
用**管理员凭据**,与业务令牌是两套东西:
| 方式 | 场景 | 配置 |
|---|---|---|
| HTTP Basic | 浏览器打开运维台 | `ADMIN_USER` + `ADMIN_PASSWORD` |
| Bearer | 脚本/接口调用 | `ADMIN_TOKEN` |
```bash
curl -u ops:change-me http://127.0.0.1:8910/api/v1/admin/basemaps
curl -H "Authorization: Bearer <ADMIN_TOKEN>" http://127.0.0.1:8910/api/v1/admin/basemaps
```
管理面可以签发令牌、改默认版本、配推送目标,所以**不要在局域网裸奔**:
`API_HOST` 绑非回环地址时若未配置任何管理员凭据,服务会**拒绝启动**。
### 1.2 业务面(编目 / 取图 / 切片)
用**服务令牌**(`bmp_` 开头,由管理面签发)。四种投递方式任选:
```
Authorization: Bearer <token>
X-Access-Token: <token>
?token=<token>
?access_token=<token>
```
查询参数形式是给地图客户端用的(`<img>`/瓦片库没法设请求头)。
令牌带 **scope**:`read`(默认,读已授权的底图/矢量)和 `slice`(提交切片任务)。
只带 `slice` 的令牌**不需要**任何底图授权,因此读不到任何瓦片。
```json
POST /api/v1/admin/tokens
{"name": "integration", "scopes": ["slice"]}
```
### 1.3 错误格式
所有错误统一:
```json
{"error": "token has no access to this basemap"}
```
| 状态码 | 含义 |
|---|---|
| 400 | 参数不合法 |
| 401 | 令牌缺失/无效/过期/停用 |
| 403 | 令牌有效,但没有该资源权限或缺少所需 scope |
| 404 | 资源或任务不存在 |
| 409 | 已有扫描在执行 |
| 500 | 服务端错误 |
---
## 2. 系统端点(无需认证)
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | `/health` | 存活探针 |
| GET | `/ready` | 就绪探针 |
| GET | `/metrics` | Prometheus 文本指标(请求数、瓦片数/404、鉴权失败、运行时长) |
| GET | `/api/v1/system` | 服务信息(版本、各 base URL、扫描根目录等) |
---
## 3. 管理 API(需管理员凭据)
### 编目
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | `/api/v1/admin/dashboard` | 运维台一次性数据(台账、令牌、推送、图层源清单等) |
| GET | `/api/v1/admin/basemaps` | 底图列表(含版本) |
| GET | `/api/v1/admin/vectors` | 矢量资源列表 |
| GET | `/api/v1/admin/scan-sources` | 扫描源列表 |
| POST | `/api/v1/admin/scan-sources` | 新增/更新扫描源 |
| POST | `/api/v1/admin/scan-sources/{code}/run` | 立即扫描该源 |
| POST | `/api/v1/admin/vectors/rescan` | 重扫矢量目录 |
| POST | `/api/v1/admin/basemaps/{code}/default-version` | 设默认版本,body `{"version":"2026-04"}` |
| POST | `/api/v1/admin/health/check` | 立即执行资源可用性巡检 |
巡检返回:
```json
{"checked_at":"2026-10-16T02:00:00Z","versions_checked":3,"versions_missing":0,
"vectors_checked":1,"vectors_missing":0}
```
### 令牌
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | `/api/v1/admin/tokens` | 令牌列表(只含前缀,不含明文) |
| POST | `/api/v1/admin/tokens` | 签发令牌 |
| POST | `/api/v1/admin/tokens/{id}/disable` | 停用 |
签发请求:
```json
{
"name": "portal-a",
"basemap_codes": ["gaode-image", "google-image"],
"vector_codes": ["全国行政区.geojson", "landuse/parcel.shp"],
"scopes": ["slice"],
"expires_at": "2027-01-01T00:00:00Z"
}
```
- `vector_codes` 可写 code、相对路径或文件名
- `expires_at` 省略时用 `TOKEN_DEFAULT_TTL`(默认 30 天);`0` 表示不过期
- 响应里的 `token` 字段是**明文,只返回这一次**,务必立即保存
### 推送
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | `/api/v1/admin/targets` | 目标系统列表 |
| POST | `/api/v1/admin/targets` | 新增/更新目标系统 |
| POST | `/api/v1/admin/pushes` | 推送底图版本 `{target_code, basemap_code, version}` |
| POST | `/api/v1/admin/vector-pushes` | 推送矢量 `{target_code, vector_code}` |
| GET | `/api/v1/admin/push-records` | 底图推送记录(最近 20) |
| GET | `/api/v1/admin/vector-push-records` | 矢量推送记录 |
| POST | `/api/v1/admin/push-records/{id}/retry` | 手动重推 |
| POST | `/api/v1/admin/vector-push-records/{id}/retry` | 手动重推(矢量) |
推送**异步执行**,提交返回 `202` 与一条 `pending` 记录;失败按 30s/60s 退避
自动重试最多 3 次,状态在 `retrying` / `failed` / `succeeded` 之间变化。
---
## 4. 编目 API(需服务令牌,`read`)
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | `/api/v1/catalog/basemaps` | 该令牌可见的底图(已按授权过滤) |
| GET | `/api/v1/catalog/basemaps/{code}` | 单个底图详情 |
| GET | `/api/v1/catalog/vectors` | 可见的矢量资源 |
| GET | `/api/v1/catalog/vectors/{code}` | 单个矢量详情 |
底图详情里的 `url_template` 就是可直接使用的取图模板:
```text
http://<tile-base>/tiles/gaode-image/current/{z}/{x}/{y}.webp
```
---
## 5. 切片 API(需 `slice` scope 的令牌)
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | `/api/v1/slice/jobs` | 提交切片任务 → `202` + 任务对象 |
| GET | `/api/v1/slice/jobs/{id}` | 轮询单个任务 → `200` + 任务对象 |
| GET | `/api/v1/slice/layers/{code}/sources` | 该图层由哪些源影像构成 |
### 5.1 提交
```json
{
"code": "uav-2026-flight-b",
"name": "2026无人机影像 - 第二次飞行",
"target_code": "uav-2026",
"source_paths": ["D:\\uav\\flight-b.tif"],
"zoom_min": 0,
"zoom_max": 18,
"tile_size": 256,
"tile_format": "webp",
"callback_url": "http://portal-a/api/tile-jobs/done",
"requested_by": "ops"
}
```
字段说明:
| 字段 | 必填 | 说明 |
|---|---|---|
| `target_code` | 是 | 图层编码,**必须 ASCII**(中文会被归一化掉)。显示名由调用端自己映射 |
| `source_paths` | 是 | 源影像绝对路径数组;单元素时也可用 `source_path` |
| `code` | 否 | 幂等键。**同一个 code 重复提交会返回既有任务**,不会排队第二个 |
| `zoom_min` / `zoom_max` | 否 | 默认 0 / 0 |
| `tile_size` | 否 | 默认 256 |
| `tile_format` | 否 | 默认 webp(仅为元数据,实际格式由 gdal2tiles 决定) |
| `callback_url` | 否 | 任务结束时回调 |
| `requested_by` | 否 | 审计用 |
**两种模式,由源的数量决定:**
- **传 1 个源** → 直接切这一张,落到 `输出目录`,用于**日常增量累加**(新飞行覆盖到哪算哪)
- **传多个源** → 先 `gdalbuildvrt` 拼成一张 VRT 再切,**逐像素镶嵌,无瓦片接缝**,用于重建整个图层
累加示例(同一个 `target_code`,每次只提交新飞的):
```bash
# 第一次
curl -X POST http://127.0.0.1:8910/api/v1/slice/jobs \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"target_code":"uav-2026","source_paths":["D:\\uav\\flight-a.tif"],"zoom_min":0,"zoom_max":18}'
# 几天后再飞,自动累加
curl -X POST http://127.0.0.1:8910/api/v1/slice/jobs \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"target_code":"uav-2026","source_paths":["D:\\uav\\flight-b.tif"],"zoom_min":0,"zoom_max":18}'
```
重建为无缝镶嵌:先取全部源,再一次性提交。
```bash
curl -H "Authorization: Bearer $TOKEN" \
http://127.0.0.1:8910/api/v1/slice/layers/uav-2026/sources
```
### 5.2 任务对象
```json
{
"id": "265f65bb-b15b-40c2-a672-2103aea2c26d",
"code": "uav-2026-flight-b",
"name": "2026无人机影像 - 第二次飞行",
"source_count": 1,
"source_paths": ["D:\\uav\\flight-b.tif"],
"target_code": "uav-2026",
"output_root": "D:\\Code\\tile-server\\data\\tiles\\uav-2026",
"zoom_min": 0, "zoom_max": 18,
"tile_size": 256, "tile_format": "webp",
"status": "pending",
"message": "",
"log_path": "",
"callback_url": "http://portal-a/api/tile-jobs/done",
"callback_status": "",
"created_at": "2026-10-16T02:00:00Z"
}
```
`status`:`pending` → `running` → `succeeded` / `failed`。
失败原因在 `message`,完整 stdout/stderr 在 `log_path`。
任务**单并发**,按提交顺序执行,且不阻塞 API。
### 5.3 完成回调
任务结束时向 `callback_url` POST:
```json
{"event": "tile.job.finished", "job": { ...任务对象... }, "delivered": "2026-10-16T02:05:00Z"}
```
失败按 30s/60s 退避重试,最多 3 次;状态在任务的 `callback_status` /
`callback_attempts` / `callback_message` 里。
---
## 6. 取图与下载(需服务令牌)
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | `/tiles/{code}/{version}/{z}/{x}/{y}.ext` | 指定版本取图 |
| GET | `/tiles/{code}/{z}/{x}/{y}.ext` | 兼容路由,取默认版本 |
| GET | `/vectors/{code}/download` | 下载矢量(shapefile 自动打包 zip) |
| GET | `/geojson/{相对路径}` | 兼容路由,按原始相对路径取 GeoJSON |
| GET | `/shp/{相对路径}` | 兼容路由,按相对路径取 shapefile(返回 zip) |
```bash
curl -o 0.webp "http://127.0.0.1:8910/tiles/gaode-image/current/0/0/0.webp?token=$TOKEN"
```
瓦片响应带 `Cache-Control: public, max-age=31536000, immutable`。
---
## 7. 局域网开放
默认 `API_HOST=127.0.0.1`,只有本机能访问。**管理台和切片可以分开暴露**:
### 推荐:切片上局域网,管理台仍只在本机
服务绑到所有网卡,但管理员路由**只接受来自回环地址的请求**(默认行为):
```ini
# config/service.env
# 1) 监听所有网卡,切片才能被其他机器取到
API_HOST=0.0.0.0
# 2) 对外地址改成局域网 IP(你这台是 192.168.1.62),
# 否则台账里的 url_template 发出去还是 127.0.0.1
API_BASE_URL=http://192.168.1.62:8910
TILE_PUBLIC_BASE_URL=http://192.168.1.62:8910
VECTOR_PUBLIC_BASE_URL=http://192.168.1.62:8910
# 3) 管理员路由保持仅回环(默认就是 false,写出来是为了明确)
ADMIN_ALLOW_REMOTE=false
```
这样:`/tiles/*`、`/vectors/*`、`/api/v1/catalog/*`、`/health` 等从局域网可达
(取图仍需令牌);`/admin/*` 与 `/api/v1/admin/*` 从其他机器请求会返回 **403**。
**改完必须重启服务并重扫一次**:
```
POST /api/v1/admin/scan-sources/local-basemaps/run
```
台账里的 `url_template` 是扫描时用 base URL 拼出来的,不重扫的话对外仍是旧的
`127.0.0.1` 地址。
放行防火墙入站:
```powershell
New-NetFirewallRule -DisplayName "Basemap Pusher 8910" -Direction Inbound `
-Protocol TCP -LocalPort 8910 -Action Allow
```
### 可选:管理台也上局域网
把 `ADMIN_ALLOW_REMOTE=true` 打开(或在局域网内直接用管理台)。此时**必须**配置
管理员凭据,否则服务拒绝启动:
```ini
ADMIN_ALLOW_REMOTE=true
ADMIN_USER=ops
ADMIN_PASSWORD=<改掉它>
```
浏览器会弹 Basic 认证框,此时局域网内访问 `http://192.168.1.62:8910/admin/`。
### 启动守卫规则
| API_HOST | ADMIN_ALLOW_REMOTE | 有管理员凭据 | 结果 |
|---|---|---|---|
| 回环 | 任意 | 任意 | 正常启动 |
| 非回环 | false(默认) | 不需要 | 正常启动,管理员路由仅回环 |
| 非回环 | true | 是 | 正常启动,管理台可远程访问 |
| 非回环 | true | 否 | **拒绝启动** |
### 安全提醒
这是明文 HTTP,Basic 凭据与令牌在网线上只是 base64,没有加密。内网自用没问题,
但如果要跨网段或给外部系统调用,建议前面挂 Nginx 上 HTTPS。