초기커밋

This commit is contained in:
2025-05-01 07:20:41 +09:00
commit 98bb2e3c5c
2747 changed files with 646947 additions and 0 deletions

View File

@@ -0,0 +1,80 @@
namespace BrokerCore.Services;
using Common;
using DbEntity;
using ServerBase;
using ServerCommon;
using MetaAssets;
using Repository;
public class DailyAmountLimitChecker
{
private readonly PlanetItemExchangeOrderAmountTotalLimitRepo m_planet_item_exchange_order_amount_total_limit;
private readonly PlanetItemExchangeOrderAmountUserLimitRepo m_planet_item_exchange_order_amount_user_limit;
public DailyAmountLimitChecker(
PlanetItemExchangeOrderAmountTotalLimitRepo planetItemExchangeOrderAmountTotalLimit,
PlanetItemExchangeOrderAmountUserLimitRepo planetItemExchangeOrderAmountUserLimit)
{
m_planet_item_exchange_order_amount_total_limit = planetItemExchangeOrderAmountTotalLimit;
m_planet_item_exchange_order_amount_user_limit = planetItemExchangeOrderAmountUserLimit;
}
public async Task<(Result result,
PlanetItemExchangeOrderAmountTotalLimit? totalLimit,
PlanetItemExchangeOrderAmountUserLimit? userLimit)>
dailyLimitCheck(PlanetItemExchangeOrder order,
PlanetItemExchangePolicyMetaData exchangePolicy, CancellationToken cancellationToken = default)
{
var result = new Result();
PlanetItemExchangeOrderAmountTotalLimit? total_limit = null;
PlanetItemExchangeOrderAmountUserLimit? user_limit = null;
try
{
var exchange_date = DateOnly.FromDateTime(order.CreatedAt.ToUniversalTime());
var total_daily_limit = exchangePolicy.DailyTotalAmountLimit;
(result, total_limit) =
await m_planet_item_exchange_order_amount_total_limit.findByExchangeMetaAndDateOrCreate(
order.ExchangeMetaId,
exchange_date,
order.SeasonId,
cancellationToken);
Guard.Against.resultFail(result);
Guard.Against.isNull(total_limit, ServerErrorCode.RdbError,
()=>"PlanetItemExchangeOrderAmountTotalLimit not found");
Guard.Against.isTrue(total_limit.DailyAmount + order.ExchangeMetaAmount > total_daily_limit,
ServerErrorCode.ExchangeTotalOrderDailyLimitExceeded,
()=>$"Daily total amount limit exceeded [{exchangePolicy.ID}] => current_total_daily_limit:{total_limit.DailyAmount} + order.ExchangeMetaAmount:{order.ExchangeMetaAmount} > total_daily_limit:{total_daily_limit}"
);
var user_daily_limit = exchangePolicy.DailyUserAmountLimit;
(result, user_limit) =
await m_planet_item_exchange_order_amount_user_limit.findByExchangeMetaAndDateOrCreate(order.ExchangeMetaId,
order.UserGuid, exchange_date, order.SeasonId, cancellationToken);
Guard.Against.resultFail(result);
Guard.Against.isNull(user_limit, ServerErrorCode.RdbError,
()=>"PlanetItemExchangeOrderAmountUserLimit not found");
Guard.Against.isTrue(user_limit.DailyAmount + order.ExchangeMetaAmount > user_daily_limit,
ServerErrorCode.ExchangeUserOrderDailyLimitExceeded,
()=>$"Daily user amount limit exceeded [{exchangePolicy.ID}] => current_user_daily_limit:{user_limit.DailyAmount} + order.ExchangeMetaAmount:{order.ExchangeMetaAmount} > user_daily_limit:{user_daily_limit}"
);
}
catch (ApiException ex)
{
result.setFail((ServerErrorCode)ex.ErrorCode, ex.Message);
return (result, total_limit, user_limit);
}
catch (Exception ex)
{
result.setFail(ServerErrorCode.InternalServerError, ex.Message);
return (result, total_limit, user_limit);
}
return (result, total_limit, user_limit);
}
}

View File

@@ -0,0 +1,16 @@
namespace BrokerCore.Services;
using DbEntity;
using Entity;
public interface IOrderCompletionStrategy
{
Task<PlanetItemExchangeOrder> completeOrder(
PlanetUserEntity planetUserEntity,
PlanetItemExchangeOrder order_created,
CancellationToken cancellationToken = default);
}

View File

@@ -0,0 +1,14 @@
using BrokerCore.DbEntity;
using MetaAssets;
namespace BrokerCore.Services;
public interface IOrderCreationStrategy
{
Task<(Result, PlanetItemExchangeOrder)> createOrder(
PlanetItemExchangeOrder order,
string planetServerType,
PlanetItemExchangePolicyMetaData exchangePolicy,
CancellationToken cancellationToken);
}

View File

@@ -0,0 +1,9 @@
using ServerCommon;
namespace BrokerCore.Services;
public interface IOrderStrategyProvider
{
IOrderCreationStrategy? getCreationStrategy(CaliverseItemType itemType);
IOrderCompletionStrategy? getCompletionStrategy(CaliverseItemType itemType);
}

View File

@@ -0,0 +1,54 @@
using Microsoft.Extensions.DependencyInjection;
using ServerCommon;
using System.Collections.Concurrent;
namespace BrokerCore.Services;
public class OrderStrategyProvider : IOrderStrategyProvider
{
private readonly IServiceProvider m_service_provider;
private readonly ConcurrentDictionary<CaliverseItemType, Type> m_creation_strategy_map;
private readonly ConcurrentDictionary<CaliverseItemType, Type> m_completion_strategy_map;
public OrderStrategyProvider(IServiceProvider serviceProvider)
{
m_service_provider = serviceProvider;
// 전략 맵핑 초기화
m_creation_strategy_map = new ConcurrentDictionary<CaliverseItemType, Type>
{
[CaliverseItemType.Currency] = typeof(CurrencyExchangeCreationStrategy),
[CaliverseItemType.CaliverseProduct] = typeof(ProductExchangeCreationStrategy)
};
m_completion_strategy_map = new ConcurrentDictionary<CaliverseItemType, Type>
{
[CaliverseItemType.Currency] = typeof(CurrencyOrderCompletionStrategy),
[CaliverseItemType.CaliverseProduct] = typeof(ProductOrderCompletionStrategy)
};
}
public IOrderCreationStrategy? getCreationStrategy(CaliverseItemType itemType)
{
if (m_creation_strategy_map.TryGetValue(itemType, out var strategy_type))
{
return m_service_provider.GetService(strategy_type) as IOrderCreationStrategy;
}
// 기본 전략 반환
return m_service_provider.GetService<CurrencyExchangeCreationStrategy>();
}
public IOrderCompletionStrategy? getCompletionStrategy(CaliverseItemType itemType)
{
if (m_completion_strategy_map.TryGetValue(itemType, out var strategy_type))
{
return m_service_provider.GetService(strategy_type) as IOrderCompletionStrategy;
}
// 기본 전략 반환
return m_service_provider.GetService<CurrencyOrderCompletionStrategy>();
}
}

View File

@@ -0,0 +1,218 @@
namespace BrokerCore.Services;
using ServerCommon;
using Common;
using DbEntity;
using Repository;
using Entity;
using System.Collections.Generic;
using BrokerBusinessLog;
using MetaAssets;
using ServerCore; using ServerBase;
public class PlanetItemExchangeService
{
readonly PlanetUserEntity m_planet_user;
private readonly IOrderStrategyProvider m_strategies;
private readonly BrokerMetaTableRef m_meta_table_ref;
private readonly PlanetItemExchangeOrderRepo m_planet_item_exchange_repo;
private readonly DynamoDbClient m_dynamo_db_client;
public PlanetItemExchangeService(
IServerLogic serverLogic,
PlanetUserEntity planetUser,
IOrderStrategyProvider strategies,
BrokerMetaTableRef metaTableRef,
PlanetItemExchangeOrderRepo planetItemExchangeOrderRepo)
{
m_planet_user = planetUser;
m_strategies = strategies;
m_meta_table_ref = metaTableRef;
m_planet_user.onInit().Wait();
m_planet_item_exchange_repo = planetItemExchangeOrderRepo;
m_dynamo_db_client = serverLogic.getDynamoDbClient();
}
public PlanetUserEntity PlanetUser => m_planet_user;
//===================================================================================================
// 로그아웃 여부만 체크
//===================================================================================================
public bool isUserLoggedIn()
{
var user_attribute = m_planet_user.getEntityAttributeNotNull<UserAttribute>();
return user_attribute.GameLoginDateTime > user_attribute.GameLogoutDateTime;
}
//===================================================================================================
// 유저 인증 및 account, user, nickname 정보 읽음
//===================================================================================================
public async Task validateAndGetUser(
string accountId, string userGuid, string planetId
)
{
m_planet_user.setPlanetId(planetId);
var action = m_planet_user.getEntityActionNotNull<UserAuthAction>();
if (!string.IsNullOrEmpty(accountId))
{
var result = await action.findAndSetAllAttributeByAccountId(accountId);
Guard.Against.resultFail(result);
}
else
{
var result = await action.findAndSetAllAttributeByUserGuid(userGuid);
Guard.Against.resultFail(result);
}
}
//=============================================================================================
// 현재 재화 요청
//=============================================================================================
public async Task<double> getCurrencyAmount(string currencyTypeString)
{
var currency_type = currencyTypeString.convertEnumTypeAndValueStringToEnum(CurrencyType.None);
Guard.Against.isFalse(currency_type != CurrencyType.None, ServerErrorCode.InvalidRequest,
() => "Invalid currency type");
CurrencyControlHelper.setDbConnector(m_dynamo_db_client);
var user_guid = m_planet_user.getEntityAttributeNotNull<AccountAttribute>().UserGuid;
var (result, current_sapphire_amount_double) =
await CurrencyControlHelper.getMoneyByUserGuid(user_guid, currency_type);
Guard.Against.resultFail(result);
return current_sapphire_amount_double;
}
//=============================================================================================
// 교환 주문 조회
//=============================================================================================
public async Task<(IEnumerable<PlanetItemExchangeOrder>, int)> findOrderList(
string planetId,
string metaId,
string seasonId,
string userGuid,
ExchangeOrderStatus? orderStatus = null,
int pageIndex = 1,
int pageSize = 20,
string sortOrder = "asc",
CancellationToken cancellationToken = default)
{
var (result, orders, total_count) =
await m_planet_item_exchange_repo.findList(
planetId,
metaId,
seasonId,
userGuid,
orderStatus,
pageIndex,
pageSize,
sortOrder,
cancellationToken);
Guard.Against.resultFail(result, ServerErrorCode.RdbError, () => result.ResultString);
Guard.Against.isNull(orders, ServerErrorCode.RdbError, () => result.ResultString);
return (orders, total_count);
}
//=============================================================================================
// 교환 주문 생성
//=============================================================================================
public async Task<(Result, PlanetItemExchangeOrder)> createOrder(
string planetId,
string seasonId,
string planetServerType,
string exchangeMetaId,
int exchangeMetaAmount,
CancellationToken cancellationToken = default)
{
// 메타 테이블에서 교환 정보 가져오기
m_meta_table_ref.MetaTable.PlanetItemExchangePolicyMetaTable.PlanetItemExchangePolicyDataListbyID
.TryGetValue(exchangeMetaId, out var exchange_policy);
Guard.Against.isNull(exchange_policy, ServerErrorCode.MetaIdInvalid,
() => $"Invalid ExchangeMetaId:{exchangeMetaId}");
// 주문 내역 생성
var order = makeExchangeOrderInfo(planetId, seasonId, exchange_policy, exchangeMetaAmount);
// 아이템 유형에 따라 전략 선택
var caliverse_item_type =
exchange_policy.CaliverseItemType.convertEnumTypeAndValueStringToEnum(CaliverseItemType.None);
var strategy = m_strategies.getCreationStrategy(caliverse_item_type);
Guard.Against.isNull(strategy, ServerErrorCode.InternalServerError,
() => "createOrder Invalid Item Type Strategy");
var (result, order_info) =
await strategy.createOrder(order, planetServerType, exchange_policy, cancellationToken);
Guard.Against.resultFail(result, ServerErrorCode.InternalServerError, () => result.ResultString);
// 비즈니스 로그 작성
BusinessLogger.collectLog(m_planet_user,
new PlanetItemExchangeBusinessLog(new LogActionEx(LogActionType.BrokerApiUserExchangeOrderCreated),
Helpers.createFromExchangeOrderLog(order)));
return (result, order_info);
}
//=============================================================================================
// 교환 주문 완료
// 주문 상태가 Pending 이어야 한다.
//=============================================================================================
public async Task<PlanetItemExchangeOrder> completeOrder(
string orderId, CancellationToken cancellationToken = default)
{
// 생성 주문 조회
var (result, order_created) =
await m_planet_item_exchange_repo.findOne(orderId, ExchangeOrderStatus.Pending, cancellationToken);
Guard.Against.resultFail(result);
Guard.Against.isNull(order_created, ServerErrorCode.ExchangeOrderIdNotFound, () => "Invalid Order Id");
// 주문 타입에 맞는 전략 선택 후 완료 처리
var strategy = m_strategies.getCompletionStrategy(order_created.CaliverseItemType);
Guard.Against.isNull(strategy, ServerErrorCode.InternalServerError, () => "completeOrder Invalid Order Type");
var order_completed = await strategy.completeOrder(m_planet_user, order_created, cancellationToken);
// 비즈니스 로그 작성
BusinessLogger.collectLog(m_planet_user,
new PlanetItemExchangeBusinessLog(new LogActionEx(LogActionType.BrokerApiUserExchangeOrderCompleted),
Helpers.createFromExchangeOrderLog(order_completed)));
return order_completed;
}
private PlanetItemExchangeOrder makeExchangeOrderInfo(
string planetId,
string seasonId,
PlanetItemExchangePolicyMetaData exchangePolicy,
int exchangeMetaAmount)
{
var now = DateTime.UtcNow;
return new PlanetItemExchangeOrder
{
OrderId = $"{now:yyMMdd}{Guid.NewGuid():N}",
OrderStatus = ExchangeOrderStatus.Pending,
SeasonId = seasonId,
PlanetId = planetId,
AccountId = m_planet_user.AccountId,
UserGuid = m_planet_user.UserGuid,
Nickname = m_planet_user.Nickname,
CaliverseItemType =
exchangePolicy.CaliverseItemType.convertEnumTypeAndValueStringToEnum(CaliverseItemType.None),
CaliverseItemId = exchangePolicy.CaliverseItemId,
CaliverseItemDeltaAmount = exchangeMetaAmount * exchangePolicy.CaliverseItemAmount,
PlanetItemType = exchangePolicy.PlanetItemType.convertEnumTypeAndValueStringToEnum(PlanetItemType.None),
PlanetItemId = exchangePolicy.PlanetItemId,
PlanetItemDeltaAmount = exchangeMetaAmount * exchangePolicy.PlanetItemAmount,
CreatedAt = now,
CompletedAt = null,
ExchangeMetaId = exchangePolicy.ID,
ExchangeMetaAmount = exchangeMetaAmount,
};
}
}

View File

@@ -0,0 +1,27 @@
using Microsoft.Extensions.DependencyInjection;
namespace BrokerCore.Services;
using Entity;
public static class ServiceCollectionExtensions
{
public static IServiceCollection addExchangeServices(this IServiceCollection services)
{
services.AddScoped<PlanetUserEntity>();
// 전략 등록
services.AddScoped<CurrencyExchangeCreationStrategy>();
services.AddScoped<ProductExchangeCreationStrategy>();
services.AddScoped<CurrencyOrderCompletionStrategy>();
services.AddScoped<ProductOrderCompletionStrategy>();
// 전략 제공자 등록
services.AddScoped<IOrderStrategyProvider, OrderStrategyProvider>();
// 서비스 등록
services.AddScoped<EchoSystemService>();
// services.AddScoped<SapphireExchangeService>();
services.AddScoped<PlanetItemExchangeService>();
return services;
}
}

View File

@@ -0,0 +1,163 @@
using BrokerCore.DbEntity;
using BrokerCore.Entity;
using BrokerCore.Repository;
using Microsoft.Extensions.Logging;
// using Polly;
// using Polly.Retry;
using MetaAssets;
using ServerCommon;
namespace BrokerCore.Services;
using Common;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage;
using Repository.Context;
using ServerCore; using ServerBase;
//=========================================================================================
// 플래닛의 아이템을 칼리버스 재화로 교환하는 주문 생성
// Broker의 rdb(순수 교환 정보)와 DynamoDB(유저의 게임정보)를 사용하여 교환 주문을 생성한다.
// 유저의 게임 정보에서 재화가 차감되면
//=========================================================================================
public class CurrencyExchangeCreationStrategy : IOrderCreationStrategy
{
private readonly PlanetUserEntity m_planet_user_entity;
private readonly MetaverseBrokerDbContext m_broker_db_context;
private readonly PlanetItemExchangeOrderRepo m_planet_item_exchange_repo;
private readonly PlanetItemExchangeOrderAmountTotalLimitRepo m_planet_item_exchange_order_amount_total_limit;
private readonly PlanetItemExchangeOrderAmountUserLimitRepo m_planet_item_exchange_order_amount_user_limit;
private readonly DynamoDbClient m_dynamo_db_client;
private readonly EchoSystemService m_echo_system_service;
private readonly ILogger<CurrencyExchangeCreationStrategy>? m_logger;
public CurrencyExchangeCreationStrategy(
IServerLogic serverLogic,
PlanetUserEntity planetUserEntity,
MetaverseBrokerDbContext brokerDbContext,
PlanetItemExchangeOrderRepo planetItemExchangeRepo,
PlanetItemExchangeOrderAmountTotalLimitRepo planetItemExchangeOrderAmountTotalLimit,
PlanetItemExchangeOrderAmountUserLimitRepo planetItemExchangeOrderAmountUserLimit,
EchoSystemService echoSystemService,
ILogger<CurrencyExchangeCreationStrategy>? logger = null!)
{
m_planet_user_entity = planetUserEntity;
m_broker_db_context = brokerDbContext;
m_planet_item_exchange_repo = planetItemExchangeRepo;
m_planet_item_exchange_order_amount_total_limit = planetItemExchangeOrderAmountTotalLimit;
m_planet_item_exchange_order_amount_user_limit = planetItemExchangeOrderAmountUserLimit;
m_dynamo_db_client = serverLogic.getDynamoDbClient();
m_echo_system_service = echoSystemService;
m_logger = logger;
}
public async Task<(Result, PlanetItemExchangeOrder)> createOrder(
PlanetItemExchangeOrder order,
string planetServerType,
PlanetItemExchangePolicyMetaData exchangePolicy, CancellationToken cancellationToken = default)
{
var exchange_date = DateOnly.FromDateTime(order.CreatedAt.ToUniversalTime());
var exchange_amount = order.ExchangeMetaAmount;
var strategy = m_broker_db_context.Database.CreateExecutionStrategy();
var execute_result = await strategy.ExecuteAsync<Result>(async () =>
{
var transaction_result = new Result();
await using var transaction = await m_broker_db_context.Database.BeginTransactionAsync(cancellationToken);
try
{
var daily_limit_checker = new DailyAmountLimitChecker(
m_planet_item_exchange_order_amount_total_limit,
m_planet_item_exchange_order_amount_user_limit);
var (result, total_limit, user_limit) = await daily_limit_checker.dailyLimitCheck(order, exchangePolicy, cancellationToken);
// 신규 주문 정보 삽입
await m_planet_item_exchange_repo.add(order, cancellationToken);
Guard.Against.resultFail(result);
Guard.Against.isNull(total_limit, ServerErrorCode.RdbError,
()=>"PlanetItemExchangeOrderAmountTotalLimit not found");
Guard.Against.isNull(user_limit, ServerErrorCode.RdbError,
()=>"PlanetItemExchangeOrderAmountUserLimit not found");
var (currency_result, _) = await updateCurrency(exchange_amount, exchangePolicy);
Guard.Against.resultFail(currency_result);
await m_planet_item_exchange_order_amount_total_limit.increaseDailyAmount(total_limit,
exchange_amount, cancellationToken);
await m_planet_item_exchange_order_amount_user_limit.increaseDailyAmount(user_limit,
exchange_amount, cancellationToken);
await transaction.CommitAsync(cancellationToken);
// 사파이어 처리인 경우 에코시스템에 통보
if (string.Equals(exchangePolicy.CaliverseItemId, CurrencyType.Sapphire.ToString(),
StringComparison.OrdinalIgnoreCase))
{
var sapphire_delta = exchange_amount * exchangePolicy.CaliverseItemAmount;
_ = notifyToEchoSystemEvent(planetServerType, sapphire_delta).ConfigureAwait(false);
}
}
catch
{
if (transaction.GetDbTransaction().Connection is not null)
{
await transaction.RollbackAsync(cancellationToken);
}
throw;
}
return transaction_result;
});
return (execute_result, order);
}
private async Task<(Result, long)> updateCurrency(
int exchangeMetaAmount,
PlanetItemExchangePolicyMetaData exchangePolicy)
{
var currency_delta = exchangeMetaAmount * exchangePolicy.CaliverseItemAmount;
CurrencyControlHelper.setDbConnector(m_dynamo_db_client);
var (result, current_sapphire_amount_double) =
await CurrencyControlHelper.spendMoneyByUserGuid(getUserGuid(), CurrencyType.Sapphire, currency_delta);
var current_sapphire_amount = Convert.ToInt64(current_sapphire_amount_double);
return (result, current_sapphire_amount);
}
//===========================================================================================
// 사파이어 처리인 경우 외부에 있는 에코시스템에 이벤트를 통보하여야 한다.
//===========================================================================================
private async Task notifyToEchoSystemEvent(
string planetServerType,
long sapphireDelta)
{
try
{
await m_echo_system_service.createAndSetEventPayload(
accountId: getUserAccountId(),
nickname: getUserNickname(),
userGuid: getUserGuid(),
sapphireDelta,
planetServerType,
CaliumEventType.extra_get.ToString()
);
_ = await m_echo_system_service.processCaliumEvent(m_planet_user_entity);
}
catch (Exception ex)
{
m_logger?.LogError(ex, "Failed to process calium event");
throw;
}
}
private string getUserAccountId() =>
m_planet_user_entity.getEntityAttributeNotNull<AccountAttribute>().AccountId;
private string getUserGuid() =>
m_planet_user_entity.getEntityAttributeNotNull<AccountAttribute>().UserGuid;
private string getUserNickname() =>
m_planet_user_entity.getEntityAttributeNotNull<NicknameAttribute>().Nickname;
}

View File

@@ -0,0 +1,28 @@
namespace BrokerCore.Services;
using Common;
using DbEntity;
using Entity;
using Repository;
// 화폐(Currency) 주문 완료 전략
public class CurrencyOrderCompletionStrategy : IOrderCompletionStrategy
{
private readonly PlanetItemExchangeOrderRepo m_planet_item_exchange_repo;
public CurrencyOrderCompletionStrategy(PlanetItemExchangeOrderRepo planetItemExchangeOrderRepo)
{
m_planet_item_exchange_repo = planetItemExchangeOrderRepo;
}
public async Task<PlanetItemExchangeOrder> completeOrder(PlanetUserEntity planetUserEntity,
PlanetItemExchangeOrder orderCreated,
CancellationToken cancellationToken = default)
{
var (result, order_completed) =
await m_planet_item_exchange_repo.findAndUpdateStatus(orderCreated.OrderId, ExchangeOrderStatus.Completed, cancellationToken);
Guard.Against.resultFail(result);
Guard.Against.isNull(order_completed, ServerErrorCode.InvalidRequest,
()=>"CurrencyOrderCompletionStrategy => Order not found");
return order_completed;
}
}

View File

@@ -0,0 +1,89 @@
namespace BrokerCore.Services;
using Common;
using DbEntity;
using Repository;
using MetaAssets;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage;
using Repository.Context;
public class ProductExchangeCreationStrategy : IOrderCreationStrategy
{
private readonly MetaverseBrokerDbContext m_broker_db_context;
private readonly PlanetItemExchangeOrderAmountTotalLimitRepo m_planet_item_exchange_order_amount_total_limit;
private readonly PlanetItemExchangeOrderAmountUserLimitRepo m_planet_item_exchange_order_amount_user_limit;
private readonly PlanetItemExchangeOrderRepo m_planet_item_exchange_repo;
public ProductExchangeCreationStrategy(
MetaverseBrokerDbContext brokerDbContext,
PlanetItemExchangeOrderAmountTotalLimitRepo planetItemExchangeOrderAmountTotalLimit,
PlanetItemExchangeOrderAmountUserLimitRepo planetItemExchangeOrderAmountUserLimit,
PlanetItemExchangeOrderRepo planetItemExchangeRepo)
{
m_broker_db_context = brokerDbContext;
m_planet_item_exchange_order_amount_total_limit = planetItemExchangeOrderAmountTotalLimit;
m_planet_item_exchange_order_amount_user_limit = planetItemExchangeOrderAmountUserLimit;
m_planet_item_exchange_repo = planetItemExchangeRepo;
}
public async Task<(Result, PlanetItemExchangeOrder)> createOrder(
PlanetItemExchangeOrder order,
string planetServerType,
PlanetItemExchangePolicyMetaData exchangePolicy,
CancellationToken cancellationToken = default)
{
var exchange_date = DateOnly.FromDateTime(order.CreatedAt.ToUniversalTime());
var strategy = m_broker_db_context.Database.CreateExecutionStrategy();
var execute_result = await strategy.ExecuteAsync<Result>(async () =>
{
Result result = null!;
await using var transaction = await m_broker_db_context.Database.BeginTransactionAsync(cancellationToken);
try
{
var daily_limit_checker = new DailyAmountLimitChecker(
m_planet_item_exchange_order_amount_total_limit,
m_planet_item_exchange_order_amount_user_limit);
(result, var total_limit, var user_limit) = await daily_limit_checker.dailyLimitCheck(order, exchangePolicy, cancellationToken);
// 신규 주문 정보 삽입
await m_planet_item_exchange_repo.add(order, cancellationToken);
Guard.Against.resultFail(result);
Guard.Against.isNull(total_limit, ServerErrorCode.RdbError,
()=>"PlanetItemExchangeOrderAmountTotalLimit not found");
Guard.Against.isNull(user_limit, ServerErrorCode.RdbError,
()=>"PlanetItemExchangeOrderAmountUserLimit not found");
// 신규 주문 정보 삽입
await m_planet_item_exchange_repo.add(order, cancellationToken);
await m_planet_item_exchange_order_amount_total_limit.increaseDailyAmount(total_limit,
order.ExchangeMetaAmount, cancellationToken);
await m_planet_item_exchange_order_amount_user_limit.increaseDailyAmount(user_limit,
order.ExchangeMetaAmount, cancellationToken);
await transaction.CommitAsync(cancellationToken);
}
catch(Exception ex)
{
if (transaction.GetDbTransaction().Connection is not null)
{
await transaction.RollbackAsync(cancellationToken);
}
return new Result
{
ErrorCode = ServerErrorCode.InternalServerError,
ResultString = $"ProductExchangeCreationStrategy Transaction Error => {ex.Message}"
};
}
return result;
});
return (execute_result, order);
}
}

View File

@@ -0,0 +1,152 @@
using ServerCore;
using ServerBase;
using ServerCommon;
namespace BrokerCore.Services;
using BrokerBusinessLog;
using Common;
using DbEntity;
using Entity;
using Entity.Actions;
using MetaAssets;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage;
using Repository;
using Repository.Context;
// 아이템 주문 완료 전략
public class ProductOrderCompletionStrategy : IOrderCompletionStrategy
{
private PlanetUserEntity m_planet_user_entity;
private readonly BrokerMetaTableRef m_meta_table_ref;
private readonly IServerLogic m_server_logic;
private readonly MetaverseBrokerDbContext m_broker_db_context;
private readonly PlanetItemExchangeOrderRepo m_planet_item_exchange_repo;
public ProductOrderCompletionStrategy(
BrokerMetaTableRef metaTableRef,
IServerLogic serverLogic,
MetaverseBrokerDbContext brokerDbContext,
PlanetItemExchangeOrderRepo planetItemExchangeRepo
)
{
m_meta_table_ref = metaTableRef;
m_server_logic = serverLogic;
m_broker_db_context = brokerDbContext;
m_planet_item_exchange_repo = planetItemExchangeRepo;
}
public async Task<PlanetItemExchangeOrder> completeOrder(
PlanetUserEntity planetUserEntity,
PlanetItemExchangeOrder orderCreated,
CancellationToken cancellationToken = default)
{
m_planet_user_entity = planetUserEntity;
// 메타데이터 조회
var exchange_policy = getExchangePolicyMeta(orderCreated.ExchangeMetaId);
var product_meta = getProductMeta(exchange_policy.CaliverseItemId);
var mail_meta = getSystemMailMeta(product_meta.SystemMail_First);
// 메일 발송 처리
var broker_mail = new BrokerMailEntity(m_planet_user_entity, m_server_logic);
var init_result = await broker_mail.onInit();
Guard.Against.resultFail(init_result, init_result.ErrorCode, ()=>"BrokerMail onInit error");
var mail_send_action = broker_mail.getEntityActionNotNull<BrokerMailSendAction>();
var mail_option = mail_send_action.createSystemSendMailOptionByMeta(product_meta, mail_meta,
m_planet_user_entity.UserGuid,
m_planet_user_entity.Nickname, orderCreated.ExchangeMetaAmount);
//==============================================================================================
// rdb 트랜잭션 시작
// 주문 완료 처리 임시
// 메일 발송
// 메일 발송 완료 시 주문 완료 처리 커밋
// TODO: 재시도 전략 연구?
//==============================================================================================
PlanetItemExchangeOrder order_completed_result_object = null!;
var strategy = m_broker_db_context.Database.CreateExecutionStrategy();
var transaction_result = await strategy.ExecuteAsync(async () =>
{
await using var transaction =
await m_broker_db_context.Database.BeginTransactionAsync(System.Data.IsolationLevel.RepeatableRead,
cancellationToken);
var (order_update_result, order_completed) = await m_planet_item_exchange_repo.findAndUpdateStatus(
orderCreated.OrderId, ExchangeOrderStatus.Completed,
cancellationToken);
if (order_update_result.isFail())
{
await transaction.RollbackAsync(cancellationToken);
return order_update_result;
}
try
{
var mail_send_result = await mail_send_action.sendMail(mail_option,
async () => await transaction.CommitAsync(cancellationToken),
async () => await transaction.RollbackAsync(cancellationToken)
);
if (mail_send_result.isFail())
{
await transaction.RollbackAsync(cancellationToken);
return mail_send_result;
}
else if (order_completed is not null)
{
order_completed_result_object = order_completed;
}
}
catch (Exception ex)
{
if (transaction.GetDbTransaction().Connection is not null)
{
await transaction.RollbackAsync(cancellationToken);
}
return new Result
{
ErrorCode = ServerErrorCode.InternalServerError,
ResultString = $"ProductOrderCompletionStrategy Transaction Error => {ex.Message}"
};
}
return new Result();
});
Guard.Against.resultFail(transaction_result);
return order_completed_result_object;
}
private PlanetItemExchangePolicyMetaData getExchangePolicyMeta(string exchangeMetaId)
{
m_meta_table_ref.MetaTable.PlanetItemExchangePolicyMetaTable.PlanetItemExchangePolicyDataListbyID.TryGetValue(
exchangeMetaId, out var policy_meta);
Guard.Against.isNull(policy_meta, ServerErrorCode.MetaIdInvalid,
()=>"planet_item_exchange_policy_meta not found");
return policy_meta;
}
private ProductMetaData getProductMeta(string productMetaIdString)
{
var product_meta_id = Convert.ToInt32(productMetaIdString);
m_meta_table_ref.MetaTable.ProductMetaTable.ProductMetaDataListbyId.TryGetValue(product_meta_id,
out var product_meta);
Guard.Against.isNull(product_meta, ServerErrorCode.MetaIdInvalid, ()=>"product meta not found");
return product_meta;
}
private SystemMailMetaData getSystemMailMeta(string systemMailDataId)
{
m_meta_table_ref.MetaTable.SystemMailMetaTable.SystemMailMetaDataListbyKey.TryGetValue(systemMailDataId,
out var mail_data);
Guard.Against.isNull(mail_data, ServerErrorCode.MetaIdInvalid, ()=>"system_mail_meta not found");
return mail_data;
}
}