using Cysharp.Threading.Tasks; using Newtonsoft.Json; using PhxhSDK; using System; using System.Collections.Generic; using UnityEngine; public partial class BIManager : Singlenton, IInitable, IUpdatable { /// /// 基础属性 /// private BIBaseInfo baseInfo; /// /// 关卡属性 /// private BILevelInfo levelInfo; /// /// BI接口 /// private IBIService _biService; /// /// 一次性事件缓存类 /// private StorageEvent _storageEvent; /// /// 是否初始化 /// private bool _isInit; /// /// 启用自己服务器打点 /// private bool sendPhxh; public PublicDictionaryPool DictionaryPool { get; } = new PublicDictionaryPool(); /// /// 参数字典 /// private Dictionary _tempEventDictionary = new(12); private readonly string PhxhBiUrl = "https://bi.kedrgame.com/api/v1/events"; /// /// BI专属accessKey,请勿与中台key混用 /// private readonly string AccessKey = "yd9kWI798IWyyq1t"; /// /// BI专属密钥,请勿与中台密钥混用 /// private readonly string SecretKey = "iQLHTbOqeXtZZpxOZve-PDxTK9DkHPSKRbpXb30eS3g="; #region 初始化 /// /// 游戏启动初始化 /// public void Init() { baseInfo = new BIBaseInfo(); levelInfo = new BILevelInfo(); _storageEvent = StorageMgr.Instance.GetStorage(Framework.Constants.Storage.BI_FTE_EVENT); _storageEvent ??= new StorageEvent(); InitDictPool(); _networkAvailable = true; _useQueueMode = _sendInterval > 0; InitService(); #if BI_PHXH sendPhxh = true; #else sendPhxh = false; #endif _isInit = true; } /// /// 登陆后初始化 /// /// public void AfterLoginInitService(SDKManager.SdkName name) { _biService = SDKManager.Instance.GetSdkHelper(name) as IBIService; if (_biService == null) { DebugUtil.Log("After Login Get BI Service error, SDK helper name is {0}", name); } } /// /// 初始化登陆后基本属性 /// public void InitLoginBaseInfo() { baseInfo.SetUserInfo(); baseInfo.PrintAllValues(); if (!_isInit || _biService == null) return; _biService.SetUser(baseInfo.actor_id); SetUserProperty(); // 发送积累事件 SendQueuedEvents(); } /// /// 初始化第三方打点平台 /// private void InitService() { var helperName = SDKManager.SdkName.TalkingData; #if SDK_FIREBASE helperName = SDKManager.SdkName.FireBase; _biService = SDKManager.Instance.GetSdkHelper(helperName) as IBIService; #endif #if SDK_TAPTAP helperName = SDKManager.SdkName.TapTap; _biService = SDKManager.Instance.GetSdkHelper(helperName) as IBIService; #endif #if SDK_TALKINGDATA // TD在登录后初始化 // helperName = SDKManager.SdkName.TalkingData; // _biService = SDKManager.Instance.GetSdkHelper(helperName) as IBIService; #endif if (_biService == null) { DebugUtil.Log("Before Login Get BI Service error, SDK helper name is {0}", helperName); } } /// /// 设置用户属性 /// private void SetUserProperty() { SetUserProperty("game_id", baseInfo.app_name); SetUserProperty("channel", baseInfo.channel); SetUserProperty("model", baseInfo.model); SetUserProperty("uuid", baseInfo.uuid); SetUserProperty("resolution", baseInfo.resolution); SetUserProperty("platform", baseInfo.platform); SetUserProperty("os", baseInfo.os); SetUserProperty("version", baseInfo.version); SetUserProperty("build_code", baseInfo.build_code); SetUserProperty("assets_version", baseInfo.assets_version); SetUserProperty("language", baseInfo.language); // SetUserProperty("actor_id", baseInfo.actor_id); 通用接口 SetUserProperty("third_platform", baseInfo.third_platform); SetUserProperty("third_id", baseInfo.third_id); #if SDK_TALKINGDATA // _biService?.SetUserProperty(TalkingDataProfileParamName.Param1, baseInfo.model); // _biService?.SetUserProperty(TalkingDataProfileParamName.Param2, baseInfo.storage_info + "MB"); SetUserProperty("storage_info", baseInfo.storage_info + "MB"); #endif } public void SetUserProperty(string name, string value) { _biService?.SetUserProperty(name, value); } #endregion #region 释放 public void Release() { _tempEventDictionary = null; _dictPool = null; // 释放对象池 if (_isInit && _networkAvailable && eventQueue.Count > 0 && _biService != null) { SendQueuedEvents(); } } #endregion #region 周期/队列 发送 /// /// 事件队列 /// private readonly Queue<(string, Dictionary)> eventQueue = new(); /// /// 是否使用队列模式(无网络或设置了发送周期) /// private bool _useQueueMode; /// /// 发送周期 默认随打随发 /// private float _sendInterval = 0f; /// /// 网络状态 通过心跳包检测 /// private bool _networkAvailable; /// /// 计时器 /// private float _timer = 0f; public void Update(float deltaTime) { if (!_isInit) return; // 检查网络状态 CheckNetworkStatus(); if (eventQueue.Count <= 0 || !_networkAvailable) return; // 周期发送 if (_sendInterval > 0) { _timer += Time.deltaTime; // 达到发送周期 if (_timer >= _sendInterval) { _timer = 0; SendQueuedEvents(); } } else { SendQueuedEvents(); } } /// /// 检查网络状态 /// private void CheckNetworkStatus() { // 获取当前网络状态 var currentNetworkStatus = NetworkManager.Instance.NetworkAvailable; // 如果网络状态发生变化 if (_networkAvailable == currentNetworkStatus) return; if (currentNetworkStatus) { // 网络恢复 _networkAvailable = true; _useQueueMode = _sendInterval > 0; // 如果有设置发送周期,仍然使用队列模式 // 如果没有设置发送周期,立即发送所有队列中的事件 if (!_useQueueMode && eventQueue.Count > 0) { SendQueuedEvents(); } } else { _networkAvailable = false; _useQueueMode = true; } } /// /// 设置事件发送周期 /// /// 发送周期,单位秒,设为0则立即发送 public void SetSendInterval(float interval) { _sendInterval = Mathf.Max(0, interval); _useQueueMode = !_networkAvailable || _sendInterval > 0; } #endregion #region 发送事件 /// /// 添加事件到队列 /// private void EnqueueEvent(string eventName, Dictionary param) { // 检查队列是否已满 if (eventQueue.Count >= MAX_QUEUE_SIZE) { DebugUtil.LogWarning("BI事件队列已满"); return; } // 从对象池获取字典并复制数据 var poolDict = GetDictFromPool(); if (param != null) { foreach (var item in param) { poolDict[item.Key] = item.Value; } } eventQueue.Enqueue((eventName, poolDict)); } /// /// 发送队列中的所有事件 /// private void SendQueuedEvents() { if (_biService == null || !_networkAvailable) return; // 只要队列有事件就全部处理 while (eventQueue.Count > 0) { //将缓存的事件上报 var (eventName, param) = eventQueue.Dequeue(); DebugUtil.Log("发送缓存事件: {0}, 事件参数: {1}", eventName, JsonConvert.SerializeObject(param)); _biService.ReportEvent(eventName, param); TrackEventPhxh(eventName, param); } } private bool IsRecorded(string eventName) { return _storageEvent.EventList.Contains(eventName); } /// /// 更新关卡信息 /// public void SetLevelInfo(int levelID, int levelType, int levelDifficulty, int levelEvent, int param0 = 0, int param1 = 0) { if (!_isInit) return; levelInfo.level_id = levelID; levelInfo.level_type = levelType; levelInfo.difficulty = levelDifficulty; levelInfo.level_event = levelEvent; levelInfo.param0 = param0; levelInfo.param1 = param1; } /// /// 上报自定义事件 /// public void TrackEvent(string eventName, string param1Key = null, object param1 = null, string param2Key = null, object param2 = null, string param3Key = null, object param3 = null, string param4Key = null, object param4 = null) { if (!_isInit) return; try { _tempEventDictionary.Clear(); var userParam = _tempEventDictionary; if (!string.IsNullOrEmpty(param1Key) && param1 != null) userParam.Add(param1Key, param1); if (!string.IsNullOrEmpty(param2Key) && param2 != null) userParam.Add(param2Key, param2); if (!string.IsNullOrEmpty(param3Key) && param3 != null) userParam.Add(param3Key, param3); if (!string.IsNullOrEmpty(param4Key) && param4 != null) userParam.Add(param4Key, param4); if (_useQueueMode || _biService == null) { EnqueueEvent(eventName, userParam); } else { DebugUtil.Log("发送事件: {0}, 事件参数: {1}", eventName, JsonConvert.SerializeObject(userParam)); _biService.ReportEvent(eventName, userParam); TrackEventPhxh(eventName, userParam); } } catch (Exception e) { DebugUtil.LogError("Track event fail! EventName:" + eventName + ",\n error msg: " + e.Message); } } /// /// 上报自定义事件 /// public void TrackEventWithRecycleDic(string eventName, RecycleDictionary recycleDictionary) { if (!_isInit) return; try { _tempEventDictionary.Clear(); var userParam = _tempEventDictionary; foreach (var item in recycleDictionary.dict) { if (item.Value != null) { userParam.Add(item.Key, item.Value); } } DictionaryPool.ReturnDictToPool(recycleDictionary); if (_useQueueMode || _biService == null) { EnqueueEvent(eventName, userParam); } else { DebugUtil.Log("发送事件: {0}, 事件参数: {1}", eventName, JsonConvert.SerializeObject(userParam)); _biService.ReportEvent(eventName, userParam); TrackEventPhxh(eventName, userParam); } } catch (Exception e) { DebugUtil.LogError("Track event fail! EventName:" + eventName + ",\n error msg: " + e.Message); } } /// /// 上报一次性自定义事件 /// public void TrackEventOnce(string eventName, string param1Key = null, object param1 = null, string param2Key = null, object param2 = null, string param3Key = null, object param3 = null) { if (IsRecorded(eventName)) { DebugUtil.Log("已经上报过 {0} 关卡事件,不会再次上报", eventName); return; } TrackEvent(eventName, param1Key, param1, param2Key, param2, param3Key, param3); } /// /// 上报关卡自定义事件 上报前需要更新关卡信息 /// public void TrackLevelEvent(string eventName, string param1Key = null, object param1 = null, string param2Key = null, object param2 = null, string param3Key = null, object param3 = null) { if (!_isInit) return; try { _tempEventDictionary.Clear(); var userParam = _tempEventDictionary; userParam.Add("level_id", levelInfo.level_id); // 关卡ID userParam.Add("level_type", levelInfo.level_type); // 关卡类型 userParam.Add("level_difficult", levelInfo.difficulty); // 关卡难度 userParam.Add("level_event", levelInfo.level_event); // 关卡进度 userParam.Add("level_param0", levelInfo.param0); // 关卡参数 userParam.Add("level_param1", levelInfo.param1); if (!string.IsNullOrEmpty(param1Key) && param1 != null) userParam.Add(param1Key, param1); if (!string.IsNullOrEmpty(param2Key) && param2 != null) userParam.Add(param2Key, param2); if (!string.IsNullOrEmpty(param3Key) && param3 != null) userParam.Add(param3Key, param3); DebugUtil.Log("发送关卡事件: {0}, 事件参数: {1}", eventName, JsonConvert.SerializeObject(userParam)); if (_useQueueMode || _biService == null) { EnqueueEvent(eventName, userParam); } else { _biService.ReportEvent(eventName, userParam); TrackEventPhxh(eventName, userParam); } } catch (Exception e) { DebugUtil.LogError("Track event fail! EventName:" + eventName + ",\n error msg: " + e.Message); } } /// /// 上报一次性关卡自定义事件 上报前需要更新关卡信息 /// public void TrackLevelEventOnce(string eventName, string param1Key = null, object param1 = null, string param2Key = null, object param2 = null, string param3Key = null, object param3 = null) { if (IsRecorded(eventName)) { DebugUtil.Log("已经上报过 {0} 关卡事件,不会再次上报", eventName); return; } TrackLevelEvent(eventName, param1Key, param1, param2Key, param2, param3Key, param3); } #endregion #region PHXH 服务器打点 private void TrackEventPhxh(string eventName, Dictionary param) { if (!_isInit || !sendPhxh) return; try { var url = PhxhBiUrl;//PackDataInst.Inst.localInfo.httpUrl + "bi/events"; //"http://bi.kedrgame.com/api/v1/events"; //192.168.2.135:17777 baseInfo ??= new BIBaseInfo(); var timestampMs = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); var sendData = new PhxhBIEvent(); if (param != null) { var data = JsonConvert.SerializeObject(param); sendData.event_value = data; } sendData.event_key = eventName; sendData.app_name = baseInfo.app_name; sendData.channel = baseInfo.channel; sendData.model = baseInfo.model; sendData.uuid = baseInfo.uuid; sendData.resolution = baseInfo.resolution; sendData.platform = baseInfo.platform; sendData.os = baseInfo.os; sendData.version = baseInfo.version; sendData.build_code = baseInfo.build_code; sendData.assets_version = baseInfo.assets_version; sendData.language = baseInfo.language; sendData.local_time = timestampMs; sendData.actor_id = baseInfo.actor_id; sendData.third_platform = baseInfo.third_platform; sendData.third_id = baseInfo.third_id; sendData.access_key = baseInfo.access_key; Dictionary headData = PostHeader.PostHeaderInfo(new Dictionary { { "event_key", sendData.event_key }, { "app_name",sendData.app_name }, { "event_value", sendData.event_value }, { "channel", sendData.channel }, { "model", sendData.model }, { "uuid", sendData.uuid }, { "resolution", sendData.resolution }, { "platform", sendData.platform }, { "os", sendData.os }, { "version", sendData.version }, { "build_code", sendData.build_code }, { "assets_version", sendData.assets_version }, { "language", sendData.language }, { "actor_id", sendData.actor_id }, { "third_platform", sendData.third_platform }, { "third_id", sendData.third_id }, { "local_time", sendData.local_time.ToString() }, { "access_key", sendData.access_key } }, SecretKey, AccessKey); HttpHelper.PostAsync(url, sendData, headData).Forget(); } catch (Exception e) { DebugUtil.LogError("Track event fail! EventName:" + eventName + ",\n error msg: " + e.Message); } } private class PhxhBIEvent { public string event_key; // 事件名称 public string event_value; // 事件参数 // public string game_id; // 游戏ID 服务器自己打 public string app_name; // 游戏名称 public string channel; // 渠道 public string model; // 设备信息 public string uuid; // 设备ID public string resolution; // 分辨率 public string platform; // 操作系统 安卓 ios public string os; // 操作系统 public string version; // 游戏版本 public string build_code; // 构建版本 public string assets_version; // 资源版本 public string language; // 语言 public string actor_id; // 玩家ID public string third_platform; // 第三方账号源 public string third_id; // 第三方账号ID public string access_key;//中台accesskey,包体打包内自带 public long local_time; // 本地时间 // public string server_time; // 服务器时间 服务器自己打 } #endregion #region 网络队列对象池 /// /// 字典对象池大小 /// private const int DICT_POOL_SIZE = 32; /// /// 对象池 /// private List> _dictPool; /// /// 对象池索引 /// private int _dictPoolIndex = 0; /// /// 事件队列最大容量 /// private const int MAX_QUEUE_SIZE = 1000; /// /// 初始化字典对象池 /// private void InitDictPool() { _dictPool = new List>(DICT_POOL_SIZE); for (var i = 0; i < DICT_POOL_SIZE; i++) { _dictPool.Add(new Dictionary(16)); } } /// /// 从对象池获取一个字典 /// private Dictionary GetDictFromPool() { if (_dictPool == null || _dictPool.Count == 0) { InitDictPool(); } var dict = _dictPool[_dictPoolIndex]; dict.Clear(); _dictPoolIndex = (_dictPoolIndex + 1) % DICT_POOL_SIZE; return dict; } #endregion #region 外部打点字典对象池 /// /// 对象池字典,请使用BIManager内部的PublicDictionaryPool获取对象 /// public class RecycleDictionary { public Dictionary dict; public RecycleDictionary() { dict = new Dictionary(); } public void Add(string key, object value) { if (dict.ContainsKey(key)) { dict[key] = value; } else { dict.Add(key, value); } } public void Clear() { dict.Clear(); } } /// /// 外部可访问的字典对象池类,用于打点事件传入参数 /// public class PublicDictionaryPool { private const int DICT_POOL_SIZE = 16; // 池大小可根据实际需求调整 private List _dictPool; private int _dictPoolIndex = 0; public PublicDictionaryPool() { InitDictPool(); } private void InitDictPool() { _dictPool = new List(DICT_POOL_SIZE); for (int i = 0; i < DICT_POOL_SIZE; i++) { _dictPool.Add(new RecycleDictionary()); } _dictPoolIndex = 0; } public RecycleDictionary GetDictFromPool() { if (_dictPoolIndex >= DICT_POOL_SIZE) { // 池已用完,临时分配一个新的字典 // TODO 也可以扩容池 return new RecycleDictionary(); } var dict = _dictPool[_dictPoolIndex]; _dictPoolIndex++; dict.Clear(); return dict; } public void ReturnDictToPool(RecycleDictionary dict) { if (_dictPoolIndex == 0) return; _dictPoolIndex--; _dictPool[_dictPoolIndex] = dict; } } #endregion }