Files
huangguo_android/lib/track_event_manager/track_session.dart
T
2026-09-15 15:44:13 +07:00

96 lines
3.4 KiB
Dart

import 'package:hgdj/hj_utils/light_model.dart';
import 'package:hgdj/hj_utils/store_keys.dart';
import 'package:uuid/uuid.dart';
/// 会话管理器 - 生成并维护统一的会话 ID (sid)
///
/// 会话 = 用户一次连续使用产生的一组行为,同一会话内所有请求/埋点共享同一个 sid。
///
/// 会话重建规则(满足任一即换新 sid):
/// 1. App 冷启动
/// 2. 从后台回前台,且后台停留 > 30 分钟
/// 3. 从后台回前台,且距会话创建 > 30 分钟
///
/// 持久化在 MMKV (lightKV):当前 sid / 会话创建时间 / 进入后台时间
class TrackSessionManager {
static final TrackSessionManager _instance = TrackSessionManager._();
factory TrackSessionManager() => _instance;
TrackSessionManager._();
/// 会话超时阈值:30 分钟
static const Duration _timeout = Duration(minutes: 30);
String _sid = '';
DateTime? _sessionAt; //会话创建时间
DateTime? _backgroundedAt; //App 进入后台的时间
bool _inited = false;
/// 当前会话 ID,为空则立即新建(防御性)
String get currentSid {
if (_sid.isEmpty) _newSession();
return _sid;
}
/// App 冷启动时调用一次(main.dart):恢复旧状态后强制开新会话
Future<void> onAppColdStart() async {
if (_inited) return;
await _restore();
_newSession();
}
/// App 进入后台:记录时刻,用于回前台时判断停留时长
/// 调用时机:AppLifecycleState.paused / inactive / detached
Future<void> onAppPause() async {
_backgroundedAt = DateTime.now();
await lightKV.setInt(StoreKeys.TRACK_SESSION_BACKGROUND_TS,
_backgroundedAt!.millisecondsSinceEpoch);
}
/// App 回到前台:后台停留超时或会话本身超时都换新 sid(规则2、3)
/// 调用时机:AppLifecycleState.resumed
Future<void> onAppResume() async {
//没进过后台不算规则2,所以要先判非空
final bgExpired = _backgroundedAt != null && _isExpired(_backgroundedAt);
if (bgExpired || _isExpired(_sessionAt)) _newSession();
_backgroundedAt = null;
await lightKV.setInt(StoreKeys.TRACK_SESSION_BACKGROUND_TS, 0);
}
/// 从存储恢复上次的 sid 和时间戳,已过期则先开新会话
Future<void> _restore() async {
_sid = (await lightKV.getString(StoreKeys.TRACK_SESSION_SID)) ?? '';
//键名沿用历史的 LAST_EVENT_TS,存的是会话创建时间
final sessionTs =
await lightKV.getInt(StoreKeys.TRACK_SESSION_LAST_EVENT_TS);
if (sessionTs != null && sessionTs > 0)
_sessionAt = DateTime.fromMillisecondsSinceEpoch(sessionTs);
final backgroundTs =
await lightKV.getInt(StoreKeys.TRACK_SESSION_BACKGROUND_TS);
if (backgroundTs != null && backgroundTs > 0)
_backgroundedAt = DateTime.fromMillisecondsSinceEpoch(backgroundTs);
if (_sid.isEmpty || _isExpired(_sessionAt)) _newSession();
_inited = true;
}
/// 距 [time] 是否已超过 30 分钟(time 为空按已过期处理)
bool _isExpired(DateTime? time) {
if (time == null) return true;
return DateTime.now().difference(time) > _timeout;
}
/// 新建会话并立即落盘,防止进程被杀丢数据
void _newSession() {
_sid = Uuid().v4().replaceAll('-', '');
_sessionAt = DateTime.now();
lightKV.setString(StoreKeys.TRACK_SESSION_SID, _sid);
lightKV.setInt(StoreKeys.TRACK_SESSION_LAST_EVENT_TS,
_sessionAt!.millisecondsSinceEpoch);
}
}