diff --git a/lib/app.dart b/lib/app.dart index e03d62c..0de2dfb 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -13,6 +13,7 @@ import 'core/data/services/app_catalog_controller.dart'; import 'core/data/services/auth_controller.dart'; import 'core/data/services/catalog_controller.dart'; import 'core/data/services/engagement_controller.dart'; +import 'core/data/services/event_tracker.dart'; import 'core/data/services/playback_session.dart'; import 'core/data/services/token_storage.dart'; import 'core/data/services/video_player_port.dart'; @@ -31,6 +32,7 @@ class LumaDramaApp extends StatelessWidget { Provider(create: (_) => PlaybackCoordinator()), Provider(create: (_) => playerFactory), Provider(create: (_) => ApiClient()), + Provider(create: (ctx) => EventTracker(ctx.read())), Provider(create: (_) => TokenStorage()), Provider( create: (ctx) => AuthRepository( diff --git a/lib/core/data/services/event_tracker.dart b/lib/core/data/services/event_tracker.dart new file mode 100644 index 0000000..f48a483 --- /dev/null +++ b/lib/core/data/services/event_tracker.dart @@ -0,0 +1,64 @@ +// Author: fruit.com.cn + +import 'dart:async'; + +import 'api_client.dart'; + +/// 核心埋点事件名(第一阶段口径,后续与运营对齐后可扩展)。 +class AppEvents { + AppEvents._(); + + static const String playbackStart = 'playback_start'; + static const String playbackEnd = 'playback_end'; + static const String paySuccess = 'pay_success'; + static const String register = 'register'; + static const String click = 'click'; +} + +/// 埋点上报:本地缓冲、批量上报,失败静默丢弃不影响主流程。 +class EventTracker { + EventTracker(this._api); + + final ApiClient _api; + + static const int _flushSize = 10; + static const Duration _flushInterval = Duration(seconds: 10); + + final List> _buffer = []; + Timer? _timer; + + void track( + String eventName, { + String? page, + Map? properties, + }) { + _buffer.add({ + 'eventName': eventName, + 'eventTime': DateTime.now().toIso8601String(), + 'page': page, + 'properties': properties, + }); + if (_buffer.length >= _flushSize) { + flush(); + } else { + _timer ??= Timer(_flushInterval, flush); + } + } + + Future flush() async { + if (_buffer.isEmpty) return; + final events = List>.from(_buffer); + _buffer.clear(); + _timer?.cancel(); + _timer = null; + try { + await _api.post('/app/events', data: { + 'deviceId': 'device-0001', + 'sessionId': 'session-0001', + 'events': events, + }); + } catch (_) { + // 埋点失败不影响主流程,静默丢弃。 + } + } +}