feat(mining-app): 完善 telemetry 埋点 - 页面访问 + 用户行为
- 新增 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 <noreply@anthropic.com>
This commit is contained in:
parent
a2a318e24c
commit
033d1cde42
|
|
@ -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<GoRouter>((ref) {
|
|||
return GoRouter(
|
||||
initialLocation: Routes.splash,
|
||||
refreshListenable: authNotifier,
|
||||
observers: [TelemetryRouteObserver()],
|
||||
redirect: (context, state) {
|
||||
// 使用 authNotifier 中缓存的状态,而不是 ref.read
|
||||
final isLoggedIn = authNotifier.isLoggedIn;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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<TradingPage> {
|
|||
.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);
|
||||
|
|
|
|||
|
|
@ -178,9 +178,11 @@ class UserNotifier extends StateNotifier<UserState> {
|
|||
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<UserState> {
|
|||
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<UserState> {
|
|||
await _authDataSource.logout(state.refreshToken!);
|
||||
} catch (_) {}
|
||||
}
|
||||
TelemetryService().logUserAction('logout');
|
||||
await TelemetryService().pauseForLogout();
|
||||
TelemetryService().setAccessToken(null);
|
||||
await _clearStorage();
|
||||
|
|
|
|||
Loading…
Reference in New Issue