修复:埋点接线与三处偏差(sessionId/eventId/设备信息)
- 生产接线修复:app.dart 组装时传 analytics 实例给 ApiAuthRepository(_buildRepository),修复 M1 遗留的「生产环境 _analytics 恒为 null、挂接点空转」问题 - sessionId 生命周期:新建 SessionTracker (WidgetsBindingObserver),冷启动生成 UUIDv7、后台超 30 分钟换新、未超阈值沿用原值,不再每事件随机生成 - eventId 改 UUIDv7:对齐 13 号规范(uuid 包已在依赖,直接用 v7()),保留插入时间局部性 - appVersion 动态注入:package_info_plus(新增依赖)异步读取后 setAppVersion,不再硬编码 '1.0.0+1' - osVersion 动态读取:Platform.operatingSystemVersion 正则提取主版本(如 android-14),不再硬编码 - 队列顺手加固:上传失败批次重回队首而非整批丢弃(一行级缓解,分段持久化属 M2 第二波) 测试新增 9 例:session_tracker_test(5 例:冷启动/短后台/长后台/级联状态/连续幂等),analytics_service_test 补强 4 例(UUIDv7/sessionId 不再逐事件生成/appVersion 可注入/失败重回队列)。 验收对照(06 号报告 §5.1 六条):1✓ SessionTracker 新建、2✓ 三条语义、3✓ 同会话 sessionId 一致、4✓ sessionId 为 UUID 不持久化、5✓ 单测 3 例(实际 5 例)、6✓ 真机脚本(开发者手测)。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,10 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:patbond_flutter/analytics/analytics_page_name.dart';
|
||||
import 'package:patbond_flutter/analytics/analytics_route_observer.dart';
|
||||
import 'package:patbond_flutter/analytics/analytics_service.dart';
|
||||
import 'package:patbond_flutter/analytics/page_view_tracker.dart';
|
||||
import 'package:patbond_flutter/analytics/session_tracker.dart';
|
||||
import 'package:patbond_flutter/core/network/api_client.dart';
|
||||
import 'package:patbond_flutter/core/network/token_refresher.dart';
|
||||
import 'package:patbond_flutter/core/theme/app_theme.dart';
|
||||
@@ -24,6 +30,10 @@ class _AppState extends State<App> {
|
||||
late final AppState appState;
|
||||
late final SessionManager sessionManager;
|
||||
late final AuthRepository authRepository;
|
||||
late final SessionTracker _sessionTracker;
|
||||
late final AnalyticsService _analytics;
|
||||
late final PageViewTracker _pageViewTracker;
|
||||
late final AnalyticsRouteObserver _routeObserver;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -32,7 +42,37 @@ class _AppState extends State<App> {
|
||||
sessionManager =
|
||||
widget.sessionManager ??
|
||||
SessionManager(store: const SecureTokenStore());
|
||||
|
||||
_sessionTracker = SessionTracker();
|
||||
WidgetsBinding.instance.addObserver(_sessionTracker);
|
||||
|
||||
_analytics = AnalyticsService(
|
||||
apiBaseUrl: patbondApiBaseUrl,
|
||||
getAccessToken: () => sessionManager.accessToken,
|
||||
getSessionId: () => _sessionTracker.sessionId,
|
||||
);
|
||||
_initAppVersion();
|
||||
|
||||
_pageViewTracker = PageViewTracker(_analytics.trackEvent);
|
||||
_routeObserver = AnalyticsRouteObserver(
|
||||
tracker: _pageViewTracker,
|
||||
resolveRootPage: _resolveRootPage,
|
||||
);
|
||||
|
||||
authRepository = widget.authRepository ?? _buildRepository();
|
||||
|
||||
// 认证状态切换补点(根路由 AnimatedSwitcher 无路由事件)
|
||||
sessionManager.addListener(_reportAuthStateChange);
|
||||
}
|
||||
|
||||
/// 异步读取 appVersion 并注入 analytics(启动后台任务,不阻塞渲染)。
|
||||
Future<void> _initAppVersion() async {
|
||||
try {
|
||||
final info = await PackageInfo.fromPlatform();
|
||||
_analytics.setAppVersion('${info.version}+${info.buildNumber}');
|
||||
} catch (error) {
|
||||
// 读取失败回退 'unknown'(AnalyticsService 构造默认值已兜底)。
|
||||
}
|
||||
}
|
||||
|
||||
AuthRepository _buildRepository() {
|
||||
@@ -47,11 +87,33 @@ class _AppState extends State<App> {
|
||||
api: api,
|
||||
session: sessionManager,
|
||||
refresher: refresher,
|
||||
analytics: _analytics,
|
||||
);
|
||||
}
|
||||
|
||||
AnalyticsPageName? _resolveRootPage() {
|
||||
// 回栈到无名根路由时解析当前认证状态页/主壳 Tab
|
||||
return switch (sessionManager.status) {
|
||||
AuthStatus.unknown => null,
|
||||
AuthStatus.unauthenticated => AnalyticsPageName.login,
|
||||
AuthStatus.authenticated => _pageViewTracker.currentTab,
|
||||
};
|
||||
}
|
||||
|
||||
void _reportAuthStateChange() {
|
||||
// 认证状态机切页补点(03 §3.2 非路由曝光 1/2)
|
||||
final page = switch (sessionManager.status) {
|
||||
AuthStatus.unauthenticated => AnalyticsPageName.login,
|
||||
AuthStatus.authenticated => _pageViewTracker.currentTab,
|
||||
AuthStatus.unknown => null,
|
||||
};
|
||||
if (page != null) _pageViewTracker.report(page);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
sessionManager.removeListener(_reportAuthStateChange);
|
||||
WidgetsBinding.instance.removeObserver(_sessionTracker);
|
||||
appState.dispose();
|
||||
if (widget.sessionManager == null) sessionManager.dispose();
|
||||
super.dispose();
|
||||
@@ -74,6 +136,7 @@ class _AppState extends State<App> {
|
||||
return MainShellPage(
|
||||
key: const ValueKey('shell'),
|
||||
appState: appState,
|
||||
pageViewTracker: _pageViewTracker,
|
||||
onLogout: authRepository.logout,
|
||||
);
|
||||
}
|
||||
@@ -85,6 +148,7 @@ class _AppState extends State<App> {
|
||||
title: 'Patbond',
|
||||
debugShowCheckedModeBanner: false,
|
||||
theme: buildAppTheme(),
|
||||
navigatorObservers: [_routeObserver],
|
||||
home: ListenableBuilder(
|
||||
listenable: sessionManager,
|
||||
builder: (context, _) {
|
||||
|
||||
Reference in New Issue
Block a user