# zcbot 移动 App 套壳对接 > 给 **platform 工程**:把 zcbot 控制台打包成移动 App。 > 架构:**原生壳(WebView)+ 原生登录页**。登录调 platform 自家接口换出 zcbot JWT,WebView 加载 zcbot 控制台时把 token 放 URL fragment 带入;token 失效时 zcbot 跳回 `relogin_url`,原生壳拦截后静默换新 token 重进。zcbot 前端不打包进 App,前端迭代不发版。 --- ## 1. 登录链路(platform 自有接口,2026-07-21 实测可用) ``` GET /api/login/check_captcha_required/ → {require_captcha: bool} GET /api/login/captcha?key=<随机串> → 验证码图(仅 require_captcha 时) POST /api/login/token → platform access_token form: username, password, key(必填,即使无验证码), grant_type=password [, captcha_text] POST /api/login/external-login → zcbot JWT header: Authorization: Bearer ``` `external-login` 返回的 `data` 字段: ```json {"token":"", "user_id":"", "name":"...", "user_name":"...", "expires_at":"...", "ttl_seconds":604800} ``` 其中 `token` / `user_id` 就是下一步进入 zcbot 所需的全部。 --- ## 2. zcbot 进入契约 拿到 JWT 后,WebView 加载: ``` https:///static/dev.html?embed=1&relogin_url=<回登录入口的地址>#token=&user_id= ``` - **fragment**:`token` / `user_id` 两个字段都必填;zcbot 读完立即清掉,不留在地址栏和历史。 - **`relogin_url`**:必须是带 scheme 的绝对地址。原生壳推荐自定义 scheme(如 `zcbotapp://relogin`);`javascript:` 等可执行 scheme 会被拒。 - **401 行为**:zcbot JWT 失效时页面 `location.replace(relogin_url)`。原生壳在 `shouldOverrideUrlLoading`(Android)/ `WKNavigationDelegate`(iOS)拦截该跳转 → 用缓存的 platform token 重调 `external-login` → 带新 fragment 重新 loadUrl。 - **token 生命周期**:zcbot JWT 7 天 TTL + 滑动续签(活跃期间自动续,页面内已处理)。App 每次冷启动都换一张新 JWT,实际只有 platform token 过期(约 30 天)才需要用户重输密码。 - 可选 `&task_id=`:进入后自动定位到该 task。 **冷启动静默登录**:壳启动 → Keystore/Keychain 里有 platform token → 后台调 `external-login` → 直接 loadUrl 进控制台,用户无感;没有或已失效 → 显示原生登录页。 --- ## 3. 原生壳杂活清单 | 项 | Android | iOS | |---|---|---| | 文件上传 | `WebChromeClient.onShowFileChooser` | 自带 | | 文件下载 | `DownloadListener` → `DownloadManager` | `WKDownloadDelegate`(iOS 14.5+) | | 返回键 | `canGoBack() ? goBack() : 退出确认` | 手势自带 | | DOM storage | `setDomStorageEnabled(true)`(必须) | 自带 | | 键盘顶起 | `windowSoftInputMode="adjustResize"` | 自带 | | 外链 | 非 platform/zcbot 域交给系统浏览器 | 同左 | | PDF/PPT/HTML 预览 | zcbot 前端内置渲染,无需原生 PDF 组件 | 同左 | SSE 流式输出两端 WebView 原生支持,无需处理。iOS 上架提醒:纯套壳有 App Store 4.2(最小功能)被拒风险;企业分发 / TestFlight / 仅 Android 无此问题。 URL 白名单只处理**主 frame**导航。Android 检查 `request.isForMainFrame`,iOS 检查 `navigationAction.targetFrame?.isMainFrame`;不要拦截 zcbot 页面内部的 worker 或子 frame 请求,否则 PDF.js worker、HTML sandbox 等站内预览仍会白屏。 --- ## 4. 备选:H5 登录页 不想写原生登录 UI 的话,可在 platform 域上放一个 `app.html`(登录表单 + 调自家接口,同源无 CORS),换到 JWT 后 `location.replace` 按 §2 契约跳进 zcbot,`relogin_url` 填 `app.html` 自己的地址(401 跳回来时若 platform token 还在缓存,静默重签再跳回,用户无感)。契约完全相同,只是登录层从原生换成网页。 --- ## 5. 安全要点 1. **`PLATFORM_KEY` 只在 platform 后端**(`external-login` 内部用),App / 前端代码里绝不出现。 2. **不存明文账密**。"记住登录"= 把 platform token 存 Keystore / Keychain,靠 token 生命周期免登,不缓存密码重放。 3. **建议 platform 侧修**:`POST /api/login/token` 响应的 `user` 对象把 `hashed_password` 原样下发了,应在序列化时剔除。 4. WebView 域名白名单:只放行 platform 与 zcbot 两个域 + 自定义 scheme,其余交系统浏览器。