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

873 lines
26 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)
```
---
## 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<Either<Failure, void>> call() async {
final mnemonic = await _walletService.generateMnemonic();
// 显示12词助记词用户手动抄写
// 验证确认随机抽3个词验证
return _walletService.confirmBackup(mnemonic);
}
}
// 社交恢复Guardian机制
class SocialRecoveryUseCase {
/// 预设3-5个可信联系人多数确认即可恢复
Future<Either<Failure, void>> setupGuardians(List<String> guardianPhones) async {
if (guardianPhones.length < 3 || guardianPhones.length > 5) {
return Left(Failure.validation(errors: {'guardians': '需要3-5个守护人'}));
}
return _repo.setupSocialRecovery(guardianPhones);
}
/// 发起恢复(需>50%守护人确认)
Future<Either<Failure, void>> initiateRecovery() async {
return _repo.initiateRecovery();
}
}
// AA钱包ERC-4337集成
class AAWalletService {
/// 邮箱/手机号作为恢复入口
Future<void> 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<Either<Failure, DepositResult>> call(DepositParams params) {
// 银行卡/信用卡/Apple Pay → 法币入金 → 后台转换USDC
return _repo.deposit(params);
}
}
class WithdrawUseCase {
Future<Either<Failure, WithdrawResult>> 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<Either<Failure, void>> 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<void> 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<String>? 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钱包/外部钱包提取/转赠记录/余额/订单/换手机号/离线核销增强/争议投诉/消息通知*