622 lines
19 KiB
C#
622 lines
19 KiB
C#
using Cysharp.Threading.Tasks;
|
||
using Newtonsoft.Json;
|
||
using PhxhSDK;
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using UnityEngine;
|
||
|
||
public partial class BIManager : Singlenton<BIManager>, IInitable, IUpdatable
|
||
{
|
||
/// <summary>
|
||
/// 基础属性
|
||
/// </summary>
|
||
private BIBaseInfo baseInfo;
|
||
|
||
/// <summary>
|
||
/// 关卡属性
|
||
/// </summary>
|
||
private BILevelInfo levelInfo;
|
||
|
||
/// <summary>
|
||
/// BI接口
|
||
/// </summary>
|
||
private IBIService _biService;
|
||
|
||
/// <summary>
|
||
/// 一次性事件缓存类
|
||
/// </summary>
|
||
private StorageEvent _storageEvent;
|
||
|
||
/// <summary>
|
||
/// 是否初始化
|
||
/// </summary>
|
||
private bool _isInit;
|
||
|
||
/// <summary>
|
||
/// 启用自己服务器打点
|
||
/// </summary>
|
||
private bool sendPhxh;
|
||
|
||
/// <summary>
|
||
/// 参数字典
|
||
/// </summary>
|
||
private Dictionary<string, object> _tempEventDictionary = new(12);
|
||
|
||
private readonly string PhxhBiUrl = "https://bi.kedrgame.com/api/v1/events";
|
||
/// <summary>
|
||
/// BI专属accessKey,请勿与中台key混用
|
||
/// </summary>
|
||
private readonly string AccessKey = "yd9kWI798IWyyq1t";
|
||
/// <summary>
|
||
/// BI专属密钥,请勿与中台密钥混用
|
||
/// </summary>
|
||
private readonly string SecretKey = "iQLHTbOqeXtZZpxOZve-PDxTK9DkHPSKRbpXb30eS3g=";
|
||
|
||
#region 初始化
|
||
|
||
/// <summary>
|
||
/// 游戏启动初始化
|
||
/// </summary>
|
||
public void Init()
|
||
{
|
||
baseInfo = new BIBaseInfo();
|
||
levelInfo = new BILevelInfo();
|
||
_storageEvent = StorageMgr.Instance.GetStorage<StorageEvent>(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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 登陆后初始化
|
||
/// </summary>
|
||
/// <param name="name"></param>
|
||
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);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 初始化登陆后基本属性
|
||
/// </summary>
|
||
public void InitLoginBaseInfo()
|
||
{
|
||
baseInfo.SetUserInfo();
|
||
baseInfo.PrintAllValues();
|
||
if (!_isInit || _biService == null) return;
|
||
_biService.SetUser(baseInfo.actor_id);
|
||
SetUserProperty();
|
||
// 发送积累事件
|
||
SendQueuedEvents();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 初始化第三方打点平台
|
||
/// </summary>
|
||
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);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置用户属性
|
||
/// </summary>
|
||
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 周期/队列 发送
|
||
|
||
/// <summary>
|
||
/// 事件队列
|
||
/// </summary>
|
||
private readonly Queue<(string, Dictionary<string, object>)> eventQueue = new();
|
||
|
||
/// <summary>
|
||
/// 是否使用队列模式(无网络或设置了发送周期)
|
||
/// </summary>
|
||
private bool _useQueueMode;
|
||
|
||
/// <summary>
|
||
/// 发送周期 默认随打随发
|
||
/// </summary>
|
||
private float _sendInterval = 0f;
|
||
|
||
/// <summary>
|
||
/// 网络状态 通过心跳包检测
|
||
/// </summary>
|
||
private bool _networkAvailable;
|
||
|
||
/// <summary>
|
||
/// 计时器
|
||
/// </summary>
|
||
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();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查网络状态
|
||
/// </summary>
|
||
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;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置事件发送周期
|
||
/// </summary>
|
||
/// <param name="interval">发送周期,单位秒,设为0则立即发送</param>
|
||
public void SetSendInterval(float interval)
|
||
{
|
||
_sendInterval = Mathf.Max(0, interval);
|
||
_useQueueMode = !_networkAvailable || _sendInterval > 0;
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 发送事件
|
||
|
||
/// <summary>
|
||
/// 添加事件到队列
|
||
/// </summary>
|
||
private void EnqueueEvent(string eventName, Dictionary<string, object> 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));
|
||
}
|
||
|
||
/// <summary>
|
||
/// 发送队列中的所有事件
|
||
/// </summary>
|
||
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);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新关卡信息
|
||
/// </summary>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 上报自定义事件
|
||
/// </summary>
|
||
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);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 上报一次性自定义事件
|
||
/// </summary>
|
||
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);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 上报关卡自定义事件 上报前需要更新关卡信息
|
||
/// </summary>
|
||
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);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 上报一次性关卡自定义事件 上报前需要更新关卡信息
|
||
/// </summary>
|
||
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<string, object> 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<string, string> headData = PostHeader.PostHeaderInfo(new Dictionary<string, string>
|
||
{
|
||
{ "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 对象池
|
||
|
||
/// <summary>
|
||
/// 字典对象池大小
|
||
/// </summary>
|
||
private const int DICT_POOL_SIZE = 32;
|
||
|
||
/// <summary>
|
||
/// 对象池
|
||
/// </summary>
|
||
private List<Dictionary<string, object>> _dictPool;
|
||
|
||
/// <summary>
|
||
/// 对象池索引
|
||
/// </summary>
|
||
private int _dictPoolIndex = 0;
|
||
|
||
/// <summary>
|
||
/// 事件队列最大容量
|
||
/// </summary>
|
||
private const int MAX_QUEUE_SIZE = 1000;
|
||
|
||
|
||
/// <summary>
|
||
/// 初始化字典对象池
|
||
/// </summary>
|
||
private void InitDictPool()
|
||
{
|
||
_dictPool = new List<Dictionary<string, object>>(DICT_POOL_SIZE);
|
||
for (var i = 0; i < DICT_POOL_SIZE; i++)
|
||
{
|
||
_dictPool.Add(new Dictionary<string, object>(16));
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从对象池获取一个字典
|
||
/// </summary>
|
||
private Dictionary<string, object> GetDictFromPool()
|
||
{
|
||
if (_dictPool == null || _dictPool.Count == 0)
|
||
{
|
||
InitDictPool();
|
||
}
|
||
|
||
var dict = _dictPool[_dictPoolIndex];
|
||
dict.Clear();
|
||
_dictPoolIndex = (_dictPoolIndex + 1) % DICT_POOL_SIZE;
|
||
return dict;
|
||
}
|
||
|
||
#endregion
|
||
|
||
} |