Appearance
访问控制
访问受保护的服务需要 API Key。这两个示例演示带 Key 访问的两种方式,以及访问失败时如何区分原因。
在沙箱里请使用未限制来源域名的 Key
沙箱的运行页出于安全考虑运行在隔离的环境里,它发出的请求不带可识别的来源域名。设置了来源域名白名单的 Key 在沙箱里一定会被拒绝(错误码 TOKEN_INVALID)。如需测试带白名单的 Key,请在沙箱里点「下载」,把得到的 HTML 放到白名单内的站点上运行,详见在线沙箱。
用 API Key 访问受保护服务
在公开的地图方案底图上叠加一个需要 API Key 的服务。默认的请求头方式:transformRequest 给发往平台的请求加上 X-GeoBridge-Token 请求头,底图与叠加层、要素查询都带 Key。另一种是地址参数方式:用 client.withKey() 把 Key 作为 token 参数补进地址,适合不能自定义请求头的库;打印日志时用 GeoBridge.maskToken() 遮蔽 Key。左上角的面板逐行显示 Key 是否填写、这个 Key 可访问的服务数、叠加的图层数,以及用要素查询统计的铁路车站数。
需要: 一个访问级别为"需要 API Key"的服务,以及对它有访问权限的 API Key(形如 gb_xxx…)。运行前先在沙箱页面顶部填写 Key。
js
在沙箱中打开/**
* @title 用 API Key 访问受保护服务
* @description 在地图方案底图上叠加需要 API Key 的交通设施服务:默认的 header 模式用请求头带 Key 出图,也演示 withKey 的 query 形态;面板显示 Key 状态、可访问的服务数与港铁站数。需要先在页面顶部填写 Key。
* @category 访问控制
* @service demo/protected
* @map demo/map
*/
// 出错时给出可理解的提示:受保护服务没带 Key 或 Key 无效时,SDK 抛出带 code 的 GeoBridgeError
const friendly = (e) =>
e && e.code === 'TOKEN_REQUIRED' ? '该服务需要 API Key:请在页面顶部填写 Key 后再运行'
: e && e.code === 'TOKEN_INVALID' ? 'API Key 无效、已吊销,或限制了来源域名'
: e && e.status === 404 ? '服务不存在,请检查服务路径' : e && e.message
// 面板:逐行显示检查结果
const panel = document.createElement('div')
panel.style.cssText = 'position:absolute;top:8px;left:8px;z-index:2;background:#fff;padding:6px 10px;border-radius:6px;box-shadow:0 1px 4px rgba(0,0,0,.3);font:13px/1.8 sans-serif;max-width:360px'
container.appendChild(panel)
const line = (text, level = 'log') => {
panel.appendChild(document.createElement('div')).textContent = text
console[level](text)
}
try {
// 拷到自己页面时:换成你的受保护服务路径,创建客户端时用 apiKey 选项传入你的 Key
const path = services['demo/protected']
line(client.hasApiKey ? 'API Key:已填写' : 'API Key:未填写(请在页面顶部填写后再运行)')
// header 模式(默认):transformRequest 对平台地址加请求头 X-GeoBridge-Token,底图与叠加层都经过它
// 提示:平台写进样式的地址以平台配置的 webBaseUrl 为前缀;它与 baseUrl 不一致时 header 模式不会带 Key
const map = new maplibregl.Map({
container,
style: client.maplibre.mapStyleUrl(maps['demo/map']),
transformRequest: client.transformRequest,
center: [114.172, 22.298],
zoom: 15,
})
map.addControl(new maplibregl.NavigationControl())
// 切片、图标等资源加载失败时 MapLibre 发 error 事件:转成提示,不影响其余内容显示
map.on('error', (e) => console.warn('地图资源加载失败:', e.error && e.error.message))
await new Promise((resolve) => map.once('load', resolve))
// 1. 列出 Key 可访问的服务:目录接口不对匿名开放(沙箱运行页也不带平台的登录状态),没有 Key 时跳过
if (client.hasApiKey) {
const list = await client.services.list()
line(`该 Key 可访问 ${list.length} 个服务`)
} else {
line('列出可访问服务需要 API Key:目录接口不对匿名开放')
}
// 2. 带 Key 叠加受保护的矢量瓦片服务,再放大服务样式里的圆点便于看清
const overlay = await client.maplibre.addVectorTileService(map, path)
for (const id of overlay.layerIds) if (map.getLayer(id).type === 'circle') map.setPaintProperty(id, 'circle-radius', 3)
line(`已叠加受保护图层:${overlay.layerIds.length} 个`)
// 3. 带 Key 查询要素服务:统计港铁站等铁路车站
const stations = await client.query.count(path, 0, { where: "fclass = 'railway_station'" })
line(`铁路车站:${stations} 个`)
// 4. query 形态:withKey 给平台地址补 token 参数,适合不能加请求头的库;打印前用 maskToken 遮蔽
console.log('query 形态的切片地址:', GeoBridge.maskToken(client.withKey(client.services.vectorTileUrl(path))))
// 整张地图都走 query 形态时,把 transformRequest 换成:(url) => ({ url: client.withKey(url) })
// 或直接创建 query 模式的客户端:new GeoBridge.GeoBridgeClient({ baseUrl, apiKey, keyTransport: 'query' })
} catch (e) {
line('访问失败:' + friendly(e), 'error')
}错误处理
分别用匿名客户端、格式正确但不存在的 Key、以及页面顶部填写的 Key 访问受保护服务。失败时 SDK 抛出 GeoBridgeError,按 code 区分原因并给出中文提示,结果以表格显示在地图上:
code | 含义 |
|---|---|
TOKEN_REQUIRED | 服务需要 API Key,请求没有带 |
TOKEN_INVALID | Key 无效、已吊销,或来源域名不在 Key 的白名单内 |
UNAUTHORIZED | 匿名访问不存在或仅限成员访问的服务;平台不透露服务是否存在 |
FORBIDDEN | 已登录,但没有该服务所在工作空间的访问权限(带 Key 的请求不会得到此码) |
NETWORK、TIMEOUT | 网络不通或请求超时 |
前两次失败是故意演示的,示例用普通日志输出;浏览器的网络面板里仍会看到这两次请求返回 401。
需要: 一个访问级别为"需要 API Key"的服务;填写了 Key 时会多一次成功的访问。
js
在沙箱中打开/**
* @title 错误处理
* @description 分别用匿名客户端、无效 Key 与页面顶部填写的 Key 访问受保护服务:失败时 SDK 抛 GeoBridgeError,按 code 给出中文提示;结果以红绿表格显示在地图上。前两次失败是故意演示,以普通日志输出。
* @category 访问控制
* @service demo/protected
* @map demo/map
*/
const path = services['demo/protected']
// 把 GeoBridgeError 的 code 翻译成给用户看的提示;拷到自己页面时可直接复用
function explain(e) {
if (!(e instanceof GeoBridge.GeoBridgeError)) return `非预期错误:${e.message}`
if (e.status === 404) return '服务不存在,请检查服务路径'
switch (e.code) {
case 'TOKEN_REQUIRED':
return '该服务需要 API Key,请先申请并填写'
case 'TOKEN_INVALID':
return 'API Key 无效、已吊销,或来源域名不在白名单内'
case 'UNAUTHORIZED':
// 匿名访问不存在或仅限成员的服务时,平台统一返回 401,不透露服务是否存在
return '服务不存在,或仅限工作空间成员登录后访问'
case 'FORBIDDEN':
return '没有访问此服务的权限'
case 'NETWORK':
case 'TIMEOUT':
return '网络不通或请求超时,请稍后重试'
default:
return `请求失败(${e.code},HTTP ${e.status})`
}
}
// 底图用公开的地图方案,匿名也能显示
new maplibregl.Map({ container, style: client.maplibre.mapStyleUrl(maps['demo/map']), center: [114.172, 22.298], zoom: 15 })
const table = document.createElement('table')
table.style.cssText = 'position:absolute;top:8px;left:8px;z-index:2;background:#fff;border-collapse:collapse;box-shadow:0 1px 4px rgba(0,0,0,.3);font:13px/1.6 sans-serif'
container.appendChild(table)
async function attempt(label, c) {
let ok = true
let text = '访问成功'
try {
await c.services.get(path, 'VectorTileServer')
} catch (e) {
ok = false
text = `${e.code}:${explain(e)}`
}
const row = table.insertRow()
for (const [i, v] of [label, ok ? '✓' : '✗', text].entries()) {
const cell = row.insertCell()
cell.textContent = v
cell.style.cssText = `padding:4px 10px;border-bottom:1px solid #eee;${i === 1 ? `color:${ok ? '#2f9e44' : '#e03131'};font-weight:bold` : ''}`
}
// 故意演示的失败也用 console.log 输出,不算示例自身出错
console.log(label, '→', text)
}
// 匿名:没有 Key
await attempt('匿名客户端', new GeoBridge.GeoBridgeClient({ baseUrl: client.baseUrl }))
// 构造的无效 Key:格式正确但不存在
const invalidKey = 'gb_' + 'x'.repeat(43)
await attempt('无效 Key', new GeoBridge.GeoBridgeClient({ baseUrl: client.baseUrl, apiKey: invalidKey }))
// 页面顶部填写的 Key(沙箱提供的 client)
if (client.hasApiKey) await attempt('页面填写的 Key', client)
else console.log('页面顶部未填写 Key,跳过第三次尝试')