using asap.core; using LitJson; using System; using System.Net; using System.Net.Http; using System.Text; using System.Threading.Tasks; using UnityEngine; using game.Account; using game.PlayerDataStorage; public interface ICustomServerMgr { Task GetStringAsync(string url); Task CustomServerPost(string url, string bodyString, string contentType = "application/json"); Task AccountRequest(string url, object data); Task EventRequest(string url, object data); Task ClubRequest(string url, object data); Task BombRequest(string url, object data); Task DuelRequest(string url, object data); Task CommonDataRequest(string url, object data); Task FriendRequest(string url, object data); Task EventPartnerRequest(string url, object data = null); Task EventPartnerRequest(string url, object data); CookieContainer Cookie { get; } Uri ApiBaseUri { get; } int SessionVersion { get; } event Action SessionChanged; void UseAccountSession(S101AccountSession session); void ClearAccountSession(); } public class CustomServerMgr : ICustomServerMgr { IConfig config { get; set; } //网络客户端 HttpClient client; private CookieContainer _cookie; private readonly Func handlerFactory; public int SessionVersion { get; private set; } public event Action SessionChanged; PlayerKVAccountLease kvLease; bool kvOnly; int pfRequests; internal event Action KVUnauthorized; internal bool IsFreshForKVLoad => pfRequests==0 && kvLease==null; internal void BindKVForValidation(PlayerKVAccountLease lease) { if(lease==null || !lease.IsCurrent || lease.ApiBaseUri!=ApiBaseUri || !IsFreshForKVLoad) throw new InvalidOperationException("Fresh matching KV lease required."); kvOnly=true; SessionVersion++; kvLease=lease; _cookie=null; var previous=client; client=null; previous?.Dispose(); SessionChanged?.Invoke(); } internal Task ClearKVForValidation() { SessionVersion++; var previous=kvLease; kvLease=null; var closing=previous?.CloseAsync() ?? Task.CompletedTask; SessionChanged?.Invoke(); return closing; } internal async Task SendKVForValidationAsync(string route) { var lease=kvLease; int version=SessionVersion; if(!kvOnly || lease==null) return new PlayerKVAccountResponse(false); var response=await lease.SendAsync(HttpMethod.Get,route); if(version!=SessionVersion || !ReferenceEquals(lease,kvLease) || !response.IsCurrent) return new PlayerKVAccountResponse(false); if(response.StatusCode==401 || response.StatusCode==403) { KVUnauthorized?.Invoke(version); return new PlayerKVAccountResponse(false,response.StatusCode); } return response; } public CookieContainer Cookie => _cookie ?? throw new System.NullReferenceException(); public Uri ApiBaseUri { get; } public CustomServerMgr(IConfig config) : this(config, null) { } public CustomServerMgr(IConfig config, Func handlerFactory) { this.handlerFactory = handlerFactory ?? (cookies => new HttpClientHandler { CookieContainer = cookies, UseCookies = true, AllowAutoRedirect = false }); this.config = config; var url = config.Get(GConstant.K_Event_API_URL, S101AccountSession.DefaultApiUrl); if (string.IsNullOrWhiteSpace(url)) url = S101AccountSession.DefaultApiUrl; ApiBaseUri = new Uri(url.TrimEnd('/') + "/"); ClearAccountSession(); } public void UseAccountSession(S101AccountSession 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) { PlayerKVShutdownRegistry.Process.Register(ClearKVForValidation()); 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 CustomServerPost(string url, string bodyString, string contentType = "application/json") => SendAsync(HttpMethod.Post, url, bodyString, contentType); public Task 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 SendAsync(HttpMethod method, string url, string body, string contentType) { if(kvOnly) throw new InvalidOperationException("Only controlled validation routes are enabled."); 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 AccountRequest(string url, object data) { return await CustomServerPost($"account/{url}", Newtonsoft.Json.JsonConvert.SerializeObject(data)); } public async Task EventRequest(string url, object data) { return await CustomServerPost($"event/{url}", Newtonsoft.Json.JsonConvert.SerializeObject(data)); } public async Task ClubRequest(string url, object data) { return await CustomServerPost($"club/{url}", Newtonsoft.Json.JsonConvert.SerializeObject(data)); } public async Task BombRequest(string url, object data) { return await CustomServerPost($"bomb/{url}", Newtonsoft.Json.JsonConvert.SerializeObject(data)); } public async Task CommonDataRequest(string url, object data) { return await CustomServerPost($"commondata/{url}", Newtonsoft.Json.JsonConvert.SerializeObject(data)); } public async Task DuelRequest(string url, object data) { return await CustomServerPost($"duel/{url}", Newtonsoft.Json.JsonConvert.SerializeObject(data)); } public async Task FriendRequest(string url, object data) { return await CustomServerPost($"friend/{url}", Newtonsoft.Json.JsonConvert.SerializeObject(data)); } // 伙伴建造请求 public async Task EventPartnerRequest(string url, object data) { // throw new NotImplementedException(); var body = Newtonsoft.Json.JsonConvert.SerializeObject(data); return await CustomServerPost($"eventpartnergather/{url}", body); } public async Task EventPartnerRequest(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(responseString); } catch (Exception e) { Debug.LogError( $"[EventPartner] Response {responseString} failed to be deserialized: {e.Message}"); return default(T); } } }