Files
shaguabijia-app-server/docs/H5-WebView改造方案.md
T
guke 46c650ecb8 docs(h5): 新增 H5/WebView 改造方案(方案1 SGBridge,服务端托管)
由 app-server 同源托管业务 H5(StaticFiles 挂 /h5);records/reports 从安卓迁入改用 SGBridge/SGApi;bridge.js 补 closePage;后端业务接口零改动;含远程开关、缓存热更、M1-M4 里程碑。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 12:14:15 +08:00

7.8 KiB

H5 / WebView 改造方案(app-server 端)

仓库:shaguabijia-app-server 分支:feat/h5-sgbridge 日期:2026-06-29 配套文档:shaguabijia-app-android 仓库 docs/H5-WebView改造方案.md(原生壳 + SGBridge.kt) 本方案 = 方案1:以本仓已有 SGBridge 为统一基准,安卓退役其 NativeBridge


1. 背景与现状

项目里同时存在两套互不兼容的 H5 方案,尚未收敛:

本仓那套(已在 main,PR #89) 安卓那套(feat/records-h5,未进 main)
SGBridge(JS)↔ SGBridgeNative(原生),协议丰富 + 事件总线 NativeBridge,仅 4 法
托管 设计为后端同源托管(相对 /api/v1、免 CORS) 本地 assets file://(跨域 + 放行 CORS)
页面 h5/mine/index.html(7771)+ h5/shared/bridge.js+api.js records(1778)、reports(603)
落地 半成品:安卓从无 SGBridge.kt;且 h5/ 实际没挂(StaticFiles 只挂了 data/media 头像上传) 已接进安卓导航但只在分支上

结论(已对齐): 目标是「远程后端托管 + 四 tab 全 H5」,该方向天然契合本仓 SGBridge(同源相对路径、mine 已成型、shared/ 已就绪)。故统一到 SGBridge,把安卓的 records/reports 页迁入本仓 h5/


2. 目标与范围

目标: 由 app-server 同源托管全部业务 H5,配齐 shared/ 桥与接口封装,支持安卓「原生壳 + 远程 H5」一套桥贯通,可不发版热更。

本仓(app-server)范围:

  • H5 托管:新增 FastAPI StaticFiles 挂载 /h5 → 指向仓库 h5/ 目录(同源、免 CORS)
  • 迁入 records / reports 两页(从安卓 feat/records-h5 移植),改用 SGBridge / SGApi
  • shared/bridge.jsclosePage(返回);shared/api.js 已同源就绪(必要时补 multipart 说明)
  • 远程开关配置接口(返回 per-page native/h5 标志)
  • 缓存 / 热更策略(cache-control + 版本)

不在本仓(见 android 文档): SGBridge.kt、WebView 宿主、原生/H5 切换接线。

后端业务接口:零改动 —— H5 复用现有 /api/v1/...(/compare/records/report/wallet/account/coupon/stats 等),只是请求方从 Kotlin 改成网页 fetch。


3. 关键决策(已锁定)

# 决策 选定 理由
D1 桥基准 SGBridge(安卓退役 NativeBridge) 协议齐、mine 成型、三端对齐
D2 托管 FastAPI StaticFiles 挂 /h5 同源免 CORS、复用现有 /media 方式、nginx 已反代 /→uvicorn,dev+prod 都能跑
D3 离线兜底 纯远程 + 安卓侧重试页 YAGNI;后端不需为离线做特殊处理
D4 地址 安卓用 BackendUrl.current 推导 H5 地址(同源) H5 与 /api/v1 同 host,自动跟随环境
D5 迁移顺序 records → reports → 首页/福利;mine 最后校准 用小页验证桥,风险分散

4. H5 托管(D2)

app/main.py 现有挂载(仅头像):

app.mount(settings.MEDIA_URL_PREFIX, StaticFiles(directory=str(_media_root)), name="media")

新增 H5 静态挂载(指向仓库根 h5/,html=True 让目录路径回落到 index.html):

_h5_root = Path(__file__).resolve().parent.parent / "h5"   # 仓库根 h5/
app.mount("/h5", StaticFiles(directory=str(_h5_root), html=True), name="h5")
  • 访问:https://app-api.shaguabijia.com/h5/records/index.html(prod)/ http://<dev-host>:8770/h5/...(debug)。
  • /api/v1 同 host → 同源:H5 内 SGApi 用相对 /api/v1,免 CORS、免 getApiBase
  • prod 可选改由 nginx 直发 h5/(绕过 uvicorn)以提性能——本期先用 StaticFiles,nginx 优化留后。

5. H5 工程结构

h5/
├── shared/
│   ├── bridge.js   # SGBridge:H5↔原生桥(本期补 closePage)
│   └── api.js      # SGApi:同源相对 /api/v1 封装(已就绪)
├── mine/index.html      # 我的页(已成型,M4 按真实 SGBridge.kt 校准)
├── records/index.html   # ← 从安卓迁入(M1)
└── reports/index.html   # ← 从安卓迁入(M2)

各页头部引入:<script src="../shared/bridge.js"></script> + <script src="../shared/api.js"></script>


6. records / reports 页迁入(从安卓移植)

搬运 + 改桥(迁移、非重写,UI/CSS/逻辑占绝大部分原样保留):

安卓原写法(NativeBridge) 迁入后(SGBridge / SGApi)
NativeBridge.getApiBase() 拼绝对 URL(8 处) 删除,改相对 /api/v1/...(同源)
NativeBridge.getToken()(7 处) SGBridge.getToken()(同名)
NativeBridge.onUnauthorized()(6 处) SGApi 内置 401 → SGBridge.requestLogin()
NativeBridge.closePage()(5 处) SGBridge.closePage()(bridge.js 本期新增)
普通 GET/POST 改用 SGApi.get/post('/...')(自动带 token、处理 401)

上报传图(records)特别说明:<input type="file"> + 客户端压缩 + FormData multipart POST /api/v1/reportSGApi 只发 JSON,不覆盖 multipart,故这段保留自定义 fetch:URL 改相对 /api/v1/reportAuthorization 头取 SGBridge.getToken()。文件选择依赖安卓宿主 onShowFileChooser(见 android 文档),后端无需改动。


7. shared/bridge.js 改动

  • 新增 closePage():SGBridge.closePage() → 调 SGBridgeNative.closePage()(无桥时 mock console.log)。records/reports 顶栏返回箭头、?from=profile 返回都用它。
  • 其余方法(getToken/requestLogin/navigate/getAuthState/事件总线…)已就绪,保持签名,供 mine 及后续 tab 使用。
  • 浏览器无 SGBridgeNative 时走 MOCK,便于本地 python -m http.server 或 StaticFiles 直接调样式。

8. SGBridge 接口契约(三端共享)

与 android 文档同表;shared/bridge.js 为权威。新增方法三端同步。

查询类(同步返回 String): getAuthState / getToken ✓ / getDeviceId / getAppVersion / getInstalledApps / getCouponClaimedToday 动作类(无返回): navigate / requestLogin ✓ / toast / openMeituan / startCompare / startCouponClaim / launchApp / closePage【新增】✓ 事件类(原生→H5,_emit): onAuthChange / onBalanceChange / onSigninChange / onResume (✓ = records 阶段 M1 最小子集)


9. 远程开关配置接口

  • 提供一个轻量配置:返回各页渲染方式 { records: "h5"|"native", reports: ..., home: ..., welfare: ..., mine: ... }
  • 安卓 AppNavHost 据此决定加载 H5 还是原生页,不发版即可灰度 / 一键回退
  • 复用现有远程配置/开关基建(参考安卓 ad-config-remote-deliverycomparing-ad-remote-flag 的下发方式),无则新增一个 GET /api/v1/app/ui-flags 之类端点。

10. 缓存与热更

  • H5 是后端托管 → 改完即生效(用户重开页即拉新),这是远程方案的核心收益。
  • index.htmlCache-Control: no-cache(或短 TTL)保证及时性;shared/*.js、图片等带版本/指纹便于长缓存。StaticFiles 可配响应头或交 nginx 处理。

11. 迁移顺序与风险

里程碑 server 侧动作
M1 records /h5;bridge.jsclosePage;records 迁入 + 改桥;配 index.html no-cache
M2 reports reports 迁入 + 改桥
M3 首页/福利 首页/福利页 H5 化(需 SGBridge P2+ 协议,配合安卓)
M4 mine + 收尾 mine 按真实 SGBridge.kt 校准;远程开关接口上线
风险 缓解
SGBridge 原生侧未验证 M1 用 records 小页验证(详见 android 文档)
multipart 上传被误套进 SGApi 文档明确:上报保留自定义 multipart fetch,不走 SGApi
同源被破坏(H5 与 API 不同 host) 坚持 StaticFiles 同源托管;若改独立前端域名,需回评 SGApi 相对路径策略
缓存导致改了不生效 index.html no-cache + 资源指纹