gcx/docs/guides/01-Flutter移动端开发指南.md

17 KiB
Raw Blame History

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/          # DTOFreezed生成
│   │   │   │   └── 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 ← DataDomain层不依赖任何外层


3. 核心模块实现

3.1 术语映射(全局执行)

所有面向用户的UI文本必须使用Web2术语禁止出现区块链术语。

// lib/core/constants/terminology.dart
class Terminology {
  // 用户界面术语 → 底层技术术语
  static const Map<String, String> mapping = {
    '我的账户': '链上钱包地址',
    '我的券': 'ERC-721/1155 NFT资产',
    '我的余额': '链上稳定币(USDC)余额',
    '转赠给朋友': 'P2P链上转移',
    '购买': '链上原子交换',
    '核销/使用': '合约兑付(Redemption)',
    '订单号': '交易哈希(TX Hash)',
    '安全验证': '链上签名(MPC钱包后台执行)',
  };
}

3.2 认证模块

// 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 }
// lib/features/auth/domain/usecases/register_usecase.dart
class RegisterUseCase {
  final AuthRepository _repo;
  RegisterUseCase(this._repo);

  /// 注册:手机号/邮箱 → 后台自动创建MPC钱包用户无感知
  Future<Either<Failure, User>> call(RegisterParams params) {
    return _repo.register(
      phone: params.phone,
      email: params.email,
      password: params.password,
    );
  }
}

3.3 券资产模块

// 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<String>? usageConditions,
  }) = _Coupon;
}

enum CouponStatus { available, used, expired, listed }
enum CouponType { utility, security }
// lib/features/coupons/presentation/providers/coupon_providers.dart
final couponListProvider = FutureProvider.autoDispose<List<Coupon>>((ref) {
  final repo = ref.watch(couponRepositoryProvider);
  return repo.getMyCoupons();
});

final couponDetailProvider = FutureProvider.autoDispose.family<Coupon, String>(
  (ref, couponId) {
    final repo = ref.watch(couponRepositoryProvider);
    return repo.getCouponDetail(couponId);
  },
);

3.4 交易模块

// 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价格校验前端 + 后端双重验证)

// lib/features/trading/presentation/providers/sell_provider.dart
class SellNotifier extends StateNotifier<SellState> {
  // Utility Track券卖出价 ≤ 面值
  bool validatePrice(double sellPrice, double faceValue, CouponType type) {
    if (type == CouponType.utility && sellPrice > faceValue) {
      return false; // 消费型券不允许溢价
    }
    return true;
  }
}

3.5 P2P转赠

// 转赠流程:输入朋友手机号 → 翻译层解析为链上地址 → 链上P2P转移
class TransferUseCase {
  final TransferRepository _repo;
  TransferUseCase(this._repo);

  Future<Either<Failure, TransferResult>> 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 悬浮入口按钮

// 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 对话面板(流式输出)

// lib/features/ai_agent/presentation/pages/agent_chat_panel.dart
class AgentChatPanel extends ConsumerStatefulWidget {
  @override
  _AgentChatPanelState createState() => _AgentChatPanelState();
}

class _AgentChatPanelState extends ConsumerState<AgentChatPanel> {
  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<String, dynamic> _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 离线核销实现

// lib/features/merchant/data/services/offline_redeem_service.dart
class OfflineRedeemService {
  final _queue = HiveBox<PendingRedemption>('offline_queue');
  final _localValidator = LocalCouponValidator();

  /// 离线核销:本地验证 + 缓存 + 联网自动同步
  Future<RedeemResult> 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<void> 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配置

// 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 错误处理

// 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<String, String> errors}) = ValidationFailure;
}

7. 状态管理模式Riverpod

// 只读数据查询
final couponListProvider = FutureProvider.autoDispose<List<Coupon>>((ref) async {
  return ref.watch(couponRepoProvider).getAll();
});

// 可变状态管理
final cartProvider = StateNotifierProvider<CartNotifier, CartState>((ref) {
  return CartNotifier(ref.watch(orderRepoProvider));
});

// 异步操作
final purchaseProvider = FutureProvider.family<Order, PurchaseParams>((ref, params) {
  return ref.watch(orderRepoProvider).purchase(params);
});

8. 应用构建配置

8.1 多环境配置

// 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构建

# 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快照回归
// 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通信全链路HTTPSCertificate 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)

文档版本: v1.0 基于: Genex 券交易平台 - 软件需求规格说明书 v4.1 技术栈: Flutter 3.x + Riverpod + Clean Architecture