merchant-map-sync.md 4.8 KB

门店人工同步到自部署地图

日期:2026-10-23。此接口仅供平台管理员显式操作,不随门店编辑、营业状态变化、报价、订单或定时任务自动调用。实现过程中没有调用真实地图写接口,没有修改业务数据库。

管理接口

POST /system/map/merchants/sync,使用平台管理端现有登录凭据与 chanting:store:edit 权限。接口没有 App 的匿名放行或 App 登录注解,普通 App 登录凭据不能代替管理端授权。请求为 JSON DTO:

{"storeIds":[12,34],"removeStoreIds":[56]}

两组 ID 合计 1~100 个,必须为正整数,不允许重复或交叉。storeIds 仅查询明确指定的 PosStore,不扫描全表;从数据库读取门店名称、地址、WGS84 经纬度,不接受调用方覆盖数据。缺失/已删除门店、下架门店或无效名称/坐标逐条报告失败,其余有效门店继续。state=1 表示休息/打烊,不作为永久停用或自动删除依据。请求不更新业务库,不改变门店营业状态。

有效门店使用稳定外部标识 foodie-store-{门店ID}。改名、迁址或重试均更新同一人工地点;普通门店和摊位都按各自门店 ID 处理。名称最多 200 字符、地址最多 500 字符;经纬度须齐全且在全球合法范围。实际部署区域由地图 API 继续校验,越界行进入失败列表。

removeStoreIds 为管理员明确选择的移除清单,以相同外部标识删除。允许本地已物理删除/软删除或已不存在的 ID,用于清理地图遗留源记录;不会因为查不到业务门店而拒绝清理。只移除人工地点来源,不删除 OSM 同名地点。删除已不存在记录视为成功,支持幂等重试。

响应 AjaxResult.data 包含:

{
  "rows":[
    {"storeId":12,"action":"UPSERT","success":true,"code":"OK","message":null},
    {"storeId":34,"action":"UPSERT","success":false,"code":"INVALID_STORE","message":"本地化错误信息"},
    {"storeId":56,"action":"REMOVE","success":true,"code":"OK","message":null}
  ],
  "indexRebuildRequired":true
}

STORE_NOT_FOUND 为业务门店不存在;INVALID_STORE 为本地字段不合法、已删除/下架或地图拒绝的行;MAP_UNAVAILABLE 为服务/鉴权/网络故障或返回结构不可信。错误消息使用四语言国际化,不回传上游正文、地址、坐标、URL 或凭据。请求整体格式错误则按业务异常返回,不进行数据库查询或外部写入。

地图侧协议与凭据

配置 map.self-hosted.base-url、map.self-hosted.admin-token(环境变量 MAP_SELF_HOSTED_ADMIN_TOKEN)。后端管理令牌必须与地图服务器 MAP_ADMIN_TOKEN 一致,与普通读接口令牌分别配置;不发送到 App、页面或日志。经地图 Nginx 的 base URL 含 /api,直连 Flask 容器时不含 /api。管理客户端只允许两个固定端点,外部 ID 使用 URI path segment 编码:

  • POST /popular_places/import,{"places":[{"external_id":"foodie-store-12","name":"...","address":"...","lat":25.03,"lng":121.56,"type":"business"}]}。按地图零基拒绝行号映射回原门店;只接受完整可信的成功/拒绝统计。
  • DELETE /popular_places/foodie-store-56,成功响应 {"status":"removed","removed":0或1,"index_rebuild_required":true}。

两者都携带管理员 Bearer 令牌,禁用自动重试及跳转;缺令牌时请求失败,不进行匿名写入。调用独立于当前 Google/自部署业务开关,管理员可在放量前准备自部署地点。

索引发布与重试

同步只写地图人工地点源表,不自动重建搜索索引。跨行、批量导入与各删除请求没有整体事务;前面的行可能已成功。超时或响应异常时也可能已写入,因此 indexRebuildRequired 在尝试任何外部写入后保守返回 true,不能把它解释为所有行成功。

管理员核对逐行结果并以相同门店 ID 修正/重试后,由开发者在地图项目指定配置执行:

python3 scripts/deploy.py index --env-file <地图环境配置>

此命令使用地图 build_index.py 构建并发布搜索索引。只有索引发布成功后,新增/改名/迁址/移除才在搜索生效;删除重试返回 removed:0 时也需要完成本批索引发布。不要对 foodie 数据库执行该命令,也不要在同步请求内触发全量重建。存量地图库迁移仍由开发者手动执行地图仓库已有 SQL 登记。

本地验证覆盖稳定 ID 更新、营业休息状态、无效行/缺失门店、地图拒绝行号、显式不存在门店的幂等删除、输入限制、模糊写入失败、固定管理路径与 Bearer 凭据,以及方法权限拒绝。真实业务库读取、PostGIS 写入、索引重建、服务器凭据与 App 联调均未执行。