new handlers architecture

This commit is contained in:
rfi 2024-04-25 10:50:09 +07:00
parent 45084b0177
commit 5ca05509fc
14 changed files with 253 additions and 239 deletions

View File

@ -0,0 +1,13 @@
using System.ComponentModel.DataAnnotations.Schema;
using System.ComponentModel.DataAnnotations;
namespace SCHALE.Common.Database.Models.Game
{
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
public class Account : AccountDB
{
[Key]
[Column("_id")]
public new uint ServerId { get; set; }
}
}

View File

@ -4,7 +4,7 @@ using System.ComponentModel.DataAnnotations.Schema;
namespace SCHALE.Common.Database.Models
{
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
public class Account
public class GuestAccount
{
[Key]
[Column("_id")]

View File

@ -3,11 +3,13 @@ using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.ValueGeneration;
using MongoDB.EntityFrameworkCore.Extensions;
using SCHALE.Common.Database.Models;
using SCHALE.Common.Database.Models.Game;
namespace SCHALE.Common.Database
{
public class SCHALEContext : DbContext
{
public DbSet<GuestAccount> GuestAccounts { get; set; }
public DbSet<Account> Accounts { get; set; }
public DbSet<Counter> Counters { get; set; }
@ -19,42 +21,66 @@ namespace SCHALE.Common.Database
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Account>().Property(x => x.Uid).HasValueGenerator<AccountAutoIncrementValueGenerator>();
modelBuilder.Entity<GuestAccount>().Property(x => x.Uid).HasValueGenerator<GuestAccountAutoIncrementValueGenerator>();
modelBuilder.Entity<GuestAccount>().ToCollection("guest_accounts");
modelBuilder.Entity<Account>().Property(x => x.ServerId).HasValueGenerator<AccountAutoIncrementValueGenerator>();
modelBuilder.Entity<Account>().ToCollection("accounts");
modelBuilder.Entity<Counter>().ToCollection("counters");
}
}
class AccountAutoIncrementValueGenerator : AutoIncrementValueGenerator
{
protected override string Collection => "account";
}
abstract class AutoIncrementValueGenerator : ValueGenerator<uint>
{
protected abstract string Collection { get; }
public override bool GeneratesTemporaryValues => false;
public override uint Next(EntityEntry entry)
private class AccountAutoIncrementValueGenerator : AutoIncrementValueGenerator
{
if (entry.Context is not SCHALEContext)
{
throw new ArgumentNullException($"{nameof(AutoIncrementValueGenerator)} is only implemented for {nameof(SCHALEContext)}");
}
protected override string Collection => "account";
}
var context = ((SCHALEContext)entry.Context);
var counter = context.Counters.SingleOrDefault(x => x.Id == Collection);
private class GuestAccountAutoIncrementValueGenerator : AutoIncrementValueGenerator
{
protected override string Collection => "guest_account";
}
if (counter is null)
private abstract class AutoIncrementValueGenerator : ValueGenerator<uint>
{
protected abstract string Collection { get; }
public override bool GeneratesTemporaryValues => false;
public override uint Next(EntityEntry entry)
{
counter = new Counter() { Id = Collection, Seq = 0 };
context.Add(counter);
if (entry.Context is not SCHALEContext)
{
throw new ArgumentNullException($"{nameof(AutoIncrementValueGenerator)} is only implemented for {nameof(SCHALEContext)}");
}
var context = ((SCHALEContext)entry.Context);
var counter = context.Counters.SingleOrDefault(x => x.Id == Collection);
if (counter is null)
{
counter = new Counter() { Id = Collection, Seq = 0 };
context.Add(counter);
}
counter.Seq++;
context.SaveChanges();
return counter.Seq;
}
}
counter.Seq++;
public override void Dispose()
{
GC.SuppressFinalize(this);
SaveChanges();
base.Dispose();
}
return counter.Seq;
public override ValueTask DisposeAsync()
{
GC.SuppressFinalize(this);
SaveChanges();
return base.DisposeAsync();
}
}
}

View File

@ -1,43 +0,0 @@
using SCHALE.Common.Database.Models;
namespace SCHALE.Common.NetworkProtocol.Account
{
public class AccountAuthResponse : ResponsePacket
{
public override Protocol Protocol => Protocol.Account_Auth;
public long CurrentVersion { get; set; }
public long MinimumVersion { get; set; }
public bool IsDevelopment { get; set; }
public bool UpdateRequired { get; set; }
public required string TTSCdnUri { get; set; }
/*public AccountDB AccountDB { get; set; }
public IEnumerable<AttendanceBookReward> AttendanceBookRewards { get; set; }
public IEnumerable<AttendanceHistoryDB> AttendanceHistoryDBs { get; set; }
public IEnumerable<OpenConditionDB> OpenConditions { get; set; }
public IEnumerable<PurchaseCountDB> RepurchasableMonthlyProductCountDBs { get; set; }
public IEnumerable<ParcelInfo> MonthlyProductParcel { get; set; }
public IEnumerable<ParcelInfo> MonthlyProductMail { get; set; }
public IEnumerable<ParcelInfo> BiweeklyProductParcel { get; set; }
public IEnumerable<ParcelInfo> BiweeklyProductMail { get; set; }
public IEnumerable<ParcelInfo> WeeklyProductParcel { get; set; }
public IEnumerable<ParcelInfo> WeeklyProductMail { get; set; }*/
public required string EncryptedUID { get; set; }
}
public class AccountAuthRequest : RequestPacket
{
public override Protocol Protocol => Protocol.Account_Auth;
public long Version { get; set; }
public string? DevId { get; set; }
public long IMEI { get; set; }
public string AccessIP { get; set; } = string.Empty;
public string MarketId { get; set; } = string.Empty;
public string? UserType { get; set; }
public string? AdvertisementId { get; set; }
public string OSType { get; set; } = string.Empty;
public string OSVersion { get; set; } = string.Empty;
public string DeviceUniqueId { get; set; } = string.Empty;
public string DeviceModel { get; set; } = string.Empty;
public int DeviceSystemMemorySize { get; set; }
}
}

View File

@ -1,27 +0,0 @@
namespace SCHALE.Common.NetworkProtocol.Account
{
public class AccountCheckYostarRequest : RequestPacket
{
public override Protocol Protocol => NetworkProtocol.Protocol.Account_CheckYostar;
public long UID { get; set; }
public string YostarToken { get; set; } = string.Empty;
public string EnterTicket { get; set; } = string.Empty;
public bool PassCookieResult { get; set; }
public string Cookie { get; set; } = string.Empty;
}
public class AccountCheckYostarResponse : ResponsePacket
{
public override Protocol Protocol
{
get
{
return NetworkProtocol.Protocol.Account_CheckYostar;
}
}
public int ResultState { get; set; }
public string ResultMessag { get; set; } = string.Empty;
public string Birth { get; set; } = string.Empty;
}
}

View File

@ -1,4 +1,5 @@
using System.Text.Json.Serialization;
using SCHALE.Common.Database;
using System.Text.Json.Serialization;
namespace SCHALE.Common.NetworkProtocol
{
@ -36,8 +37,8 @@ namespace SCHALE.Common.NetworkProtocol
{
public long ServerTimeTicks { get; set; } = DateTimeOffset.Now.Ticks;
public ServerNotificationFlag ServerNotification { get; set; }
// public List<MissionProgressDB> MissionProgressDBs { get; set; }
// public Dictionary<long, List<MissionProgressDB>> EventMissionProgressDBDict { get; set; }
public List<MissionProgressDB> MissionProgressDBs { get; set; }
public Dictionary<long, List<MissionProgressDB>> EventMissionProgressDBDict { get; set; }
// public Dictionary<OpenConditionContent, OpenConditionLockReason> StaticOpenConditions { get; set; }
}

View File

@ -1,26 +0,0 @@
namespace SCHALE.Common.NetworkProtocol.Queuing
{
public class QueuingGetTicketRequest : RequestPacket
{
public override Protocol Protocol => Protocol.Queuing_GetTicket;
public long YostarUID { get; set; }
public required string YostarToken { get; set; }
public bool MakeStandby { get; set; }
public bool PassCheck { get; set; }
public bool PassCheckYostar { get; set; }
public string WaitingTicket { get; set; } = string.Empty;
public string ClientVersion { get; set; } = "0.0.0";
}
public class QueuingGetTicketResponse : ResponsePacket
{
public override Protocol Protocol => Protocol.Queuing_GetTicket;
public string WaitingTicket { get; set; } = string.Empty;
public string EnterTicket { get; set; } = string.Empty;
public long TicketSequence { get; set; }
public long AllowedSequence { get; set; }
public double RequiredSecondsPerUser { get; set; }
public string Birth { get; set; } = string.Empty;
public string ServerSeed { get; set; } = string.Empty;
}
}

View File

@ -34,28 +34,28 @@ namespace SCHALE.Common.NetworkProtocol
public string EncryptedUID { get; set; }
}
//public class AccountAuthRequest : RequestPacket
//{
// public override Protocol Protocol
// {
// get
// {
// return NetworkProtocol.Protocol.Account_Auth;
// }
// }
// public long Version { get; set; }
// public string DevId { get; set; }
// public long IMEI { get; set; }
// public string AccessIP { get; set; }
// public string MarketId { get; set; }
// public string UserType { get; set; }
// public string AdvertisementId { get; set; }
// public string OSType { get; set; }
// public string OSVersion { get; set; }
// public string DeviceUniqueId { get; set; }
// public string DeviceModel { get; set; }
// public int DeviceSystemMemorySize { get; set; }
//}
public class AccountAuthRequest : RequestPacket
{
public override Protocol Protocol
{
get
{
return NetworkProtocol.Protocol.Account_Auth;
}
}
public long Version { get; set; }
public string DevId { get; set; }
public long IMEI { get; set; }
public string AccessIP { get; set; }
public string MarketId { get; set; }
public string UserType { get; set; }
public string AdvertisementId { get; set; }
public string OSType { get; set; }
public string OSVersion { get; set; }
public string DeviceUniqueId { get; set; }
public string DeviceModel { get; set; }
public int DeviceSystemMemorySize { get; set; }
}
public class AccountCurrencySyncResponse : ResponsePacket
@ -527,36 +527,36 @@ namespace SCHALE.Common.NetworkProtocol
}
//public class AccountCheckYostarRequest : RequestPacket
//{
// public override Protocol Protocol
// {
// get
// {
// return NetworkProtocol.Protocol.Account_CheckYostar;
// }
// }
// public long UID { get; set; }
// public string YostarToken { get; set; }
// public string EnterTicket { get; set; }
// public bool PassCookieResult { get; set; }
// public string Cookie { get; set; }
//}
public class AccountCheckYostarRequest : RequestPacket
{
public override Protocol Protocol
{
get
{
return NetworkProtocol.Protocol.Account_CheckYostar;
}
}
public long UID { get; set; }
public string YostarToken { get; set; }
public string EnterTicket { get; set; }
public bool PassCookieResult { get; set; }
public string Cookie { get; set; }
}
//public class AccountCheckYostarResponse : ResponsePacket
//{
// public override Protocol Protocol
// {
// get
// {
// return NetworkProtocol.Protocol.Account_CheckYostar;
// }
// }
// public int ResultState { get; set; }
// public string ResultMessag { get; set; }
// public string Birth { get; set; }
//}
public class AccountCheckYostarResponse : ResponsePacket
{
public override Protocol Protocol
{
get
{
return NetworkProtocol.Protocol.Account_CheckYostar;
}
}
public int ResultState { get; set; }
public string ResultMessag { get; set; }
public string Birth { get; set; }
}
public class AccountResetRequest : RequestPacket
@ -9282,23 +9282,23 @@ namespace SCHALE.Common.NetworkProtocol
}
//public class QueuingGetTicketRequest : RequestPacket
//{
// public override Protocol Protocol
// {
// get
// {
// return NetworkProtocol.Protocol.Queuing_GetTicket;
// }
// }
// public long YostarUID { get; set; }
// public string YostarToken { get; set; }
// public bool MakeStandby { get; set; }
// public bool PassCheck { get; set; }
// public bool PassCheckYostar { get; set; }
// public string WaitingTicket { get; set; }
// public string ClientVersion { get; set; }
//}
public class QueuingGetTicketRequest : RequestPacket
{
public override Protocol Protocol
{
get
{
return NetworkProtocol.Protocol.Queuing_GetTicket;
}
}
public long YostarUID { get; set; }
public string YostarToken { get; set; }
public bool MakeStandby { get; set; }
public bool PassCheck { get; set; }
public bool PassCheckYostar { get; set; }
public string WaitingTicket { get; set; }
public string ClientVersion { get; set; }
}
public class MultiFloorRaidEnterBattleResponse : ResponsePacket
@ -9625,26 +9625,26 @@ namespace SCHALE.Common.NetworkProtocol
}
//public class QueuingGetTicketResponse : ResponsePacket
//{
// public override Protocol Protocol
// {
// get
// {
// return NetworkProtocol.Protocol.Queuing_GetTicket;
// }
// }
// public string WaitingTicket { get; set; }
// public string EnterTicket { get; set; }
// public long TicketSequence { get; set; }
// public long AllowedSequence { get; set; }
// public double RequiredSecondsPerUser { get; set; }
// public string Birth { get; set; }
// public string ServerSeed { get; set; }
// public void Reset()
// {
// }
//}
public class QueuingGetTicketResponse : ResponsePacket
{
public override Protocol Protocol
{
get
{
return NetworkProtocol.Protocol.Queuing_GetTicket;
}
}
public string WaitingTicket { get; set; }
public string EnterTicket { get; set; }
public long TicketSequence { get; set; }
public long AllowedSequence { get; set; }
public double RequiredSecondsPerUser { get; set; }
public string Birth { get; set; }
public string ServerSeed { get; set; }
public void Reset()
{
}
}
public class ScenarioRetreatResponse : ResponsePacket

View File

@ -2,7 +2,6 @@
using SCHALE.Common.Crypto;
using SCHALE.Common.NetworkProtocol;
using SCHALE.GameServer.Controllers.Api.ProtocolHandlers;
using System.Buffers.Binary;
using System.IO.Compression;
using System.Text;
using System.Text.Json;
@ -59,17 +58,16 @@ namespace SCHALE.GameServer.Controllers.Api
}
var payload = (JsonSerializer.Deserialize(payloadStr, requestType) as RequestPacket)!;
var handler = protocolHandlerFactory.GetProtocolHandler(payload.Protocol);
if (handler is null)
var rsp = protocolHandlerFactory.Invoke(protocol, [payload]);
if (rsp is null)
{
logger.LogDebug("{Protocol} {Payload:j}", payload.Protocol, payloadStr);
logger.LogError("Protocol {Protocol} is unimplemented and left unhandled", payload.Protocol);
goto protocolErrorRet;
}
var rsp = handler.Invoke(null, [payload]);
return Results.Json(new
{
packet = JsonSerializer.Serialize(rsp),

View File

@ -1,26 +1,37 @@
using SCHALE.Common.NetworkProtocol;
using SCHALE.Common.NetworkProtocol.Account;
namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers
{
public static class Account
public class Account : ProtocolHandlerBase
{
public Account(IServiceScopeFactory scopeFactory, IProtocolHandlerFactory protocolHandlerFactory) : base(scopeFactory, protocolHandlerFactory) { }
[ProtocolHandler(Protocol.Account_CheckYostar)]
public static ResponsePacket CheckYostarHandler(AccountCheckYostarRequest req)
public ResponsePacket CheckYostarHandler(AccountCheckYostarRequest req)
{
var account = Context.GuestAccounts.SingleOrDefault(x => x.Uid == uint.Parse(req.EnterTicket.Split(":", StringSplitOptions.None).First()) && x.Token == req.EnterTicket.Split(":", StringSplitOptions.None).Last());
if (account is null)
{
return new AccountCheckYostarResponse()
{
ResultState = 0,
ResultMessag = "Invalid account (EnterTicket, AccountCheckYostar)"
};
}
return new AccountCheckYostarResponse()
{
ResultState = 1,
SessionKey = new()
{
MxToken = req.EnterTicket,
AccountServerId = 1
AccountServerId = account.Uid
}
};
}
[ProtocolHandler(Protocol.Account_Auth)]
public static ResponsePacket AuthHandler(AccountAuthRequest req)
public ResponsePacket AuthHandler(AccountAuthRequest req)
{
return new ErrorPacket()
{

View File

@ -1,4 +1,5 @@
using SCHALE.Common.NetworkProtocol;
using SCHALE.Common.Database;
using SCHALE.Common.NetworkProtocol;
using Serilog;
using System.Reflection;
@ -17,27 +18,20 @@ namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers
public interface IProtocolHandlerFactory
{
public ResponsePacket? Invoke(Protocol protocol, params object?[]? args);
public MethodInfo? GetProtocolHandler(Protocol protocol);
public Type? GetRequestPacketTypeByProtocol(Protocol protocol);
public void RegisterInstance(Type t, object? inst);
}
public class ProtocolHandlerFactory : IProtocolHandlerFactory
{
private readonly Dictionary<Protocol, MethodInfo> handlers = [];
private readonly Dictionary<Protocol, Type> requestPacketTypes = [];
private readonly Dictionary<Type, object?> handlerInstances = [];
public ProtocolHandlerFactory()
{
foreach (var method in Assembly.GetExecutingAssembly().GetTypes().SelectMany(x => x.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).Where(x => x.GetCustomAttribute<ProtocolHandlerAttribute>() is not null)))
{
var attr = method.GetCustomAttribute<ProtocolHandlerAttribute>()!;
if (handlers.ContainsKey(attr.Protocol))
continue;
handlers.Add(attr.Protocol, method);
Log.Debug($"Loaded {method.Name} for {attr.Protocol}");
}
foreach (var requestPacketType in Assembly.GetAssembly(typeof(RequestPacket))!.GetTypes().Where(x => x.IsAssignableTo(typeof(RequestPacket))))
{
if (requestPacketType == typeof(RequestPacket))
@ -53,6 +47,31 @@ namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers
}
}
public void RegisterInstance(Type t, object? inst)
{
handlerInstances.Add(t, inst);
foreach (var method in t.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).Where(x => x.GetCustomAttribute<ProtocolHandlerAttribute>() is not null))
{
var attr = method.GetCustomAttribute<ProtocolHandlerAttribute>()!;
if (handlers.ContainsKey(attr.Protocol))
continue;
handlers.Add(attr.Protocol, method);
Log.Debug($"Loaded {method.Name} for {attr.Protocol}");
}
}
public ResponsePacket? Invoke(Protocol protocol, params object?[]? args)
{
var handler = GetProtocolHandler(protocol);
if (handler is null)
return null;
handlerInstances.TryGetValue(handler.DeclaringType!, out var inst);
return (ResponsePacket?)handler.Invoke(inst, args);
}
public MethodInfo? GetProtocolHandler(Protocol protocol)
{
handlers.TryGetValue(protocol, out var handler);
@ -68,11 +87,50 @@ namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers
}
}
public abstract class ProtocolHandlerBase : IHostedService
{
private readonly IServiceScopeFactory scopeFactory;
public ProtocolHandlerBase(IServiceScopeFactory _scopeFactory, IProtocolHandlerFactory protocolHandlerFactory)
{
scopeFactory = _scopeFactory;
protocolHandlerFactory.RegisterInstance(GetType(), this);
}
public SCHALEContext Context
{
get
{
using (var scope = scopeFactory.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService<SCHALEContext>();
return db;
}
}
}
public Task StartAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
internal static class ServiceExtensions
{
public static void AddProtocolHandlerFactory(this IServiceCollection services)
{
services.AddSingleton<IProtocolHandlerFactory, ProtocolHandlerFactory>();
}
public static void AddProtocolHandlerGroup<T>(this IServiceCollection services) where T : ProtocolHandlerBase
{
services.AddHostedService<T>();
}
}
}

View File

@ -1,16 +1,17 @@
using SCHALE.Common.NetworkProtocol;
using SCHALE.Common.NetworkProtocol.Queuing;
namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers
{
public static class Queuing
public class Queuing : ProtocolHandlerBase
{
public Queuing(IServiceScopeFactory scopeFactory, IProtocolHandlerFactory protocolHandlerFactory) : base(scopeFactory, protocolHandlerFactory) { }
[ProtocolHandler(Protocol.Queuing_GetTicket)]
public static ResponsePacket GetTicketHandler(QueuingGetTicketRequest req)
public ResponsePacket GetTicketHandler(QueuingGetTicketRequest req)
{
return new QueuingGetTicketResponse()
{
EnterTicket = req.YostarToken
EnterTicket = $"{req.YostarUID}:{req.YostarToken}"
};
}
}

View File

@ -30,7 +30,7 @@ namespace SCHALE.GameServer.Controllers
public IResult Create([FromForm] string deviceId)
{
UserCreateResponse rsp = new() { Result = 0, IsNew = 0 };
var account = context.Accounts.SingleOrDefault(x => x.DeviceId == deviceId);
var account = context.GuestAccounts.SingleOrDefault(x => x.DeviceId == deviceId);
if (account is null)
{
@ -49,7 +49,7 @@ namespace SCHALE.GameServer.Controllers
[HttpPost("login")]
public IResult Login([FromForm] uint uid, [FromForm] string token, [FromForm] string storeId)
{
var account = context.Accounts.SingleOrDefault(x => x.Uid == uid && x.Token == token);
var account = context.GuestAccounts.SingleOrDefault(x => x.Uid == uid && x.Token == token);
if (account is not null)
{
return Results.Json(new UserLoginResponse()

View File

@ -46,6 +46,8 @@ namespace SCHALE.GameServer
builder.Services.AddControllers();
builder.Services.AddMongoDBProvider(config.GetConnectionString("MongoDB") ?? throw new ArgumentNullException("ConnectionStrings/MongoDB in appsettings is missing"));
builder.Services.AddProtocolHandlerFactory();
builder.Services.AddProtocolHandlerGroup<Account>();
builder.Services.AddProtocolHandlerGroup<Queuing>();
var app = builder.Build();