# 门店人工同步到自部署地图 日期:2026-10-23。此接口仅供平台管理员显式操作,不随门店编辑、营业状态变化、报价、订单或定时任务自动调用。实现过程中没有调用真实地图写接口,没有修改业务数据库。 ## 管理接口 `POST /system/map/merchants/sync`,使用平台管理端现有登录凭据与 `chanting:store:edit` 权限。接口没有 App 的匿名放行或 App 登录注解,普通 App 登录凭据不能代替管理端授权。请求为 JSON DTO: ```json {"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` 包含: ```json { "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 修正/重试后,由开发者在地图项目指定配置执行: ```bash python3 scripts/deploy.py index --env-file <地图环境配置> ``` 此命令使用地图 `build_index.py` 构建并发布搜索索引。只有索引发布成功后,新增/改名/迁址/移除才在搜索生效;删除重试返回 `removed:0` 时也需要完成本批索引发布。不要对 foodie 数据库执行该命令,也不要在同步请求内触发全量重建。存量地图库迁移仍由开发者手动执行地图仓库已有 SQL 登记。 本地验证覆盖稳定 ID 更新、营业休息状态、无效行/缺失门店、地图拒绝行号、显式不存在门店的幂等删除、输入限制、模糊写入失败、固定管理路径与 Bearer 凭据,以及方法权限拒绝。真实业务库读取、PostGIS 写入、索引重建、服务器凭据与 App 联调均未执行。