From 033d1cde42d99905ec5b953c554f9b233c10d3e7 Mon Sep 17 00:00:00 2001 From: hailin Date: Thu, 5 Mar 2026 09:36:04 -0800 Subject: [PATCH] =?UTF-8?q?feat(mining-app):=20=E5=AE=8C=E5=96=84=20teleme?= =?UTF-8?q?try=20=E5=9F=8B=E7=82=B9=20-=20=E9=A1=B5=E9=9D=A2=E8=AE=BF?= =?UTF-8?q?=E9=97=AE=20+=20=E7=94=A8=E6=88=B7=E8=A1=8C=E4=B8=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 TelemetryRouteObserver,挂载到 GoRouter 自动追踪全部页面访问 无需每个页面手动调用 logPageView(),覆盖 27 个路由 - user_providers: login_success/login_failed(区分密码/短信)、logout - trading_page: buy_shares、sell_shares(含价格/数量/结果)、transfer_shares(含方向/金额/结果) Co-Authored-By: Claude Sonnet 4.6 --- .../lib/core/router/app_router.dart | 2 + .../core/router/telemetry_route_observer.dart | 59 +++++++++++++++++++ .../pages/trading/trading_page.dart | 13 ++++ .../providers/user_providers.dart | 5 ++ 4 files changed, 79 insertions(+) create mode 100644 frontend/mining-app/lib/core/router/telemetry_route_observer.dart diff --git a/frontend/mining-app/lib/core/router/app_router.dart b/frontend/mining-app/lib/core/router/app_router.dart index 0e04acf7..a1069baf 100644 --- a/frontend/mining-app/lib/core/router/app_router.dart +++ b/frontend/mining-app/lib/core/router/app_router.dart @@ -31,6 +31,7 @@ import '../../presentation/pages/profile/notification_inbox_page.dart'; import '../../presentation/widgets/main_shell.dart'; import '../../presentation/providers/user_providers.dart'; import 'routes.dart'; +import 'telemetry_route_observer.dart'; /// 不需要登录就能访问的公开路由 const _publicRoutes = { @@ -72,6 +73,7 @@ final appRouterProvider = Provider((ref) { return GoRouter( initialLocation: Routes.splash, refreshListenable: authNotifier, + observers: [TelemetryRouteObserver()], redirect: (context, state) { // 使用 authNotifier 中缓存的状态,而不是 ref.read final isLoggedIn = authNotifier.isLoggedIn; diff --git a/frontend/mining-app/lib/core/router/telemetry_route_observer.dart b/frontend/mining-app/lib/core/router/telemetry_route_observer.dart new file mode 100644 index 00000000..a165a9eb --- /dev/null +++ b/frontend/mining-app/lib/core/router/telemetry_route_observer.dart @@ -0,0 +1,59 @@ +import 'package:flutter/material.dart'; +import '../telemetry/telemetry_service.dart'; + +/// 路由遥测观察者 +/// +/// 挂载到 GoRouter.observers,自动追踪所有页面访问。 +/// 无需在每个页面手动调用 logPageView()。 +class TelemetryRouteObserver extends NavigatorObserver { + static const _pageNames = { + '/': 'splash', + '/login': 'login', + '/register': 'register', + '/forgot-password': 'forgot_password', + '/change-password': 'change_password', + '/trade-password': 'trade_password', + '/contribution': 'contribution', + '/trading': 'trading', + '/asset': 'asset', + '/profile': 'profile', + '/edit-profile': 'edit_profile', + '/mining-records': 'mining_records', + '/batch-mining-records': 'batch_mining_records', + '/contribution-records': 'contribution_records', + '/planting-records': 'planting_records', + '/send-shares': 'send_shares', + '/receive-shares': 'receive_shares', + '/c2c-market': 'c2c_market', + '/c2c-publish': 'c2c_publish', + '/c2c-order-detail': 'c2c_order_detail', + '/my-team': 'my_team', + '/trading-records': 'trading_records', + '/transfer-records': 'transfer_records', + '/p2p-transfer-records': 'p2p_transfer_records', + '/notifications': 'notifications', + '/help-center': 'help_center', + '/about': 'about', + }; + + void _log(Route? route) { + if (route == null) return; + final name = route.settings.name; + if (name == null || name.isEmpty) return; + final path = name.split('?').first; + final pageName = _pageNames[path] ?? path.replaceAll('/', '').replaceAll('-', '_'); + TelemetryService().logPageView(pageName); + } + + @override + void didPush(Route route, Route? previousRoute) { + super.didPush(route, previousRoute); + _log(route); + } + + @override + void didReplace({Route? newRoute, Route? oldRoute}) { + super.didReplace(newRoute: newRoute, oldRoute: oldRoute); + _log(newRoute); + } +} diff --git a/frontend/mining-app/lib/presentation/pages/trading/trading_page.dart b/frontend/mining-app/lib/presentation/pages/trading/trading_page.dart index 4c69047f..0dd933ad 100644 --- a/frontend/mining-app/lib/presentation/pages/trading/trading_page.dart +++ b/frontend/mining-app/lib/presentation/pages/trading/trading_page.dart @@ -19,6 +19,7 @@ import '../../widgets/shimmer_loading.dart'; import '../../widgets/kline_chart/kline_chart_widget.dart'; import '../../widgets/trade_password_dialog.dart'; import '../../../data/models/capability_model.dart'; +import '../../../core/telemetry/telemetry_service.dart'; class TradingPage extends ConsumerStatefulWidget { const TradingPage({super.key}); @@ -1540,6 +1541,13 @@ class _TradingPageState extends ConsumerState { .sellShares(_priceController.text, _quantityController.text); } + final action = isBuy ? 'buy_shares' : 'sell_shares'; + TelemetryService().logUserAction(action, properties: { + 'success': success, + 'price': _priceController.text, + 'quantity': _quantityController.text, + }); + if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( @@ -1960,6 +1968,11 @@ class _TransferBottomSheetState extends ConsumerState<_TransferBottomSheet> { success = await ref.read(tradingNotifierProvider.notifier).transferOut(amount); } + TelemetryService().logUserAction('transfer_shares', properties: { + 'direction': _direction == 0 ? 'in' : 'out', + 'amount': amount, + 'success': success, + }); if (mounted) { if (success) { Navigator.pop(context); diff --git a/frontend/mining-app/lib/presentation/providers/user_providers.dart b/frontend/mining-app/lib/presentation/providers/user_providers.dart index 842fe962..8f1be6d8 100644 --- a/frontend/mining-app/lib/presentation/providers/user_providers.dart +++ b/frontend/mining-app/lib/presentation/providers/user_providers.dart @@ -178,9 +178,11 @@ class UserNotifier extends StateNotifier { TelemetryService().setUserId(result.user.accountSequence); TelemetryService().setAccessToken(result.accessToken); TelemetryService().resumeAfterLogin(); + TelemetryService().logUserAction('login_success', properties: {'method': 'password'}); // 登录后获取详细信息 await fetchProfile(); } catch (e) { + TelemetryService().logUserAction('login_failed', properties: {'method': 'password', 'error': e.toString()}); state = state.copyWith(isLoading: false, error: e.toString()); rethrow; } @@ -204,9 +206,11 @@ class UserNotifier extends StateNotifier { TelemetryService().setUserId(result.user.accountSequence); TelemetryService().setAccessToken(result.accessToken); TelemetryService().resumeAfterLogin(); + TelemetryService().logUserAction('login_success', properties: {'method': 'sms'}); // 登录后获取详细信息 await fetchProfile(); } catch (e) { + TelemetryService().logUserAction('login_failed', properties: {'method': 'sms', 'error': e.toString()}); state = state.copyWith(isLoading: false, error: e.toString()); rethrow; } @@ -218,6 +222,7 @@ class UserNotifier extends StateNotifier { await _authDataSource.logout(state.refreshToken!); } catch (_) {} } + TelemetryService().logUserAction('logout'); await TelemetryService().pauseForLogout(); TelemetryService().setAccessToken(null); await _clearStorage();