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.
11 KiB
API 调用文档
面向调用方(前端、下游系统、脚本)。服务默认监听 http://127.0.0.1:8910,
局域网开放方式见文末。
1. 认证
1.1 管理面(/api/v1/admin/*、/admin/*)
用管理员凭据,与业务令牌是两套东西:
| 方式 | 场景 | 配置 |
|---|---|---|
| HTTP Basic | 浏览器打开运维台 | ADMIN_USER + ADMIN_PASSWORD |
| Bearer | 脚本/接口调用 | ADMIN_TOKEN |
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 的令牌不需要任何底图授权,因此读不到任何瓦片。
POST /api/v1/admin/tokens
{"name": "integration", "scopes": ["slice"]}
1.3 错误格式
所有错误统一:
{"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 |
立即执行资源可用性巡检 |
巡检返回:
{"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 |
停用 |
签发请求:
{
"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 就是可直接使用的取图模板:
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 提交
{
"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,每次只提交新飞的):
# 第一次
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}'
重建为无缝镶嵌:先取全部源,再一次性提交。
curl -H "Authorization: Bearer $TOKEN" \
http://127.0.0.1:8910/api/v1/slice/layers/uav-2026/sources
5.2 任务对象
{
"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:
{"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) |
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,只有本机能访问。管理台和切片可以分开暴露:
推荐:切片上局域网,管理台仍只在本机
服务绑到所有网卡,但管理员路由只接受来自回环地址的请求(默认行为):
# 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 地址。
放行防火墙入站:
New-NetFirewallRule -DisplayName "Basemap Pusher 8910" -Direction Inbound `
-Protocol TCP -LocalPort 8910 -Action Allow
可选:管理台也上局域网
把 ADMIN_ALLOW_REMOTE=true 打开(或在局域网内直接用管理台)。此时必须配置
管理员凭据,否则服务拒绝启动:
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。