初始化
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user