560 lines
17 KiB
Markdown
560 lines
17 KiB
Markdown
# 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<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通信全链路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)
|
||
```
|
||
|
||
---
|
||
|
||
*文档版本: v1.0*
|
||
*基于: Genex 券交易平台 - 软件需求规格说明书 v4.1*
|
||
*技术栈: Flutter 3.x + Riverpod + Clean Architecture*
|