# 换皮应用开发完整流程指南 本文档说明如何使用 `client_proxy_framework` 从零创建并完成一个换皮应用。 > **推荐优先阅读**:[《创建新换皮应用 — 步骤清单》](create_new_skin_app.md) — 与当前框架实现(JSON 配置、`ClientBootstrap`、固定 Facebook Channel、默认归因回调等)保持同步的精简步骤。 > > **`getDeviceId` / `computeSign`(与 FunyMee、app_client 对齐)**:[device_id_and_sign.md](device_id_and_sign.md) *** > **重要说明**:本指南**仅完成数据框架的对接**,包括: > > - API 请求封装与字段映射 > - 第三方 SDK(Adjust、Facebook、Google Play)集成 > - 用户认证与归因追踪 > > **UI 组件需要另行对接**,包括: > > - 首页、生成页、个人中心等业务页面 > - 主题配色、字体、图标等 UI 样式 > - 业务组件和交互逻辑 > > 详见本文档最后一章 [后续开发指引](#后续开发指引)。 *** ## 目录 1. [准备阶段:创建项目目录](#1-准备阶段创建项目目录) 2. [填写配置模板](#2-填写配置模板) 3. [创建 Flutter 项目](#3-创建-flutter-项目) 4. [集成框架](#4-集成框架) 5. [配置应用信息](#5-配置应用信息) 6. [配置第三方-sdk](#6-配置第三方-sdk) 7. [实现业务代码](#7-实现业务代码) 8. [调试与测试](#8-调试与测试) 9. [上线准备](#9-上线准备) 10. [后续开发指引](#10-后续开发指引) *** ## 1. 准备阶段:创建项目目录 ### 1.1 创建项目目录 在合适的位置创建项目目录: ```bash # 请将 your_app_name 替换为实际的应用名称 mkdir your_app_name cd your_app_name ``` ### 1.2 复制配置模板 从框架目录复制配置模板到项目的 `docs` 目录下: ```bash mkdir docs cp ../client_proxy_framework/docs/new_app_config_template.md docs/ cp ../client_proxy_framework/docs/sdk_integration_guide.md docs/ ``` 复制后的目录结构: ``` your_app_name/ ├── docs/ │ ├── new_app_config_template.md # 应用配置模板 │ └── sdk_integration_guide.md # SDK 集成指南 └── ... ``` ### 1.3 打开配置模板 使用编辑器打开配置模板文件: ```bash # 使用 VSCode code docs/new_app_config_template.md # 或使用其他编辑器 open docs/new_app_config_template.md ``` ### 1.4 下一步 > **请先完成配置模板的填写**,再继续执行后续步骤。 > > 配置模板包含以下信息: > - 应用基本信息(appId、packageName、AES 密钥等) > - Adjust 配置(App Token、Event Tokens) > - Facebook 配置(App ID、Client Token) > - Google Play 配置(商品 ID) 填写完成后,继续执行第 3 步。 *** ## 2. 填写配置模板 请参考 `docs/new_app_config_template.md` 文件,填写以下配置信息: ### 2.1 应用基本信息 | 配置项 | 占位符 | 填写值 | |-------|-------|-------| | 应用名称 | `填写应用名称` | | | Android 包名 | `填写 Android 包名` | | | iOS Bundle ID | `填写 iOS Bundle ID` | | | 应用 ID(appId) | `填写 appId` | | ### 2.2 服务器配置 | 配置项 | 占位符 | 填写值 | |-------|-------|-------| | 预发布环境 URL | `填写预发布环境 URL` | | | 生产环境 URL | `填写生产环境 URL` | | | 代理接口路径 | `填写代理接口路径` | | ### 2.3 Adjust SDK 配置 | 配置项 | 占位符 | 填写值 | |-------|-------|-------| | App Token | `填写 Adjust App Token` | | | Environment | `sandbox` / `production` | | ### 2.4 Facebook SDK 配置 | 配置项 | 占位符 | 填写值 | |-------|-------|-------| | App ID | `填写 Facebook App ID` | | | Client Token | `填写 Client Token` | | ### 2.5 AES 密钥 | 配置项 | 占位符 | 填写值 | |-------|-------|-------| | AES 密钥 | `填写 16 字符 AES-128 密钥` | | > 框架使用 AES-128-ECB 模式,密钥固定为 16 字符。 ### 2.6 下一步 > **配置填写完成后,继续执行第 3 步。** > > 如果配置复杂或有任何疑问,请将填写好的配置发送给我进行确认。 *** ## 3. 创建 Flutter 项目 ### 3.1 在项目目录下执行 确保你在之前创建的项目目录下执行: ```bash # 如果不在项目目录下,先进入 cd your_app_name ``` ### 3.2 创建 Flutter 项目 ```bash # 请将 com.yourcompany 替换为你的组织包名 flutter create --org com.yourcompany your_app_name cd your_app_name ``` ### 3.3 复制配置模板到项目 ```bash mkdir docs cp ../client_proxy_framework/docs/new_app_config_template.md docs/ cp ../client_proxy_framework/docs/sdk_integration_guide.md docs/ ``` ### 3.4 下一步 > 继续执行 [第 4 步:集成框架](#4-集成框架) *** ## 4. 集成框架 ### 4.1 添加依赖 编辑 `pubspec.yaml`: ```yaml dependencies: flutter: sdk: flutter # 框架依赖 client_proxy_framework: path: ../client_proxy_framework # 或使用 pub 发布的版本 # 框架需要的额外依赖 http: ^1.2.2 encrypt: ^5.0.3 crypto: ^3.0.3 logger: ^2.0.2 shared_preferences: ^2.2.2 device_info_plus: ^11.1.0 ``` ### 4.2 目录结构 建议按以下结构组织代码: ``` lib/ ├── main.dart # 入口文件 ├── app.dart # MaterialApp ├── core/ │ ├── config/ │ │ └── app_config.dart # 应用配置类 │ ├── auth/ │ │ └── auth_service.dart # 认证服务实现 │ ├── user/ │ │ └── user_state.dart # 用户状态管理 │ └── theme/ │ ├── app_colors.dart # 主题配色 │ ├── app_typography.dart # 字体样式 │ └── app_spacing.dart # 间距常量 ├── features/ │ ├── home/ # 首页模块 │ ├── create/ # 生成页模块 │ ├── profile/ # 个人中心模块 │ └── recharge/ # 充值页模块 └── shared/ ├── tab_selector_scope.dart # Tab 导航作用域 └── widgets/ # 共享组件 ``` *** ## 5. 配置应用信息 ### 5.1 创建应用配置类 创建 `lib/core/config/app_config.dart`: ```dart import 'package:client_proxy_framework/client_proxy_framework.dart'; class YourAppConfig implements AppConfig { @override String get appId => 'your_app_id'; @override String get packageName => 'com.yourcompany.yourapp'; @override String get aesKey => 'your_16_char_key'; // AES-128 需要 16 字符 @override String get preBaseUrl => 'https://pre-api.example.com'; @override String get prodBaseUrl => 'https://api.example.com'; @override String get proxyPath => '/v1/proxy'; @override FieldMapping? get fieldMapping => DefaultFieldMapping.instance; } ``` ### 5.2 创建用户状态管理 创建 `lib/core/user/user_state.dart`: ```dart import 'package:flutter/foundation.dart'; class UserState { static String? _userId; static int _credits = 0; static String? _avatar; static String? _userName; static String? _countryCode; static String? get userId => _userId; static int get credits => _credits; static String? get avatar => _avatar; static String? get userName => _userName; static String? get countryCode => _countryCode; static void setUserId(String id) => _userId = id; static void setCredits(int credits) => _credits = credits; static void setAvatar(String avatar) => _avatar = avatar; static void setUserName(String name) => _userName = name; static void setCountryCode(String code) => _countryCode = code; } ``` *** ## 6. 配置第三方 SDK ### 6.1 填写配置信息 参考 [SDK 集成配置指南](sdk_integration_guide.md),获取并填写以下信息: | SDK | 必需信息 | | ----------- | ---------------------- | | Adjust | App Token, Environment | | Facebook | App ID, Client Token | | Google Play | 无(商品 ID 从后端获取) | ### 6.2 Android 配置 #### 6.2.1 创建 strings.xml 创建 `android/app/src/main/res/values/strings.xml`: ```xml your_facebook_app_id your_facebook_client_token ``` #### 6.2.2 更新 AndroidManifest.xml 在 `` 标签之前添加: ```xml ``` #### 6.2.3 创建 Application 类 创建 `android/app/src/main/kotlin/com/yourcompany/yourapp/App.kt`: ```kotlin package com.yourcompany.yourapp import android.app.Application import android.os.Handler import android.os.Looper import com.facebook.FacebookSdk import com.facebook.LoggingBehavior import io.flutter.embedding.engine.FlutterEngine import io.flutter.embedding.engine.FlutterEngineCache import io.flutter.plugin.common.MethodChannel class App : Application() { companion object { const val CHANNEL = "com.yourcompany.yourapp/facebook_sdk" const val ENGINE_ID = "main" } override fun onCreate() { super.onCreate() FacebookSdk.sdkInitialize(this) FacebookSdk.setIsDebugEnabled(true) FacebookSdk.addLoggingBehavior(LoggingBehavior.APP_EVENTS) Handler(Looper.getMainLooper()).postDelayed({ val engine = FlutterEngineCache.getInstance().get(ENGINE_ID) if (engine != null) { MethodChannel(engine.dartExecutor.binaryMessenger, CHANNEL) .invokeMethod("onFacebookSdkInitialized", null) } }, 100) } } ``` #### 6.2.4 创建 MainActivity 创建 `android/app/src/main/kotlin/com/yourcompany/yourapp/MainActivity.kt`: ```kotlin package com.yourcompany.yourapp import android.os.Handler import android.os.Looper import com.facebook.appevents.AppEventsLogger import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.engine.FlutterEngine import io.flutter.embedding.engine.FlutterEngineCache import io.flutter.plugin.common.MethodChannel class MainActivity : FlutterActivity() { companion object { const val CHANNEL = "com.yourcompany.yourapp/facebook_sdk" const val ENGINE_ID = "main" } override fun configureFlutterEngine(flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) AppEventsLogger.activateApp(application) MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL) .setMethodCallHandler { call, result -> when (call.method) { "waitForFacebookSdkInit" -> { AppEventsLogger.activateApp(application) result.success(true) // 必须调用 result.success() 否则 Dart 端会卡住 } "onFacebookSdkInitialized" -> { result.success(true) } else -> { result.notImplemented() } } } Handler(Looper.getMainLooper()).postDelayed({ FlutterEngineCache.getInstance().put(ENGINE_ID, flutterEngine) }, 100) } } ``` > **注意**:`result.success()` 必须被调用,否则 Dart 端的 `await` 会一直等待响应,导致应用卡住。 #### 6.2.5 更新 AndroidManifest.xml 使用 Application ```xml ``` ### 6.3 iOS 配置 #### 6.3.1 更新 Info.plist 在 `ios/Runner/Info.plist` 中添加: ```xml AdjustAppToken your_adjust_app_token AdjustEnvironment sandbox FacebookAppID your_facebook_app_id FacebookClientToken your_facebook_client_token FacebookDisplayName Your App Name ``` *** ## 7. 实现业务代码 认证回调中的 **`getDeviceId`**、**`computeSign`** 与后端 **`fast_login`** 强相关;**与 FunyMee / app_client 一致的字段含义、依赖与兜底策略**见框架文档 [device_id_and_sign.md](device_id_and_sign.md)(下文示例侧重代码结构,生产环境请以该规范为准)。 ### 7.1 创建认证服务实现 创建 `lib/core/auth/auth_service.dart`: ```dart import 'dart:convert'; import 'package:client_proxy_framework/client_proxy_framework.dart'; import 'package:crypto/crypto.dart'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:flutter/foundation.dart'; import '../user/user_state.dart'; /// 归因回调实现 class AppAttributionCallbacks implements AttributionCallbacks { @override Future getReferrer() async { final attribution = AnalyticsService.getAttribution(); if (attribution != null) { return _attributionToJson(attribution); } return ''; } String _attributionToJson(AttributionData data) { final map = { 'trackerToken': data.trackerToken, 'trackerName': data.trackerName, 'network': data.network, 'campaign': data.campaign, 'adgroup': data.adgroup, 'creative': data.creative, 'clickLabel': data.clickLabel, 'costType': data.costType, 'costAmount': _jsonEncodableCostAmount(data.costAmount), 'costCurrency': data.costCurrency, 'jsonResponse': data.jsonResponse, 'fbInstallReferrer': data.fbInstallReferrer, }; return base64Encode(utf8.encode(jsonEncode(map))); } Object? _jsonEncodableCostAmount(double? v) { if (v == null) return null; if (v.isNaN || !v.isFinite) return v.toString(); return v; } @override Future getAdjustReferrer() async => getReferrer(); @override Future getPlatformReferrer() async => null; } /// 认证回调实现 class AppAuthCallbacks implements AuthServiceCallbacks { @override Future getDeviceId() async { final deviceInfo = DeviceInfoPlugin(); switch (defaultTargetPlatform) { case TargetPlatform.android: final android = await deviceInfo.androidInfo; return android.id; case TargetPlatform.iOS: final ios = await deviceInfo.iosInfo; return ios.identifierForVendor ?? 'ios-unknown'; default: return 'device-${DateTime.now().millisecondsSinceEpoch}'; } } @override String computeSign(String deviceId) { return md5.convert(utf8.encode(deviceId)).toString().toUpperCase(); } @override void onLoginSuccess(FastLoginResponse data) { if (data.userId != null) UserState.setUserId(data.userId!); if (data.credits != null) UserState.setCredits(data.credits!); if (data.avatar != null) UserState.setAvatar(data.avatar!); if (data.userName != null) UserState.setUserName(data.userName!); } @override void onCommonInfoLoaded(CommonInfoResponse data) { if (data.credits != null) UserState.setCredits(data.credits!); if (data.avatar != null) UserState.setAvatar(data.avatar!); if (data.userName != null) UserState.setUserName(data.userName!); } @override void onLoginFailed(String msg) { debugPrint('[AuthService] Login failed: $msg'); } } /// 认证服务 class AuthService { static final _authCallbacks = AppAuthCallbacks(); static final _attributionCallbacks = AppAttributionCallbacks(); static Future init() async { AttributionService.init(_attributionCallbacks); FrameworkAuthService.init(_authCallbacks); await FrameworkAuthService.start(); } static Future get loginComplete => FrameworkAuthService.loginComplete; } ``` ### 7.2 创建 main.dart 创建 `lib/main.dart`: ```dart import 'package:flutter/material.dart'; import 'package:client_proxy_framework/client_proxy_framework.dart'; import 'app.dart'; import 'core/auth/auth_service.dart'; import 'core/config/app_config.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); // 1. 初始化 API 客户端 ApiClient.init(YourAppConfig()); // 2. 初始化分析服务 await AnalyticsService.init( AnalyticsConfig( packageName: 'com.yourcompany.yourapp', // 包名,MethodChannel 使用 adjustConfig: AdjustConfig( appToken: 'your_adjust_app_token', environment: AdjustEnv.sandbox, // 上线改为 production ), facebookConfig: FacebookConfig( appId: 'your_facebook_app_id', clientToken: 'your_facebook_client_token', ), ), ); // 3. 提前获取归因 await AnalyticsService.initAttribution(); // 4. 启动应用 runApp(const App()); // 5. 初始化认证 AuthService.init(); } ``` ### 7.3 创建 App 组件 创建 `lib/app.dart`: ```dart import 'package:flutter/material.dart'; import 'home_screen.dart'; class App extends StatelessWidget { const App({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Your App Name', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue), useMaterial3: true, ), home: const HomeScreen(), ); } } ``` *** ## 8. 调试与测试 ### 8.1 运行调试版本 ```bash flutter run ``` ### 8.2 检查日志 运行时应看到以下日志: ``` D/Adjust (xxxxx): Adjust SDK started I/flutter (xxxxx): [AuthService] start: deviceId=xxx I/flutter (xxxxx): Facebook App Events initialized (from native callback) ``` ### 8.3 常见问题排查 | 问题 | 解决方案 | | --------------------------------- | ---------------------------------------- | | Adjust 日志显示 "SANDBOX" | 正常,测试环境会显示 | | Facebook 报错 "must be initialized" | 检查 AndroidManifest.xml 和 Application 类配置 | | 归因未上报 | 检查 initAttribution() 是否在 main() 中调用 | *** ## 9. 上线准备 ### 9.1 修改环境配置 #### main.dart ```dart // Adjust 改为 production adjustConfig: AdjustConfig( appToken: 'your_production_app_token', environment: AdjustEnv.production, // 修改这里 ), // Facebook 关闭调试日志 facebookConfig: FacebookConfig( appId: 'your_facebook_app_id', clientToken: 'your_facebook_client_token', debugLogs: false, // 关闭调试日志 ), ``` #### Android ```xml ``` #### iOS ```xml AdjustEnvironment production ``` ### 9.2 构建发布版本 ```bash # Android flutter build apk --release flutter build appbundle --release # iOS flutter build ios --release ``` ### 9.3 检查清单 - [ ] Adjust App Token 已替换为生产环境 - [ ] Adjust Environment 已改为 production - [ ] Facebook 调试日志已关闭 - [ ] Facebook Client Token 已更新(生产环境) - [ ] 应用名称和包名正确 - [ ] 所有第三方 SDK 配置完成 *** ## 附录:框架 API 快速参考 ### 分析服务 ```dart // 初始化 await AnalyticsService.init(config); // 埋点 AnalyticsService.trackEvent('event_token'); AnalyticsService.trackPurchase(amount: 9.99, currency: 'USD'); AnalyticsService.trackRegister(); AnalyticsService.trackSubscribe('monthly_vip'); // 获取归因 final attribution = AnalyticsService.getAttribution(); ``` ### 认证服务 ```dart // 获取登录完成状态 await AuthService.loginComplete; // 发起登录 await AuthService.init(); ``` ### API 请求 ```dart // 获取代理客户端 final proxy = ApiClient.instance.proxy; // GET 请求 final res = await proxy.request( path: '/v1/endpoint', method: 'GET', ); // POST 请求 final res = await proxy.request( path: '/v1/endpoint', method: 'POST', body: {'key': 'value'}, ); // 请求带字段映射的实体 final res = await proxy.requestEntity( path: '/v1/entity', method: 'GET', entityFactory: YourEntity.fromJson, ); ``` *** ## 10. 后续开发指引 完成本指南后,数据框架已就绪。接下来需要开发 **UI 业务层**: ### 10.1 框架提供的 API 服务 | 服务类 | 用途 | | ------------- | ------------- | | `ImageApi` | 图片/视频生成相关 API | | `PaymentApi` | 支付相关 API | | `FeedbackApi` | 举报/反馈相关 API | | `UserApi` | 用户账户相关 API | ### 10.2 框架提供的实体类 参考 `client_proxy_framework/lib/src/entities/` 目录: | 实体类 | 用途 | | ------------------ | ------------ | | `UserEntities` | 用户、登录、通用信息响应 | | `ImageEntities` | 分类、任务、生成结果 | | `PaymentEntities` | 商品、订单、支付结果 | | `FeedbackEntities` | 举报反馈相关 | ### 10.3 典型的 UI 开发流程 ```dart // 1. 在业务页面中使用框架 API class HomeScreen extends StatefulWidget { @override void initState() { super.initState(); _loadData(); } Future _loadData() async { // 等待登录完成(如果需要) await AuthService.loginComplete; // 调用框架 API 获取数据 final res = await ImageApi.getCategoryList(); if (res.isSuccess) { // 处理数据... } } } // 2. 使用 UserState 管理状态 UserState.setCredits(credits); UserState.setUserId(userId); // 3. 调用分析服务埋点 AnalyticsService.trackPurchase(amount: 9.99, currency: 'USD'); AnalyticsService.trackEvent('your_event_token'); ``` ### 10.4 建议的 UI 开发顺序 1. **首页(HomeScreen)** - 展示分类和任务卡片 2. **生成页(CreateScreen)** - 创建生成任务 3. **个人中心(ProfileScreen)** - 用户信息和设置 4. **充值页(RechargeScreen)** - 内购支付流程 ### 10.5 参考实现 参考 `app_client_1` 项目中的 UI 实现: - `lib/features/home/home_screen.dart` - 首页实现 - `lib/shared/widgets/` - 共享组件(按钮、卡片等) - `lib/core/theme/` - 主题配置