# Genex Flutter 移动端开发指南 > Consumer App(消费者端)+ Merchant App(商户核销端) --- ## 1. 技术栈概览 | 技术 | 版本 | 用途 | |------|------|------| | **Flutter** | 3.x | 跨平台UI框架 | | **Dart** | 3.x | 开发语言 | | **Riverpod** | 2.x | 状态管理(Provider替代方案,编译安全) | | **GoRouter** | 最新 | 声明式路由、深链接 | | **Dio** | 5.x | HTTP客户端 | | **Freezed** | 最新 | 不可变数据模型代码生成 | | **Hive/Isar** | 最新 | 本地持久化 | | **WebSocket** | 内置 | AI Agent实时通信、行情推送 | | **flutter_localizations** | 内置 | 国际化 | --- ## 2. 项目架构:Clean Architecture + Riverpod ### 2.1 目录结构 ``` genex_mobile/ ├── lib/ │ ├── main.dart # 应用入口 │ ├── app/ │ │ ├── app.dart # MaterialApp配置 │ │ ├── router.dart # GoRouter路由定义 │ │ └── theme/ # 主题配置 │ ├── core/ │ │ ├── constants/ # 常量(API地址、术语映射表) │ │ ├── network/ # Dio配置、拦截器、错误处理 │ │ ├── storage/ # 本地存储封装 │ │ ├── utils/ # 工具类 │ │ └── extensions/ # Dart扩展方法 │ ├── features/ # 按功能模块组织 │ │ ├── auth/ # 注册/登录 │ │ │ ├── data/ │ │ │ │ ├── datasources/ # 远程/本地数据源 │ │ │ │ ├── models/ # DTO(Freezed生成) │ │ │ │ └── repositories/ # Repository实现 │ │ │ ├── domain/ │ │ │ │ ├── entities/ # 领域实体 │ │ │ │ ├── repositories/ # Repository接口 │ │ │ │ └── usecases/ # 用例 │ │ │ └── presentation/ │ │ │ ├── providers/ # Riverpod Providers │ │ │ ├── pages/ # 页面Widget │ │ │ └── widgets/ # 模块专用组件 │ │ ├── coupons/ # 券浏览/购买/持有 │ │ ├── trading/ # 二级市场交易 │ │ ├── wallet/ # 余额/资产/交易记录 │ │ ├── redeem/ # 券使用/核销 │ │ ├── transfer/ # P2P转赠 │ │ ├── profile/ # 个人中心/KYC │ │ ├── ai_agent/ # AI Agent对话/建议 │ │ └── merchant/ # 商户端核销(共享模块) │ ├── shared/ │ │ ├── widgets/ # 全局共享组件 │ │ ├── providers/ # 全局Provider │ │ └── models/ # 共享数据模型 │ └── l10n/ # 国际化资源 ├── test/ # 单元/Widget测试 ├── integration_test/ # 集成测试 └── pubspec.yaml ``` ### 2.2 Clean Architecture 分层 ``` ┌────────────────────────────────────────┐ │ Presentation Layer │ │ Pages → Widgets → Riverpod Providers │ ├────────────────────────────────────────┤ │ Domain Layer │ │ Entities → UseCases → Repository(接口)│ ├────────────────────────────────────────┤ │ Data Layer │ │ Models(DTO) → DataSources → Repo实现 │ └────────────────────────────────────────┘ ``` **依赖方向**:Presentation → Domain ← Data(Domain层不依赖任何外层) --- ## 3. 核心模块实现 ### 3.1 术语映射(全局执行) > 所有面向用户的UI文本必须使用Web2术语,禁止出现区块链术语。 ```dart // lib/core/constants/terminology.dart class Terminology { // 用户界面术语 → 底层技术术语 static const Map mapping = { '我的账户': '链上钱包地址', '我的券': 'ERC-721/1155 NFT资产', '我的余额': '链上稳定币(USDC)余额', '转赠给朋友': 'P2P链上转移', '购买': '链上原子交换', '核销/使用': '合约兑付(Redemption)', '订单号': '交易哈希(TX Hash)', '安全验证': '链上签名(MPC钱包后台执行)', }; } ``` ### 3.2 认证模块 ```dart // lib/features/auth/domain/entities/user.dart @freezed class User with _$User { const factory User({ required String id, required String phone, // 或 email required KycLevel kycLevel, // L0/L1/L2/L3 required WalletMode walletMode, // standard/pro String? displayName, String? avatar, }) = _User; } enum KycLevel { L0, L1, L2, L3 } enum WalletMode { standard, pro } ``` ```dart // lib/features/auth/domain/usecases/register_usecase.dart class RegisterUseCase { final AuthRepository _repo; RegisterUseCase(this._repo); /// 注册:手机号/邮箱 → 后台自动创建MPC钱包,用户无感知 Future> call(RegisterParams params) { return _repo.register( phone: params.phone, email: params.email, password: params.password, ); } } ``` ### 3.3 券资产模块 ```dart // lib/features/coupons/domain/entities/coupon.dart @freezed class Coupon with _$Coupon { const factory Coupon({ required String id, // 券ID(链上唯一) required String issuerName, // 发行方名称 required double faceValue, // 面值 required double currentPrice, // 当前市场价 required DateTime expiryDate, // 到期日期 required CouponStatus status, // 可用/已使用/已过期 required CouponType type, // utility/security String? imageUrl, String? description, List? usageConditions, }) = _Coupon; } enum CouponStatus { available, used, expired, listed } enum CouponType { utility, security } ``` ```dart // lib/features/coupons/presentation/providers/coupon_providers.dart final couponListProvider = FutureProvider.autoDispose>((ref) { final repo = ref.watch(couponRepositoryProvider); return repo.getMyCoupons(); }); final couponDetailProvider = FutureProvider.autoDispose.family( (ref, couponId) { final repo = ref.watch(couponRepositoryProvider); return repo.getCouponDetail(couponId); }, ); ``` ### 3.4 交易模块 ```dart // lib/features/trading/domain/entities/order.dart @freezed class TradeOrder with _$TradeOrder { const factory TradeOrder({ required String orderId, // 订单号(映射TX Hash) required String couponId, required OrderSide side, // buy/sell required double price, required OrderStatus status, required DateTime createdAt, }) = _TradeOrder; } enum OrderSide { buy, sell } enum OrderStatus { pending, matched, settled, cancelled } ``` **Utility Track价格校验(前端 + 后端双重验证)**: ```dart // lib/features/trading/presentation/providers/sell_provider.dart class SellNotifier extends StateNotifier { // Utility Track券:卖出价 ≤ 面值 bool validatePrice(double sellPrice, double faceValue, CouponType type) { if (type == CouponType.utility && sellPrice > faceValue) { return false; // 消费型券不允许溢价 } return true; } } ``` ### 3.5 P2P转赠 ```dart // 转赠流程:输入朋友手机号 → 翻译层解析为链上地址 → 链上P2P转移 class TransferUseCase { final TransferRepository _repo; TransferUseCase(this._repo); Future> call({ required String couponId, required String recipientPhone, // 手机号(非链上地址) }) { return _repo.transferByPhone( couponId: couponId, recipientPhone: recipientPhone, ); // 后端翻译层:手机号 → 链上地址 → Gas代付 → 链上P2P转移 } } ``` --- ## 4. AI Agent 集成 ### 4.1 架构 ``` ┌──────────────────────────────┐ │ AI Agent UI Layer │ │ 悬浮按钮 / 对话面板 / 建议条 │ ├──────────────────────────────┤ │ AI Agent SDK │ │ 对话管理 / 上下文组装 / 流式 │ ├──────────────────────────────┤ │ WebSocket连接 │ │ Agent Gateway API │ └──────────────────────────────┘ ``` ### 4.2 悬浮入口按钮 ```dart // lib/features/ai_agent/presentation/widgets/ai_fab.dart class AiAgentFab extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final unreadCount = ref.watch(aiUnreadCountProvider); return Positioned( right: 16, bottom: 80, child: GestureDetector( onTap: () => _showAgentPanel(context), onLongPress: () => _showQuickActions(context), child: Stack( children: [ CircleAvatar( radius: 28, child: Icon(Icons.smart_toy_outlined), ), if (unreadCount > 0) Positioned( right: 0, top: 0, child: Badge(count: unreadCount), ), ], ), ), ); } } ``` ### 4.3 对话面板(流式输出) ```dart // lib/features/ai_agent/presentation/pages/agent_chat_panel.dart class AgentChatPanel extends ConsumerStatefulWidget { @override _AgentChatPanelState createState() => _AgentChatPanelState(); } class _AgentChatPanelState extends ConsumerState { final _channel = WebSocketChannel.connect( Uri.parse('wss://api.gogenex.com/agent/ws'), ); void _sendMessage(String text) { final context = _buildContext(); // 组装上下文 _channel.sink.add(jsonEncode({ 'type': 'chat', 'message': text, 'context': context, })); } Map _buildContext() { return { 'user_profile': ref.read(userProfileProvider).toJson(), 'current_page': GoRouter.of(context).location, 'coupon_portfolio': ref.read(myCouponsProvider).toSummary(), 'recent_actions': ref.read(recentActionsProvider), }; } } ``` ### 4.4 消费者端AI场景 | 场景 | 实现方式 | |------|---------| | 智能选券推荐 | 首页Feed中插入AI推荐卡片 | | 价格顾问 | 券详情页底部AI分析标签 | | 到期管理 | 我的券列表AI排序+推送提醒 | | 出售定价 | 出售页面AI建议价格+解释 | | 自然语言搜索 | 对话面板返回筛选券卡片 | --- ## 5. 商户端核销模块 ### 5.1 核销方式 | 方式 | 场景 | 技术 | |------|------|------| | 扫码核销 | 门店扫消费者券码 | Camera → QR解码 → API调用 | | 输码核销 | 手动输入券码 | 文本输入 → API调用 | | 离线核销 | 网络不可用 | 本地验证 → 队列缓存 → 联网同步 | ### 5.2 离线核销实现 ```dart // lib/features/merchant/data/services/offline_redeem_service.dart class OfflineRedeemService { final _queue = HiveBox('offline_queue'); final _localValidator = LocalCouponValidator(); /// 离线核销:本地验证 + 缓存 + 联网自动同步 Future redeemOffline(String couponCode) async { // 1. 本地验证(预下载的有效券列表 + 签名验证) final isValid = await _localValidator.validate(couponCode); if (!isValid) return RedeemResult.invalid(); // 2. 本地标记已核销,加入同步队列 await _queue.add(PendingRedemption( couponCode: couponCode, timestamp: DateTime.now(), storeId: currentStoreId, )); return RedeemResult.pendingSync(); } /// 联网后自动同步 Future syncPendingRedemptions() async { final pending = _queue.values.toList(); for (final item in pending) { try { await _api.confirmRedemption(item); await _queue.delete(item.key); } catch (_) { // 重试逻辑 } } } } ``` --- ## 6. 网络层 ### 6.1 Dio配置 ```dart // lib/core/network/api_client.dart class ApiClient { late final Dio _dio; ApiClient() { _dio = Dio(BaseOptions( baseUrl: 'https://api.gogenex.com/api/v1', connectTimeout: Duration(seconds: 10), receiveTimeout: Duration(seconds: 30), )); _dio.interceptors.addAll([ AuthInterceptor(), // JWT Token注入 LoggingInterceptor(), // 日志 RetryInterceptor(), // 重试策略 ErrorInterceptor(), // 统一错误处理 ]); } } ``` ### 6.2 错误处理 ```dart // lib/core/network/failure.dart @freezed class Failure with _$Failure { const factory Failure.network({String? message}) = NetworkFailure; const factory Failure.server({required int code, String? message}) = ServerFailure; const factory Failure.auth({String? message}) = AuthFailure; const factory Failure.validation({required Map errors}) = ValidationFailure; } ``` --- ## 7. 状态管理模式(Riverpod) ```dart // 只读数据查询 final couponListProvider = FutureProvider.autoDispose>((ref) async { return ref.watch(couponRepoProvider).getAll(); }); // 可变状态管理 final cartProvider = StateNotifierProvider((ref) { return CartNotifier(ref.watch(orderRepoProvider)); }); // 异步操作 final purchaseProvider = FutureProvider.family((ref, params) { return ref.watch(orderRepoProvider).purchase(params); }); ``` --- ## 8. 应用构建配置 ### 8.1 多环境配置 ```dart // lib/core/config/env.dart enum Environment { dev, staging, prod } class AppConfig { static late Environment env; static String get apiBase => switch (env) { Environment.dev => 'https://dev-api.gogenex.com', Environment.staging => 'https://staging-api.gogenex.com', Environment.prod => 'https://api.gogenex.com', }; } ``` ### 8.2 Flavor构建 ```yaml # Consumer App flutter run --flavor consumer -t lib/main_consumer.dart # Merchant App flutter run --flavor merchant -t lib/main_merchant.dart ``` Consumer和Merchant共享核心模块(`core/`、`shared/`),通过不同入口和路由配置区分功能。 --- ## 9. 测试策略 | 层级 | 工具 | 覆盖目标 | |------|------|---------| | 单元测试 | `flutter_test` + `mocktail` | UseCase、Repository、Provider | | Widget测试 | `flutter_test` | 关键页面组件 | | 集成测试 | `integration_test` | 购买流程、核销流程、转赠流程 | | Golden测试 | `golden_toolkit` | UI快照回归 | ```dart // test/features/trading/sell_notifier_test.dart void main() { test('Utility券不允许溢价出售', () { final notifier = SellNotifier(); expect( notifier.validatePrice(110.0, 100.0, CouponType.utility), false, // 110 > 面值100,拒绝 ); expect( notifier.validatePrice(85.0, 100.0, CouponType.utility), true, // 85 < 面值100,允许 ); }); } ``` --- ## 10. 性能优化 | 策略 | 实现 | |------|------| | 图片懒加载 | `cached_network_image` + CDN | | 列表虚拟化 | `ListView.builder` + 分页加载 | | 离线缓存 | Hive本地数据库 + 增量同步 | | 启动优化 | 延迟初始化非核心服务 | | 包体积 | Tree-shaking + 延迟加载 | | 内存管理 | `autoDispose` Provider自动释放 | --- ## 11. 安全规范 - JWT Token存储于Flutter Secure Storage(非SharedPreferences) - 敏感操作(大额交易、转赠)需二次验证(PIN/生物识别) - API通信全链路HTTPS,Certificate Pinning - 本地数据加密存储(Hive加密Box) - 禁止在日志中输出敏感信息(Token、用户手机号) - ProGuard/R8混淆(Android) --- ## 12. 发布流程 ``` 开发 → 提交PR → CI自动测试 → Code Review → 合入main ↓ 自动构建(GitHub Actions / GitLab CI) ↓ ┌────────────────┼────────────────┐ ↓ ↓ ↓ Dev Build Staging Build Prod Build (内部测试) (TestFlight/Beta) (App Store/Play Store) ``` --- ## 13. Pro模式(加密原生用户) ### 13.1 Pro模式切换 ```dart // lib/features/profile/presentation/pages/pro_mode_settings.dart class ProModeSettings extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final user = ref.watch(userProfileProvider); return Column(children: [ // 风险提示确认 RiskDisclosureCard( text: '切换至Pro模式后,您将自行管理钱包私钥。' '平台无法帮您恢复丢失的私钥或资产。', ), // 开启Pro模式 SwitchListTile( title: Text('开启Pro模式'), value: user.walletMode == WalletMode.pro, onChanged: (enabled) async { if (enabled) { // 必须确认风险提示 final confirmed = await showRiskConfirmDialog(context); if (confirmed) { await ref.read(walletProvider.notifier).switchToProMode(); } } else { // Pro→标准:需将资产转回平台托管 await ref.read(walletProvider.notifier).switchToStandard(); } }, ), if (user.walletMode == WalletMode.pro) ...[ // 链上地址显示 CopyableAddressField(address: user.chainAddress), // WalletConnect连接 WalletConnectButton(), // MetaMask连接 MetaMaskConnectButton(), // 交易哈希查看入口 ListTile( title: Text('查看链上交易'), onTap: () => launchUrl(explorerUrl), ), ], ]); } } ``` ### 13.2 助记词备份与社交恢复 ```dart // lib/features/wallet/domain/usecases/backup_usecase.dart class BackupSeedPhraseUseCase { /// Pro模式开启时强制提示备份助记词 Future> call() async { final mnemonic = await _walletService.generateMnemonic(); // 显示12词助记词,用户手动抄写 // 验证确认(随机抽3个词验证) return _walletService.confirmBackup(mnemonic); } } // 社交恢复(Guardian机制) class SocialRecoveryUseCase { /// 预设3-5个可信联系人,多数确认即可恢复 Future> setupGuardians(List guardianPhones) async { if (guardianPhones.length < 3 || guardianPhones.length > 5) { return Left(Failure.validation(errors: {'guardians': '需要3-5个守护人'})); } return _repo.setupSocialRecovery(guardianPhones); } /// 发起恢复(需>50%守护人确认) Future> initiateRecovery() async { return _repo.initiateRecovery(); } } // AA钱包(ERC-4337)集成 class AAWalletService { /// 邮箱/手机号作为恢复入口 Future recoverViaEmail(String email) async { // ERC-4337 Account Abstraction恢复流程 } } ``` --- ## 14. 提取到外部钱包 ```dart // lib/features/wallet/presentation/pages/extract_to_external.dart class ExtractToExternalPage extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { return Column(children: [ // KYC L2+才可提取 KycGuard(requiredLevel: KycLevel.L2, child: Column(children: [ // 输入外部钱包地址(EVM兼容) TextField( decoration: InputDecoration(labelText: '外部钱包地址(0x...)'), controller: _addressController, ), // 风险提示 WarningCard( text: '提取后平台将不再托管该券。' '您需自行保管钱包私钥,平台无法冻结、恢复或干预该券。', ), // 选择要提取的券 CouponSelector(onSelected: (coupons) => setState(() => _selected = coupons)), // 确认提取 ElevatedButton( onPressed: () => _confirmExtract(ref), child: Text('确认提取'), ), ])), ]); } } ``` --- ## 15. 个人中心完整模块 ### 15.1 转赠记录 ```dart // lib/features/transfer/presentation/pages/transfer_history.dart class TransferHistoryPage extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final transfers = ref.watch(transferHistoryProvider); return transfers.when( data: (list) => ListView.builder( itemCount: list.length, itemBuilder: (_, i) => TransferHistoryCard( direction: list[i].direction, // sent / received recipientDisplay: list[i].recipientPhone ?? '外部钱包', couponName: list[i].couponName, timestamp: list[i].createdAt, status: list[i].status, // completed / pending ), ), loading: () => SkeletonList(), error: (e, _) => ErrorWidget(e), ); } } ``` ### 15.2 我的余额/充值/提现 ```dart // lib/features/wallet/domain/entities/balance.dart @freezed class WalletBalance with _$WalletBalance { const factory WalletBalance({ required double totalBalance, // 总余额(链上+链下聚合,美元显示) required double availableBalance, // 可用余额 required double frozenBalance, // 冻结金额(挂单中/待结算) required double withdrawable, // 可提现 }) = _WalletBalance; } // 充值/提现流程 class DepositUseCase { Future> call(DepositParams params) { // 银行卡/信用卡/Apple Pay → 法币入金 → 后台转换USDC return _repo.deposit(params); } } class WithdrawUseCase { Future> call(WithdrawParams params) { // USDC → 法币 → 银行卡(T+1到账) return _repo.withdraw(params); } } ``` ### 15.3 我的订单 ```dart // lib/features/trading/presentation/pages/order_history.dart class OrderHistoryPage extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final orders = ref.watch(orderHistoryProvider); return DefaultTabController( length: 3, child: Column(children: [ TabBar(tabs: [ Tab(text: '全部'), Tab(text: '买入'), Tab(text: '卖出'), ]), Expanded(child: TabBarView(children: [ OrderList(orders: orders), OrderList(orders: orders.where((o) => o.side == OrderSide.buy)), OrderList(orders: orders.where((o) => o.side == OrderSide.sell)), ])), ]), ); } } ``` ### 15.4 换手机号 ```dart // lib/features/profile/domain/usecases/change_phone.dart class ChangePhoneUseCase { /// 换手机号:KYC身份验证(人脸+证件)后迁移账户 Future> call(ChangePhoneParams params) async { // 1. 验证新手机号未注册 // 2. KYC身份验证(人脸识别 + 证件对比) // 3. 旧手机号验证码确认 // 4. 更新映射表(旧手机→新手机,链上地址不变) return _repo.changePhone(params); } } ``` --- ## 16. 离线核销增强 ```dart // lib/features/merchant/data/services/offline_redeem_service.dart // 补充:离线核销限额与冲突处理 class OfflineRedeemConfig { static const maxSingleAmount = 500.0; // 单笔离线核销限额$500 static const maxDailyAmount = 5000.0; // 单日离线核销限额$5,000 static const maxDailyCount = 50; // 单日离线核销笔数限制 } class OfflineConflictResolver { /// 冲突处理:同一张券被两个门店离线核销 /// 以先上链者为准,后者自动退回并通知 Future resolveConflict(PendingRedemption local, ChainRedemption chain) async { if (chain.redeemedBy != local.storeId) { // 该券已被其他门店核销,本地核销无效 await _notifyStore(local.storeId, '券${local.couponCode}已被其他门店核销'); await _queue.delete(local.key); } } } ``` --- ## 17. 争议与投诉 ```dart // lib/features/support/domain/entities/ticket.dart @freezed class SupportTicket with _$SupportTicket { const factory SupportTicket({ required String id, required TicketCategory category, // transaction/account/coupon required String description, required TicketStatus status, String? orderId, // 关联订单号 List? attachments, }) = _SupportTicket; } enum TicketCategory { transaction, account, coupon, compliance, other } enum TicketStatus { open, inProgress, waitingUser, resolved, closed } ``` --- ## 18. 消息通知 ```dart // lib/features/notification/domain/entities/notification.dart @freezed class AppNotification with _$AppNotification { const factory AppNotification({ required String id, required NotificationType type, required String title, required String body, required DateTime createdAt, required bool isRead, String? deepLink, // 点击跳转(如券详情、订单详情) }) = _AppNotification; } enum NotificationType { tradeComplete, // 交易完成 couponExpiringSoon, // 券即将过期 priceAlert, // 价格变动 transferReceived, // 收到转赠 systemAnnouncement, // 系统公告 issuerNotice, // 发行方公告 } ``` --- *文档版本: v2.0* *基于: Genex 券交易平台 - 软件需求规格说明书 v4.1* *技术栈: Flutter 3.x + Riverpod + Clean Architecture* *更新: 补充Pro模式/助记词/社交恢复/AA钱包/外部钱包提取/转赠记录/余额/订单/换手机号/离线核销增强/争议投诉/消息通知*