269 lines
5.9 KiB
Markdown
269 lines
5.9 KiB
Markdown
# Explore tab Flutter App 对接文档
|
||
|
||
本文档定义 App 端 Explore 页 H5 tab 的读取接口。后台在 `HYApp 管理平台 / APP配置 / Explore配置` 维护 tab 名称和 H5 链接;App 只读取已启用配置并按服务端返回顺序展示。
|
||
|
||
## 基础约定
|
||
|
||
本地网关地址:
|
||
|
||
```text
|
||
http://127.0.0.1:13000
|
||
```
|
||
|
||
所有 `/api/v1` 业务接口使用统一响应外壳:
|
||
|
||
```json
|
||
{
|
||
"code": "OK",
|
||
"message": "ok",
|
||
"request_id": "req_xxx",
|
||
"data": {}
|
||
}
|
||
```
|
||
|
||
客户端成功判断:
|
||
|
||
```text
|
||
HTTP 200 && code == "OK"
|
||
```
|
||
|
||
失败时保留 `request_id`,用于后端排查日志。
|
||
|
||
## App 标识 Header
|
||
|
||
Explore tab 按 App 隔离,接口没有 query/body 形式的 `app_code` 参数。gateway 通过 Header 解析 App:
|
||
|
||
| Header | 必填 | 说明 |
|
||
| --- | --- | --- |
|
||
| `X-App-Code` | 推荐 | 内部 App code,例如 `lalu`。 |
|
||
| `X-HY-App-Code` | 否 | `X-App-Code` 的兼容别名。 |
|
||
| `X-App-Package` | 推荐二选一 | App 包名,例如 `com.org.laluparty`。传包名时 gateway 可解析到内部 `app_code`。 |
|
||
| `X-App-Platform` | 包名解析时推荐 | 平台,例如 `android`、`ios`。 |
|
||
|
||
当前开发默认 `app_code` 是 `lalu`。Flutter 仍建议显式传 `X-App-Code` 或 `X-App-Package`,避免多 App 环境串配置。
|
||
|
||
## 获取 Explore tab 列表
|
||
|
||
接口地址:
|
||
|
||
```http
|
||
GET /api/v1/app/explore-tabs
|
||
```
|
||
|
||
完整本地地址:
|
||
|
||
```text
|
||
http://127.0.0.1:13000/api/v1/app/explore-tabs
|
||
```
|
||
|
||
鉴权:
|
||
|
||
```text
|
||
不需要 Bearer token
|
||
```
|
||
|
||
服务端行为:
|
||
|
||
- 只返回当前 App 已启用的 Explore tabs。
|
||
- 后台关闭的 tab 不下发给 App。
|
||
- 返回顺序为后台 `sort_order` 升序,同排序时按较新的记录优先。
|
||
|
||
### 请求 Header
|
||
|
||
```http
|
||
X-App-Code: lalu
|
||
```
|
||
|
||
或:
|
||
|
||
```http
|
||
X-App-Package: com.org.laluparty
|
||
X-App-Platform: android
|
||
```
|
||
|
||
### Query 参数
|
||
|
||
无。
|
||
|
||
### Request Body
|
||
|
||
无。
|
||
|
||
### curl 示例
|
||
|
||
```bash
|
||
curl -X GET 'http://127.0.0.1:13000/api/v1/app/explore-tabs' \
|
||
-H 'X-App-Code: lalu'
|
||
```
|
||
|
||
包名解析方式:
|
||
|
||
```bash
|
||
curl -X GET 'http://127.0.0.1:13000/api/v1/app/explore-tabs' \
|
||
-H 'X-App-Package: com.org.laluparty' \
|
||
-H 'X-App-Platform: android'
|
||
```
|
||
|
||
### 成功响应
|
||
|
||
```json
|
||
{
|
||
"code": "OK",
|
||
"message": "ok",
|
||
"request_id": "req_xxx",
|
||
"data": {
|
||
"items": [
|
||
{
|
||
"id": 7,
|
||
"app_code": "lalu",
|
||
"tab": "Farm",
|
||
"h5_url": "https://www.baidu.com",
|
||
"enabled": true,
|
||
"sort_order": 1,
|
||
"updated_at_ms": 1700000000000
|
||
}
|
||
],
|
||
"total": 1
|
||
}
|
||
}
|
||
```
|
||
|
||
### `data` 字段
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
| --- | --- | --- |
|
||
| `items` | array | Explore tab 列表。 |
|
||
| `total` | int | 本次返回数量。 |
|
||
|
||
### `items[]` 字段
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
| --- | --- | --- |
|
||
| `id` | int | 后台配置记录 ID,只用于排查或本地缓存 key,不作为业务路由。 |
|
||
| `app_code` | string | tab 所属 App,例如 `lalu`。 |
|
||
| `tab` | string | App 展示的 tab 文案。 |
|
||
| `h5_url` | string | 点击 tab 后加载的 H5 地址。 |
|
||
| `enabled` | bool | 是否启用。App 接口只返回 `true` 的记录。 |
|
||
| `sort_order` | int | 展示排序,数值越小越靠前。 |
|
||
| `updated_at_ms` | int | 更新时间,Unix epoch milliseconds。 |
|
||
|
||
### 空列表响应
|
||
|
||
当后台没有启用的 Explore tab 时,返回空数组,不是错误:
|
||
|
||
```json
|
||
{
|
||
"code": "OK",
|
||
"message": "ok",
|
||
"request_id": "req_xxx",
|
||
"data": {
|
||
"items": [],
|
||
"total": 0
|
||
}
|
||
}
|
||
```
|
||
|
||
### 错误响应
|
||
|
||
配置读取异常时:
|
||
|
||
```json
|
||
{
|
||
"code": "UPSTREAM_ERROR",
|
||
"message": "upstream service error",
|
||
"request_id": "req_xxx"
|
||
}
|
||
```
|
||
|
||
客户端处理建议:
|
||
|
||
- `UPSTREAM_ERROR`:展示空态或保留上一次本地缓存。
|
||
- 非 `OK`:不要使用 `data`。
|
||
- 记录 `request_id` 到客户端日志,便于问题排查。
|
||
|
||
## Flutter 数据模型
|
||
|
||
```dart
|
||
class ExploreTabList {
|
||
final List<ExploreTabItem> items;
|
||
final int total;
|
||
|
||
ExploreTabList({
|
||
required this.items,
|
||
required this.total,
|
||
});
|
||
|
||
factory ExploreTabList.fromJson(Map<String, dynamic> json) {
|
||
final rawItems = json['items'] as List<dynamic>? ?? const [];
|
||
return ExploreTabList(
|
||
items: rawItems
|
||
.map((e) => ExploreTabItem.fromJson(e as Map<String, dynamic>))
|
||
.toList(),
|
||
total: json['total'] as int? ?? rawItems.length,
|
||
);
|
||
}
|
||
}
|
||
|
||
class ExploreTabItem {
|
||
final int id;
|
||
final String appCode;
|
||
final String tab;
|
||
final String h5Url;
|
||
final bool enabled;
|
||
final int sortOrder;
|
||
final int updatedAtMs;
|
||
|
||
ExploreTabItem({
|
||
required this.id,
|
||
required this.appCode,
|
||
required this.tab,
|
||
required this.h5Url,
|
||
required this.enabled,
|
||
required this.sortOrder,
|
||
required this.updatedAtMs,
|
||
});
|
||
|
||
factory ExploreTabItem.fromJson(Map<String, dynamic> json) {
|
||
return ExploreTabItem(
|
||
id: json['id'] as int? ?? 0,
|
||
appCode: json['app_code'] as String? ?? '',
|
||
tab: json['tab'] as String? ?? '',
|
||
h5Url: json['h5_url'] as String? ?? '',
|
||
enabled: json['enabled'] as bool? ?? false,
|
||
sortOrder: json['sort_order'] as int? ?? 0,
|
||
updatedAtMs: json['updated_at_ms'] as int? ?? 0,
|
||
);
|
||
}
|
||
}
|
||
```
|
||
|
||
## Flutter Dio 示例
|
||
|
||
```dart
|
||
Future<ExploreTabList> fetchExploreTabs(Dio dio) async {
|
||
final response = await dio.get(
|
||
'/api/v1/app/explore-tabs',
|
||
options: Options(
|
||
headers: {
|
||
'X-App-Code': 'lalu',
|
||
},
|
||
),
|
||
);
|
||
|
||
final body = response.data as Map<String, dynamic>;
|
||
if (body['code'] != 'OK') {
|
||
throw Exception('${body['code']}: ${body['message']} request_id=${body['request_id']}');
|
||
}
|
||
|
||
return ExploreTabList.fromJson(body['data'] as Map<String, dynamic>);
|
||
}
|
||
```
|
||
|
||
## 展示和跳转规则
|
||
|
||
- App 按 `items` 返回顺序展示,不需要二次排序。
|
||
- `tab` 为空或 `h5_url` 为空时,建议 App 跳过该项,避免展示不可点击入口。
|
||
- 点击 tab 后打开 `h5_url`,由 H5 页面自身处理内部导航。
|
||
- 如果接口失败,App 可以使用本地缓存的上一版 tabs;没有缓存时展示空态。
|
||
|