提交模块化内容

This commit is contained in:
2026-06-04 10:22:38 +08:00
parent 6b8b282347
commit fcf9128dd3
623 changed files with 38437 additions and 2 deletions
+62
View File
@@ -0,0 +1,62 @@
# 🧰 NetKit 模块说明
`NetKit` 是一个网络接口相关工具模块,涵盖心跳、登录、网络核心、数据打点及数据结构等功能。
------
## 🚀 快速开始
1. **初始化网络组件**
```csharp
NetKit.Instance.Init();
```
2. **执行登录请求**
```csharp
LoginKit.Instance.LoginRequest(channel, haveSimCard, (success, loginModel) => {
if (success) {
// 登录成功逻辑
} else {
// 登录失败逻辑
}
});
```
3. **发送事件打点**
```csharp
TrackKit.SendEvent(ADEventTrack.Event, ADEventTrack.Property.fail_click);
```
4. **开启心跳**
```csharp
HeartbeatKit.Instance.StartHeartbeat();
```
------
## 📦 模块介绍
| 模块名 | 功能描述 |
| -------------- | ------------------------------------- |
| `Model` | 📄 请求与响应数据模型定义 |
| `Core/NetKit` | ⚙️ 网络请求核心,管理请求与 Token |
| `Track` | 📊 事件与属性注册及数据打点 |
| `ErrorLogKit` | 🛠️ 异常日志收集及去重上报 |
| `HeartbeatKit` | ❤️ 心跳管理,确保连接持续活跃 |
| `LoginKit` | 🔐 登录请求、认证及状态管理 |
| `NetGmTool` | 🛠️ 本地调试辅助,支持 Token 和状态测试 |
------
## 📖 说明
- **NetKit** 负责所有网络通信,自动处理数据加解密和 Token 刷新。
- **TrackKit** 提供强大的事件打点功能,支持静态及动态事件注册。
- **ErrorLogKit** 实现错误日志的收集、去重与上传,方便问题排查。
- **HeartbeatKit** 管理心跳发送,支持应用前后台切换时的状态保存与恢复。
- **LoginKit** 负责用户登录流程与自动重新认证逻辑。
- **NetGmTool** 为游戏调试提供实用工具,方便状态和 Token 测试。
+7
View File
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 461e8e64590ffe249b3f7c1451512685
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+3
View File
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 377616a3c9914ce282defb0ea95df273
timeCreated: 1750131791
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: c5faf4e0266639349b4b5bf725766915
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,13 @@
using Newtonsoft.Json;
namespace SGModule.NetKit {
public class H5RefreshTimes {
[JsonProperty("link")] public string Link;
[JsonProperty("times")] public int Times;
}
public class H5SendClass {
[JsonProperty("link")] public string Link;
[JsonProperty("type")] public string Type;
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: f1b181d7aace436781161651452781e4
timeCreated: 1750149344
@@ -0,0 +1,22 @@
using Newtonsoft.Json;
namespace SGModule.NetKit {
public class LoginModel {
[JsonProperty("cdn_url")] public string CdnURL;
[JsonProperty("country")] public string Country;
[JsonProperty("debug_log")] public bool DebugLog = true;
[JsonProperty("enwp")] public int Enwp;
[JsonProperty("expires_at")] public long ExpiresAt;
[JsonProperty("invite_code")] public string InviteCode;
[JsonProperty("is_magic")] public bool IsMagic;
[JsonProperty("last_login_time")] public long LastLoginTime;
[JsonProperty("login_time")] public long LoginTime;
[JsonProperty("new_player")] public bool NewPlayer;
[JsonProperty("play_data")] public string PlayData;
[JsonProperty("play_data_ver")] public long PlayDataVer;
[JsonProperty("reg_time")] public long RegTime;
[JsonProperty("setting")] public string Setting;
[JsonProperty("token")] public string Token;
[JsonProperty("uid")] public long Uid;
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: eb0f97d9dd47436985c16d162e2685f3
timeCreated: 1750149344
@@ -0,0 +1,30 @@
using Newtonsoft.Json;
namespace SGModule.NetKit {
public class OrderModel {
}
public class PayOutUserInfoData {
[JsonProperty("email")] public string Email;
[JsonProperty("first_name")] public string FirstName;
[JsonProperty("last_name")] public string LastName;
}
public class PayerData {
[JsonProperty("amount")] public int Amount;
[JsonProperty("email")] public string Email;
[JsonProperty("name")] public string Name;
[JsonProperty("tel")] public string Tel;
}
public class OrderData {
[JsonProperty("code")] public int Code;
[JsonProperty("order_id")] public string OrderID;
[JsonProperty("pay_url")] public string PayURL;
}
public class OrderState {
[JsonProperty("order_id")] public string OrderID;
[JsonProperty("status")] public int Status;
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 46a43334a3ce46a19994cde06807db90
timeCreated: 1750149344
@@ -0,0 +1,16 @@
using Newtonsoft.Json;
namespace SGModule.NetKit {
public class RequestLoginData {
[JsonProperty("app_version")] public string AppVersion;
[JsonProperty("channel")] public string Channel;
[JsonProperty("device_id")] public string DeviceID;
[JsonProperty("pack_name")] public string PackName;
[JsonProperty("sim")] public bool Sim;
}
public class RequestTokenData {
[JsonProperty("expires_at")] public long ExpiresAt;
[JsonProperty("token")] public string Token;
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 1fbee719fd2e44ef80b7316d0dbf86bc
timeCreated: 1750149344
@@ -0,0 +1,8 @@
using Newtonsoft.Json;
namespace SGModule.NetKit {
public class RequestResultData {
[JsonProperty("result")] public string Result;
[JsonProperty("type")] public int Type;
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 47baead934174984ba39556242043834
timeCreated: 1750149344
@@ -0,0 +1,8 @@
using Newtonsoft.Json;
namespace SGModule.NetKit {
public class RequestSavePlayData {
[JsonProperty("data")] public string Data;
[JsonProperty("version")] public long Version;
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 0be72479fa3145a08ea156a655a15439
timeCreated: 1750149344
@@ -0,0 +1,15 @@
using Newtonsoft.Json;
namespace SGModule.NetKit {
public class RequestStageData {
[JsonProperty("begin_time")] public int BeginTime;
[JsonProperty("end_time")] public int EndTime;
[JsonProperty("item_costs")] public int[] ItemCosts;
[JsonProperty("item_type")] public int ItemType;
[JsonProperty("layer")] public int Layer;
[JsonProperty("level")] public int Level;
[JsonProperty("pass")] public bool Pass;
[JsonProperty("remove_item")] public int RemoveItem;
[JsonProperty("total_item")] public int TotalItem;
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 74a78e3c4c5342d58c8b7c7f8fa1ebac
timeCreated: 1750149344
@@ -0,0 +1,17 @@
using Newtonsoft.Json;
namespace SGModule.NetKit {
public class RespDebugData {
[JsonProperty("channel")] public string Channel;
[JsonProperty("device")] public string Device;
[JsonProperty("device_id")] public string DeviceID;
[JsonProperty("level")] public string Level;
[JsonProperty("message")] public string Message;
[JsonProperty("network")] public string Network;
[JsonProperty("os_ver")] public string OSVer;
[JsonProperty("pack_name")] public string PackName;
[JsonProperty("stacktrace")] public string Stacktrace;
[JsonProperty("uid")] public long Uid;
[JsonProperty("version")] public string Version;
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d6e06882fc534baeb5b28e1c969c9f3b
timeCreated: 1750149344
@@ -0,0 +1,14 @@
using Newtonsoft.Json;
namespace SGModule.NetKit {
public class RespLoginFunnelData {
[JsonProperty("channel")] public string Channel;
[JsonProperty("device_id")] public string DeviceID;
[JsonProperty("pack_name")] public string PackName;
[JsonProperty("payload")] public string Payload;
[JsonProperty("trace_id")] public string TraceID;
[JsonProperty("type")] public string Type;
[JsonProperty("uid")] public long Uid;
[JsonProperty("version")] public string Version;
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 74c68fa36c4649c283f499dbc71204cb
timeCreated: 1750149344
@@ -0,0 +1,9 @@
using Newtonsoft.Json;
namespace SGModule.NetKit {
public class TrackData {
[JsonProperty("event")] public string Event;
[JsonProperty("n")] public int N;
[JsonProperty("property")] public string Property;
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 07578d2c40f54591b67a1897fea057f7
timeCreated: 1750149344
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a4e6e38198f91e545ae7b37e06c9a7de
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 817656c157454e70a7bf6677e6f2eb34
timeCreated: 1750318367
@@ -0,0 +1,96 @@
using System.Collections;
using System.Collections.Generic;
using SGModule.Common;
using SGModule.Common.Helper;
using SGModule.Net;
using UnityEngine.Events;
namespace SGModule.NetKit {
public class NetKit : NetCore<NetKit> {
public void Init() {
Log.NetKit.Info("NetworkKit Init!!!!!");
}
protected override string Encrypt(string text, string key) {
return ConfigManager.GameConfig.isRelease ? Cryptor.Encrypt(text, key) : text;
}
protected override string Decrypt(string text, string key) {
return ConfigManager.GameConfig.isRelease ? Cryptor.Decrypt(text, key) : text;
}
protected override TokenManager.RefreshTokenDelegate RefreshTokenHandler() {
return onCompleted => PostAsync<RequestTokenData>(
"tokenRefresh",
onCompleted: response => {
if (response.IsSuccess) {
var token = response.Data.Token;
var expiresAt = response.Data.ExpiresAt;
onCompleted?.Invoke(token, expiresAt);
}
else {
onCompleted?.Invoke(null, 0);
}
});
}
protected override TokenManager.ReauthenticateDelegate ReauthenticateHandler() {
return onCompleted => LoginKit.Instance.ReauthenticateOnExpiration(success => {
if (success) {
var model = LoginKit.Instance.LoginModel;
onCompleted?.Invoke(model.Token, model.ExpiresAt);
}
else {
onCompleted?.Invoke(null, 0);
}
});
}
protected override NetCoreModel ProvideConfig() {
var netConf = ConfigManager.NetworkConfig;
return new NetCoreModel {
RequestHost = ConfigManager.GameConfig.isRelease ? netConf.releaseHost : netConf.debugHost,
ConnMode = netConf.connectionMode
};
}
/// <summary>
/// Post请求(异步)
/// </summary>
/// <param name="path">请求路径</param>
/// <param name="requestData">请求参数</param>
/// <param name="onCompleted">请求回调</param>
/// <param name="withToken">是否带Token</param>
/// <param name="headers">额外的请求头</param>
public new IEnumerator PostAsync<T>(string path, object requestData = null, UnityAction<ResponseData<T>> onCompleted = null, bool withToken = true, Dictionary<string, string> headers = null) {
return base.PostAsync(path, requestData, onCompleted, withToken, headers);
}
/// <summary>
/// Post请求
/// </summary>
/// <param name="path">请求路径</param>
/// <param name="requestData">请求参数</param>
/// <param name="onCompleted">请求回调</param>
/// <param name="withToken">是否带Token</param>
/// <param name="useCoroutine">是否使用协程</param>
/// <param name="headers">额外的请求头</param>
public void Post(string path, object requestData = null, UnityAction<ResponseData<object>> onCompleted = null, bool withToken = true, bool useCoroutine = true, Dictionary<string, string> headers = null) {
base.Post(path, requestData, onCompleted, withToken, useCoroutine, headers);
}
/// <summary>
/// Post请求(泛型)
/// </summary>
/// <param name="path">请求路径</param>
/// <param name="requestData">请求参数</param>
/// <param name="onCompleted">请求回调</param>
/// <param name="withToken">是否带Token</param>
/// <param name="useCoroutine">是否使用协程</param>
/// <param name="headers">额外的请求头</param>
/// <typeparam name="T">返回数据中Data的类型</typeparam>
public new void Post<T>(string path, object requestData = null, UnityAction<ResponseData<T>> onCompleted = null, bool withToken = true, bool useCoroutine = true, Dictionary<string, string> headers = null) {
base.Post(path, requestData, onCompleted, withToken, useCoroutine, headers);
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 2f42dbabd49047c1af47fd8e5b5e32c7
timeCreated: 1750148161
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 701ea16802aa4ad5b5655d0f7e785345
timeCreated: 1750318405
@@ -0,0 +1,85 @@
using System;
using System.Collections.Generic;
using SGModule.Common.Helper;
using UnityEngine;
using UnityEngine.Events;
namespace SGModule.NetKit {
public static class ErrorLogKit {
private static readonly Dictionary<string, Dictionary<string, bool>> _statusDic = new();
private static bool _isErrorLoggingEnabled = true;
/// <summary>
/// 初始化方法
/// </summary>
/// <param name="isErrorLoggingEnabled">是否开启错误日志上报</param>
public static void Init(bool isErrorLoggingEnabled) {
_isErrorLoggingEnabled = isErrorLoggingEnabled;
}
/// <summary>
/// 发送错误日志
/// </summary>
/// <param name="level"></param>
/// <param name="message"></param>
/// <param name="stackTrace"></param>
/// <param name="attribution"></param>
public static void Send(string level, string message, string stackTrace, string attribution = "organic") {
if (!_isErrorLoggingEnabled) {
return;
}
// 如果只需要日期部分,可以使用ToShortDateString()或ToString("yyyy-MM-dd")等进行格式化
var currentDate = DateTime.Now;
var formattedDate = currentDate.ToString("yyyy_MM_dd_HH_mm");
var md5Str = MD5Helper.GetStringMD5(message + stackTrace);
if (!_statusDic.ContainsKey(formattedDate)) {
_statusDic.Add(formattedDate, new Dictionary<string, bool>());
}
if (_statusDic[formattedDate].ContainsKey(md5Str)) {
return;
}
_statusDic[formattedDate].Add(md5Str, true);
SendLogToServer(level, message, stackTrace, attribution);
}
public static void SendDebugToServer(string error, string stackTrace = "")
{
Send("debug", error, stackTrace);
}
private static void SendLogToServer(string level, string message, string stackTrace, string attribution, UnityAction<bool> callback = null) {
var requestData = new RespDebugData {
Channel = attribution,
Uid = LoginKit.Instance.LoginModel?.Uid ?? -1,
Device = SystemInfo.deviceModel,
OSVer = SystemInfo.operatingSystem,
Network = GetNetworkType(),
DeviceID = DeviceHelper.GetDeviceID(),
PackName = Application.identifier,
Version = Application.version,
Level = level,
Message = message,
Stacktrace = stackTrace
};
NetKit.Instance.Post<RespDebugData>("event/cliDebugLog", requestData, response => {
callback?.Invoke(response.IsSuccess);
}, false);
}
private static string GetNetworkType() {
return Application.internetReachability switch {
NetworkReachability.ReachableViaCarrierDataNetwork => "Mobile Data",
NetworkReachability.ReachableViaLocalAreaNetwork => "Wi-Fi",
_ => "Not Connected"
};
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 7a44a0584561409fbbe702a3b9f563f4
timeCreated: 1731321384
@@ -0,0 +1,129 @@
using System;
using System.Collections;
using SGModule.Common;
using SGModule.Common.Base;
using SGModule.Common.Helper;
using SGModule.Net;
using UnityEngine;
namespace SGModule.NetKit {
public class HeartbeatKit : SingletonMonoBehaviour<HeartbeatKit> {
private Coroutine _heartbeatCoroutine;
private static int HeartbeatInterval => 60;
private const string HeartbeatForegroundTime = "HeartbeatForegroundTime";
/// <summary>
/// 后台时逻辑
/// </summary>
/// <param name="pauseStatus"></param>
private void OnApplicationPause(bool pauseStatus) {
Log.NetKit.Warning($"OnApplication Pause :{pauseStatus}");
// 应用程序失去焦点
if (pauseStatus) {
SaveRemainingHeartbeatTime();
}
else {
RestoreNextHeartbeatTime();
}
}
// 应用程序退出
private void OnApplicationQuit() {
Log.NetKit.Warning("OnApplication Quit");
SaveRemainingHeartbeatTime();
}
public void StartHeartbeat() {
_heartbeatCoroutine??= StartCoroutine(HeartbeatRequestCoroutine());
}
private IEnumerator HeartbeatRequestCoroutine() {
RestoreNextHeartbeatTime(); // 恢复之前剩余时间或默认值
while (true) {
if (Now() >= NextHeartbeatTime) {
NextHeartbeatTime += HeartbeatInterval;
RequestHeart();
}
yield return new WaitForSeconds(1);
}
}
// 应用程序失去焦点, 保存心跳剩余时间
private void SaveRemainingHeartbeatTime() {
var remain = Math.Max((int) (NextHeartbeatTime - Now()), 0);
LastRemainingInterval = remain % HeartbeatInterval;
}
// 应用程序获得焦点, 恢复下次心跳时间
private void RestoreNextHeartbeatTime() {
var now = Now();
if (LastRemainingInterval < 0) {
NextHeartbeatTime = now;
return;
}
var remain = LastRemainingInterval % HeartbeatInterval;
if (remain <= 0) {
remain = HeartbeatInterval;
}
NextHeartbeatTime = now + remain;
}
private long _nextHeartbeatTime;
private long NextHeartbeatTime {
get {
if (_nextHeartbeatTime <= 0) {
_nextHeartbeatTime = Now() + HeartbeatInterval;
}
return _nextHeartbeatTime;
}
set {
var now = Now();
if (value <= now) {
value = now + HeartbeatInterval;
}
_nextHeartbeatTime = value;
}
}
private int? _lastRemainingInterval;
private int LastRemainingInterval {
get {
if (_lastRemainingInterval != null) {
return _lastRemainingInterval.Value;
}
_lastRemainingInterval = PlayerPrefs.GetInt(HeartbeatForegroundTime, -1);
return _lastRemainingInterval.Value;
}
set {
_lastRemainingInterval = value;
PlayerPrefs.SetInt(HeartbeatForegroundTime, value);
}
}
private void RequestHeart() {
if (ConfigManager.NetworkConfig.connectionMode == ConnectionMode.WebSocket) {
WebSocketService.Instance.SendHeartbeat();
}
else {
NetKit.Instance.Post("user/health");
}
}
private static long Now() {
return DateTimeOffset.UtcNow.ToUnixTimeSeconds();
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: efd2140efaf44544a7879ad411a0cf84
timeCreated: 1750147028
@@ -0,0 +1,127 @@
using System.Collections;
using SGModule.Common;
using SGModule.Common.Base;
using SGModule.Common.Helper;
using SGModule.Net;
using UnityEngine;
using UnityEngine.Events;
namespace SGModule.NetKit
{
public class LoginKit : SingletonMonoBehaviour<LoginKit>
{
private string _channel;
private bool _sim;
private bool _loginSuccess;
private LoginModel _loginModel;
private bool _isFirstLoginSuccess = true;
private RequestLoginData _requestLoginData;
private string _packName;
public string Channel => _channel;
public void LoginRequest(string channel, bool haveSimCard, UnityAction<bool, LoginModel> onCompleted)
{
_packName = ConfigManager.GameConfig.packageName;
_channel = string.IsNullOrEmpty(channel) ? "organic" : channel;
_sim = haveSimCard;
_requestLoginData = new RequestLoginData
{
DeviceID = DeviceHelper.GetDeviceID(),
AppVersion = Application.version,
PackName = _packName,
Channel = _channel,
Sim = _sim
};
StartCoroutine(LoginCoroutine(_requestLoginData, (isSuccess, loginModel) =>
{
if (isSuccess)
{
OnLoginSuccess();
}
onCompleted?.Invoke(isSuccess, loginModel);
}));
}
public IEnumerator ReauthenticateOnExpiration(UnityAction<bool> onCompleted)
{
if (_requestLoginData == null)
{
Log.NetKit.Error("登录数据为空,重新登录失败");
onCompleted?.Invoke(false);
yield break;
}
yield return LoginCoroutine(_requestLoginData, (isSuccess, _) =>
{
onCompleted?.Invoke(isSuccess);
});
}
private IEnumerator LoginCoroutine(RequestLoginData reqData, UnityAction<bool, LoginModel> onCompleted)
{
const int maxRetry = 5;
for (var attempt = 0; attempt < maxRetry; attempt++)
{
if (attempt > 0)
{
Log.NetKit.Warning($"登录失败,重试第 {attempt} 次...");
yield return new WaitForSeconds(5f);
}
yield return DoLoginRequest(reqData);
if (_loginSuccess)
{
onCompleted?.Invoke(true, _loginModel);
yield break;
}
}
// Log.NetKit.Error("登录最终失败");
onCompleted?.Invoke(false, null);
}
private IEnumerator DoLoginRequest(RequestLoginData reqData)
{
yield return NetKit.Instance.PostAsync<LoginModel>(
"login",
reqData,
response =>
{
_loginSuccess = response.IsSuccess;
_loginModel = response.Data;
},
withToken: false
);
}
private void OnLoginSuccess()
{
Log.NetKit.Info("登录成功,处理登录后逻辑");
ServerClock.Init(_loginModel.LoginTime);
Net.TokenManager.Instance.UpdateTokenCache(_loginModel.Token, _loginModel.ExpiresAt);
ErrorLogKit.Init(_loginModel.DebugLog);
// 开始心跳
HeartbeatKit.Instance.StartHeartbeat();
StartCoroutine(TrackKit.NoSentTrackProcessing()); // 处理堆积的打点数据
}
public LoginModel LoginModel => _loginModel ??= new LoginModel();
public bool LoginSuccess => _loginSuccess;
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c4397a02039f4461bd27548285617aa2
timeCreated: 1731380564
@@ -0,0 +1,51 @@
using SGModule.Common.Base;
using SGModule.Common.Helper;
using SGModule.Net;
namespace SGModule.NetKit {
public class NetGmTool : SingletonMonoBehaviour<NetGmTool> {
public void Init() {
GMTool.Instance.AddItem(new GMToolItem(GUIType.Separator, () => "网络模块"));
GMTool.Instance.AddItem(
new GMToolItem(GUIType.Label, () => $"当前网络状态:{NetChecker.Instance.GetConnectionStatus()}"));
GMTool.Instance.AddItem(new GMToolItem(GUIType.Label,
() => $"loginModel uid{(LoginKit.Instance.LoginModel == null ? "null" : LoginKit.Instance.LoginModel.Uid)}"));
GMTool.Instance.AddItem(new GMToolItem(GUIType.Label,
() =>
$"Token过期时间:{TimeHelper.ConvertToLocalTime(TokenManager.Instance.CachedExpiresAt)}"));
GMTool.Instance.AddItem(new GMToolItem(GUIType.Label,
() => {
var str = "";
foreach (var record in Net.TokenManager.Instance.GetTokenRecord()) {
str += $"Token: {record} " + "\n";
}
return str;
})
);
GMTool.Instance.AddItem(new GMToolItem(GUIType.Button, () => "Token刷新测试",
s => {
VerifyTokenExpiryImminent();
}));
GMTool.Instance.AddItem(new GMToolItem(GUIType.Button, () => "Token过期测试",
s => {
VerifyTokenExpirationBehavior();
}));
}
/// <summary>
/// Token即将过期(测试功能用)
/// </summary>
public void VerifyTokenExpiryImminent() {
TokenManager.Instance.CachedExpiresAt -= 85400;
}
/// <summary>
/// token过期(测试功能用)
/// </summary>
public void VerifyTokenExpirationBehavior() {
TokenManager.Instance.CachedExpiresAt -= 86400;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4fc2222765694fea9075b15bca3cb0ac
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: fd3b50565e714d6f8e3ce3b99665ba08
timeCreated: 1750318517
@@ -0,0 +1,46 @@
using System.Collections.Generic;
using System.Linq;
using SGModule.Common.Helper;
namespace SGModule.NetKit {
public static class TrackEvent {
private static readonly HashSet<string> _registeredStaticEvents = new();
private static readonly HashSet<string> _registeredDynamicEventPrefixes = new();
public static string Register(string eventKey, bool isDynamic = false) {
var targetSet = isDynamic ? _registeredDynamicEventPrefixes : _registeredStaticEvents;
if (!targetSet.Add(eventKey))
{
var type = isDynamic ? "动态" : "静态";
Log.NetKit.Error($"[Track] 重复的{type} Event 注册: {eventKey}");
}
return eventKey;
}
/// <summary>
/// 是否为注册的静态 property(完全匹配)
/// </summary>
private static bool ContainsExact(string eventKey) {
return _registeredStaticEvents.Contains(eventKey);
}
/// <summary>
/// 是否匹配任何已注册的动态前缀
/// </summary>
private static bool ContainsDynamic(string eventKey) {
return _registeredDynamicEventPrefixes.Any(eventKey.StartsWith);
}
/// <summary>
/// 检查是否存在(静态或动态)
/// </summary>
public static bool Contains(string eventKey) {
return ContainsExact(eventKey) || ContainsDynamic(eventKey);
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b125ff11596c4c639f9b69eedf446e6b
timeCreated: 1750319472
@@ -0,0 +1,139 @@
using System;
using System.Collections;
using System.Collections.Generic;
using SGModule.Common;
using SGModule.Common.Helper;
using UnityEngine;
using UnityEngine.Events;
namespace SGModule.NetKit {
public static class TrackKit {
private static string _traceID;
private static readonly Dictionary<string, bool> _statusDic = new();
//登录前的打点数据,没有token无法正确发送,登录成功后重新发一遍
private static readonly Queue<TrackData> _waitLoginSuccessTrackQueue = new();
private static void PostTrack(TrackData trackData, UnityAction<bool, TrackData> callback = null) {
if (!LoginKit.Instance.LoginSuccess) {
_waitLoginSuccessTrackQueue.Enqueue(trackData);
return;
}
NetKit.Instance.Post<TrackData>("/event/incrN", trackData,
response => {
callback?.Invoke(response.IsSuccess, response.Data);
});
}
/// <summary>
/// 发送未发送的打点数据
/// </summary>
/// <returns></returns>
public static IEnumerator NoSentTrackProcessing() {
//处理未发送的打点数据
while (_waitLoginSuccessTrackQueue.Count > 0) {
var trackData = _waitLoginSuccessTrackQueue.Dequeue();
PostTrack(trackData);
yield return new WaitForSeconds(1f);
}
_waitLoginSuccessTrackQueue.Clear();
}
public static void SendEvent(string @event, string property = "", int n = 1) {
// 检查Key是否注册
if (!TrackEvent.Contains(@event)) {
Log.NetKit.Warning($"Not found event key: {@event}");
return;
}
if (!TrackProperty.Contains(property)) {
Log.NetKit.Warning($"Not found property key: {property}");
return;
}
var data = new TrackData { Event = @event, Property = property, N = n };
Log.NetKit.Info($"Sending track {data.Event} | {data.Property} | {data.N}");
PostTrack(data);
}
#region
private static readonly Dictionary<LoginFunnelEventType, string> _loginFunnelEventMap = new() {
{ LoginFunnelEventType.Bootstrap, "bootstrap" },
{ LoginFunnelEventType.AfSend, "afSend" },
{ LoginFunnelEventType.AfRecv, "afRecv" },
{ LoginFunnelEventType.LoginSend, "loginSend" },
{ LoginFunnelEventType.LoginRecv, "loginRecv" },
{ LoginFunnelEventType.LoadBegin, "loadBegin" },
{ LoginFunnelEventType.LoadFinish, "loadFinish" },
{ LoginFunnelEventType.EnterButtonShow, "enterButtonShow" },
{ LoginFunnelEventType.EnterButtonClick, "enterButtonClick" },
{ LoginFunnelEventType.EnterHall, "enterHall" },
};
/// <summary>
/// 登录漏斗打点
/// </summary>
/// <param name="loginFunnelEventType"></param>
/// <param name="message"></param>
/// <exception cref="ArgumentOutOfRangeException"></exception>
public static void TrackLoginFunnel(LoginFunnelEventType loginFunnelEventType, string message = "") {
if (!_loginFunnelEventMap.TryGetValue(loginFunnelEventType, out var trackName)) {
throw new ArgumentOutOfRangeException(nameof(loginFunnelEventType), loginFunnelEventType, null);
}
if (_statusDic.TryGetValue(trackName, out var status)) {
if (status) {
return;
}
}
_statusDic.Add(trackName, true);
PostFunnelLogin(trackName, message);
}
private static void PostFunnelLogin(string type, string payload) {
if (type == "bootstrap") {
var timestamp = DateTime.Now.ToUniversalTime().Ticks - 621355968000000000;
_traceID = timestamp.ToString();
}
var requestData = new RespLoginFunnelData {
Uid = LoginKit.Instance.LoginModel?.Uid ?? -1,
TraceID = _traceID,
DeviceID = DeviceHelper.GetDeviceID(),
PackName = ConfigManager.GameConfig.packageName,
Version = Application.version,
Channel = LoginKit.Instance.Channel,
Type = type,
Payload = payload
};
NetKit.Instance.Post("event/funnelLogin", requestData, withToken: false);
}
#endregion
}
public enum LoginFunnelEventType {
Bootstrap,
AfSend,
AfRecv,
LoginSend,
LoginRecv,
LoadBegin,
LoadFinish,
EnterButtonShow,
EnterButtonClick,
EnterHall
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 26c30475fb634d57b705e283a0f5b943
timeCreated: 1731395165
@@ -0,0 +1,43 @@
using System.Collections.Generic;
using System.Linq;
using SGModule.Common.Helper;
namespace SGModule.NetKit {
public static class TrackProperty {
private static readonly HashSet<string> _registeredStaticProperties = new();
private static readonly HashSet<string> _registeredDynamicPropertyPrefixes = new();
public static string Register(string propertyKey, bool isDynamic = false) {
var targetSet = isDynamic ? _registeredDynamicPropertyPrefixes : _registeredStaticProperties;
if (!targetSet.Add(propertyKey)) {
var type = isDynamic ? "动态" : "静态";
Log.NetKit.Error($"[Track] 重复的{type} Property 注册: {propertyKey}");
}
return propertyKey;
}
/// <summary>
/// 是否为注册的静态 property(完全匹配)
/// </summary>
private static bool ContainsExact(string propertyKey) {
return _registeredStaticProperties.Contains(propertyKey);
}
/// <summary>
/// 是否匹配任何已注册的动态前缀
/// </summary>
private static bool ContainsDynamic(string propertyKey) {
return _registeredDynamicPropertyPrefixes.Any(propertyKey.StartsWith);
}
/// <summary>
/// 检查是否存在(静态或动态)
/// </summary>
public static bool Contains(string propertyKey) {
return ContainsExact(propertyKey) || ContainsDynamic(propertyKey);
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 3265ffa2bf4b44cfbb3ee93a91692c59
timeCreated: 1750319513
@@ -0,0 +1,136 @@
using SGModule.Common.Helper;
using UnityEngine.Events;
namespace SGModule.NetKit {
public static class NetApi {
#region
public static void RequestPlayerData(UnityAction<bool, string> onCompleted) {
NetKit.Instance.Post<string>("user/userData", onCompleted: response => {
onCompleted?.Invoke(response.IsSuccess, response.Data);
});
}
/// <summary>
/// 立即发送请求更新用户数据
/// </summary>
/// <param name="version"></param>
/// <param name="data"></param>
public static void UploadPlayerDataUpdate(long version, string data) {
var requestSavePlayData = new RequestSavePlayData {
Version = version,
Data = data
};
NetKit.Instance.Post("user/updateData", requestSavePlayData, useCoroutine: false);
}
/// <summary>
/// 启动协程更新用户数据
/// </summary>
/// <param name="version"></param>
/// <param name="data"></param>
/// <param name="onCompleted"></param>
public static void UploadPlayerDataUpdate(long version, string data, UnityAction<bool> onCompleted) {
var requestSavePlayData = new RequestSavePlayData {
Version = version,
Data = data
};
NetKit.Instance.Post<object>("user/updateData", requestSavePlayData,
response => {
onCompleted?.Invoke(response.IsSuccess);
});
}
public static void DeltaUpdateData(long version, string data, UnityAction<bool> onCompleted) {
var requestSavePlayData = new RequestSavePlayData {
Version = version,
Data = data
};
NetKit.Instance.Post<object>("user/deltaUpdateData", requestSavePlayData,
response => {
onCompleted?.Invoke(response.IsSuccess);
});
}
#endregion
#region Pay
public static void PixPayIn<T>(string account, string phone, string email, int amount,
UnityAction<bool, T> onCompleted = null) {
var data = new PayerData {
Name = account,
Tel = phone,
Email = email,
Amount = amount
};
NetKit.Instance.Post<T>("shop/pixPayIn", data,
response => {
onCompleted?.Invoke(response.IsSuccess, response.Data);
});
}
public static void PixPayOrderQuery<T>(string orderID, UnityAction<bool, T> onCompleted = null) {
var data = new OrderData {
OrderID = orderID
};
NetKit.Instance.Post<T>("shop/pixPayOrderQuery", data,
response => {
onCompleted?.Invoke(response.IsSuccess, response.Data);
});
}
public static void PayOutUserInfo<T>(string email, string firstName, string lastName,
UnityAction<bool, T> onCompleted = null) {
var data = new PayOutUserInfoData {
Email = email,
FirstName = firstName,
LastName = lastName
};
NetKit.Instance.Post<T>("shop/payOutUserInfo", data,
response => {
onCompleted?.Invoke(response.IsSuccess, response.Data);
});
}
#endregion
#region H5
public static void H5RefreshTimes<T>(string link, string type, UnityAction<bool, T> onCompleted = null) {
var info = new H5SendClass {
Link = link,
Type = type
};
NetKit.Instance.Post<T>("event/h5Impressions", info,
response => {
onCompleted?.Invoke(response.IsSuccess, response.Data);
});
}
#endregion
#region
public static void SettleUp(int gameType, RequestStageData stageData, UnityAction<bool> onCompleted = null) {
var requestJson = SerializeHelper.ToJsonIndented(stageData);
var reqData = new RequestResultData {
Type = gameType,
Result = requestJson
};
NetKit.Instance.Post<object>("game/settleUp", reqData, response => {
onCompleted?.Invoke(response.IsSuccess);
});
}
#endregion
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 518e3aed166f4dce98dbaed4a0dca443
timeCreated: 1750316780