1
0
mirror of synced 2024-11-27 23:50:49 +01:00

Start implementing card

This commit is contained in:
asesidaa 2023-02-09 17:25:42 +08:00
parent f7761f200f
commit 9c759c3b1a
28 changed files with 872 additions and 89 deletions

View File

@ -11,11 +11,14 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ChoETL" Version="1.2.1.52" />
<PackageReference Include="MediatR" Version="11.1.0" /> <PackageReference Include="MediatR" Version="11.1.0" />
<PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="11.0.0" /> <PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="11.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.2.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.2" /> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.2" />
<PackageReference Include="Microsoft.Extensions.Options" Version="7.0.0" /> <PackageReference Include="Microsoft.Extensions.Options" Version="7.0.0" />
<PackageReference Include="Riok.Mapperly" Version="2.7.0-next.2" /> <PackageReference Include="Riok.Mapperly" Version="2.7.0-next.2" />
<PackageReference Include="Throw" Version="1.3.1" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -0,0 +1,24 @@
using MediatR.Pipeline;
using Microsoft.Extensions.Logging;
namespace Application.Common.Behaviours;
public class LoggingBehaviour<TRequest> : IRequestPreProcessor<TRequest> where TRequest : notnull
{
private readonly ILogger<TRequest> logger;
// ReSharper disable once ContextualLoggerProblem
public LoggingBehaviour(ILogger<TRequest> logger)
{
this.logger = logger;
}
public Task Process(TRequest request, CancellationToken cancellationToken)
{
var requestName = typeof(TRequest).Name;
logger.LogInformation("Received request: {RequestName}, content: {Request}", requestName, request);
return Task.CompletedTask;
}
}

View File

@ -0,0 +1,33 @@
using MediatR;
using Microsoft.Extensions.Logging;
namespace Application.Common.Behaviours;
public class UnhandledExceptionBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
private readonly ILogger<TRequest> logger;
// ReSharper disable once ContextualLoggerProblem
public UnhandledExceptionBehaviour(ILogger<TRequest> logger)
{
this.logger = logger;
}
public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
{
try
{
return await next();
}
catch (Exception ex)
{
var requestName = typeof(TRequest).Name;
logger.LogError(ex, "Unhandled Exception for Request {Name} {@Request}", requestName, request);
throw;
}
}
}

View File

@ -0,0 +1,8 @@
namespace Application.Common.Exceptions;
public class CardExistsException : Exception
{
public CardExistsException(string? message) : base(message)
{
}
}

View File

@ -0,0 +1,17 @@
using ChoETL;
using Throw;
namespace Application.Common;
public static class XmlSerializationExtensions
{
public static T DeserializeCardData<T>(this string source) where T : class
{
using var reader = new ChoXmlReader<T>(new StringReader(source)).WithXPath("/root/data");
var result = reader.Read();
result.ThrowIfNull();
return result;
}
}

View File

@ -0,0 +1,140 @@
namespace Application.Common.Models;
/// <summary>
/// All errors contained in ServiceResult objects must return an error of this type
/// Error codes allow the caller to easily identify the received error and take action.
/// Error messages allow the caller to easily show error messages to the end user.
///
/// Taken from https://github.com/iayti/CleanArchitecture/blob/master/src/Common/CleanArchitecture.Application/Common/Models/ServiceError.cs
/// </summary>
[Serializable]
public class ServiceError
{
/// <summary>
/// CTOR
/// </summary>
public ServiceError(string message, int code)
{
Message = message;
Code = code;
}
public ServiceError()
{
}
/// <summary>
/// Human readable error message
/// </summary>
public string Message { get; } = string.Empty;
/// <summary>
/// Machine readable error code
/// </summary>
public int Code { get; }
/// <summary>
/// Default error for when we receive an exception
/// </summary>
public static ServiceError DefaultError => new("An unknown exception occured.", 999);
/// <summary>
/// Default validation error. Use this for invalid parameters in controller actions and service methods.
/// </summary>
public static ServiceError ModelStateError(string validationError)
{
return new ServiceError(validationError, 998);
}
/// <summary>
/// Use this for unauthorized responses.
/// </summary>
public static ServiceError ForbiddenError => new("You are not authorized to call this action.", 998);
/// <summary>
/// Use this to send a custom error message
/// </summary>
public static ServiceError CustomMessage(string errorMessage)
{
return new ServiceError(errorMessage, 997);
}
public static ServiceError UserNotFound => new("User with this id does not exist", 996);
public static ServiceError UserFailedToCreate => new("Failed to create User.", 995);
public static ServiceError Canceled => new("The request canceled successfully!", 994);
public static ServiceError NotFound => new("The specified resource was not found.", 990);
public static ServiceError ValidationFormat => new("Request object format is not true.", 901);
public static ServiceError Validation => new("One or more validation errors occurred.", 900);
public static ServiceError SearchAtLeastOneCharacter =>
new("Search parameter must have at least one character!", 898);
/// <summary>
/// Default error for when we receive an exception
/// </summary>
public static ServiceError ServiceProviderNotFound =>
new("Service Provider with this name does not exist.", 700);
public static ServiceError ServiceProvider => new("Service Provider failed to return as expected.", 600);
public static ServiceError DateTimeFormatError =>
new("Date format is not true. Date format must be like yyyy-MM-dd (2019-07-19)", 500);
#region Override Equals Operator
/// <summary>
/// Use this to compare if two errors are equal
/// Ref: https://msdn.microsoft.com/ru-ru/library/ms173147(v=vs.80).aspx
/// </summary>
public override bool Equals(object? obj)
{
// If parameter cannot be cast to ServiceError or is null return false.
var error = obj as ServiceError;
// Return true if the error codes match. False if the object we're comparing to is null
// or if it has a different code.
return Code == error?.Code;
}
public bool Equals(ServiceError error)
{
// Return true if the error codes match. False if the object we're comparing to is null
// or if it has a different code.
return Code == error?.Code;
}
public override int GetHashCode()
{
return Code;
}
public static bool operator ==(ServiceError? a, ServiceError? b)
{
// If both are null, or both are same instance, return true.
if (ReferenceEquals(a, b))
{
return true;
}
// If one is null, but not both, return false.
if (a is null || b is null)
{
return false;
}
// Return true if the fields match:
return a.Equals(b);
}
public static bool operator !=(ServiceError a, ServiceError b)
{
return !(a == b);
}
#endregion
}

View File

@ -0,0 +1,65 @@
namespace Application.Common.Models;
/// <summary>
/// A standard response for service calls.
/// </summary>
/// <typeparam name="T">Return data type</typeparam>
public class ServiceResult<T> : ServiceResult
{
public T? Data { get; set; }
public ServiceResult(T? data)
{
Data = data;
}
public ServiceResult(T? data, ServiceError error) : base(error)
{
Data = data;
}
public ServiceResult(ServiceError error) : base(error)
{
}
}
public class ServiceResult
{
public bool Succeeded => Error == null;
public ServiceError? Error { get; set; }
public ServiceResult(ServiceError? error)
{
error ??= ServiceError.DefaultError;
Error = error;
}
public ServiceResult() { }
#region Helper Methods
public static ServiceResult Failed(ServiceError error)
{
return new ServiceResult(error);
}
public static ServiceResult<T> Failed<T>(ServiceError error)
{
return new ServiceResult<T>(error);
}
public static ServiceResult<T> Failed<T>(T data, ServiceError error)
{
return new ServiceResult<T>(data, error);
}
public static ServiceResult<T> Success<T>(T data)
{
return new ServiceResult<T>(data);
}
#endregion
}

View File

@ -1,4 +1,7 @@
using System.Reflection; using System.Reflection;
using Application.Common.Behaviours;
using Application.Game.Card;
using Application.Interfaces;
using MediatR; using MediatR;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
@ -10,6 +13,8 @@ public static class DependencyInjection
{ {
services.AddMediatR(Assembly.GetExecutingAssembly()); services.AddMediatR(Assembly.GetExecutingAssembly());
services.AddScoped<ICardDependencyAggregate, CardDependencyAggregate>();
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(UnhandledExceptionBehaviour<,>));
return services; return services;
} }
} }

View File

@ -1,4 +1,5 @@
using System.Xml.Serialization; using System.ComponentModel;
using System.Xml.Serialization;
namespace Application.Dto; namespace Application.Dto;
@ -8,6 +9,7 @@ public class CardDto
public long CardId { get; set; } public long CardId { get; set; }
[XmlElement(ElementName = "player_name")] [XmlElement(ElementName = "player_name")]
[DefaultValue("")]
public string PlayerName { get; set; } = string.Empty; public string PlayerName { get; set; } = string.Empty;
[XmlElement("score_i1")] [XmlElement("score_i1")]
@ -23,11 +25,14 @@ public class CardDto
public long Fcol3 { get; set; } public long Fcol3 { get; set; }
[XmlElement("achieve_status")] [XmlElement("achieve_status")]
[DefaultValue("")]
public string AchieveStatus { get; set; } = string.Empty; public string AchieveStatus { get; set; } = string.Empty;
[XmlElement("created")] [XmlElement("created")]
[DefaultValue("")]
public string Created { get; set; } = string.Empty; public string Created { get; set; } = string.Empty;
[XmlElement("modified")] [XmlElement("modified")]
[DefaultValue("")]
public string Modified { get; set; } = string.Empty; public string Modified { get; set; } = string.Empty;
} }

View File

@ -0,0 +1,15 @@
using Application.Interfaces;
namespace Application.Game.Card;
public class CardDependencyAggregate : ICardDependencyAggregate
{
public CardDependencyAggregate(ICardDbContext cardDbContext, IMusicDbContext musicDbContext)
{
CardDbContext = cardDbContext;
MusicDbContext = musicDbContext;
}
public ICardDbContext CardDbContext { get; }
public IMusicDbContext MusicDbContext { get; }
}

View File

@ -0,0 +1,36 @@
using Application.Common;
using Application.Common.Exceptions;
using Application.Common.Models;
using Application.Dto;
using Application.Interfaces;
using Application.Mappers;
using MediatR;
using Microsoft.Extensions.Logging;
using Throw;
namespace Application.Game.Card;
public record CardRegisterCommand(long CardId, string Data) : IRequestWrapper<string>;
public class CardRegisterCommandHandler : CardRequestHandlerBase<CardRegisterCommand, string>
{
public CardRegisterCommandHandler(ICardDependencyAggregate aggregate) : base(aggregate)
{
}
public override async Task<ServiceResult<string>> Handle(CardRegisterCommand request, CancellationToken cancellationToken)
{
var exists = CardDbContext.CardMains.Any(card => card.CardId == request.CardId);
if (!exists)
{
return ServiceResult.Failed<string>(ServiceError.CustomMessage($"Card {request.CardId} already exists!"));
}
var card = request.Data.DeserializeCardData<CardDto>().CardDtoToCardMain();
card.CardId = request.CardId;
CardDbContext.CardMains.Add(card);
await CardDbContext.SaveChangesAsync(cancellationToken);
return new ServiceResult<string>(request.Data);
}
}

View File

@ -0,0 +1,23 @@
using Domain;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
namespace Application.Game.Card;
public class CardRequest
{
[ModelBinder(Name = "mac_addr")]
public string Mac { get; set; } = string.Empty;
[ModelBinder(Name = "cmd_str")]
public int CardCommandType { get; set; }
[ModelBinder(Name = "type")]
public int CardRequestType { get; set; }
[ModelBinder(Name = "card_no")]
public long CardId { get; set; }
[ModelBinder(Name = "data")]
public string Data { get; set; } = string.Empty;
}

View File

@ -0,0 +1,21 @@
using Application.Common.Models;
using Application.Interfaces;
using MediatR;
using Microsoft.Extensions.Logging;
namespace Application.Game.Card;
public abstract class CardRequestHandlerBase<TIn, TOut>: IRequestHandlerWrapper<TIn, TOut>
where TIn : IRequestWrapper<TOut>
{
public ICardDbContext CardDbContext { get; }
public IMusicDbContext MusicDbContext { get; }
public CardRequestHandlerBase(ICardDependencyAggregate aggregate)
{
CardDbContext = aggregate.CardDbContext;
MusicDbContext = aggregate.MusicDbContext;
}
public abstract Task<ServiceResult<TOut>> Handle(TIn request, CancellationToken cancellationToken);
}

View File

@ -0,0 +1,9 @@
using Microsoft.Extensions.Logging;
namespace Application.Interfaces;
public interface ICardDependencyAggregate
{
ICardDbContext CardDbContext { get; }
IMusicDbContext MusicDbContext { get; }
}

View File

@ -0,0 +1,14 @@
using Application.Common.Models;
using MediatR;
namespace Application.Interfaces;
public interface IRequestWrapper<T> : IRequest<ServiceResult<T>>
{
}
public interface IRequestHandlerWrapper<TIn, TOut> : IRequestHandler<TIn, ServiceResult<TOut>> where TIn : IRequestWrapper<TOut>
{
}

View File

@ -8,4 +8,6 @@ namespace Application.Mappers;
public static partial class CardMapper public static partial class CardMapper
{ {
public static partial CardDto CardMainToCardDto(this CardMain cardMain); public static partial CardDto CardMainToCardDto(this CardMain cardMain);
public static partial CardMain CardDtoToCardMain(this CardDto cardDto);
} }

View File

@ -0,0 +1,20 @@
namespace Domain.Config;
public class GameConfig
{
public const string GAME_SECTION = "Game";
public int AvatarCount { get; set; }
public int NavigatorCount { get; set; }
public int ItemCount { get; set; }
public int SkinCount { get; set; }
public int SeCount { get; set; }
public int TitleCount { get; set; }
public List<int> UnlockableSongIds { get; set; } = new();
}

View File

@ -0,0 +1,9 @@
namespace Domain;
public enum CardCommandType
{
CardReadRequest = 256,
CardWriteRequest = 768,
RegisterRequest = 512,
ReissueRequest = 1536
}

View File

@ -0,0 +1,55 @@
namespace Domain;
public enum CardRequestType
{
#region Read
ReadCard = 259,
ReadCardDetail = 260,
ReadCardDetails = 261,
ReadCardBData = 264,
ReadAvatar = 418,
ReadItem = 420,
ReadSkin = 422,
ReadTitle = 424,
ReadMusic = 428,
ReadEventReward = 441,
ReadNavigator = 443,
ReadMusicExtra = 465,
ReadMusicAou = 467,
ReadCoin = 468,
ReadUnlockReward = 507,
ReadUnlockKeynum = 509,
ReadSoundEffect = 8458,
ReadGetMessage = 8461,
ReadCond = 8465,
ReadTotalTrophy = 8468,
#endregion
#region Session
GetSession = 401,
StartSession = 402,
#endregion
#region Write
WriteCard = 771,
WriteCardDetail = 772,
WriteCardBData = 776,
WriteAvatar = 929,
WriteItem = 931,
WriteTitle = 935,
WriteMusicDetail = 941,
WriteNavigator = 954,
WriteCoin = 980,
WriteSkin = 933,
WriteUnlockKeynum = 1020,
WriteSoundEffect = 8969,
#endregion
#region Online Matching
StartOnlineMatching = 8705,
UpdateOnlineMatching = 8961,
UploadOnlineMatchingResult = 8709,
#endregion
}

View File

@ -0,0 +1,27 @@
namespace Domain;
public enum CardReturnCode
{
/// <summary>
/// Normal
/// 処理は正常に完了しました in debug string
/// </summary>
Ok = 1,
/// <summary>
/// New card
/// 未登録のカードです in debug string
/// </summary>
CardNotRegistered = 23,
/// <summary>
/// Not reissue, to determine whether it is a new card or reissued card
/// 再発行予約がありません in debug string
/// </summary>
NotReissue = 27,
/// <summary>
/// Server side validation error
/// </summary>
Unknown = 999
}

View File

@ -10,77 +10,89 @@ namespace Infrastructure.Migrations
/// <inheritdoc /> /// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder) protected override void Up(MigrationBuilder migrationBuilder)
{ {
migrationBuilder.CreateTable( if (!MigrationHelper.Exists("card_bdata"))
name: "card_bdata", {
columns: table => new migrationBuilder.CreateTable(
{ name: "card_bdata",
cardid = table.Column<long>(name: "card_id", type: "INTEGER", nullable: false), columns: table => new
bdata = table.Column<string>(type: "TEXT", nullable: true), {
bdatasize = table.Column<long>(name: "bdata_size", type: "INTEGER", nullable: false) cardid = table.Column<long>(name: "card_id", type: "INTEGER", nullable: false),
}, bdata = table.Column<string>(type: "TEXT", nullable: true),
constraints: table => bdatasize = table.Column<long>(name: "bdata_size", type: "INTEGER", nullable: false)
{ },
table.PrimaryKey("PK_card_bdata", x => x.cardid); constraints: table =>
}); {
table.PrimaryKey("PK_card_bdata", x => x.cardid);
});
}
migrationBuilder.CreateTable( if (!MigrationHelper.Exists("card_detail"))
name: "card_detail", {
columns: table => new migrationBuilder.CreateTable(
{ name: "card_detail",
cardid = table.Column<long>(name: "card_id", type: "INTEGER", nullable: false), columns: table => new
pcol1 = table.Column<long>(type: "INTEGER", nullable: false), {
pcol2 = table.Column<long>(type: "INTEGER", nullable: false), cardid = table.Column<long>(name: "card_id", type: "INTEGER", nullable: false),
pcol3 = table.Column<long>(type: "INTEGER", nullable: false), pcol1 = table.Column<long>(type: "INTEGER", nullable: false),
scorei1 = table.Column<long>(name: "score_i1", type: "INTEGER", nullable: false), pcol2 = table.Column<long>(type: "INTEGER", nullable: false),
scoreui1 = table.Column<long>(name: "score_ui1", type: "INTEGER", nullable: false), pcol3 = table.Column<long>(type: "INTEGER", nullable: false),
scoreui2 = table.Column<long>(name: "score_ui2", type: "INTEGER", nullable: false), scorei1 = table.Column<long>(name: "score_i1", type: "INTEGER", nullable: false),
scoreui3 = table.Column<long>(name: "score_ui3", type: "INTEGER", nullable: false), scoreui1 = table.Column<long>(name: "score_ui1", type: "INTEGER", nullable: false),
scoreui4 = table.Column<long>(name: "score_ui4", type: "INTEGER", nullable: false), scoreui2 = table.Column<long>(name: "score_ui2", type: "INTEGER", nullable: false),
scoreui5 = table.Column<long>(name: "score_ui5", type: "INTEGER", nullable: false), scoreui3 = table.Column<long>(name: "score_ui3", type: "INTEGER", nullable: false),
scoreui6 = table.Column<long>(name: "score_ui6", type: "INTEGER", nullable: false), scoreui4 = table.Column<long>(name: "score_ui4", type: "INTEGER", nullable: false),
scorebi1 = table.Column<long>(name: "score_bi1", type: "INTEGER", nullable: false), scoreui5 = table.Column<long>(name: "score_ui5", type: "INTEGER", nullable: false),
lastplaytenpoid = table.Column<string>(name: "last_play_tenpo_id", type: "TEXT", nullable: true), scoreui6 = table.Column<long>(name: "score_ui6", type: "INTEGER", nullable: false),
fcol1 = table.Column<long>(type: "INTEGER", nullable: false), scorebi1 = table.Column<long>(name: "score_bi1", type: "INTEGER", nullable: false),
fcol2 = table.Column<long>(type: "INTEGER", nullable: false), lastplaytenpoid = table.Column<string>(name: "last_play_tenpo_id", type: "TEXT", nullable: true),
fcol3 = table.Column<long>(type: "INTEGER", nullable: false), fcol1 = table.Column<long>(type: "INTEGER", nullable: false),
lastplaytime = table.Column<long>(name: "last_play_time", type: "INTEGER", nullable: false) fcol2 = table.Column<long>(type: "INTEGER", nullable: false),
}, fcol3 = table.Column<long>(type: "INTEGER", nullable: false),
constraints: table => lastplaytime = table.Column<long>(name: "last_play_time", type: "INTEGER", nullable: false)
{ },
table.PrimaryKey("PK_card_detail", x => new { x.cardid, x.pcol1, x.pcol2, x.pcol3 }); constraints: table =>
}); {
table.PrimaryKey("PK_card_detail", x => new { x.cardid, x.pcol1, x.pcol2, x.pcol3 });
});
}
migrationBuilder.CreateTable( if (!MigrationHelper.Exists("card_main"))
name: "card_main", {
columns: table => new migrationBuilder.CreateTable(
{ name: "card_main",
cardid = table.Column<long>(name: "card_id", type: "INTEGER", nullable: false), columns: table => new
playername = table.Column<string>(name: "player_name", type: "TEXT", nullable: false), {
scorei1 = table.Column<long>(name: "score_i1", type: "INTEGER", nullable: false), cardid = table.Column<long>(name: "card_id", type: "INTEGER", nullable: false),
fcol1 = table.Column<long>(type: "INTEGER", nullable: false), playername = table.Column<string>(name: "player_name", type: "TEXT", nullable: false),
fcol2 = table.Column<long>(type: "INTEGER", nullable: false), scorei1 = table.Column<long>(name: "score_i1", type: "INTEGER", nullable: false),
fcol3 = table.Column<long>(type: "INTEGER", nullable: false), fcol1 = table.Column<long>(type: "INTEGER", nullable: false),
achievestatus = table.Column<string>(name: "achieve_status", type: "TEXT", nullable: false), fcol2 = table.Column<long>(type: "INTEGER", nullable: false),
created = table.Column<string>(type: "TEXT", nullable: true), fcol3 = table.Column<long>(type: "INTEGER", nullable: false),
modified = table.Column<string>(type: "TEXT", nullable: true) achievestatus = table.Column<string>(name: "achieve_status", type: "TEXT", nullable: false),
}, created = table.Column<string>(type: "TEXT", nullable: true),
constraints: table => modified = table.Column<string>(type: "TEXT", nullable: true)
{ },
table.PrimaryKey("PK_card_main", x => x.cardid); constraints: table =>
}); {
table.PrimaryKey("PK_card_main", x => x.cardid);
});
}
migrationBuilder.CreateTable( if (!MigrationHelper.Exists("CardPlayCount"))
name: "CardPlayCount", {
columns: table => new migrationBuilder.CreateTable(
{ name: "CardPlayCount",
cardid = table.Column<long>(name: "card_id", type: "INTEGER", nullable: false), columns: table => new
playcount = table.Column<long>(name: "play_count", type: "INTEGER", nullable: false), {
lastplayedtime = table.Column<long>(name: "last_played_time", type: "INTEGER", nullable: false) cardid = table.Column<long>(name: "card_id", type: "INTEGER", nullable: false),
}, playcount = table.Column<long>(name: "play_count", type: "INTEGER", nullable: false),
constraints: table => lastplayedtime = table.Column<long>(name: "last_played_time", type: "INTEGER", nullable: false)
{ },
table.PrimaryKey("PK_CardPlayCount", x => x.cardid); constraints: table =>
}); {
table.PrimaryKey("PK_CardPlayCount", x => x.cardid);
});
}
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@ -20,14 +20,14 @@ public static class MigrationHelper
return reader.Read()? (long)reader[0] == 1 : false; return reader.Read()? (long)reader[0] == 1 : false;
} }
public static string GetConnectionString() private static string GetConnectionString()
{ {
var builder = new ConfigurationBuilder() var builder = new ConfigurationBuilder()
.SetBasePath(PathHelper.ConfigurationPath) .SetBasePath(PathHelper.ConfigurationPath)
.AddJsonFile("database.json", optional: false, reloadOnChange: false); .AddJsonFile("database.json", optional: false, reloadOnChange: false);
var cardDbName = builder.Build()["CardDbName"]; var cardDbName = builder.Build()["CardDbName"] ?? "card.db3";
var cardDbPath = Path.Combine(PathHelper.DatabasePath, cardDbName); var cardDbPath = Path.Combine(PathHelper.DatabasePath, cardDbName);
return cardDbPath; return cardDbPath;
} }

View File

@ -1,15 +1,17 @@
{ {
"AvatarCount": 356, "Game": {
"NavigatorCount": 118, "AvatarCount": 356,
"ItemCount": 21, "NavigatorCount": 118,
"SkinCount": 21, "ItemCount": 21,
"SeCount": 26, "SkinCount": 21,
"TitleCount": 5530, "SeCount": 26,
"UnlockableSongIds": "TitleCount": 5530,
[ "UnlockableSongIds":
11, 13, 149, 273, 291, 320, 321, 371, 378, 384, 464, 471, 474, 475, 492, [
494, 498, 520, 548, 551, 558, 561, 565, 570, 577, 583, 612, 615, 622, 11, 13, 149, 273, 291, 320, 321, 371, 378, 384, 464, 471, 474, 475, 492,
632, 659, 666, 668, 670, 672, 676, 680, 682, 685, 686, 697, 700, 701, 494, 498, 520, 548, 551, 558, 561, 565, 570, 577, 583, 612, 615, 622,
711, 720, 749, 875, 876, 877 632, 659, 666, 668, 670, 672, 676, 680, 682, 685, 686, 697, 700, 701,
] 711, 720, 749, 875, 876, 877
]
}
} }

View File

@ -20,5 +20,20 @@ namespace MainServer.Controllers.API
{ {
return context.MusicUnlocks.First(); return context.MusicUnlocks.First();
} }
[HttpPost]
public ActionResult<string> TestXmlInputOutput([FromForm(Name = "my_model")]TestModel model,
[FromForm(Name = "my_type")]int type)
{
return Ok($"{model.Name}\n{model.Age}\n{type}");
}
} }
public class TestModel
{
public string Name { get; set; } = string.Empty;
public int Age { get; set; }
}
} }

View File

@ -0,0 +1,134 @@
using System.Net;
using Application.Common.Models;
using Application.Game.Card;
using Domain;
using Microsoft.AspNetCore.Mvc;
using Throw;
namespace MainServer.Controllers.Game;
[ApiController]
[Route("service/card")]
public class CardController : BaseController<CardController>
{
[HttpPost("cardn.cgi")]
public async Task<ActionResult<string>> CardService([FromForm]CardRequest request)
{
var cardRequestType = (CardRequestType)request.CardRequestType;
var cardCommandType = (CardCommandType)request.CardCommandType;
cardCommandType.Throw().IfOutOfRange();
if (cardCommandType is CardCommandType.CardReadRequest or CardCommandType.CardWriteRequest)
{
cardRequestType.Throw().IfOutOfRange();
}
request.Data = WebUtility.UrlDecode(request.Data);
var result = ServiceResult.Failed<string>(ServiceError.DefaultError);
switch (cardCommandType)
{
case CardCommandType.CardReadRequest:
{
switch (cardRequestType)
{
case CardRequestType.ReadCard:
break;
case CardRequestType.ReadCardDetail:
break;
case CardRequestType.ReadCardDetails:
break;
case CardRequestType.ReadCardBData:
break;
case CardRequestType.ReadAvatar:
break;
case CardRequestType.ReadItem:
break;
case CardRequestType.ReadSkin:
break;
case CardRequestType.ReadTitle:
break;
case CardRequestType.ReadMusic:
break;
case CardRequestType.ReadEventReward:
break;
case CardRequestType.ReadNavigator:
break;
case CardRequestType.ReadMusicExtra:
break;
case CardRequestType.ReadMusicAou:
break;
case CardRequestType.ReadCoin:
break;
case CardRequestType.ReadUnlockReward:
break;
case CardRequestType.ReadUnlockKeynum:
break;
case CardRequestType.ReadSoundEffect:
break;
case CardRequestType.ReadGetMessage:
break;
case CardRequestType.ReadCond:
break;
case CardRequestType.ReadTotalTrophy:
break;
case CardRequestType.GetSession:
break;
case CardRequestType.StartSession:
break;
case CardRequestType.WriteCard:
break;
case CardRequestType.WriteCardDetail:
break;
case CardRequestType.WriteCardBData:
break;
case CardRequestType.WriteAvatar:
break;
case CardRequestType.WriteItem:
break;
case CardRequestType.WriteTitle:
break;
case CardRequestType.WriteMusicDetail:
break;
case CardRequestType.WriteNavigator:
break;
case CardRequestType.WriteCoin:
break;
case CardRequestType.WriteSkin:
break;
case CardRequestType.WriteUnlockKeynum:
break;
case CardRequestType.WriteSoundEffect:
break;
case CardRequestType.StartOnlineMatching:
break;
case CardRequestType.UpdateOnlineMatching:
break;
case CardRequestType.UploadOnlineMatchingResult:
break;
default:
throw new ArgumentOutOfRangeException(message: "Should not happen", paramName:null);
}
break;
}
case CardCommandType.CardWriteRequest:
break;
case CardCommandType.RegisterRequest:
result = await Mediator.Send(new CardRegisterCommand(request.CardId, request.Data));
break;
case CardCommandType.ReissueRequest:
break;
default:
throw new ArgumentOutOfRangeException(message: "Should not happen", paramName:null);
}
if (result.Succeeded)
{
return Ok(result.Data);
}
var errorMessage = $"{(int)CardReturnCode.Unknown}\n" +
$"{result.Error!.Message}";
return Ok(errorMessage);
}
}

View File

@ -0,0 +1,81 @@
using System.Diagnostics;
using Application.Common.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
namespace MainServer.Filters;
/// <summary>
/// Exception filters
/// </summary>
public class ApiExceptionFilterService : ExceptionFilterAttribute
{
private readonly IDictionary<Type, Action<ExceptionContext>> exceptionHandlers;
private readonly ILogger<ApiExceptionFilterService> logger;
/// <summary>
/// Constructor
/// </summary>
public ApiExceptionFilterService(ILogger<ApiExceptionFilterService> logger)
{
this.logger = logger;
// Register known exception types and handlers.
exceptionHandlers = new Dictionary<Type, Action<ExceptionContext>>
{
{ typeof(ArgumentOutOfRangeException), HandleArgumentOutOfRangeException }
};
}
/// <summary>
/// On exception event
/// </summary>
/// <param name="context"></param>
public override void OnException(ExceptionContext context)
{
HandleException(context);
base.OnException(context);
}
private void HandleException(ExceptionContext context)
{
var type = context.Exception.GetType();
if (exceptionHandlers.ContainsKey(type))
{
exceptionHandlers[type].Invoke(context);
return;
}
HandleUnknownException(context);
}
private static void HandleUnknownException(ExceptionContext context)
{
var details = ServiceResult.Failed(ServiceError.DefaultError);
context.Result = new ObjectResult(details)
{
StatusCode = StatusCodes.Status500InternalServerError
};
context.ExceptionHandled = true;
}
private void HandleArgumentOutOfRangeException(ExceptionContext context)
{
logger.LogError(context.Exception, "");
var exception = context.Exception as ArgumentOutOfRangeException;
Debug.Assert(exception != null, nameof(exception) + " != null");
var variable = exception.ParamName ?? "Unknown";
var details = ServiceResult.Failed(ServiceError.CustomMessage($"Argument {variable} out of bounds!"));
context.Result = new ObjectResult(details)
{
StatusCode = StatusCodes.Status400BadRequest
};
context.ExceptionHandled = true;
}
}

View File

@ -43,6 +43,7 @@
<ItemGroup> <ItemGroup>
<Folder Include="Controllers\API" /> <Folder Include="Controllers\API" />
<Folder Include="Logs" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -5,6 +5,7 @@ using Domain.Config;
using Infrastructure; using Infrastructure;
using Infrastructure.Common; using Infrastructure.Common;
using Infrastructure.Persistence; using Infrastructure.Persistence;
using MainServer.Filters;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Serilog; using Serilog;
using Serilog.Extensions.Logging; using Serilog.Extensions.Logging;
@ -38,6 +39,8 @@ try
builder.Configuration.GetSection(EventConfig.EVENT_SECTION)); builder.Configuration.GetSection(EventConfig.EVENT_SECTION));
builder.Services.Configure<RelayConfig>( builder.Services.Configure<RelayConfig>(
builder.Configuration.GetSection(RelayConfig.RELAY_SECTION)); builder.Configuration.GetSection(RelayConfig.RELAY_SECTION));
builder.Services.Configure<GameConfig>(
builder.Configuration.GetSection(GameConfig.GAME_SECTION));
var serverIp = builder.Configuration["ServerIp"] ?? "127.0.0.1"; var serverIp = builder.Configuration["ServerIp"] ?? "127.0.0.1";
var certificateManager = new CertificateService(serverIp, new SerilogLoggerFactory(Log.Logger).CreateLogger("")); var certificateManager = new CertificateService(serverIp, new SerilogLoggerFactory(Log.Logger).CreateLogger(""));
@ -51,7 +54,9 @@ try
configuration.WriteTo.Console().ReadFrom.Configuration(context.Configuration); configuration.WriteTo.Console().ReadFrom.Configuration(context.Configuration);
}); });
builder.Services.AddControllers().AddXmlSerializerFormatters(); builder.Services.AddControllers(options =>
options.Filters.Add<ApiExceptionFilterService>());
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer(); builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(); builder.Services.AddSwaggerGen();
@ -80,21 +85,23 @@ try
var eventService = app.Services.GetService<IEventManagerService>(); var eventService = app.Services.GetService<IEventManagerService>();
eventService.ThrowIfNull(); eventService.ThrowIfNull();
eventService.InitializeEvents(); eventService.InitializeEvents();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment()) if (app.Environment.IsDevelopment())
{ {
app.UseSwagger(); app.UseSwagger();
app.UseSwaggerUI(); app.UseSwaggerUI();
} }
// app.UseExceptionHandler();
app.UseStaticFiles();
app.MapControllers(); app.MapControllers();
app.Run(); app.Run();
} }
catch (Exception ex) catch (Exception ex)
{ {
Log.Fatal(ex, "Unhandled exception"); Log.Fatal(ex, "Unhandled exception in startup");
} }
finally finally
{ {