268 lines
8.7 KiB
Markdown
268 lines
8.7 KiB
Markdown
# Flutter H5 排行榜对接文档
|
||
|
||
本文说明 Flutter App 如何打开 H5 排行榜、向 H5 传递登录态,以及接收 H5 通过 JSBridge 发回的导航事件。
|
||
|
||
## 入口地址
|
||
|
||
本地开发:
|
||
|
||
```text
|
||
http://127.0.0.1:30000/rank/index.html?env=local&token={access_token}&app_code=lalu
|
||
```
|
||
|
||
测试环境:
|
||
|
||
```text
|
||
https://{h5-test-domain}/rank/index.html?env=test&token={access_token}&app_code=lalu
|
||
```
|
||
|
||
线上环境:
|
||
|
||
```text
|
||
https://{h5-domain}/rank/index.html?token={access_token}&app_code=lalu
|
||
```
|
||
|
||
`env` 规则:
|
||
|
||
| 参数 | 说明 |
|
||
| ----------- | -------------------------------------------------------- |
|
||
| 不传 | H5 请求 `https://api.global-interaction.com/` |
|
||
| `env=test` | H5 请求 `https://api-test.global-interaction.com/` |
|
||
| `env=local` | H5 请求 `http://localhost:13000/`,用于本机 gateway 联调 |
|
||
|
||
## URL 参数
|
||
|
||
| 参数 | 必填 | 说明 |
|
||
| -------------- | ---- | --------------------------------------------------------------------------------- |
|
||
| `token` | 是 | App 登录后拿到的 access token。H5 会作为 `Authorization: Bearer <token>` 调后端。 |
|
||
| `access_token` | 否 | `token` 的兼容别名。 |
|
||
| `accessToken` | 否 | `token` 的兼容别名。 |
|
||
| `app_code` | 是 | App 编码,当前传 `lalu`。H5 会作为 `X-App-Code` 请求头。 |
|
||
| `appCode` | 否 | `app_code` 的兼容别名。 |
|
||
| `env` | 否 | API 环境切换,见上表。 |
|
||
|
||
H5 启动后会调用:
|
||
|
||
```http
|
||
GET /api/v1/users/me/overview
|
||
Authorization: Bearer <token>
|
||
X-App-Code: lalu
|
||
```
|
||
|
||
这个接口用于确认登录态并缓存当前用户信息。排行榜数据使用同一个 token 和 app code 请求。
|
||
|
||
## H5 页面能力
|
||
|
||
排行榜页面地址:
|
||
|
||
```text
|
||
/rank/index.html
|
||
```
|
||
|
||
页面内包含三个榜单,通过 tab 切换:
|
||
|
||
| H5 Tab | 后端 `board_type` | 说明 |
|
||
| -------- | ----------------- | ---------------------------- |
|
||
| `Wealth` | `sent` | 财富榜,按用户送礼总金币排行 |
|
||
| `Room` | `room` | 房间榜,按房间收礼总金币排行 |
|
||
| `Charm` | `received` | 魅力榜,按用户收礼总金币排行 |
|
||
|
||
时间维度:
|
||
|
||
| H5 Tab | 后端 `period` |
|
||
| --------- | ------------- |
|
||
| `Daily` | `today` |
|
||
| `Weekly` | `week` |
|
||
| `Monthly` | `month` |
|
||
|
||
排行榜接口:
|
||
|
||
```http
|
||
GET /api/v1/activities/user-leaderboards?board_type=sent&period=today&page=1&page_size=20
|
||
Authorization: Bearer <token>
|
||
X-App-Code: lalu
|
||
```
|
||
|
||
H5 会分页加载,`page_size=20`。如果无 token 或接口失败,页面会展示 Figma 默认占位数据,不阻塞页面打开。
|
||
|
||
## Flutter JSBridge
|
||
|
||
H5 会按以下优先级尝试发送消息:
|
||
|
||
1. `window.flutter_inappwebview.callHandler("HyAppBridge", action, payload)`
|
||
2. `window.HyAppBridgeChannel.postMessage(jsonString)`
|
||
3. `window.FlutterBridge.postMessage(jsonString)`
|
||
4. `window.webkit.messageHandlers.HyAppBridge.postMessage({ action, payload })`
|
||
|
||
Flutter 只需要实现其中一种即可。推荐统一注册 `HyAppBridge`。
|
||
|
||
### 消息格式
|
||
|
||
除 `flutter_inappwebview.callHandler` 外,postMessage 的消息是 JSON 字符串:
|
||
|
||
```json
|
||
{
|
||
"action": "openUser",
|
||
"payload": {
|
||
"user_id": "10001"
|
||
}
|
||
}
|
||
```
|
||
|
||
### H5 发出的 action
|
||
|
||
| action | payload | 触发场景 | Flutter 行为 |
|
||
| -------------- | -------------------------- | --------------------------------- | -------------------------------------------------- |
|
||
| `ready` | `{ "page": "rank" }` | H5 初始化完成 | 可关闭加载态 |
|
||
| `back` | `{}` | 用户点击左上角返回 | 关闭 WebView 或返回上一页 |
|
||
| `openUser` | `{ "user_id": "10001" }` | 点击财富榜/魅力榜用户头像、榜单项 | 打开用户详情页 |
|
||
| `openRoom` | `{ "room_id": "room_id" }` | 点击房间榜房间头像、榜单项 | 进入或打开房间详情 |
|
||
| `openRankNote` | `{ "board_type": "sent" }` | 点击右上角问号 | 当前 H5 已内置说明页;Flutter 可只打点,不需要跳转 |
|
||
|
||
`board_type` 取值:
|
||
|
||
| 值 | 说明 |
|
||
| ---------- | ------ |
|
||
| `sent` | 财富榜 |
|
||
| `room` | 房间榜 |
|
||
| `received` | 魅力榜 |
|
||
|
||
## Flutter 示例
|
||
|
||
### flutter_inappwebview
|
||
|
||
```dart
|
||
final url = Uri.parse(
|
||
'http://127.0.0.1:30000/rank/index.html'
|
||
'?env=local'
|
||
'&token=${Uri.encodeComponent(accessToken)}'
|
||
'&app_code=lalu',
|
||
);
|
||
|
||
InAppWebView(
|
||
initialUrlRequest: URLRequest(url: WebUri(url.toString())),
|
||
onWebViewCreated: (controller) {
|
||
controller.addJavaScriptHandler(
|
||
handlerName: 'HyAppBridge',
|
||
callback: (args) async {
|
||
final action = args.isNotEmpty ? args[0] as String : '';
|
||
final payload = args.length > 1 && args[1] is Map
|
||
? Map<String, dynamic>.from(args[1] as Map)
|
||
: <String, dynamic>{};
|
||
|
||
switch (action) {
|
||
case 'ready':
|
||
// 关闭 loading
|
||
break;
|
||
case 'back':
|
||
Navigator.of(context).maybePop();
|
||
break;
|
||
case 'openUser':
|
||
final userId = payload['user_id']?.toString() ?? '';
|
||
if (userId.isNotEmpty) {
|
||
// TODO: 打开用户详情页
|
||
}
|
||
break;
|
||
case 'openRoom':
|
||
final roomId = payload['room_id']?.toString() ?? '';
|
||
if (roomId.isNotEmpty) {
|
||
// TODO: 打开房间或房间详情页
|
||
}
|
||
break;
|
||
case 'openRankNote':
|
||
// H5 内部已展示说明页,这里可做埋点
|
||
break;
|
||
}
|
||
return {'ok': true};
|
||
},
|
||
);
|
||
},
|
||
)
|
||
```
|
||
|
||
### webview_flutter JavaScriptChannel
|
||
|
||
如果使用 `webview_flutter`,可以注入 `HyAppBridgeChannel`:
|
||
|
||
```dart
|
||
final controller = WebViewController()
|
||
..setJavaScriptMode(JavaScriptMode.unrestricted)
|
||
..addJavaScriptChannel(
|
||
'HyAppBridgeChannel',
|
||
onMessageReceived: (message) {
|
||
final data = jsonDecode(message.message) as Map<String, dynamic>;
|
||
final action = data['action']?.toString() ?? '';
|
||
final payload = data['payload'] is Map
|
||
? Map<String, dynamic>.from(data['payload'] as Map)
|
||
: <String, dynamic>{};
|
||
|
||
switch (action) {
|
||
case 'ready':
|
||
break;
|
||
case 'back':
|
||
Navigator.of(context).maybePop();
|
||
break;
|
||
case 'openUser':
|
||
final userId = payload['user_id']?.toString() ?? '';
|
||
break;
|
||
case 'openRoom':
|
||
final roomId = payload['room_id']?.toString() ?? '';
|
||
break;
|
||
}
|
||
},
|
||
)
|
||
..loadRequest(Uri.parse(rankUrl));
|
||
```
|
||
|
||
## 后端依赖
|
||
|
||
H5 依赖以下 Gateway HTTP 接口:
|
||
|
||
| 接口 | 说明 |
|
||
| ------------------------------------------ | ----------------------------- |
|
||
| `GET /api/v1/users/me/overview` | 解析 token 后获取当前用户信息 |
|
||
| `GET /api/v1/activities/user-leaderboards` | 获取排行榜 |
|
||
|
||
业务接口返回 envelope:
|
||
|
||
```json
|
||
{
|
||
"code": "OK",
|
||
"message": "ok",
|
||
"request_id": "req_xxx",
|
||
"data": {}
|
||
}
|
||
```
|
||
|
||
H5 也兼容旧的 `code: 0` 成功格式。
|
||
|
||
## 本地联调
|
||
|
||
在 `hyapp-h5` 目录运行:
|
||
|
||
```bash
|
||
make run
|
||
```
|
||
|
||
默认打开:
|
||
|
||
```text
|
||
http://127.0.0.1:30000/rank/index.html?env=local
|
||
```
|
||
|
||
Flutter 真机调本机服务时,`env=local` 对 H5 来说会请求 `http://localhost:13000/`。如果 WebView 运行在真机上,`localhost` 指向手机自身,不是开发机。真机联调建议:
|
||
|
||
1. 使用测试环境 `env=test`。
|
||
2. 或把 H5/API 部署到同一局域网可访问地址。
|
||
3. 或后续给 H5 增加可配置 `api_base` 参数。
|
||
|
||
## 验收点
|
||
|
||
1. Flutter 打开 H5 时带上 `token` 和 `app_code`。
|
||
2. H5 首次加载后发出 `ready`。
|
||
3. 点击左上角返回,Flutter 收到 `back` 并关闭 WebView。
|
||
4. 点击财富榜/魅力榜用户,Flutter 收到 `openUser`。
|
||
5. 点击房间榜房间,Flutter 收到 `openRoom`。
|
||
6. 切换 `Wealth / Room / Charm` 和 `Daily / Weekly / Monthly` 时,H5 正确请求排行榜接口。
|
||
7. 点击右上角问号,H5 展示说明页,返回后仍停留在排行榜页。
|