Files
social-app/apps/lib/core/analytics/tracker.dart
T
2026-04-01 18:35:32 +08:00

248 lines
5.9 KiB
Dart

import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:uuid/uuid.dart';
import 'events/events.dart';
import 'queue/event_queue.dart';
import 'sender.dart';
class AnalyticsTracker {
static AnalyticsTracker? _instance;
late final AnalyticsSender _sender;
late final EventQueue _queue;
late final String _deviceId;
late final String _sessionId;
late final String _platform;
late final String _appVersion;
late final String? _appBuild;
late final String _env;
String? _userId;
AnalyticsTracker._();
static AnalyticsTracker get instance {
if (_instance == null) {
throw StateError('AnalyticsTracker not initialized. Call init() first.');
}
return _instance!;
}
static Future<void> init({
required String endpoint,
required String deviceId,
}) async {
if (_instance != null) return;
final packageInfo = await PackageInfo.fromPlatform();
final sessionId = await _getOrCreateSessionId();
final platform = Platform.isAndroid ? 'android' : 'ios';
final env = kDebugMode ? 'dev' : 'prod';
final tracker = AnalyticsTracker._();
tracker._sender = AnalyticsSender(endpoint: endpoint);
tracker._queue = EventQueue(
maxSize: 50,
flushInterval: const Duration(seconds: 30),
onFlush: tracker._handleFlush,
);
tracker._deviceId = deviceId;
tracker._sessionId = sessionId;
tracker._platform = platform;
tracker._appVersion = packageInfo.version;
tracker._appBuild = packageInfo.buildNumber.isNotEmpty
? packageInfo.buildNumber
: null;
tracker._env = env;
tracker._queue.start();
_instance = tracker;
}
static Future<String> _getOrCreateSessionId() async {
const uuid = Uuid();
final prefs = await SharedPreferences.getInstance();
var sessionId = prefs.getString('_analytics_session_id');
if (sessionId == null) {
sessionId = 'sess_${uuid.v4()}';
await prefs.setString('_analytics_session_id', sessionId);
}
return sessionId;
}
void setUserId(String? userId) {
_userId = userId;
}
String get userId => _userId ?? 'anonymous';
String get sessionId => _sessionId;
void track(BaseAnalyticsEvent event) {
_queue.add(event);
}
void trackLogin({
required String method,
String? traceId,
String? requestId,
AnalyticsContext? context,
}) {
track(
SessionLoginEvent(
eventId: _generateEventId(),
timestamp: DateTime.now(),
userId: userId,
deviceId: _deviceId,
sessionId: _sessionId,
platform: _platform,
appVersion: _appVersion,
appBuild: _appBuild,
env: _env,
pageName: 'login',
traceId: traceId,
requestId: requestId,
method: method,
context: context,
),
);
}
void trackLogout({
String? reason,
int? sessionDurationS,
String? pageName,
String? traceId,
AnalyticsContext? context,
}) {
track(
SessionLogoutEvent(
eventId: _generateEventId(),
timestamp: DateTime.now(),
userId: userId,
deviceId: _deviceId,
sessionId: _sessionId,
platform: _platform,
appVersion: _appVersion,
appBuild: _appBuild,
env: _env,
pageName: pageName,
traceId: traceId,
reason: reason,
sessionDurationS: sessionDurationS,
context: context,
),
);
}
void trackAgentChatCompleted({
required String conversationId,
String? scenario,
int? messageCount,
int? responseTimeMs,
String? traceId,
String? requestId,
AnalyticsContext? context,
}) {
track(
AgentChatCompletedEvent(
eventId: _generateEventId(),
timestamp: DateTime.now(),
userId: userId,
deviceId: _deviceId,
sessionId: _sessionId,
platform: _platform,
appVersion: _appVersion,
appBuild: _appBuild,
env: _env,
pageName: 'chat',
traceId: traceId,
requestId: requestId,
conversationId: conversationId,
scenario: scenario,
messageCount: messageCount,
responseTimeMs: responseTimeMs,
context: context,
),
);
}
void trackPageView({
required String pageName,
String? pageFrom,
int? stayDurationMs,
int? clickCount,
String? traceId,
AnalyticsContext? context,
}) {
track(
PageViewEvent(
eventId: _generateEventId(),
timestamp: DateTime.now(),
userId: userId,
deviceId: _deviceId,
sessionId: _sessionId,
platform: _platform,
appVersion: _appVersion,
appBuild: _appBuild,
env: _env,
pageName: pageName,
pageFrom: pageFrom,
stayDurationMs: stayDurationMs,
clickCount: clickCount,
traceId: traceId,
context: context,
),
);
}
void trackClick({
required String pageName,
required String elementId,
String? elementType,
String? traceId,
AnalyticsContext? context,
}) {
track(
UiClickEvent(
eventId: _generateEventId(),
timestamp: DateTime.now(),
userId: userId,
deviceId: _deviceId,
sessionId: _sessionId,
platform: _platform,
appVersion: _appVersion,
appBuild: _appBuild,
env: _env,
pageName: pageName,
elementId: elementId,
elementType: elementType,
traceId: traceId,
context: context,
),
);
}
String _generateEventId() {
return const Uuid().v4();
}
Future<void> _handleFlush(List<BaseAnalyticsEvent> events) async {
try {
await _sender.send(events);
} catch (e) {
// TODO: 失败时落盘本地,下次启动重试
debugPrint('Analytics send failed: $e');
}
}
void dispose() {
_queue.stop();
}
}