diff --git a/lib/app.dart b/lib/app.dart index 3f353e5..3a6393f 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -41,6 +41,7 @@ import 'core/data/services/download_controller.dart'; import 'core/data/services/favorite_controller.dart'; import 'core/data/services/interaction_controller.dart'; import 'core/data/services/locale_controller.dart'; +import 'core/data/services/event_tracker.dart'; import 'core/data/services/playback_session.dart'; import 'core/data/services/video_player_port.dart'; import 'core/data/services/wallet_controller.dart'; @@ -235,6 +236,7 @@ class _LumaDramaAppState extends State { return MultiProvider( providers: [ Provider(create: (_) => PlaybackCoordinator()), + Provider(create: (_) => EventTracker(_apiClient)), Provider(create: (_) => widget.playerFactory), Provider.value(value: _catalogRepository), Provider.value(value: _playback), diff --git a/lib/core/data/services/event_tracker.dart b/lib/core/data/services/event_tracker.dart new file mode 100644 index 0000000..d60cec4 --- /dev/null +++ b/lib/core/data/services/event_tracker.dart @@ -0,0 +1,64 @@ +// Author: fruit.com.cn + +import 'dart:async'; + +import '../remote/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', body: { + 'deviceId': 'device-0001', + 'sessionId': 'session-0001', + 'events': events, + }); + } catch (_) { + // 埋点失败不影响主流程,静默丢弃。 + } + } +}