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

560 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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术语禁止出现区块链术语。
```dart
// 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 认证模块
```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<Either<Failure, User>> 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<String>? 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<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 交易模块
```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<SellState> {
// 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<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 悬浮入口按钮
```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<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 离线核销实现
```dart
// 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配置
```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<String, String> errors}) = ValidationFailure;
}
```
---
## 7. 状态管理模式Riverpod
```dart
// 只读数据查询
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 多环境配置
```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通信全链路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*