由 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>
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.js补closePage(返回);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/report。SGApi 只发 JSON,不覆盖 multipart,故这段保留自定义 fetch:URL 改相对 /api/v1/report、Authorization 头取 SGBridge.getToken()。文件选择依赖安卓宿主 onShowFileChooser(见 android 文档),后端无需改动。
7. shared/bridge.js 改动
- 新增
closePage():SGBridge.closePage()→ 调SGBridgeNative.closePage()(无桥时 mockconsole.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-delivery、comparing-ad-remote-flag的下发方式),无则新增一个GET /api/v1/app/ui-flags之类端点。
10. 缓存与热更
- H5 是后端托管 → 改完即生效(用户重开页即拉新),这是远程方案的核心收益。
index.html用Cache-Control: no-cache(或短 TTL)保证及时性;shared/*.js、图片等带版本/指纹便于长缓存。StaticFiles 可配响应头或交 nginx 处理。
11. 迁移顺序与风险
| 里程碑 | server 侧动作 |
|---|---|
| M1 records | 挂 /h5;bridge.js 加 closePage;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 + 资源指纹 |