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 BILevelInfo LevelInfo; private IBIService _biService; private StorageEvent _storageEvent; private bool _isInit; private bool sendPhxh = false; private Dictionary _tempEventDictionary = new(4); #region 网络状态和发送周期相关 /// /// 事件队列 /// private Queue<(string, Dictionary)> eventQueue = new(); /// /// 是否使用队列模式(无网络或设置了发送周期) /// private bool _useQueueMode = false; /// /// 发送周期 默认1秒 /// private float _sendInterval = 1.0f; /// /// 网络状态 通过心跳包检测 /// private bool _networkAvailable = true; /// /// 计时器 /// private float _timer = 0f; #endregion #region 基本属性 /// /// 设备机型 /// private string deviceInfo; /// /// 设备ID /// private string deviceID; /// /// 版本 /// private string gameVersion; /// /// 平台 /// private string platform; /// /// 渠道 /// private string channel; /// /// 剩余存储空间 /// private int storageInfo; #endregion #region 初始化 public void Init() { InitBiService(); #if BI_PHXH sendPhxh = true; #endif LevelInfo = new BILevelInfo(); _storageEvent = StorageMgr.Instance.GetStorage("StorageEvent"); _storageEvent ??= new StorageEvent(); _isInit = true; // 初始化网络状态 RegisterHeartbeatEvents(); //初次启动游戏打点 //TrackEventOnce(BI_GameStart, param: new Dictionary() { { "event", "first_open" } }); } /// /// 登陆后初始化信息 /// /// public void BiAfterLoginInit(SDKManager.SdkName name) { _biService = SDKManager.Instance.GetSdkHelper(name) as IBIService; if (_biService == null) { DebugUtil.Log("Get BI Service error, SDK helper name is {0}", name); return; } // 设置登陆后才有的基本属性 如用户ID _biService.SetUser(SDKManager.Instance.ApplicationUserID); SendQueuedEvents(); } private void InitBiService() { var helperName = SDKManager.SdkName.TapTap; #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 helperName = SDKManager.SdkName.TalkingData; _biService = SDKManager.Instance.GetSdkHelper(helperName) as IBIService; //TD在登录后初始化 #endif if (_biService == null) { DebugUtil.Log("Get BI Service error, SDK helper name is {0}", helperName); return; } // 初始化基本属性 deviceInfo = SystemInfo.deviceModel; storageInfo = SystemInfo.systemMemorySize; deviceID = SystemInfo.deviceUniqueIdentifier; gameVersion = Application.version; platform = DeviceHelper.GetPlatformString(); channel = PlayerPrefs.GetString("BI_Platform", "test"); // TODO 设置通用属性 例如 服务器 _biService?.SetUserProperty("platform", DeviceHelper.GetPlatformString()); DebugUtil.Log("device platform:{0}", platform); #if SDK_TALKINGDATA _biService?.SetUserProperty(TalkingDataProfileParamName.Param1, deviceInfo); _biService?.SetUserProperty(TalkingDataProfileParamName.Param2, storageInfo.ToString() + "MB"); #endif } /// /// 注册心跳包相关事件,用于检测网络状态 /// private void RegisterHeartbeatEvents() { // 注册心跳包响应事件 Framework.EventManager.Instance.Register(Framework.EventManager.EventName.NetHandshakeDone, (Action)OnNetworkConnected); Framework.EventManager.Instance.Register(Framework.EventManager.EventName.NetError, (Action)OnNetworkError); // 初始状态假设网络可用,但使用队列模式 _networkAvailable = true; _useQueueMode = _sendInterval > 0; } /// /// 设置用户属性 /// /// /// public void SetUserProperty(string key, string value) { if (!_isInit) return; _biService?.SetUserProperty(key, value); } #endregion #region 释放 public void Release() { _tempEventDictionary = null; // 在释放时尝试发送剩余的队列事件 if (_isInit && _networkAvailable && eventQueue.Count > 0 && _biService != null) { SendQueuedEvents(); } // 注销事件监听 Framework.EventManager.Instance.Unregister(Framework.EventManager.EventName.NetHandshakeDone, (Action)OnNetworkConnected); Framework.EventManager.Instance.Unregister(Framework.EventManager.EventName.NetError, (Action)OnNetworkError); } #endregion #region 周期发送 public void Update(float deltaTime) { if (!_isInit) return; // 如果队列中有事件并且有网络连接 if (eventQueue.Count > 0 && _networkAvailable) { // 如果使用周期发送模式 if (_sendInterval > 0) { _timer += Time.deltaTime; // 达到发送周期 if (_timer >= _sendInterval) { _timer = 0; SendQueuedEvents(); } } // 如果不使用周期模式(立即发送)但是之前网络不可用导致有队列 else { SendQueuedEvents(); } } } /// /// 网络连接成功 /// private void OnNetworkConnected(uint netID) { _networkAvailable = true; _useQueueMode = _sendInterval > 0; // 如果有设置发送周期,仍然使用队列模式 // 如果不使用队列模式,立即发送所有队列中的事件 if (!_useQueueMode && eventQueue.Count > 0) { SendQueuedEvents(); } } /// /// 网络连接错误 /// private void OnNetworkError(uint netID) { _networkAvailable = false; _useQueueMode = true; // 网络错误时启用队列模式 } /// /// 设置事件发送周期 /// /// 发送周期,单位秒,设为0则立即发送 public void SetSendInterval(float interval) { _sendInterval = Mathf.Max(0, interval); _useQueueMode = !_networkAvailable || _sendInterval > 0; } #endregion #region 发送事件 /// /// 发送队列中的所有事件 /// private void SendQueuedEvents() { if (_biService == null || !_networkAvailable) return; while (eventQueue.Count > 0) { //将缓存的事件上报 var (eventName, param) = eventQueue.Dequeue(); _biService.ReportEvent(eventName, param); TrackEventPhxh(eventName, param); } } /// /// 用于传输参数自定义命名事件 (普通事件) /// /// /// public void TrackEvent(string eventName, Dictionary param) { if (!_isInit) return; try { // 使用队列模式或者服务未初始化时,暂存事件 if (_useQueueMode || _biService == null) { eventQueue.Enqueue((eventName, param)); } else { _biService.ReportEvent(eventName, param); TrackEventPhxh(eventName, param); } } catch (Exception e) { DebugUtil.LogError("Track event fail! EventName:" + eventName + ",\n error msg: " + e.Message); } } /// /// 上报普通事件 TD有在用 谨慎删除 /// public void TrackEvent(string eventName, string param1 = null, string param2 = null, string param3 = null) { if (!_isInit) return; try { _tempEventDictionary.Clear(); var userParam = _tempEventDictionary; if (!string.IsNullOrEmpty(param1)) { userParam.Add("param1", param1); } if (!string.IsNullOrEmpty(param2)) { userParam.Add("param2", param2); } if (!string.IsNullOrEmpty(param3)) { userParam.Add("param3", param3); } if (_biService != null) { _biService.ReportEvent(eventName, userParam); } else { eventQueue.Enqueue((eventName, userParam)); } _tempEventDictionary.Clear(); } catch (Exception e) { DebugUtil.LogError("BI Event: {0}, TrackEvent Error: {1}", eventName, e); } } /// /// 用于传输参数自定义命名事件 (一次性事件) /// /// /// public void TrackEventOnce(string firstEventName, Dictionary param) { if (!_isInit) return; try { if (IsRecorded(firstEventName)) { DebugUtil.Log("已经上报过 {0} 事件, 不会再次上报", firstEventName); return; } _storageEvent.EventList.Add(firstEventName); // 使用队列模式或者服务未初始化时,暂存事件 if (_useQueueMode || _biService == null) { eventQueue.Enqueue((firstEventName, param)); } else { _biService.ReportEvent(firstEventName, param); } StorageMgr.Instance.SyncForce = true; } catch (Exception e) { DebugUtil.LogError("BI Event: {0}, TrackEventOnce Error: {1}", firstEventName, e); } } /// /// 上报关卡内普通事件 /// public void TrackEventLevel(string eventName, BILevelInfo levelInfo, Dictionary normalParam) { if (!_isInit) return; try { LevelInfo.Clone(levelInfo); var levelParam = Utils.PropertiesNameValue2ObjectDictionary(LevelInfo); foreach (var param in normalParam) { levelParam.Add(param.Key, param.Value); } // 使用队列模式或者服务未初始化时,暂存事件 if (_useQueueMode || _biService == null) { eventQueue.Enqueue((eventName, levelParam)); } else { _biService.ReportEvent(eventName, levelParam); TrackEventPhxh(eventName, levelParam); } } catch (Exception e) { DebugUtil.LogError("BI Event: {0}, TrackEventLevel Error: {1}", eventName, e); } } /// /// 上报关卡内一次性事件 /// public void TrackEventLevelOnce(string firstEventName, BILevelInfo levelInfo, Dictionary normalParam) { if (!_isInit) return; try { if (IsRecorded(firstEventName)) { DebugUtil.Log("已经上报过 {0} 关卡事件,不会再次上报", firstEventName); return; } LevelInfo.Clone(levelInfo); var levelParam = Utils.PropertiesNameValue2ObjectDictionary(LevelInfo); foreach (var param in normalParam) { levelParam.Add(param.Key, param.Value); } _storageEvent.EventList.Add(firstEventName); // 使用队列模式或者服务未初始化时,暂存事件 if (_useQueueMode || _biService == null) { eventQueue.Enqueue((firstEventName, levelParam)); } else { _biService.ReportEvent(firstEventName, levelParam); } StorageMgr.Instance.SyncForce = true; } catch (Exception e) { DebugUtil.LogError("BI Event: {0}, TrackEventLevelOnce Error: {1}", firstEventName, e); } } private bool IsRecorded(string eventName) { return _storageEvent.EventList.Contains(eventName); } #endregion #region PHXH 服务器打点 private void TrackEventPhxh(string eventName, Dictionary param) { if (!_isInit || !sendPhxh) return; try { string url = PostHeader.PostUrl + "bi/events"; //"http://bi.kedrgame.com/api/v1/events"; //192.168.2.135:17777 string data = JsonConvert.SerializeObject(param); var sendData = new PhxhBIEvent(); sendData.event_name = eventName; sendData.event_key = eventName; if (param != null) sendData.event_value = data; sendData.actor_id = SDKManager.Instance.ApplicationUserID; sendData.model = deviceInfo; sendData.uuid = deviceID; sendData.version = gameVersion; sendData.platform = platform; sendData.channel = channel; Dictionary headData = PostHeader.PostHeaderInfo(new Dictionary { { "event_name", sendData.event_name }, //string { "event_key", sendData.event_key }, { "event_value", sendData.event_value }, { "tag", sendData.tag }, { "game_id", sendData.game_id.ToString() }, { "actor_id", sendData.actor_id }, //string { "details", sendData.details }, //json { "uuid", sendData.uuid }, //string { "model", sendData.model }, //string { "version", sendData.version }, //string { "platform", sendData.platform }, //string { "channel", sendData.channel }, //string { "timestamp", DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString() }, }); 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_name; // 标记类型 public string event_key; public string event_value; public string actor_id; public string details; public string version; // 游戏版本 public string tag; // tag: 区分多游戏 public string platform; // 平台:安卓和IOS public string model; // 手机型号 public string uuid; // 设备唯一标识 public string channel; // 渠道 public int game_id; } #endregion }