chatapp3-flutter/docs/h5-summary.md
NIGGER SLAYER 77ce33bac9 appid
2026-04-15 12:53:01 +08:00

17 KiB
Raw Blame History

H5 相关实现梳理

1. 总体概览

项目里和 H5 相关的能力,当前主要分成 3 条线:

  1. 内嵌 H5 页面承载:使用 webview_flutter 打开 H5 页面。
  2. Flutter 与 H5 双向通讯:通过 JS 注入 + JavaScriptChannel 做交互。
  3. 外部 H5 / Scheme 回流 App通过 app_links 监听深链。

相关依赖:

  • webview_flutter: 4.4.2pubspec.yaml:61
  • app_links: ^6.4.1pubspec.yaml:130
  • url_launcher: ^6.3.1 已安装:pubspec.yaml:63,但本次检索未发现当前 lib/ 下有明确的 H5 主流程调用。

2. H5 核心承载页

核心文件:lib/modules/webview/webview_page.dart

2.1 页面职责

WebViewPage 是项目里统一的 H5 容器页,负责:

  • 加载 H5 URL
  • 给 URL 统一追加 lang 参数
  • 开启 JS
  • 注入 window.app
  • 接收 H5 发给 Flutter 的消息
  • 读取 H5 页面的 document.title
  • 承接标题栏显隐和样式

2.2 加载流程

关键代码位置:

  • URL 组装:lib/modules/webview/webview_page.dart:48-53
  • 开启 JSlib/modules/webview/webview_page.dart:55-58
  • 注册消息通道:lib/modules/webview/webview_page.dart:58-119
  • 页面完成后延迟注入:lib/modules/webview/webview_page.dart:132-140
  • 加载请求:lib/modules/webview/webview_page.dart:147

实际流程:

  1. 传入 widget.url
  2. 若 URL 已有查询参数则追加 &lang=...,否则追加 ?lang=...
  3. 使用 WebViewController 加载页面
  4. onPageFinished 后延迟 550ms再执行 JS 注入
  5. 同时读取 document.title 作为标题

说明:

  • JS 模式为 JavaScriptMode.unrestricted
  • 延迟注入的注释写明是“等待 Vue3 初始化”
  • 页面销毁时会加载 about:blanklib/modules/webview/webview_page.dart:150-153

3. Flutter 与 H5 的通讯方式

3.1 H5 -> Flutter

Flutter 侧注册的 JS Channel 名称是:

  • FlutterPageControllib/modules/webview/webview_page.dart:58-60

H5 发送消息后Flutter 根据字符串内容执行不同动作。

已支持的消息协议

H5 消息 Flutter 行为 代码位置
close_page 关闭当前 WebView 页 lib/modules/webview/webview_page.dart:62-63
view_user_info:{userId} 跳到个人资料页 lib/modules/webview/webview_page.dart:64-75
go_to_room:{roomId} 进入房间 lib/modules/webview/webview_page.dart:76-87
private_chat:{userId} 打开私聊会话 lib/modules/webview/webview_page.dart:88-110
uploadImgFile 调起图片选择,并把图片路径再注入给 H5 lib/modules/webview/webview_page.dart:111-112184-206
editingRoom 跳转房间管理搜索页 lib/modules/webview/webview_page.dart:113-114
editingUser 跳转用户管理搜索页 lib/modules/webview/webview_page.dart:115-116

H5 侧当前更稳妥的调用方式

从 Flutter 代码来看,当前真正注册的 channel 只有 FlutterPageControl,因此 H5 若要主动发消息,建议按下面方式理解:

FlutterPageControl.postMessage('close_page')
FlutterPageControl.postMessage('go_to_room:123456')
FlutterPageControl.postMessage('view_user_info:10001')
FlutterPageControl.postMessage('private_chat:10001')

3.2 Flutter -> H5

Flutter 会在页面加载完成后给 H5 注入 window.app 对象:lib/modules/webview/webview_page.dart:156-181

注入的方法

方法 作用 代码位置
window.app.getAccessOrigin() 返回一段 JSON 字符串,里面包含鉴权和设备/渠道信息 lib/modules/webview/webview_page.dart:162-170
window.app.getAuth() 当前默认等价于 getAccessOrigin() lib/modules/webview/webview_page.dart:173-175
window.app.sendToFlutter(data) 试图把消息回传给 Flutter lib/modules/webview/webview_page.dart:177-180

getAccessOrigin() 返回内容

其返回值是 JSON 字符串,不是 JS 对象H5 需要自行 JSON.parse(...)

大致结构如下:

{
  "Authorization": "Bearer <token>",
  "Req-Lang": "<lang>",
  "Req-App-Intel": "build=<build>;version=<version>;model=<model>;channel=<channel>;Req-Imei=<imei>",
  "Req-Sys-Origin": "origin=<origin>;originChild=<originChild>"
}

这些值来自:

  • TokenAccountStorage,见 lib/shared/data_sources/sources/local/user_manager.dart:23-55
  • 语言、版本、渠道、设备、来源:SCGlobalConfig,见 lib/app/constants/sc_global_config.dart:24-43
  • 设备唯一标识:SCDeviceIdUtils.getDeviceId(),见 lib/shared/tools/sc_deviceId_utils.dart:31-62

图片上传相关注入

当 H5 发出 uploadImgFileFlutter 会调起图片选择器,然后重新注入另一组方法:lib/modules/webview/webview_page.dart:184-206

这时会新增:

  • window.app.getImagePath():返回所选图片本地路径的 JSON 字符串

并且会把:

  • window.app.getAuth()

改写成:

  • 返回 window.app.getImagePath()

也就是说,图片选择之后,getAuth() 不再表示鉴权信息,而变成了“图片路径”。


4. H5 路由与承载入口

4.1 统一路由

项目提供统一路由:

  • 路由常量:SCMainRoute.webViewPage = '/main/webViewPage'
    位置:lib/modules/index/main_route.dart:42

  • 路由解析逻辑:
    lib/modules/index/main_route.dart:185-198

解析行为:

  • url 会先 Uri.decodeComponent
  • title 直接透传
  • showTitle 直接从 query 里取字符串

4.2 标题栏显隐规则

WebViewPage 默认 showTitle = "true"lib/modules/webview/webview_page.dart:27-31

但通过路由进入时:

  • showTitle: params['showTitle']?.first ?? ""lib/modules/index/main_route.dart:191-195

因此目前有两种进入方式:

  1. 直接 WebViewPage(...)
    • 不传 showTitle 时,标题栏默认显示
  2. 通过 SCMainRoute.webViewPage?...
    • 通常会显式传 showTitle=false
    • 若未来有人漏传 showTitle,最终拿到的是空字符串 "",标题栏也会被隐藏

5. 当前已接入的 H5 页面入口

5.1 登录页 / 关于页协议页

这两处是直接 new WebViewPage,默认显示标题栏。

场景 URL 来源 代码位置
登录页《Terms of Service》 SCGlobalConfig.userAgreementUrl lib/modules/auth/login_page.dart:311-324
登录页《Privacy Policy》 SCGlobalConfig.privacyAgreementUrl lib/modules/auth/login_page.dart:350-363
设置-关于-《Terms of Service》 SCGlobalConfig.userAgreementUrl lib/modules/user/settings/about/about_page.dart:80-90
设置-关于-《Privacy Policy》 SCGlobalConfig.privacyAgreementUrl lib/modules/user/settings/about/about_page.dart:112-123

5.2 首页 Party 榜单 H5

这些入口都走统一路由,并显式 showTitle=false

入口 URL 来源 代码位置
财富榜 SCGlobalConfig.wealthRankUrl lib/modules/home/popular/party/sc_home_party_page.dart:295-300
房间榜 SCGlobalConfig.roomRankUrl lib/modules/home/popular/party/sc_home_party_page.dart:330-335
魅力榜 SCGlobalConfig.charmRankUrl lib/modules/home/popular/party/sc_home_party_page.dart:365-370

5.3 我的页面角色中心 H5

角色身份先通过 /app/h5/identity 拉取,再决定展示哪些入口。

身份拉取:

  • lib/services/auth/user_profile_manager.dart:90-95
  • lib/shared/data_sources/sources/repositories/sc_user_repository_impl.dart:539-551

角色字段定义:

  • lib/shared/business_logic/models/res/sc_user_identity_res.dart:6-87

当前角色入口:

身份条件 打开页面 代码位置
anchor && agent agencyCenterUrl lib/modules/user/me_page2.dart:295-308
anchor && !agent hostCenterUrl lib/modules/user/me_page2.dart:309-322
!admin && bdLeader bdLeaderUrl lib/modules/user/me_page2.dart:325-339
!admin && bd bdCenterUrl lib/modules/user/me_page2.dart:340-353
freightAgent coinSellerUrl lib/modules/user/me_page2.dart:356-370
admin adminUrl lib/modules/user/me_page2.dart:372-386

5.4 Banner H5

Banner 打开逻辑在:

  • lib/shared/tools/sc_banner_utils.dart:10-27

规则:

  1. item.content == ENTER_ROOM 时,直接进房间
  2. 否则只要 paramshttp://https:// 开头,就用内嵌 WebView 打开

注意:

  • 这里只校验是不是 URL没有域名白名单

5.5 消息中心 / 通知里的外链 H5

只要 message.extraData.linkhttp / https 开头,就会直接进入统一 WebView

  • 活动消息:lib/modules/chat/activity/message_activity_page.dart:268-277
  • 通知消息:lib/modules/chat/noti/message_notifcation_page.dart:280-289

这两处同样没有域名白名单。


6. H5 URL 配置清单

配置接口定义在:

  • lib/app/config/app_config.dart:21-66
  • lib/app/constants/sc_global_config.dart:7-18
  • lib/app/config/configs/sc_variant1_config.dart:26-71

当前 variant1 中的 H5 URL

配置项 URL 当前使用情况
privacyAgreementUrl https://h5.haiyihy.com/privacy.html 已使用
userAgreementUrl https://h5.haiyihy.com/service.html 已使用
anchorAgentUrl https://h5.haiyihy.com/apply/index.html 本次检索未发现引用
hostCenterUrl https://h5.haiyihy.com/host-center/index.html 已使用
bdCenterUrl https://h5.haiyihy.com/bd-center/index.html 已使用
bdLeaderUrl https://h5.haiyihy.com/bd-leader-center/index.html 已使用
coinSellerUrl https://h5.haiyihy.com/coin-seller/index.html 已使用
adminUrl https://h5.haiyihy.com/admin-center/index.html 已使用
agencyCenterUrl https://h5.haiyihy.com/agency-center/index.html 已使用
gamesKingUrl https://h5.haiyihy.com/games-king/index.html 本次检索未发现引用
wealthRankUrl https://h5.haiyihy.com/ranking/index.html?first=Wealth 已使用
charmRankUrl https://h5.haiyihy.com/ranking/index.html?first=Charm 已使用
roomRankUrl https://h5.haiyihy.com/ranking/index.html?first=Room 已使用
inviteNewUserUrl https://h5.haiyihy.com/invitation/invite-new-user/index.html 本次检索未发现引用

7. /app/h5/identity 接口

这个接口是 H5 相关链路里的关键接口。

7.1 接口位置

  • 未授权校验时直接请求:lib/shared/data_sources/sources/remote/net/api.dart:49-89
  • 路径映射表:lib/shared/data_sources/sources/remote/net/network_client.dart:199
  • Repository 包装:lib/shared/data_sources/sources/repositories/sc_user_repository_impl.dart:539-551

7.2 当前用途

  1. 拉取当前用户身份,用于决定“我的”页面展示哪些 H5 入口
  2. 在网络层发生 401 时,用它来复核当前会话是否仍然有效

7.3 身份字段

当前模型里可见的角色字段包括:

  • agent
  • anchor
  • bd
  • admin
  • superAdmin
  • bdLeader
  • superFreightAgent
  • manager
  • freightAgent

定义位置:lib/shared/business_logic/models/res/sc_user_identity_res.dart:6-87


8.1 依赖与处理器

深链能力不是走 WebView而是走 app_links

  • 依赖:pubspec.yaml:130
  • 处理器:lib/shared/tools/sc_deep_link_handler.dart:1-61

SCDeepLinkHandler 做了两件事:

  1. 启动时读取 initialLink
  2. 运行时监听 uriLinkStream

8.2 App 根层接入

main.dart 已初始化深链监听:

  • 挂载处理器:lib/main.dart:246
  • initState 调用:lib/main.dart:248-257
  • 深链初始化:lib/main.dart:266-274

但是当前真正处理链接的 _handleLink(Uri uri) 里只有日志和简单取参:

  • lib/main.dart:276-281

也就是说:

  • 深链基础设施已经接好了
  • 但业务路由尚未真正实现

8.3 Android 配置

AndroidManifest 里已配置:

  • https://h5.yumi.com
  • yumi://app

位置:android/app/src/main/AndroidManifest.xml:55-64

8.4 iOS 配置现状

本次检索结果里:

  • 未发现 applinks: Associated Domains
  • 未发现针对 yumi://app 的明确 CFBundleURLSchemes
  • Info.plistCFBundleURLTypes 目前只有一个空壳配置:ios/Runner/Info.plist:38-44

因此至少从当前代码检索结果看:

  • Android 侧有显式深链声明
  • iOS 侧未看到与当前 H5 域名 / scheme 对应的完整配置

9. WebView 样式与测试

WebView 页面样式做了策略抽象:

  • 接口定义:lib/app/config/business_logic_strategy.dart:1451-1491
  • Base 实现:lib/app/config/strategies/base_business_logic_strategy.dart:2024-2078
  • Variant1 实现:lib/app/config/strategies/variant1_business_logic_strategy.dart:2003-2057

测试覆盖位置:

  • test/business_logic_strategy_test.dart:1122-1183

当前测试主要覆盖:

  • 普通 WebView 页面的颜色策略
  • 游戏 WebView 页面的颜色策略
  • Variant1 对应策略返回值

额外说明:

  • WebViewPage 里有 _progressBar(...) 方法:lib/modules/webview/webview_page.dart:275-283
  • 但当前 build 里没有真正挂载这条进度条

10. 当前实现里的注意点 / 风险点

10.1 JS Channel 名称与注入 helper 名称不一致

Flutter 注册的是:

  • FlutterPageControl

但注入到 H5 的 helper 写的是:

  • FlutterApp.postMessage(data)lib/modules/webview/webview_page.dart:178-179202-203

本次检索未发现项目里其他地方定义了 FlutterApp JS 对象。

这意味着:

  • 如果 H5 直接调用 window.app.sendToFlutter(data),大概率会找不到 FlutterApp
  • 当前更像是 H5 需要自己直接调用 FlutterPageControl.postMessage(...)

10.2 uploadImgFile 会覆盖 getAuth()

在初始注入时:

  • getAuth() 等于 getAccessOrigin()

在用户选图后:

  • getAuth() 被改成 getImagePath()

这会导致 H5 侧如果沿用 getAuth() 获取鉴权信息,选图后语义会发生变化。

10.3 缺少域名白名单,且注入内容包含鉴权信息

当前以下入口只检查“是不是 http/https”没有限制域名

  • Bannerlib/shared/tools/sc_banner_utils.dart:18-24
  • 活动消息:lib/modules/chat/activity/message_activity_page.dart:269-276
  • 通知消息:lib/modules/chat/noti/message_notifcation_page.dart:281-288

WebViewPage 会对已加载页面统一注入:

  • Bearer Token
  • 语言
  • 设备标识
  • 构建版本
  • 渠道和来源信息

因此当前实现等于:

  • 只要某个外链能被塞进 Banner 或消息链接
  • 该页面理论上就能拿到 window.app.getAccessOrigin() 这组数据

这是当前 H5 链路里最需要重点关注的安全点。

10.4 Android 深链域名与业务配置域名不一致

当前业务配置里的 H5 域名主要是:

  • h5.haiyihy.com

但 AndroidManifest 里声明的可验证 Host 是:

  • h5.yumi.com

位置分别在:

  • lib/app/config/configs/sc_variant1_config.dart:26-71
  • android/app/src/main/AndroidManifest.xml:59-64

如果期望 H5 页面通过系统级深链回流 App这个域名不一致需要单独确认。

SCDeepLinkHandler 已经能收到链接,但 main.dart 里的 _handleLink 还没有落业务跳转逻辑。

当前状态更接近:

  • “能收到”
  • “还没消费”

10.6 lang 参数是无条件追加

当前 URL 处理逻辑只判断是否已有 ?,不会判断原 URL 是否已经存在 lang 参数:

  • lib/modules/webview/webview_page.dart:48-53

如果某些 H5 页面本来就自带 lang,最终可能出现重复参数。


11. 给 H5 同学的当前可参考约定

如果只根据当前 Flutter 实现来对接,比较稳妥的方式是:

读取鉴权/设备信息

const auth = JSON.parse(window.app.getAccessOrigin())

向 Flutter 发消息

FlutterPageControl.postMessage('close_page')
FlutterPageControl.postMessage('view_user_info:10001')
FlutterPageControl.postMessage('go_to_room:123456')
FlutterPageControl.postMessage('private_chat:10001')
FlutterPageControl.postMessage('uploadImgFile')

选图后读取图片路径

const imageInfo = JSON.parse(window.app.getImagePath())

注意:

  • window.app.sendToFlutter(...) 目前从代码上看不可靠,因为它引用的是 FlutterApp
  • window.app.getAuth() 在选图前后语义不同,不建议 H5 继续把它当成固定鉴权接口

12. 本次检索范围

本次重点检查了以下文件/模块:

  • lib/modules/webview/webview_page.dart
  • lib/modules/index/main_route.dart
  • lib/shared/tools/sc_banner_utils.dart
  • lib/modules/auth/login_page.dart
  • lib/modules/user/settings/about/about_page.dart
  • lib/modules/home/popular/party/sc_home_party_page.dart
  • lib/modules/user/me_page2.dart
  • lib/modules/chat/activity/message_activity_page.dart
  • lib/modules/chat/noti/message_notifcation_page.dart
  • lib/app/config/app_config.dart
  • lib/app/config/configs/sc_variant1_config.dart
  • lib/app/constants/sc_global_config.dart
  • lib/shared/data_sources/sources/remote/net/api.dart
  • lib/shared/data_sources/sources/remote/net/network_client.dart
  • lib/shared/data_sources/sources/repositories/sc_user_repository_impl.dart
  • lib/shared/business_logic/models/res/sc_user_identity_res.dart
  • lib/services/auth/user_profile_manager.dart
  • lib/shared/tools/sc_deep_link_handler.dart
  • lib/main.dart
  • android/app/src/main/AndroidManifest.xml
  • ios/Runner/Info.plist
  • test/business_logic_strategy_test.dart