geo:新增地址联想接口 /api/v1/geo/tips(高德 inputtips 代理)laoyang/FamilyBook#36

鸿蒙端地点输入要「输入简单地址 → 下拉带详细地址」,但高德 Web 服务 key
不能进客户端(反编译/抓包即得,配额会被跑光、key 可能被封)。
网页端用的 JS API key + 安全密钥是域名白名单那套,原生端没有等价机制
⇒ 走服务端代理,与网页端 addr_suggest.js 同一个上游(/v3/assistant/inputtips)。

- Geo::tips():调高德 inputtips,把三套不齐的字段归一成
  name / detail(省市区+门牌) / district / address / lng / lat
- 过滤公交地铁线路(typecode 15xx)—— 对填地点没用
- 只看 body 里的 status 而不是 HTTP 码(配额用完高德回 200 + status=0)
- 少于 2 字不查,返回空数组而不是报错(阈值与网页端一致)
- ApiHandler::geo():GET only、要求登录(这是花配额的接口)

⚠️ 现有 key 是 JS API 类型,调 Web 服务接口返回 USERKEY_PLAT_NOMATCH(10009),
   需另配 Web 服务 key 才能真正工作。同样原因,既有 Geo::regeo() 一直是失效的
   (cityFromPoint 在走离线兜底),此问题另行处理。

解析逻辑已用 4 组模拟高德响应验证(同名可区分/行政区划兜底/线路过滤/
空 name 跳过/失败返回空),19 项全通过,含源码一致性检查。
This commit is contained in:
2026-10-30 08:03:20 +08:00
parent df657e5e5a
commit 29b2ca0e3c
2 changed files with 192 additions and 0 deletions
+50
View File
@@ -112,6 +112,13 @@ final class ApiHandler
case 'resources': $this->resources($id, $sub, $method); return;
case 'books': $this->books($id, $sub, $method); return;
case 'notes': $this->notes($id, $sub, $method); return;
/* 🔴 2026-10-30(工单 #36):高德**地址联想**代理。
端上地点输入框要"输入简单地址 → 下拉带详细地址",
但高德 Web 服务 key 不能进客户端(反编译即泄漏、配额被跑光)。
⇒ 走服务端代理,与网页端 `assets/js/addr_suggest.js` 同一个上游。
⚠️ 与 `geo` 平级,不能做成 `calendar/tips` —— 它是站点级工具,
不属于某个业务模块(影院/行程也用得上)。 */
case 'geo': $this->geo($sub, $method); return;
// 🔴 v45:登录(手机号 + 密码 → API 令牌)。
// 为什么必须走 API 而不是网页登录后复制 Cookie:
// App 是 Bearer 令牌那套,没有会话;而原先前端只能让人
@@ -1409,6 +1416,49 @@ final class ApiHandler
Api::ok($rows);
}
/**
* `GET /api/v1/geo/tips?kw=星巴克&limit=10` —— 地址联想(高德输入提示代理)。
*
* 🔴 2026-10-30(工单 #36):网页端地址联想早就有,"地名后面必须带详细地址"
* 是用户同一天的诉求(#32);鸿蒙端原本**连联想都没有**(纯文本框),
* 这里给端上补上,字段口径与网页端 `result.tips` 对齐:
* 每条都给 `name` + `detail`(省市区 + 门牌),端上把 detail 画在名字下面,
* 同名地点才分得开 —— 这正是用户要的"作为参考,让用户更好地选择"。
*
* 为什么要**服务端代理**而不是端上直连高德:
* 高德 Web 服务 key 是服务端密钥,打进安装包等于公开(反编译/抓包即可拿到),
* 配额会被跑光、key 还可能被高德封。网页端那套 JS API key + 安全密钥
* 是给浏览器域名白名单用的,原生端没有等价机制。
*
* @param string $sub 只认 `tips`(留出扩展余地,将来要 `regeo` 也能挂这里)
* @param string $method 只允许 GET
*/
private function geo(string $sub, string $method): void
{
/* ⚠️ 要登录:这是**要花配额**的接口,不开放匿名调用
(否则别人拿本站在跑自己的地址联想,配额算我们的)。 */
Api::me();
if ($method !== 'GET') {
Api::fail('地址联想只支持 GET', 'method_not_allowed', 405);
}
if ($sub !== 'tips') {
Api::fail('未知的地理接口:' . $sub, 'not_found', 404);
}
$kw = Api::str('kw', '', 60);
/* 少于 2 个字直接返回空数组(而不是报错):
端上照常手敲,不该因为"字太少"弹一个错误框。
阈值与网页端 addr_suggest.js 一致,两边体验才对得上。 */
if (mb_strlen($kw, 'UTF-8') < 2) {
Api::ok([]);
}
$limit = (int)Api::str('limit', '10');
if ($limit <= 0) { $limit = 10; }
Api::ok(Geo::tips($kw, $limit));
}
private function events(int $id, string $sub, string $method): void
{
[$fam, $pid] = Api::me();