fix:1、更换项目,使用winter来创建
This commit is contained in:
@@ -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 测试。
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 461e8e64590ffe249b3f7c1451512685
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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,111 @@
|
||||
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;
|
||||
|
||||
|
||||
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
|
||||
Reference in New Issue
Block a user