深链(Deep Link) 通过 URL 参数直达应用内某个业务状态:分享一个 baseurl.com/navigate?id=xxx,打开页面即自动进入路线面板(route-panel)并填好目的地(结束点),经纬度由 id 在数据集中解析,而不是把坐标写死在 URL 里。

为什么用 id,而不是把坐标塞进 URL

  • id 稳定:uid 是业务主键;坐标会因坐标系(WGS84 / GCJ02 / BD09)或数据修订而变,写进 URL 的坐标只是快照,会过期。
  • 链接友好:?lng=121.56&lat=29.82&name=xxx 又长又乱码;?id=<uid> 短、可读、可分享。
  • 单一数据源:客户端统一从公开数据集解析,搜索列表与深链解析用同一份数据,前端/后端口径一致。

全栈实现链路(本项目实际落地)

  1. 后端深链路由:FastAPI 加 GET /navigate?id=<uid>,白名单透传参数(id/dst/ak/backend),302 到真实静态页 /navi/index.html。

    • 为什么 302 而不是直接 FileResponse:导航页用相对路径引用资源(styles.css、app.js、../logo.png)。若直接在 /navigate 返回文件,浏览器会把相对路径解析成 /styles.css → 404。302 到静态页,让浏览器按真实目录解析相对路径。
  2. 数据接口:/browse 输出全部 POI(含 uid 与导航坐标 navi_location,本项目为 BD09),搜索与 id 解析共用,不新增端点。
  3. 页面解析参数:读 ?id= 存入 pendingDestinationId;等待「地图就绪 + POI 数据加载完成」两个条件同时满足后,按 uid 匹配 POI → selectDestination() 填好路线面板并规划。
  4. 多触发点、幂等消费:initializeMap / 定位成功回调 / 数据加载完成 三处都调用同一个 tryResolvePendingId();内部命中后清空 pendingDestinationId。保证无论数据先到还是定位先到,都只触发一次——不重复定位、不漏触发。
  5. 入口复用同一 id:SPA 聊天里「智能导航选项」卡片按钮把同一个 uid 作为 ?id= 传给内嵌 iframe;卡片附「复制链接」按钮生成可分享 URL,全链路统一。

关键设计决策

  • 经纬不进 URL,由 id 解析;未来换坐标系只改数据集,链接永不过期。
  • 深链路由用 302,保持静态页相对路径语义(深链路由常见坑)。
  • 参数白名单透传,不原样转发整段 query,防无关参数/注入。
  • iframe 与整页两种形态共用一套参数协议(?id= / ?dst= / backend)。APK 与网页端差异只在 backend:APK 内经 SPA 注入公网后端地址让导航页跨域 fetch(CORS 已开),网页端同源无需传。

验证方法

  • curl -i baseurl.com/navigate?id=<uid> 应返回 302 且 location: /navi/index.html?id=<uid>。
  • 手机浏览器直接打开分享链接,应自动进入路线面板、填好目的地,定位后规划路线。
  • 无效 id 有兜底提示(本项目 toast「未找到该目的地」);数据加载失败也有独立提示。

常见坑 / 适用边界

  • 静态页相对路径 + 深链路由:直接 FileResponse 会 404 资源,必须 302(或改写相对路径)。
  • 就绪顺序不确定:id 解析必须「数据 + 地图」双条件齐全;多处触发要幂等,否则重复定位或漏触发。
  • 坐标系:解析出的坐标必须匹配目标地图(百度 BMapGL 用 BD09;高德用 GCJ02)。
  • 适用场景:一切「分享/收藏一个带业务实体的页面并自动填表」——预约链接、商品详情、打卡页同理。

关联

  • URL Scheme (URL 协议)——深链的协议基础。
  • 项目落地:校园导航 Android Webview AI-canpus Map 仓库 README「导航深链(URL 传参)」章节,代码见 server.py 的 /navigate 与 frontend/public/navi/app.js 的 tryResolvePendingId。

本文归档于个人知识库 · 领域:CS & AI · 模块:04 Project_CS_AI/Web