feat(upserver): 接入认证与KV存档并完成限定钓鱼闭环

This commit is contained in:
Liubing\LB
2026-09-28 18:14:01 +08:00
parent f40a816cc4
commit 0eb43b2b40
129 changed files with 11612 additions and 573 deletions

View File

@@ -5,6 +5,7 @@ using System.Threading.Tasks;
using UnityEngine;
using System.Net;
using asap.core;
using Microsoft.AspNetCore.Http.Connections;
using Microsoft.AspNetCore.SignalR.Client;
using Newtonsoft.Json;
using UniRx;
@@ -22,184 +23,159 @@ public interface IRTService
public Task<string> Publish(string methodName, string arg1, string arg2);
//public void Reset();
public void Release();
public Task ReleaseAsync();
}
public class SignalRService : IRTService
{
private HubConnection _hubConnection;
private ICustomServerMgr _serverMgr;
private HashSet<Type> _bindedEvent;
private IEventAggregator _eventAgg;
private string _url;
private Dictionary<string, Action<string>> _cachedEvents;
private readonly ICustomServerMgr _serverMgr;
private readonly IEventAggregator _eventAgg = new EventAggregator();
private readonly string _url;
private readonly Dictionary<string, Action<string>> handlers = new Dictionary<string, Action<string>>();
private readonly System.Threading.SemaphoreSlim lifecycle = new System.Threading.SemaphoreSlim(1, 1);
private IDisposable loginSubscription;
private bool released;
private int connectionVersion;
private System.Threading.CancellationTokenSource connecting;
public SignalRService(ICustomServerMgr serverMgr, IConfig config)
{
this._serverMgr = serverMgr;
var baseUrl = config.Get<string>(GConstant.K_Event_API_URL, string.Empty);
#if UNITY_EDITOR
UnityEngine.Assertions.Assert.IsTrue(!string.IsNullOrEmpty(baseUrl));
#endif
_url = baseUrl + "gamehub";
_eventAgg = new EventAggregator();
_cachedEvents = new Dictionary<string, Action<string>>();
_bindedEvent = new HashSet<Type>();
_serverMgr = serverMgr;
_url = new Uri(serverMgr.ApiBaseUri, "gamehub").AbsoluteUri;
_serverMgr.SessionChanged += OnSessionChanged;
}
public void WaitForAPISvrLogin()
{
GContext.OnEvent<game.EvtAPISvrLoginSuccess>().Subscribe(OnApiSvrLoginSuccess);
if (released || loginSubscription != null) return;
loginSubscription = GContext.OnEvent<EvtAPISvrLoginSuccess>().Subscribe(OnApiSvrLoginSuccess);
}
private async void OnApiSvrLoginSuccess(EvtAPISvrLoginSuccess evt)
{
try{
Reset();
await Connect();
}
catch (Exception ex)
{
Debug.LogException(ex);
}
try { await Connect(); }
catch (Exception ex) { Debug.LogException(ex); }
}
public void Release()
private async void OnSessionChanged()
{
if (_hubConnection != null)
{
_hubConnection.StopAsync();
_hubConnection.DisposeAsync();
_hubConnection = null;
}
if (_bindedEvent != null)
{
_bindedEvent.Clear();
_bindedEvent = null;
}
if (_eventAgg != null)
{
_eventAgg = null;
}
if(_cachedEvents != null)
{
_cachedEvents.Clear();
_cachedEvents = null;
}
connectionVersion++;
connecting?.Cancel();
try { await Disconnect(); }
catch (Exception ex) { Debug.LogException(ex); }
}
private void Reset()
private async Task CloseConnection()
{
if (_hubConnection != null)
{
_hubConnection.StopAsync();
_hubConnection.DisposeAsync();
_hubConnection = null;
}
var cookie = _serverMgr.Cookie;
_hubConnection = new HubConnectionBuilder()
.WithUrl(_url, option => option.Cookies = cookie)
.WithAutomaticReconnect()
.Build();
_hubConnection.Reconnected += OnHubConnected;
_hubConnection.On<string,string, string>("ForceQuit", ForceQuit);
if (_bindedEvent != null)
{
_bindedEvent.Clear();
}
else
{
_bindedEvent = new HashSet<Type>();
}
var previous = _hubConnection;
_hubConnection = null;
if (previous == null) return;
try { await previous.StopAsync(); }
finally { await previous.DisposeAsync(); }
}
private Task OnHubConnected(string connectionId)
public async void Release()
{
if(_cachedEvents.Count > 0)
{
foreach(var kv in _cachedEvents)
{
_hubConnection.On<string>(kv.Key, kv.Value);
}
_cachedEvents.Clear();
}
_hubConnection.Reconnected -= OnHubConnected;
return Task.CompletedTask;
try { await ReleaseAsync(); }
catch (Exception ex) { Debug.LogException(ex); }
}
public bool IsConnected => _hubConnection != null && _hubConnection.State == HubConnectionState.Connected;
public async Task ReleaseAsync()
{
released = true;
connectionVersion++;
connecting?.Cancel();
loginSubscription?.Dispose();
loginSubscription = null;
_serverMgr.SessionChanged -= OnSessionChanged;
await lifecycle.WaitAsync();
try { await CloseConnection(); handlers.Clear(); }
finally { lifecycle.Release(); }
}
private bool IsCurrent(HubConnection hub, int version, int session) =>
!released && version == connectionVersion && session == _serverMgr.SessionVersion && ReferenceEquals(hub, _hubConnection);
public bool IsConnected => !released && _hubConnection != null && _hubConnection.State == HubConnectionState.Connected;
public async Task Connect()
{
if(_hubConnection == null)
var session = _serverMgr.SessionVersion;
var version = connectionVersion;
var cookies = _serverMgr.Cookie;
await lifecycle.WaitAsync();
try
{
Debug.LogWarning("[RTService] hubConnection is null");
return;
if (released || version != connectionVersion || session != _serverMgr.SessionVersion) return;
if (_hubConnection != null && _hubConnection.State != HubConnectionState.Disconnected) return;
await CloseConnection();
if (released || version != connectionVersion || session != _serverMgr.SessionVersion) return;
var hub = new HubConnectionBuilder()
.WithUrl(_url, option =>
{
option.Cookies = cookies;
option.SkipNegotiation = true;
option.Transports = HttpTransportType.WebSockets;
})
.WithAutomaticReconnect()
.Build();
_hubConnection = hub;
hub.On<string, string, string>("ForceQuit", (title, info, button) =>
{
if (IsCurrent(hub, version, session)) ForceQuit(title, info, button, version, session);
});
foreach (var handler in handlers)
Bind(hub, version, session, handler.Key, handler.Value);
connecting?.Dispose();
connecting = new System.Threading.CancellationTokenSource();
try { await hub.StartAsync(connecting.Token); }
catch { await CloseConnection(); throw; }
if (!IsCurrent(hub, version, session)) { await CloseConnection(); return; }
Debug.Log("[RTService] Connected to SignalR server True");
}
finally { lifecycle.Release(); }
}
//UnityEngine.Assertions.Assert.IsNotNull(_hubConnection, "[RTService] hubConnection is null");
if (_hubConnection.State == HubConnectionState.Disconnected)
private void Bind(HubConnection hub, int version, int session, string name, Action<string> handler)
{
hub.On<string>(name, data =>
{
await _hubConnection.StartAsync();
Debug.Log($"[RTService] Connected to SignalR server {_hubConnection.State == HubConnectionState.Connected}");
}
if (IsCurrent(hub, version, session)) handler(data);
});
}
public async Task Disconnect()
{
if(_hubConnection == null)
{
Debug.LogWarning("[RTService] hubConnection is null");
return;
}
//UnityEngine.Assertions.Assert.IsNotNull(_hubConnection, "[RTService] hubConnection is null");
if (_hubConnection.State == HubConnectionState.Connected)
{
await _hubConnection.StopAsync();
Debug.Log($"[RTService] Disconnected to SignalR server {_hubConnection.State == HubConnectionState.Disconnected}");
}
await lifecycle.WaitAsync();
try { await CloseConnection(); }
finally { lifecycle.Release(); }
}
public IDisposable OnEvent<T>(Action<T> handler)
{
var methodName = typeof(T).Name;
if(!_bindedEvent.Contains(typeof(T)))
if (released) throw new ObjectDisposedException(nameof(SignalRService));
var name = typeof(T).Name;
if (!handlers.ContainsKey(name))
{
_bindedEvent.Add(typeof(T));
if(IsConnected)
{
_hubConnection.On<string>(methodName, data => {
var result =JsonConvert.DeserializeObject<T>(data);
_eventAgg.Publish(result);
});
}
else
{
_cachedEvents[methodName] = data => {
var result =JsonConvert.DeserializeObject<T>(data);
_eventAgg.Publish(result);
};
}
Action<string> dispatch = data => _eventAgg.Publish(JsonConvert.DeserializeObject<T>(data));
handlers.Add(name, dispatch);
if (_hubConnection != null)
Bind(_hubConnection, connectionVersion, _serverMgr.SessionVersion, name, dispatch);
}
return _eventAgg.GetEvent<T>().Subscribe(handler);
}
public async Task<string> Publish(string methodName, string arg1)
{
var hub = _hubConnection;
var version = connectionVersion;
var session = _serverMgr.SessionVersion;
if (hub == null || !IsCurrent(hub, version, session)) return null;
try
{
return await _hubConnection.InvokeAsync<string>(methodName, arg1);
var result = await hub.InvokeAsync<string>(methodName, arg1);
return IsCurrent(hub, version, session) ? result : null;
}
catch(Exception e)
{
@@ -210,9 +186,14 @@ public class SignalRService : IRTService
public async Task<string> Publish(string methodName, string arg1, string arg2)
{
var hub = _hubConnection;
var version = connectionVersion;
var session = _serverMgr.SessionVersion;
if (hub == null || !IsCurrent(hub, version, session)) return null;
try
{
return await _hubConnection.InvokeAsync<string>(methodName, arg1, arg2);
var result = await hub.InvokeAsync<string>(methodName, arg1, arg2);
return IsCurrent(hub, version, session) ? result : null;
}
catch(Exception e)
{
@@ -221,15 +202,17 @@ public class SignalRService : IRTService
}
}
private async void ForceQuit(string titleKey, string infoKey, string btnKey)
private async void ForceQuit(string titleKey, string infoKey, string btnKey, int version, int session)
{
await Awaiters.NextFrame;
if (released || version != connectionVersion || session != _serverMgr.SessionVersion) return;
GameObject go = await UIManager.Instance.ShowUI(UITypes.NoticeConfirmPopupPanel);
if (released || version != connectionVersion || session != _serverMgr.SessionVersion) return;
var noticeConfirmPopupPanel = go.GetComponent<NoticeConfirmPopupPanel>();
string title = LocalizationMgr.GetText(titleKey ?? "UI_NoticePopupPanel_3");
string info = LocalizationMgr.GetText(infoKey ?? "UI_NoticePopupPanel_4");
string label = LocalizationMgr.GetText(btnKey ?? "UI_COMMON_confirm");
noticeConfirmPopupPanel.Init(1, title, info, onClickLeft: Restart, onClose: Restart, btn_left_text: label);
noticeConfirmPopupPanel.Init(1, title, info, onClickLeft: () => { if (!released && version == connectionVersion && session == _serverMgr.SessionVersion) Restart(); }, onClose: () => { if (!released && version == connectionVersion && session == _serverMgr.SessionVersion) Restart(); }, btn_left_text: label);
}
private async void Restart()