248 lines
5.9 KiB
Dart
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();
|
||
|
|
}
|
||
|
|
}
|