135 lines
8.5 KiB
Markdown
135 lines
8.5 KiB
Markdown
# 视频首页数据获取流程(以首款换皮应用 FunyMee 为参考)
|
||
|
||
本文描述**框架侧**在典型换皮应用(首款为 **FunyMee**)中,从冷启动到「视频首页」可展示分类与模板列表的**数据链路与调用顺序**。FunyMee 宿主 UI 不在本仓库内;流程以 `client_proxy_framework` 中的 `FrameworkAuthService`、`ExtConfigRuntime`、`VideoHomeRuntime`、`ImageApi` 为准,与代码注释中引用的线网接口(如 `GET /v1/image/img2video/tasks`)一致。
|
||
|
||
---
|
||
|
||
## 1. 前提与配置
|
||
|
||
1. **`ClientBootstrap.initFromAsset`**:加载 `skin_config.json`,构建 `SkinConfig` 并 `ApiClient.init`,使后续请求走统一代理、AES 与 V2 包装(见 `lib/src/bootstrap/client_bootstrap.dart`)。
|
||
2. **`FrameworkAuthService.init` + `start`**:宿主实现 `AuthServiceCallbacks`(设备 ID、签名等),由框架编排登录与 `common_info`(见 `lib/src/services/auth_service.dart`)。
|
||
3. **`skin_config.json`** 中与首页相关的片段:
|
||
- **`extConfig`**:`keys.showVideoMenu` 映射到线网布尔(示例中为 `go_run` / `need_wait` 等,见 `lib/src/config/skin_config.example.json`);`items` 与 `taskItemMapping` 决定 `common_info` 下发的运营位卡片如何解析为与任务列表同形的 `ExtConfigItem`。
|
||
- **`videoHome`**:`imagesTabLabel`、`imagesTabFirst` 控制「Images」虚拟 Tab 的文案及与接口分类的排序(见 `lib/src/config/skin_config.dart`)。
|
||
4. **`fieldMapping`**:FunyMee 等换皮下线网字段与逻辑字段不一致时,响应体会先经映射再进入实体解析(注释示例见 `lib/src/services/image_api.dart`)。
|
||
|
||
---
|
||
|
||
## 2. 总览时序
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant Host as 宿主 main / UI
|
||
participant Boot as ClientBootstrap
|
||
participant Auth as FrameworkAuthService
|
||
participant User as UserApi / ProxyClient
|
||
participant Ext as ExtConfigRuntime
|
||
participant VH as VideoHomeRuntime
|
||
participant Img as ImageApi
|
||
|
||
Host->>Boot: initFromAsset(skin_config)
|
||
Host->>Auth: init(callbacks); start()
|
||
Auth->>User: POST /v1/user/fast_login
|
||
User-->>Auth: userToken, userId
|
||
Auth->>User: GET /v1/user/common_info
|
||
User-->>Auth: extConfig 等
|
||
Auth->>Ext: applyCommonInfoSuccess
|
||
Auth->>VH: hydrateAfterCommonInfo (异步)
|
||
VH->>Img: GET /v1/image/img2video/categories
|
||
Img-->>VH: 分类列表
|
||
VH->>VH: 合并 Tabs(接口分类 + ext items → Images)
|
||
VH->>Img: GET /v1/image/img2video/tasks?categoryId=…
|
||
Img-->>VH: 当前分类模板列表
|
||
```
|
||
|
||
---
|
||
|
||
## 3. 分阶段说明
|
||
|
||
### 3.1 启动与快速登录
|
||
|
||
- 延迟与重试策略由 `FrameworkAuthService.start` 控制(默认启动延迟、登录重试等)。
|
||
- **`UserApi.fastLogin`**:`POST /v1/user/fast_login`。线网内层 headers 保留 `pkg`,但**不注入** `User_token`(见 `UserApi` / `ProxyClient` 注释)。Query `type` 未传时默认为 **`gg`**(Google Play 归因)。`FrameworkAuthService` 强制 `type=gg`:`referer` 优先 Play Install Referrer,取不到时用 `utm_source=google-play&utm_medium=organic`。
|
||
- 成功后框架将返回的 `userToken` 写入 **`ApiClient.instance.setUserToken`**,此后代理请求自动附带 `pkg` 与 `User_token`(见 `ProxyClient` 行为与 `UserApi` 说明)。
|
||
|
||
### 3.2 归因上报(与首页数据并行准备)
|
||
|
||
- 在拉取 `common_info` 之前,若存在 Adjust / Play 等 referrer,会依次调用 **`UserApi.referrer`**(`POST /v1/user/referrer`)上报,不阻塞后续 `common_info` 的成功与否结论,但属于同一 `_reportReferrersAndLoadCommonInfo` 流程(见 `auth_service.dart`)。
|
||
|
||
### 3.3 通用信息与 extConfig
|
||
|
||
- **`UserApi.getCommonInfo`**:`GET /v1/user/common_info`,query 含 `app`(iOS/Android 后端渠道,与 `skin_config.backend` 一致)、`pkg`、`userId`、`deviceId` 等(见 `user_api.dart`)。
|
||
- 成功时调用 **`ExtConfigRuntime.applyCommonInfoSuccess`**:
|
||
- 将 `extConfig` 与本地 `extConfig.defaults` 浅合并;
|
||
- 按 `extConfigKeySchema` 解析为 **`ExtConfigData`**(含 `showVideoMenu`、`items` 等)(见 `ext_config_runtime.dart`、`ext_config_models.dart`)。
|
||
- **`ExtConfigRuntime.commonInfoSucceeded`**:宿主可用「登录完成且 `common_info` 成功」再展示主业务界面(见 `ext_config_runtime.dart` 注释建议)。
|
||
|
||
### 3.4 视频首页运行时:水合(hydrate)
|
||
|
||
在 `common_info` **成功**后,`FrameworkAuthService` 会 **fire-and-forget** 调用 **`VideoHomeRuntime.hydrateAfterCommonInfo`**(不阻塞 `loginComplete` 的完成)(见 `auth_service.dart`)。
|
||
|
||
`hydrateAfterCommonInfo` 的进入条件与行为(见 `video_home_runtime.dart`):
|
||
|
||
| 条件 | 行为 |
|
||
|------|------|
|
||
| `userId` 为空,或 `ExtConfigData.showVideoMenu != true` | **`VideoHomeRuntime.reset`**,不拉分类、无 images Tab |
|
||
| 否则 | 置 `snapshot.loading = true`,再请求分类列表 |
|
||
|
||
水合步骤概要:
|
||
|
||
1. **`ImageApi.getCategoryList`** → `GET /v1/image/img2video/categories`,得到服务端分类(`CategoryItem`,含 `id` / `name`)。
|
||
2. 从 **`ExtConfigRuntime.data`** 读取 `items`,过滤 **`ExtConfigItem.isUsableOnHome`**,非空则存在 **Images** 虚拟 Tab,文案为 `AppConfig.videoHomeImagesTabLabel`(来自 `skin_config.videoHome.imagesTabLabel`)。
|
||
3. **Tab 顺序**:由 `videoHomeImagesTabFirst` 决定是先 Images 还是先接口分类。
|
||
4. **默认选中 Tab**:默认选中**第一个服务端分类**(非 Images);若仅有 Images Tab 则下标为 0;并发水合与用户提前切换时有保护逻辑(见 `video_home_runtime.dart` 内注释)。
|
||
5. 水合结束后调用 **`VideoHomeRuntime.ensureTabItems(selectedTabIndex)`**,为当前 Tab 拉取模板数据(见下节)。
|
||
|
||
若分类接口失败且没有任何 Tab 可构建,`snapshot.error` 会携带失败信息。
|
||
|
||
### 3.5 按 Tab 拉取模板列表
|
||
|
||
- **Images Tab**:数据直接来自 **`ExtConfigData.items`**(已在水合前解析),**不再**请求 `img2video/tasks`。
|
||
- **服务端分类 Tab**:首次选中该 Tab 时,**`VideoHomeRuntime.ensureTabItems`** 调用 **`ImageApi.getImg2VideoTasks(categoryId: id)`** → `GET /v1/image/img2video/tasks?categoryId=...`(与 FunyMee 文档一致)。结果通过 `ExtConfigItem.fromTaskItem` 写入 **`VideoHomeSnapshot.networkItemsByCategoryId`**,并按分类 id 缓存,避免重复请求。
|
||
|
||
---
|
||
|
||
## 4. 宿主 UI 对接要点
|
||
|
||
- **监听状态**:`VideoHomeRuntime.snapshot`、`VideoHomeRuntime.selectedTabIndex` 为 `ValueNotifier`,可用 `ValueListenableBuilder` 或类似方式驱动顶栏 Tab 与内容区。
|
||
- **切换 Tab**:切换 `selectedTabIndex` 后应调用 **`VideoHomeRuntime.ensureTabItems(newIndex)`**(框架水合末尾已对初始 Tab 调用一次),以按需加载该分类下的模板列表。
|
||
- **登录 / common_info 失败**:`ExtConfigRuntime.commonInfoSucceeded == false` 或 `userId` 为空时,不会进入视频首页水合;宿主应展示错误态或重试入口。
|
||
|
||
---
|
||
|
||
## 5. 相关 HTTP 接口汇总
|
||
|
||
| 顺序 | 方法 | 路径 | 用途 |
|
||
|------|------|------|------|
|
||
| 1 | POST | `/v1/user/fast_login` | 设备登录,取得 token |
|
||
| 2 | POST | `/v1/user/referrer` | 可选,归因上报 |
|
||
| 3 | GET | `/v1/user/common_info` | 开关与 `extConfig`(含首页运营位 `items`) |
|
||
| 4 | GET | `/v1/image/img2video/categories` | 视频首页顶栏服务端分类 |
|
||
| 5 | GET | `/v1/image/img2video/tasks` | 某分类下模板列表(query:`categoryId`) |
|
||
|
||
所有经 `ProxyClient` 的业务请求均走 **`skin_config` 中的 `proxyPath`**,body 为加密后的代理载荷;响应经解密与 **`fieldMapping`** 还原为逻辑字段后再解析实体。
|
||
|
||
---
|
||
|
||
## 6. 代码索引(便于跳转)
|
||
|
||
| 模块 | 路径 |
|
||
|------|------|
|
||
| 启动换皮配置 | `lib/src/bootstrap/client_bootstrap.dart` |
|
||
| 登录与 common_info 编排 | `lib/src/services/auth_service.dart` |
|
||
| 用户接口 | `lib/src/services/user_api.dart` |
|
||
| 图/视频分类与任务列表 | `lib/src/services/image_api.dart` |
|
||
| extConfig 运行时 | `lib/src/config/ext_config_runtime.dart` |
|
||
| 视频首页 Tab 与缓存 | `lib/src/config/video_home_runtime.dart` |
|
||
| extConfig / items 解析 | `lib/src/config/ext_config_models.dart` |
|
||
| JSON 配置示例 | `lib/src/config/skin_config.example.json` |
|
||
|
||
---
|
||
|
||
## 7. 与《创建新换皮应用》的关系
|
||
|
||
从零搭建宿主工程、资产路径与 `main` 初始化顺序,仍以 **[create_new_skin_app.md](create_new_skin_app.md)** 为准;本文仅补充 **「视频首页」在框架内的数据流**,与 FunyMee 首推路线一致。
|