# Client Proxy Framework 使用指南 ## 1. 框架概述 `client_proxy_framework` 是一个通用代理 API 框架,用于 Flutter 移动应用与后端 API 的通信。框架已封装好请求/响应的加密解密、字段映射等逻辑,换皮应用只需配置少量参数即可使用。 ### 核心特性 - **自动加解密**:请求体和响应体自动进行 AES 加密/解密 - **字段映射**:框架自动将原始字段名转换为 V2 字段名,响应自动转换回来 - **强类型实体**:返回强类型实体类,开发者直接使用映射后的字段 - **统一响应**:`EntityResponse` 提供强类型返回值 ### 换皮应用:从零搭建 从零创建一个新的换皮应用(`skin_config.json`、`ClientBootstrap`、`main` 初始化顺序、Android/iOS 要点等),请阅读 **[《创建新换皮应用 — 步骤清单》](create_new_skin_app.md)**。 ### 功能总览与用法 按模块整理的“功能 + 最小调用示例”请见 **[《Framework 功能与用法总览》](framework_feature_usage.md)**。 以首款换皮应用 **FunyMee** 为参考的 **视频首页**(`common_info` → 分类 → 按分类拉模板)数据流说明,见 **[《视频首页数据获取流程》](video_home_data_flow.md)**。 --- ## 2. 快速开始 ### 2.1 添加依赖 在 `pubspec.yaml` 中添加: ```yaml dependencies: client_proxy_framework: path: ../client_proxy_framework ``` ### 2.2 实现配置 创建一个类继承 `AppConfig`: ```dart import 'package:client_proxy_framework/client_proxy_framework.dart'; class MyAppConfig extends AppConfig { @override String get appId => 'YourAppId'; @override String get packageName => 'com.yourapp.package'; @override String get aesKey => 'your-16-char-key'; @override String get preBaseUrl => 'https://pre-api.example.com'; @override String get prodBaseUrl => 'https://api.example.com'; @override String get proxyPath => '/quester/defender/summoner'; } ``` ### 2.3 初始化 在 `main.dart` 中初始化: ```dart void main() { ApiClient.init(MyAppConfig()); runApp(const MyApp()); } ``` --- ## 3. 设计理念 ### 3.1 请求流程 ``` 调用层 (原始字段) ↓ ProxyClient.request()(自动加 `pkg`;默认加 `User_token`) ↓ 字段映射 (原始 → V2) ↓ AES 加密 ↓ 发送请求 ``` **请求头**:`ProxyClient` 会将 [AppConfig.packageName] 写入映射后的包名字段(原始名 `pkg`);若已设置用户 token,默认还会写入 `User_token`。**`UserApi.fast_login` 等无需登录态接口**内部使用 `includeUserTokenInHeader: false`,避免把旧 token 打进 `filter_type`。其余请求也可在直接调用 `ProxyClient.request` 时传入该参数。 **请求体**:各 `*Api` 方法使用与《客户端指南》解密表一致的**原始字段名**(如 `referer`、`deviceId`、`fileUrls` / `contentType` / `content`)。 ### 3.2 响应流程 ``` 接收响应 ↓ AES 解密 ↓ 字段映射 (V2 → 原始) ↓ 转换为实体类 ↓ 调用层 (实体类) ``` --- ## 4. 配置项详解 ### AppConfig 配置 | 配置项 | 必填 | 说明 | |--------|------|------| | `appId` | 是 | 应用标识,对应代理请求的 `hero_class` | | `packageName` | 是 | 应用包名,如 `com.example.app` | | `aesKey` | 是 | AES 加密密钥,长度需为 16 字符 | | `preBaseUrl` | 是 | 预发环境域名 | | `prodBaseUrl` | 是 | 生产环境域名 | | `proxyPath` | 是 | 代理入口路径 | | `debugBaseUrlOverride` | 否 | 调试时本地代理地址 | | `fieldMapping` | 否 | 字段映射表,默认使用 `petsHeroAIFieldMapping` | --- ## 5. API 服务 所有 API 方法使用**原始字段名**,返回**强类型实体**。 ### 5.1 UserApi - 用户相关 #### 5.1.1 fastLogin - 设备快速登录 ```dart final res = await UserApi.fastLogin( deviceId: '设备ID', sign: 'MD5(deviceId)大写', app: 'HAndroid', // 必填:HIOS / HAndroid referer: '归因来源', // 可选 ch: '渠道号', // 可选 type: 'fb', // 可选;未传时默认 fb ); if (res.isSuccess) { final loginInfo = res.data!; final token = loginInfo.userToken; final credits = loginInfo.credits; final userId = loginInfo.userId; } ``` **返回实体**: `FastLoginResponse` - `userToken`: 用户 Token - `userId`: 用户 ID - `credits`: 积分 - `avatar`: 头像 - `userName`: 用户名 - `countryCode`: 国家码 - `isVip`: 是否 VIP - 等等... #### 5.1.2 getCommonInfo - 获取用户通用信息 ```dart final config = ApiClient.instance.config; final backendApp = defaultTargetPlatform == TargetPlatform.iOS ? config.backendAppTypeIOS : config.backendAppTypeAndroid; final res = await UserApi.getCommonInfo( app: backendApp, // 必填:HIOS / HAndroid 等与后端约定 pkg: config.packageName, // 必填:包名 userId: '用户ID', // 可选 deviceId: '设备ID', // 可选 // client / ch / inviteBy / clientId 可选 ); if (res.isSuccess) { final info = res.data!; } ``` **返回实体**: `CommonInfoResponse` #### 5.1.3 getAccount - 获取账户信息 ```dart final res = await UserApi.getAccount( app: 'HAndroid', userId: '用户ID', // 可选 ); if (res.isSuccess) { final account = res.data!; final credits = account.credits; final isVip = account.isVip; } ``` **返回实体**: `AccountResponse` - `credits`: 积分 - `avatar`: 头像 - `userName`: 用户名 - `isVip`: 是否 VIP - `freeTimes`: 免费次数 #### 5.1.4 getAppLanguage - App 语言配置 `GET /v1/config/app-language`(需登录) ```dart final res = await UserApi.getAppLanguage( pkg: ApiClient.instance.config.packageName, lang: 'en', // 可选 ); ``` **返回实体**: `AppLanguageConfigResponse`(`config` 为语言配置 JSON 字符串) #### 5.1.5 getCreditsPage - 积分分页流水 与 `ImageApi.getCreditsPageInfo` 相同,路径 `GET /v1/user/credits-page`。 ```dart final res = await UserApi.getCreditsPage(page: '1', size: '10', type: '1'); ``` #### 5.1.6 getUserPayments - 用户支付记录 ```dart final res = await UserApi.getUserPayments( app: backendApp, userId: '用户ID', ); ``` **返回实体**: `UserPaymentsListResponse`(`items`: `UserPaymentRecord` 列表) #### 5.1.7 getUnreadMessageCount - 未读消息数 ```dart final res = await UserApi.getUnreadMessageCount(types: '类型列表'); // types 可选 ``` **返回实体**: `UnreadMessageCountResponse` --- ### 5.2 PaymentApi - 支付相关 #### 5.2.1 getGooglePayActivities - 获取 Android 商品列表 ```dart final res = await PaymentApi.getGooglePayActivities( app: 'HAndroid', // 可选;默认 `AppConfig.backendAppTypeAndroid` client: '客户端', // 可选 country: '国家', // 可选 ); if (res.isSuccess) { final products = res.data!.productList; for (final product in products ?? []) { final id = product.productId; final price = product.actualAmount; final bonus = product.bonus; } } ``` **返回实体**: `PaymentProductsResponse` - `productList`: 商品列表 - `productId`: 商品 ID - `activityId`: 活动 ID - `actualAmount`: 实际金额 - `originAmount`: 原价 - `bonus`: 赠送积分 - `title`: 标题 #### 5.2.2 getPaymentMethods - 获取支付方式列表 ```dart final res = await PaymentApi.getPaymentMethods( activityId: 12345, // int,与接口一致 country: '国家', // 可选 ); if (res.isSuccess) { final methods = res.data!.paymentMethods; for (final method in methods ?? []) { final pm = method.paymentMethod; final name = method.name; final isRecommend = method.recommend; } } ``` **返回实体**: `PaymentMethodsResponse` - `paymentMethods`: 支付方式列表 - `paymentMethod`: 支付方式 - `subPaymentMethod`: 子支付方式 - `name`: 显示名称 - `icon`: 图标 - `recommend`: 是否推荐 #### 5.2.3 createPayment - 创建支付订单 ```dart final res = await PaymentApi.createPayment( app: 'HAndroid', // HIOS / HAndroid userId: '用户ID', activityId: '活动ID', paymentMethod: '支付方式', paymentType: '支付子类型', // 可选 ); if (res.isSuccess) { final order = res.data!; final orderId = order.orderId; final payUrl = order.payUrl; } ``` **返回实体**: `CreatePaymentResponse` - `orderId`: 订单 ID - `payUrl`: 支付链接 - `status`: 状态 #### 5.2.4 getPaymentDetailList - 支付订单列表 `GET /v1/payment/getPaymentDetailList` ```dart final res = await PaymentApi.getPaymentDetailList( app: 'HAndroid', userId: '用户ID', paymentMethod: 'GOOGLEPAY', // 可选 filterStatus: 'SUCCESS', // 可选 ); ``` **返回实体**: `PaymentOrderListResponse`(`orders`: `PaymentOrderSummary` 列表) #### 5.2.5 getOrderDetail - 获取订单详情 Query 使用原始字段 `id`(值为订单/支付 ID)。 ```dart final res = await PaymentApi.getOrderDetail( userId: '用户ID', orderId: '订单ID', ); if (res.isSuccess) { final order = res.data!; final status = order.status; } ``` **返回实体**: `OrderDetailResponse` - `orderId`: 订单 ID - `status`: 状态 - `amount`: 金额 #### 5.2.6 googlepay - Google Pay 回调 ```dart final res = await PaymentApi.googlepay( signature: '购买签名', purchaseData: '购买数据', orderId: '订单ID', userId: '用户ID', ); if (res.isSuccess) { final result = res.data!; final status = result.status; } ``` **返回实体**: `GooglePayCallbackResponse` - `orderId`: 订单 ID - `status`: 状态 - `creditsAdded`: 是否已加积分 --- ### 5.3 ImageApi - 图片/视频相关 #### 5.3.1 getCategoryList - 获取分类列表 ```dart final res = await ImageApi.getCategoryList(); if (res.isSuccess) { final categories = res.data!.categories; } ``` **返回实体**: `CategoryListResponse` - `categories`: 分类列表 - `id`: ID - `name`: 名称 - `icon`: 图标 #### 5.3.2 getImg2VideoTasks - 获取任务列表 ```dart final res = await ImageApi.getImg2VideoTasks( categoryId: 123, // 可选 ); if (res.isSuccess) { final tasks = res.data!.tasks; } ``` **返回实体**: `TasksResponse` #### 5.3.3 getProgress - 查询任务进度 ```dart final res = await ImageApi.getProgress( app: '应用ID', taskId: '任务ID', userId: '用户ID', // 可选 ); if (res.isSuccess) { final progress = res.data!; final status = progress.status; final resultUrl = progress.resultUrl; } ``` **返回实体**: `ProgressResponse` - `taskId`: 任务 ID - `status`: 状态 - `progress`: 进度 (0-100) - `resultUrl`: 结果 URL #### 5.3.4 createTask - 创建任务 ```dart final res = await ImageApi.createTask( userId: '用户ID', prompt: '提示词', // 可选 resolution: '1024x1024', // 可选 imgUrl: '图片URL', // 可选 allowance: false, // 可选 ); if (res.isSuccess) { final taskId = res.data!.taskId; } ``` **返回实体**: `CreateTaskResponse` - `taskId`: 任务 ID - `status`: 状态 #### 5.3.5 getMyTasks - 获取我的任务列表 ```dart final res = await ImageApi.getMyTasks( app: '应用ID', page: '1', // 可选 pageSize: '10', // 可选 cursor: '游标', // 可选 ); if (res.isSuccess) { final tasks = res.data!.tasks; final total = res.data!.total; } ``` **返回实体**: `MyTasksResponse` - `tasks`: 任务列表 - `total`: 总数 - `cursor`: 游标 #### 5.3.6 getCreditsPageInfo - 获取积分页面信息 对应 `GET /v1/user/credits-page`(与 `UserApi.getCreditsPage` 相同)。 ```dart final res = await ImageApi.getCreditsPageInfo( page: '1', size: '10', type: '1', ); // 或:UserApi.getCreditsPage(page: '1', size: '10', type: '1'); if (res.isSuccess) { final info = res.data!; final total = info.total; final records = info.records; final credits = info.credits; // 若后端仍返回单屏字段则可用 } ``` **返回实体**: `CreditsPageInfoResponse` - `total` / `current` / `pages` / `size` / `records`: 分页与流水(新接口) - `credits` / `freeTimes` / `isVip` / `vipExpireTime`: 兼容旧字段 - `records` 项见 `CreditRecordItem` --- ### 5.4 FeedbackApi - 举报/反馈相关 #### 5.4.1 getUploadPresignedUrl - 获取上传预签名 URL ```dart final res = await FeedbackApi.getUploadPresignedUrl( fileName: 'image.jpg', ); if (res.isSuccess) { final result = res.data!; final uploadUrl = result.uploadUrl; final filePath = result.filePath; } ``` **返回实体**: `FeedbackUploadPresignedUrlResponse` - `uploadUrl`: 上传 URL - `filePath`: 文件路径 #### 5.4.2 submit - 提交反馈 ```dart final res = await FeedbackApi.submit( fileUrls: ['https://...'], content: '反馈内容', contentType: 'text/plain', ); if (res.isSuccess) { final success = res.data!.success; } ``` **返回实体**: `SubmitFeedbackResponse` - `success`: 是否成功 - `feedbackId`: 反馈 ID --- ## 6. 响应处理 ### 6.1 EntityResponse 所有返回实体类的方法都使用 `EntityResponse`: ```dart final res = await UserApi.fastLogin(...); // 检查成功 if (res.isSuccess) { // 访问实体 final data = res.data!; final token = data.userToken; } else { // 处理错误 print('Error: ${res.msg}'); } ``` ### EntityResponse 属性 | 属性 | 类型 | 说明 | |------|------|------| | `code` | int | 响应码,0 表示成功 | | `msg` | String | 响应消息 | | `data` | T? | 实体数据 | | `isSuccess` | bool | 便捷属性,code == 0 时为 true | --- ## 7. 设置用户 Token 登录成功后,调用以下方法设置用户 Token: ```dart // 登录成功后 final res = await UserApi.fastLogin(...); if (res.isSuccess) { ApiClient.instance.setUserToken(res.data!.userToken); } // 登出时 ApiClient.instance.setUserToken(null); ``` --- ## 8. 调试模式 框架会自动根据 `kDebugMode` 选择环境: - **调试模式**:使用 `preBaseUrl`(或 `debugBaseUrlOverride`) - **发布模式**:使用 `prodBaseUrl` --- ## 9. 字段映射 框架自动处理字段映射,调用层使用原始字段名。 ### 请求字段映射(原始 → V2) | 原始字段 | V2 字段 | |----------|---------| | app | sentinel | | userId | asset | | deviceId | origin | | sign | resolution | | referer | digest | | activityId | warrior | | country | vambrace | | paymentMethod | resource | | paymentType | ceremony | | orderId | federation | | signature | sample | | purchaseData | merchant | | taskId | tree | | prompt | ledger | | resolution | guild | | srcImgUrls | commission | | fileName1 | gateway | | contentType | pauldron | | expectedSize | stronghold | ### 响应字段映射(V2 → 原始) | V2 字段 | 原始字段 | |---------|----------| | helm | code/响应码 | | rampart | msg | | sidekick | data | | summon | productList | | renew | paymentMethods | | reveal | credits | | reevaluate | userToken | --- ## 10. 代码示例 ### 完整登录流程 ```dart import 'package:client_proxy_framework/client_proxy_framework.dart'; class AuthService { static Future login() async { final deviceId = await getDeviceId(); final sign = md5(deviceId).toUpperCase(); final res = await UserApi.fastLogin( deviceId: deviceId, sign: sign, app: 'HAndroid', // 或 AppConfig.backendAppTypeAndroid / IOS referer: 'organic', ); if (res.isSuccess) { final loginInfo = res.data!; ApiClient.instance.setUserToken(loginInfo.userToken!); UserState.setUserId(loginInfo.userId!); UserState.setCredits(loginInfo.credits ?? 0); return true; } return false; } } ``` ### 完整支付流程 ```dart class PaymentService { // 1. 获取商品列表 static Future> getProducts() async { final res = await PaymentApi.getGooglePayActivities(); return res.data?.productList ?? []; } // 2. 获取支付方式 static Future> getPaymentMethods(int activityId) async { final res = await PaymentApi.getPaymentMethods(activityId: activityId); return res.data?.paymentMethods ?? []; } // 3. 创建订单 static Future createOrder({ required String userId, required String activityId, required String paymentMethod, }) async { final res = await PaymentApi.createPayment( app: ApiClient.instance.config.backendAppTypeAndroid, userId: userId, activityId: activityId, paymentMethod: paymentMethod, ); if (res.isSuccess) { return res.data?.orderId; } return null; } // 4. Google Pay 回调 static Future verifyGooglePay({ required String signature, required String purchaseData, required String orderId, required String userId, }) async { final res = await PaymentApi.googlepay( signature: signature, purchaseData: purchaseData, orderId: orderId, userId: userId, ); return res.isSuccess; } } ``` --- ## 11. 常见问题 ### Q: 如何修改字段映射? A: 覆盖 `AppConfig.fieldMapping`: ```dart @override FieldMapping get fieldMapping => const FieldMapping({ 'deviceId': 'origin', 'userId': 'asset', // ... 其他字段 }); ``` ### Q: 响应 data 为空怎么办? A: 检查以下几点: 1. `ApiClient.init()` 是否已调用 2. 网络是否正常 3. `appId`、`aesKey` 等配置是否正确 --- ## 12. 文件结构 ``` lib/ ├── client_proxy_framework.dart # 入口文件 └── src/ ├── api/ │ ├── api_client.dart # 全局客户端 │ ├── api_crypto.dart # 加解密工具 │ ├── api_response.dart # 响应对象 │ └── proxy_client.dart # 代理请求客户端(含实体转换) ├── config/ │ ├── app_config.dart # 应用配置抽象类 │ ├── default_field_mapping.dart # 默认字段映射 │ └── field_mapping.dart # 字段映射类 ├── entities/ │ ├── entity.dart # 实体基类 │ ├── user_entities.dart # 用户相关实体 │ ├── payment_entities.dart # 支付相关实体 │ ├── image_entities.dart # 图片/视频实体 │ └── feedback_entities.dart # 反馈实体 ├── log/ │ └── app_logger.dart # 日志工具 └── services/ ├── user_api.dart # 用户 API ├── payment_api.dart # 支付 API ├── image_api.dart # 图片/视频 API └── feedback_api.dart # 反馈 API ```