290 lines
11 KiB
C#
290 lines
11 KiB
C#
using asap.core;
|
|
using LitJson;
|
|
using System;
|
|
using System.Net;
|
|
using System.Net.Http;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using UnityEngine;
|
|
using game.Account;
|
|
using game.KV;
|
|
|
|
public interface ICustomServerMgr
|
|
{
|
|
Task<string> GetStringAsync(string url);
|
|
Task<string> CustomServerPost(string url, string bodyString, string contentType = "application/json");
|
|
Task<string> AccountRequest(string url, object data);
|
|
Task<string> EventRequest(string url, object data);
|
|
Task<string> ClubRequest(string url, object data);
|
|
Task<string> BombRequest(string url, object data);
|
|
Task<string> DuelRequest(string url, object data);
|
|
Task<string> CommonDataRequest(string url, object data);
|
|
Task<string> FriendRequest(string url, object data);
|
|
Task<T> EventPartnerRequest<T>(string url, object data = null);
|
|
Task<string> EventPartnerRequest(string url, object data);
|
|
CookieContainer Cookie { get; }
|
|
Uri ApiBaseUri { get; }
|
|
int SessionVersion { get; }
|
|
event Action SessionChanged;
|
|
void UseAccountSession(GameServerAccountSession session);
|
|
void ClearAccountSession();
|
|
}
|
|
|
|
public class CustomServerMgr : ICustomServerMgr
|
|
{
|
|
public const string DefaultApiUrl = "http://s101.local:18080/";
|
|
|
|
IConfig config { get; set; }
|
|
|
|
//网络客户端
|
|
HttpClient client;
|
|
|
|
private CookieContainer _cookie;
|
|
private readonly Func<CookieContainer, HttpMessageHandler> handlerFactory;
|
|
private readonly SynchronizationContext ownerContext;
|
|
public int SessionVersion { get; private set; }
|
|
public event Action SessionChanged;
|
|
|
|
private KvAccountLease kvLease;
|
|
private bool kvOnly;
|
|
private bool kvBusinessReady;
|
|
private bool kvBusinessPublished;
|
|
private int pfRequests;
|
|
internal event Action<int> KVUnauthorized;
|
|
internal bool IsFreshForKVLoad => pfRequests==0 && kvLease==null;
|
|
internal void BindKVForGame(KvAccountLease lease)
|
|
{
|
|
if (lease == null || !lease.IsCurrent ||
|
|
lease.ApiBaseUri.AbsoluteUri != ApiBaseUri.AbsoluteUri || !IsFreshForKVLoad)
|
|
{
|
|
throw new InvalidOperationException("Fresh formal KV lease required.");
|
|
}
|
|
kvOnly=true; kvBusinessReady=false; kvBusinessPublished=false; SessionVersion++; kvLease=lease; _cookie=null;
|
|
var previous=client; client=null; previous?.Dispose(); SessionChanged?.Invoke();
|
|
}
|
|
internal void EnableKVBusiness(long epoch)
|
|
{
|
|
if (!kvOnly || kvBusinessReady || kvBusinessPublished || kvLease == null || !kvLease.IsCurrent ||
|
|
kvLease.Epoch != epoch || kvLease.ApiBaseUri.AbsoluteUri != ApiBaseUri.AbsoluteUri)
|
|
{
|
|
throw new InvalidOperationException("Current formal KV lease required for business transport.");
|
|
}
|
|
kvBusinessPublished=true; kvBusinessReady=true;
|
|
}
|
|
internal void DisableKVBusiness()
|
|
{
|
|
if(!kvOnly || !kvBusinessReady) return;
|
|
kvBusinessReady=false; SessionVersion++;
|
|
try { SessionChanged?.Invoke(); }
|
|
catch(Exception ex) { Debug.LogError($"KV session change failed: {ex.GetType().Name}"); }
|
|
}
|
|
internal Task ClearKVForGame()
|
|
{
|
|
kvBusinessReady = false;
|
|
SessionVersion++;
|
|
var previous = kvLease;
|
|
kvLease = null;
|
|
_cookie = null;
|
|
var closing = previous?.CloseAsync() ?? Task.CompletedTask;
|
|
SessionChanged?.Invoke();
|
|
return closing;
|
|
}
|
|
|
|
|
|
public CookieContainer Cookie => kvOnly
|
|
? (kvBusinessReady && kvLease != null ? kvLease.FormalCookies : null) ?? throw new NullReferenceException()
|
|
: _cookie ?? throw new NullReferenceException();
|
|
public Uri ApiBaseUri { get; }
|
|
|
|
public CustomServerMgr(IConfig config) : this(config, null) { }
|
|
|
|
public CustomServerMgr(IConfig config, Func<CookieContainer, HttpMessageHandler> handlerFactory)
|
|
{
|
|
ownerContext=SynchronizationContext.Current;
|
|
this.handlerFactory = handlerFactory ?? (cookies => new HttpClientHandler
|
|
{
|
|
CookieContainer = cookies, UseCookies = true, AllowAutoRedirect = false
|
|
});
|
|
this.config = config;
|
|
|
|
var url = config.Get<string>(GConstant.K_Event_API_URL, DefaultApiUrl);
|
|
if (string.IsNullOrWhiteSpace(url))
|
|
{
|
|
url = DefaultApiUrl;
|
|
}
|
|
ApiBaseUri = new Uri(url.TrimEnd('/') + "/");
|
|
ClearAccountSession();
|
|
}
|
|
|
|
public void UseAccountSession(GameServerAccountSession session)
|
|
{
|
|
if(kvOnly) throw new InvalidOperationException("Controlled container cannot switch to PlayFab.");
|
|
if (session == null || !session.IsAuthenticated || session.ApiBaseUri != ApiBaseUri)
|
|
throw new ArgumentException("An authenticated session for this API is required.", nameof(session));
|
|
ReplaceTransport(session.Cookies);
|
|
}
|
|
|
|
public void ClearAccountSession()
|
|
{
|
|
if (kvOnly)
|
|
{
|
|
KvShutdownRegistry.Process.Register(ClearKVForGame());
|
|
return;
|
|
}
|
|
ReplaceTransport(new CookieContainer());
|
|
}
|
|
|
|
private void ReplaceTransport(CookieContainer cookies)
|
|
{
|
|
SessionVersion++;
|
|
var previous = client;
|
|
_cookie = cookies;
|
|
client = new HttpClient(handlerFactory(cookies)) { BaseAddress = ApiBaseUri, Timeout = TimeSpan.FromSeconds(15) };
|
|
previous?.Dispose();
|
|
SessionChanged?.Invoke();
|
|
}
|
|
|
|
public Task<string> CustomServerPost(string url, string bodyString,
|
|
string contentType = "application/json") => SendAsync(HttpMethod.Post, url, bodyString, contentType);
|
|
|
|
public Task<string> GetStringAsync(string url) => SendAsync(HttpMethod.Get, url, null, null);
|
|
|
|
// Unity callers use the main thread. Capture a fixed transport before the first await.
|
|
private async Task<string> SendAsync(HttpMethod method, string url, string body, string contentType)
|
|
{
|
|
if(kvOnly)
|
|
{
|
|
if(ownerContext!=null && !ReferenceEquals(SynchronizationContext.Current,ownerContext))
|
|
{
|
|
var completion=new TaskCompletionSource<string>(TaskCreationOptions.RunContinuationsAsynchronously);
|
|
ownerContext.Post(async _=>
|
|
{
|
|
try { completion.TrySetResult(await SendAsync(method,url,body,contentType)); }
|
|
catch(Exception ex) { completion.TrySetException(ex); }
|
|
},null);
|
|
return await completion.Task.ConfigureAwait(false);
|
|
}
|
|
if(!kvBusinessReady) throw new InvalidOperationException("Formal KV business transport is not ready.");
|
|
var lease=kvLease; var kvVersion=SessionVersion;
|
|
if (lease == null || !lease.IsCurrent)
|
|
{
|
|
return null;
|
|
}
|
|
try
|
|
{
|
|
var response=await lease.SendAsync(method,url,body,contentType);
|
|
if(!kvBusinessReady || kvVersion!=SessionVersion || !ReferenceEquals(lease,kvLease) || !response.IsCurrent)
|
|
return null;
|
|
if(response.StatusCode==(int)HttpStatusCode.OK) return response.Body;
|
|
if(response.StatusCode==(int)HttpStatusCode.Unauthorized || response.StatusCode==(int)HttpStatusCode.Forbidden)
|
|
{
|
|
DisableKVBusiness();
|
|
KVUnauthorized?.Invoke(SessionVersion);
|
|
}
|
|
else if(response.StatusCode.HasValue)
|
|
Debug.LogError($"API request failed: HTTP {response.StatusCode.Value}");
|
|
return null;
|
|
}
|
|
catch(Exception e)
|
|
{
|
|
if(kvVersion==SessionVersion) Debug.LogError($"API request failed: {e.GetType().Name}");
|
|
return null;
|
|
}
|
|
}
|
|
pfRequests++;
|
|
var transport = client;
|
|
var version = SessionVersion;
|
|
try
|
|
{
|
|
using (var request = new HttpRequestMessage(method, url))
|
|
{
|
|
if (body != null) request.Content = new StringContent(body, Encoding.UTF8, contentType);
|
|
using (var response = await transport.SendAsync(request))
|
|
{
|
|
var result = await response.Content.ReadAsStringAsync();
|
|
if (version != SessionVersion) return null;
|
|
if (response.StatusCode == HttpStatusCode.OK) return result;
|
|
if (response.StatusCode == HttpStatusCode.Unauthorized || response.StatusCode == HttpStatusCode.Forbidden)
|
|
GContext.Publish(new game.EvtAPISvrUnauthorized { SessionVersion = version });
|
|
else
|
|
Debug.LogError($"API request failed: HTTP {(int)response.StatusCode}");
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
if (version == SessionVersion) Debug.LogError($"API request failed: {e.GetType().Name}");
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public async Task<string> AccountRequest(string url, object data)
|
|
{
|
|
return await CustomServerPost($"account/{url}",
|
|
Newtonsoft.Json.JsonConvert.SerializeObject(data));
|
|
}
|
|
|
|
public async Task<string> EventRequest(string url, object data)
|
|
{
|
|
return await CustomServerPost($"event/{url}",
|
|
Newtonsoft.Json.JsonConvert.SerializeObject(data));
|
|
}
|
|
|
|
public async Task<string> ClubRequest(string url, object data)
|
|
{
|
|
return await CustomServerPost($"club/{url}",
|
|
Newtonsoft.Json.JsonConvert.SerializeObject(data));
|
|
}
|
|
|
|
public async Task<string> BombRequest(string url, object data)
|
|
{
|
|
return await CustomServerPost($"bomb/{url}",
|
|
Newtonsoft.Json.JsonConvert.SerializeObject(data));
|
|
}
|
|
|
|
public async Task<string> CommonDataRequest(string url, object data)
|
|
{
|
|
return await CustomServerPost($"commondata/{url}",
|
|
Newtonsoft.Json.JsonConvert.SerializeObject(data));
|
|
}
|
|
|
|
public async Task<string> DuelRequest(string url, object data)
|
|
{
|
|
return await CustomServerPost($"duel/{url}",
|
|
Newtonsoft.Json.JsonConvert.SerializeObject(data));
|
|
}
|
|
|
|
public async Task<string> FriendRequest(string url, object data)
|
|
{
|
|
return await CustomServerPost($"friend/{url}",
|
|
Newtonsoft.Json.JsonConvert.SerializeObject(data));
|
|
}
|
|
|
|
// 伙伴建造请求
|
|
public async Task<string> EventPartnerRequest(string url, object data)
|
|
{
|
|
// throw new NotImplementedException();
|
|
var body = Newtonsoft.Json.JsonConvert.SerializeObject(data);
|
|
return await CustomServerPost($"eventpartnergather/{url}", body);
|
|
}
|
|
|
|
public async Task<T> EventPartnerRequest<T>(string url, object data = null)
|
|
{
|
|
var body = Newtonsoft.Json.JsonConvert.SerializeObject(data);
|
|
var responseString = await CustomServerPost($"eventpartner/{url}", body);
|
|
try
|
|
{
|
|
return Newtonsoft.Json.JsonConvert.DeserializeObject<T>(responseString);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Debug.LogError(
|
|
$"<color=red>[EventPartner] Response {responseString} failed to be deserialized: {e.Message}</color>");
|
|
return default(T);
|
|
}
|
|
}
|
|
}
|
|
|