초기커밋

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,660 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using System.Text;
using System.Text.Json.Nodes;
using System.Threading.Tasks;
using NLog.Layouts;
using Newtonsoft.Json.Linq;
using Nettention.Proud;
using Amazon.DynamoDBv2.DocumentModel;
using MySqlConnector;
using StackExchange.Redis;
using NeoSmart.AsyncLock;
using NeoSmart;
using Org.BouncyCastle.Bcpg.OpenPgp;
using Axion.Collections.Concurrent;
using Amazon.DynamoDBv2;
using MongoDB.Driver;
using ServerCore;
using MODULE_ID = System.UInt32;
using DYNAMO_DB_TABLE_NAME = System.String;
using LOG_ACTION_TYPE = System.String;
using LOG_DOMAIN_TYPE = System.String;
using SESSION_ID = System.Int32;
using WORLD_ID = System.UInt32;
using META_ID = System.UInt32;
using ENTITY_GUID = System.String;
using MASTER_GUID = System.String;
using SUMMENED_ENTITY_GUID = System.String;
using ACCOUNT_ID = System.String;
using OWNER_GUID = System.String;
using USER_GUID = System.String;
using CHARACTER_GUID = System.String;
using ITEM_GUID = System.String;
using Microsoft.Extensions.Configuration;
namespace ServerBase;
//=============================================================================================
//
//=============================================================================================
public partial class Initializers
{
public List<IInitializer> getInitializers() => m_initializers;
public string getContentsName() => m_contents_name;
}
//=============================================================================================
//
//=============================================================================================
public partial class ServerConfig
{
public UInt16 getAppParamPort() => m_app_param_port;
public void setAppParamPort(UInt16 port) => m_app_param_port = port;
public string getConfigFilePath() => m_config_file_path;
public void setConfigFilePath(string filePath) => m_config_file_path = filePath;
public JObject? getLoadedConfig() => m_loaded_config_nullable;
public string getRegionId() => m_region_id;
public void setRegionId(string id) => m_region_id = id;
public WORLD_ID getWorldId() => m_world_id;
public void setWorldId(UInt16 worldId) => m_world_id = worldId;
public UInt32 getChannelNo() => m_channel_no;
public void setChannelNo(UInt32 channelNo) => m_channel_no = channelNo;
public UInt16 SessionKeepAliveTimeSec { get; set; } = 30;
public bool StandaloneMode { get; set; } = false;
public bool ClientProgramVersionCheck { get; set; } = false;
public UInt64 ClientMinimumRequiredLogicVersion { get; set; } = 0;
public bool CheatCommandAlwaysAllow { get; set; } = false;
public string LogDir { get; private set; } = "./Logs";
public string DumpDir { get; private set; } = "./Dumps";
public bool LocalServer { get; private set; } = false;
public bool SingleThreaded { get; private set; } = false;
public string ClientListenIp { get; private set; } = string.Empty;
public UInt16 ClientListenPort { get; private set; }
public bool AccountLoginBlockEnable { get; private set; } = false;
public string ServiceType { get; private set; } = "Live";
public int MinWorkerThreadCount { get; private set; } = 0;
public int MinIOCThreadCount { get; private set; } = 0;
public ServiceType ServiceTypeNew { get; private set; } = global::ServiceType.Live;
public int DefaultMaxUser { get; set; } = 0;
public bool OfflineMode { get; private set; } = false;
public RabbitmqConfig Rabbitmq { get; private set; } = new RabbitmqConfig();
public string SsoAccountDb { get; private set; } = "Server=127.0.0.1;Port=3306;User ID=external_ro;Password=bQNEXbRWQTtV6bwlqktGyBiuf2KqYF;Database=caliverse";
public string SsoAccountAuthJwtSecretKey { get; private set; } = "Gudx7xjCbCKxZsLEWr7HL5auSkScVPTUaYnZEztN";
public string AccountNftDb { get; private set; } = "Server=127.0.0.1;Port=3306;User ID=external_ro;Password=bQNEXbRWQTtV6bwlqktGyBiuf2KqYF;Database=caliverse";
public const UInt16 RedisDefaultPort = 6379;
public string Redis { get; private set; } = "127.0.0.1:6379";
public string Dynamodb { get; private set; } = "http://localhost:8000";
public GameConf GameConfig = new();
public MongoDbConf MongoDbConfig = new();
public AWSConf AWS { get; private set; } = new AWSConf();
public bool ControlAgentEnable { get; private set; }
public bool PerformanceCheckEnable { get; private set; }
public bool BattleSystemEnable { get; private set; }
public string EC2TemplateIMG { get; private set; } = string.Empty;
public Dictionary<ServerUrlType, ServerUrl> getServerUrls() => m_server_urls;
public AuthRule AuthRule { get; private set; } = new();
public NftRule NftRule { get; private set; } = new();
public AIChatConfig AIChatConfig { get; private set; } = new();
public EchoSystemConfig EchoSystemConfig { get; private set; } = new();
public BillingConfig BillingConfig { get; private set; } = new();
public UgqApiServerConfig UgqApiServerConfig { get; private set; } = new();
public LoadBalancingRule LoadBalancingRule { get; set; } = new();
public bool isLoadedConfig() => m_is_loaded_config;
}
public class GameConf
{
public int ReservationWaitTimeMSec { get; set; } = 30_000;
public int LoginCacheExpiryTimeMSec { get; set; } = 60 * 60 * 1000;
public int ServerSwitchCacheExpiryTimeMSec { get; set; } = 5 * 60 * 1000;
}
public class MongoDbConf
{
public string ConnectionString { get; set; } = string.Empty;
public string DatabaseName { get; set; } = string.Empty;
public int MinConnectionPoolSize { get; set; } = 0;
public int MaxConnectionPoolSize { get; set; } = 100;
public int WaitQueueTimeoutSecs { get; set; } = 120;
}
//=============================================================================================
//
//=============================================================================================
public class AWSConf
{
public bool Enable { get; init; }
public bool LocalDynamoDB { get; init; }
public string AccessKey { get; init; } = string.Empty;
public string SecretKey { get; init; } = string.Empty;
public string Region { get; init; } = string.Empty;
public string MilestoneName { get; init; } = string.Empty;
public CloudWatchLogConf CloudWatchLog { get; set; } = new();
public S3Conf S3 { get; set; } = new();
}
public class CloudWatchLogConf
{
public bool Enable { get; init; }
public string? CloudWatchLogGroup { get; init; }
public string? CloudWatchLogNamePattern { get; init; }
public string? CloudWatchLogLevel { get; init; }
public JsonLayout CloudWatchLogLayout { get; set; } = new JsonLayout();
}
public class S3Conf
{
public string ServiceFolderName { get; init; } = string.Empty;
public string MyhomeUgcInfoBucketName { get; init; } = string.Empty;
public string BeaconAppProfileBucketName { get; init; } = string.Empty;
}
public static partial class ServerConfigHelper
{
public static ServerConfig? getServerConfig() => m_server_config;
public static Flags<AuthRule.FlagType> getAuthRuleFlags() => m_auth_rules_flags;
public static Dictionary<Tuple<ServerType, ServerType>, LoadBalancingRule.Config> getLoadBalancingRuleConfigs() => m_load_balancing_rule_configs;
}
//=============================================================================================
//
//=============================================================================================
public abstract partial class SessionBase : ISession, IInitializer
{
public IRmiHost getRmiHost() => m_net_host;
public ConnectionState getConnectionState() => m_connection_state;
public void setConnectionState(ConnectionState state) => m_connection_state = state;
public bool isConnected() => m_connection_state == ConnectionState.Connected;
public bool isTryConneting() => m_connection_state == ConnectionState.TryConnecting;
public bool isDisconnected() => m_connection_state == ConnectionState.Disconnected;
public PacketReceiver getPacketReceiver() => m_packet_receiver;
public PacketSender getPacketSender() => m_packet_sender;
public int getLargePacketCheckSize() => m_large_packet_check_size;
public int getPacketChunkSize() => m_packet_chunk_size;
public LargePacketHandler getLargePacketHandler() => m_large_packet_handler;
}
//=============================================================================================
//
//=============================================================================================
public abstract partial class ListenSessionBase : SessionBase
{
public string getServerType() => m_server_type;
public ConcurrentDictionary<SESSION_ID, IEntityWithSession> getEntityWithSessions() => m_entity_with_sessions;
public NetServer getNetServer()
{
var net_server = getRmiHost() as NetServer;
NullReferenceCheckHelper.throwIfNull(net_server, () => $"net_server is null !!!");
return net_server;
}
}
//=============================================================================================
//
//=============================================================================================
public abstract partial class ConnectToServerSessionBase : SessionBase
{
public string getHostIp() => m_host_ip;
public UInt16 getHostPort() => m_host_port;
public string getHostAddress() => m_host_address;
public void setHostAddress(string hostAddress, UInt16 port)
{
m_host_address = $"{hostAddress}:{port}";
m_host_port = port;
}
public string getToConnectServerType() => m_to_connect_server_type;
}
//=============================================================================================
//
//=============================================================================================
public abstract partial class ServerLogicBase : IServerLogic
{
public AtomicBool getAccountLoginBlockEnable() => m_account_login_block_enable;
public ConcurrentDictionary<Type, RedisRequestSharedBase> getRedisGlobalSharedCaches() => m_redis_global_shared_caches;
public ConcurrentDictionary<Type, IRule> getRules() => m_rules;
public CancellationTokenSource getCancellationTokenSource() => m_cancel_token;
public ServerProgramVersion getServerProgramVersion() => m_server_program_version;
public void setInstanceId(string instanceId) => m_server_instanceid = instanceId;
public string getInstanceId() => m_server_instanceid;
public void setConfiguration(IConfigurationRoot config) => m_configuration_root = config;
public IConfigurationRoot getConfiguration()
{
NullReferenceCheckHelper.throwIfNull(m_configuration_root, () => $"m_configuration_root is null !!!");
return m_configuration_root;
}
public ServerConfig getServerConfig()
{
NullReferenceCheckHelper.throwIfNull(m_server_config, () => $"m_server_config is null !!!");
return m_server_config;
}
public void setServerConfig(ServerConfig config) => m_server_config = config;
public string getServerName() => m_server_name;
public void initServerName(string name) => m_server_name = name;
public void setServerName(string name) => m_server_name = name;
protected void replaceServerName(string name) => m_server_name = name;
public void setServerType(string serverType) => m_server_type = serverType;
public string getServerType() => m_server_type;
public ConfigManager getConfigManager()
{
NullReferenceCheckHelper.throwIfNull(m_config_manager, () => $"m_config_manager is null !!!");
return m_config_manager;
}
}
//=============================================================================================
//
//=============================================================================================
public abstract partial class EntityBase : IActor, IInitializer, IWithTaskSerializer
{
public EntityType getEntityType() => m_entity_type;
public bool hasParent() => null != m_parent_nullable;
public EntityBase? getDirectParent() => m_parent_nullable;
public TaskSerializer getTaskSerializer() => m_task_serializer;
public async Task<IDisposable> getAsyncLock() => await m_async_lock.LockAsync();
public ENTITY_GUID getEntityGuid() => m_entity_guid;
public ConcurrentDictionary<Type, EntityAttributeBase> getEntityAttributes() => m_entity_attributes;
public ConcurrentDictionary<Type, EntityActionBase> getEntityActions() => m_entity_actions;
public bool hasMasterGuid() => false == m_master_guid.isNullOrWhiteSpace();
public MASTER_GUID getMasterGuid() => m_master_guid;
public ConcurrentHashSet<SUMMENED_ENTITY_GUID> getSummenedEntityGuids() => m_summoned_entities;
}
//=============================================================================================
//
//=============================================================================================
public abstract partial class EntityAttributeBase : IInitializer
{
public void setTryPendingDocBase(DynamoDbDocBase docBase) => m_try_pending_doc_base_nullable = docBase;
public DynamoDbDocBase? getTryPendingDocBase() => m_try_pending_doc_base_nullable;
public void resetTryPendingDocBase() => m_try_pending_doc_base_nullable = null;
public EntityBase? getEntityOfOwnerEntityType() => m_entity_of_owner_entity_type;
public OwnerEntityType getOwnerEntityType() => m_owner_entity_type;
public void bindEntityRecorder(EntityRecorder recorder) => m_recorder_nullable = recorder;
public bool isOrigin() => m_is_origin;
public bool isDelete() => m_is_delete;
public void resetEntityAttriubteState() => m_attribute_state.reset();
public bool setCloned() => m_is_origin = false;
public void resetDelete() => m_is_delete = false;
public void setDelete() => m_is_delete = true;
public bool isApplicableCommonResult() => m_is_applicable_to_common_result;
public AttributeState getAttributeState() => m_attribute_state;
public EntityRecorder? getEntityRecorder() => m_recorder_nullable;
public ReaderWriterLockSlim getRWLock() => m_rw_lock;
public void setEntityOfOwnerEntityType(OwnerEntityType ownerEntityType, EntityBase entityOfOwnerEntityType)
{
m_owner_entity_type = ownerEntityType;
m_entity_of_owner_entity_type = entityOfOwnerEntityType;
}
public EntityBase getOwner() => m_owner;
}
//=============================================================================================
//
//=============================================================================================
public abstract partial class EntityAttributeTransactorBase<TEntityAttribute> : IEntityAttributeTransactor
where TEntityAttribute : EntityAttributeBase
{
public EntityAttributeBase? getOriginEntityAttribute() => m_origin_entity_attribute_nullable as TEntityAttribute;
public EntityAttributeBase? getClonedEntityAttribute() => m_cloned_entity_attribute_nullable as TEntityAttribute;
public TransactionState getTransactionState() => m_transaction_state;
public void setTransactionState(TransactionState state) => m_transaction_state = state;
public EntityRecorder getEntityRecorder() => m_entity_recorder;
public EntityBase getOwner() => m_owner;
}
//=============================================================================================
//
//=============================================================================================
public abstract partial class EntityActionBase : IInitializer
{
public EntityBase getOwner() => m_owner;
}
//=============================================================================================
//
//=============================================================================================
public abstract partial class EntityHFSMBase : HFSMBase<EntityStateTriggerType, EntityStateType>, IWithEntityOwner
{
public EntityBase getOwner() => m_owner;
}
//=============================================================================================
//
//=============================================================================================
public partial class TransactionRunner : IDisposable
{
public ConcurrentDictionary<string, EntityCommonResult> getEntityCommonResults() => m_entity_common_results;
public ConcurrentDictionary<EntityBase, ConcurrentDictionary<EntityType, ReservedSlotOnInven>> getReservedInvenSlotAllOfOwners() => m_reserved_inven_slots_of_owners;
private bool isBinding() => m_is_binding;
private void setBinding() => m_is_binding = true;
public string getTransactionName() => m_transaction_name;
public TransactionIdType getTransactionIdType() => m_transaction_id_type;
public string getTransId() => m_trans_id;
public EntityBase getOwner() => m_owner;
}
//=============================================================================================
//
//=============================================================================================
public abstract partial class UserManagerBase<TPrimaryKey, TSubKey, TEntity>
where TPrimaryKey : notnull
where TSubKey : notnull
where TEntity : EntityBase
{
public UInt16 getUserCount() => (UInt16)m_users.Count;
public MultiIndexDictionary<TPrimaryKey, TSubKey, TEntity> getUsers() => m_users;
}
//=============================================================================================
//
//=============================================================================================
public abstract partial class RedisRequestBase : IInitializer
{
public RedisConnector getRedisConnector() => m_redis_connector;
public IDatabase getDatabase()
{
var db = m_redis_connector.getDatabase();
NullReferenceCheckHelper.throwIfNull(db, () => $"db is null !!!");
return db;
}
public string getKey()
{
ConditionValidCheckHelper.throwIfFalseWithCondition(() => false == m_key.isNullOrWhiteSpace(), () => $"Invalid Key !!!");
return m_key;
}
public string getMember() => m_member;
public void setMember(string member) => m_member = member;
}
//=============================================================================================
//
//=============================================================================================
public abstract partial class RedisRequestPrivateBase : RedisRequestBase
{
public IActor getOwner() => m_owner;
}
//=============================================================================================
//
//=============================================================================================
public abstract partial class RedisRequestSharedBase : RedisRequestBase
{
public string getSharedKey()
{
ConditionValidCheckHelper.throwIfFalseWithCondition(() => false == m_shared_key.isNullOrWhiteSpace(), () => $"Invalid SharedKey !!!");
return m_shared_key;
}
}
//=============================================================================================
//
//=============================================================================================
public partial class DynamoDbDocumentQueryContext : IQueryContext
{
public Document getDocument() => m_document;
public QueryType getQueryType() => m_query_type;
public DynamoDbQueryExceptionNotifier.ExceptionHandler? getExceptionHandler() => m_exception_handler_nullable;
}
//=============================================================================================
//
//=============================================================================================
public partial class DynamoDbItemRequestQueryContext : IQueryContext
{
public AmazonDynamoDBRequest getAmazonDynamoDBRequest() => m_db_request;
public QueryType getQueryType() => m_query_type;
public DynamoDbQueryExceptionNotifier.ExceptionHandler? getExceptionHandler() => m_exception_handler_nullable;
}
//=============================================================================================
//
//=============================================================================================
public abstract partial class DynamoDbDocBase : IRowData
{
public PrimaryKey getPrimaryKey() => m_key;
public string getPK() => m_key.PK;
public string getSK() => m_key.SK.isNullOrWhiteSpace() == false ? m_key.SK : DynamoDbClient.SK_EMPTY;
public string getDocType() => m_doc_type;
public void setCombinationKeyForPKSK(string keyForPK, string keyForSK)
{
m_combination_key_for_pk = keyForPK;
m_combination_key_for_sk = keyForSK;
}
public string getCombinationKeyForPK() => m_combination_key_for_pk;
public void setCombinationKeyForPK(string uniqueKey) => m_combination_key_for_pk = uniqueKey;
public string getCombinationKeyForSK() => m_combination_key_for_sk;
public void setCombinationKeyForSK(string sortKey) => m_combination_key_for_sk = sortKey;
public ConcurrentDictionary<Type, IAttribWrapper> getAttribWrappers() => m_attrib_wrappers;
public DbTimestamp getCreatedDateTime() => m_created_datetime;
public DbTimestamp getUpdatedDateTime() => m_updated_datetime;
public DbTimestamp getDeletedDateTime() => m_deleted_datetime;
public DbTimestamp getRestoredDateTime() => m_restored_datetime;
public void setExceptionHandler(DynamoDbQueryExceptionNotifier.ExceptionHandler exceptionHandler) => m_exception_handler_nullable = exceptionHandler;
public DynamoDbQueryExceptionNotifier.ExceptionHandler? getExceptionHandler() => m_exception_handler_nullable;
public void setInitializeResult(Result result) => m_initialize_result = result;
public Result getInitializeResult() => m_initialize_result;
public QueryType getQueryType() => m_query_type;
public QueryType setQueryType(QueryType queryType) => m_query_type = queryType;
}
//=============================================================================================
//
//=============================================================================================
public abstract partial class AttribBase
{
}
//=============================================================================================
//
//=============================================================================================
public partial class AttribWrapper<TAttrib> : IAttribWrapper
where TAttrib : AttribBase, new()
{
public TAttrib getAttrib() => m_attrib;
public AttribBase getAttribBase() => m_attrib;
public Type getAttribType() => m_attrib.GetType();
}
//=============================================================================================
//
//=============================================================================================
public partial class MySqlDbConnector
{
public MySqlConnection? getConnection() => m_connection;
public System.Data.ConnectionState getLastConnectionState() => m_last_connection_state;
public string getConnectionString() => m_connection_string;
}
//=============================================================================================
//
//=============================================================================================
public partial class LogAction
{
public LOG_ACTION_TYPE getLogActionType() => m_log_action_type;
public System.Guid getTranId() => m_tran_id;
}
//=============================================================================================
//
//=============================================================================================
public abstract partial class ILogInvoker
{
public LogAction? GetLogAction() => m_log_action_nullable;
public LOG_ACTION_TYPE getLogActionType() => m_log_action_nullable != null ? m_log_action_nullable.getLogActionType() : string.Empty;
public LOG_DOMAIN_TYPE getLogDomainType() => m_log_domain_type;
public System.Guid getTranId() => m_log_action_nullable != null ? m_log_action_nullable.getTranId() : System.Guid.Empty;
}

View File

@@ -0,0 +1,674 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections.Concurrent;
using System.Collections;
using Amazon.DynamoDBv2.DocumentModel;
using Axion.Collections.Concurrent;
using Newtonsoft.Json.Linq;
using Microsoft.Extensions.Configuration;
using ServerCore;
using ServerBase;
using MODULE_ID = System.UInt32;
using SESSION_ID = System.Int32;
using META_ID = System.UInt32;
using ENTITY_GUID = System.String;
using ACCOUNT_ID = System.String;
using OWNER_GUID = System.String;
using USER_GUID = System.String;
using CHARACTER_GUID = System.String;
using ITEM_GUID = System.String;
namespace ServerBase;
//=============================================================================================
// Module 관련 인터페이스
//
// author : kangms
//
//=============================================================================================
public interface IModule
{
ModuleContext getModuleContext();
Task<Result> startModule();
Task<Result> stopModule();
string toBasicString();
}
//=============================================================================================
// ConfigParam 관련 인터페이스
//
// author : kangms
//
//=============================================================================================
public interface IConfigParam
{
Result tryReadFromJsonOrDefault(JObject jObject);
string toBasicString();
}
//=============================================================================================
// IConfiguration 관련 인터페이스
//
// author : kangms
//
//=============================================================================================
public interface IWithConfiguration
{
Result mergeConfiguration(IConfiguration configuration);
string toBasicString();
}
//=============================================================================================
// Actor 관련 인터페이스 : Root 인터페이스
//
// author : kangms
//
//=============================================================================================
public interface IActor
{
string toBasicString();
}
//=============================================================================================
// LogActor 관련 인터페이스
//
// author : kangms
//
//=============================================================================================
public interface IWithLogActor
{
ILogActor toLogActor();
}
//=============================================================================================
// 함수 파라메터 관련 인터페이스
//
// author : kangms
//
//=============================================================================================
public interface IWithParam
{
string toBasicString();
}
//=============================================================================================
// Result 반환 관련 인터페이스
//
// author : kangms
//
//=============================================================================================
public interface IWithResult
{
Result Result { get; set; }
bool isResultOnly();
}
//=============================================================================================
// Result + TResultValue 반환 관련 인터페이스
//
// author : kangms
//
//=============================================================================================
public interface IWithResultValue<out TResultValue> : IWithResult
{
TResultValue ValueOfResult { get; }
}
//=============================================================================================
// Task Serializer 관련 인터페이스
//
// author : kangms
//
//=============================================================================================
public interface IWithTaskSerializer
{
TaskSerializer getTaskSerializer();
}
//=============================================================================================
// Owner 관련 인터페이스
//
// author : kangms
//
//=============================================================================================
public interface IWithEntityOwner
{
EntityBase getOwner();
}
//=============================================================================================
// Tick 관련 제어 인터페이스
//
// author : kangms
//
//=============================================================================================
public interface ITicker
{
void onTick();
}
//=============================================================================================
// Task 기반 Tick 관련 제어 인터페이스
//
// author : kangms
//
//=============================================================================================
public interface ITaskTicker
{
Task<Result> onCreateTask();
Task onTaskTick();
}
//=============================================================================================
// 초기화 관련 제어 인터페이스
//
// author : kangms
//
//=============================================================================================
public interface IInitializer
{
Task<Result> onInit();
}
public interface IWithInitializers
{
void appendInitializer(IInitializer initializer);
List<T> tryConvertInitializes<T>() where T : class;
}
//=============================================================================================
// Network 관련 인터페이스
//
// author : kangms
//
//=============================================================================================
public interface IPacketCommand
{
string getCommandKey();
string toBasicString();
}
public interface IWithPacketNamespaceVerifier
{
bool isValidPacketNamespace(string toCheckNamespace, IPacketCommand packetCommand);
}
public interface ISession
{
string toBasicString();
}
public interface IProudNetSession : ISession
{
Nettention.Proud.IRmiHost getRmiHost();
}
public interface IRabbitMqSession : ISession
{
}
//=============================================================================================
// HelperFunction 관련 제어 인터페이스
//
// author : kangms
//
//=============================================================================================
public interface IHelperFunction
{
IEntityWithSession getOwner();
}
//=============================================================================================
// Entity와 연결된 Session 인터페이스
//
// author : kangms
//
//=============================================================================================
public interface IEntityWithSession : ISession
{
Nettention.Proud.HostID getHostId();
ListenSessionBase? getListenSessionBase();
void setListenSessionBase(ListenSessionBase listenSessionBase);
SESSION_ID getSessionId();
string getAccountId();
string getUserGuid();
string getUserNickname();
string toSummaryString();
}
//=============================================================================================
// AttribBase의 Wrapper Class 인터페이스
//
// author : kangms
//
//=============================================================================================
public interface IAttribWrapper
{
bool onCopyToDynamoDbEntry(ref Amazon.DynamoDBv2.DocumentModel.Document doc);
bool onCopyFromDynamoDbEntry(Amazon.DynamoDBv2.DocumentModel.Document doc);
bool onFillupJsonStringToJObject(ref JObject jobject);
Type getAttribType();
AttribBase getAttribBase();
}
//=============================================================================================
// DynamoDbDoc 데이터를 Cache & EntityAttribute & BackupDoc 복사해주는 인터페이스
//
// author : kangms
//
//=============================================================================================
public interface ICopyCacheFromDoc
{
bool copyCacheFromDoc(DynamoDbDocBase customDoc);
}
public interface ICopyEntityAttributeFromDoc
{
bool copyEntityAttributeFromDoc(DynamoDbDocBase customDoc);
}
public interface ICopyBackupDocFromDoc
{
bool copyBackupDocFromDoc(DynamoDbDocBase customDoc);
}
//=============================================================================================
// Cache 데이터를 특정 데이터 타입으로 복사해주는 인터페이스
//
// author : kangms
//
//=============================================================================================
public interface ICopyDocFromCache
{
bool copyDocFromCache(CacheBase cacheBase);
}
public interface ICopyEntityAttributeFromCache
{
bool copyEntityAttributeFromCache(CacheBase cacheBase);
}
//=============================================================================================
// EntityAttribute 데이터를 특정 데이터 타입으로 복사해주는 인터페이스
//
// author : kangms
//
//=============================================================================================
public interface ICopyDocFromEntityAttribute
{
bool copyDocFromEntityAttribute(EntityAttributeBase entityAttributeBase);
}
public interface ICopyCacheFromEntityAttribute
{
bool copyCacheFromEntityAttribute(EntityAttributeBase entityAttributeBase);
}
// DataCopyHelper.copyEntityAttributeTransactorFromEntityAttribute() 호출하고 있지 않다.
// TransactionRunner가 IEntityAttributeTransactor.cloneFromOriginEntityAttribute() 호출하여 복사 하고 있다.
// IEntityAttributeTransactor.cloneFromOriginEntityAttribute() 호출시
// Origin의 속성값과 비교하여, 초기값이 아니고, 값이 다른 경우만 복사해 주는 로직으로 수정 한다. - kangms
public interface ICopyEntityAttributeTransactorFromEntityAttribute
{
bool copyEntityAttributeTransactorFromEntityAttribute(EntityAttributeBase entityAttributeBase);
}
//=============================================================================================
// EntityAttributeTransactor 데이터를 특정 데이터 타입으로 복사해주는 인터페이스
//
// author : kangms
//
//=============================================================================================
public interface ICopyDocFromEntityAttributeTransactor
{
bool copyDocFromEntityAttributeTransactor(IEntityAttributeTransactor entityAttributeTransactor);
}
public interface ICopyEntityAttributeFromEntityAttributeTransactor
{
bool copyEntityAttributeFromEntityAttributeTransactor(IEntityAttributeTransactor entityAttributeTransactor);
}
//=============================================================================================
// Cache 정보 관리를 위한 인터페이스
//
// author : kangms
//
//=============================================================================================
public interface ICache
{
string toBasicString();
}
//=============================================================================================
// Event Task를 위한 인터페이스
//
// author : kangms
//
//=============================================================================================
public interface IEventTask
{
void setCompleted();
bool isCompleted();
Task<Result> onTriggerEffect();
string toBasicString();
}
//=============================================================================================
// Rule를 위한 인터페이스
//
// author : kangms
//
//=============================================================================================
public interface IRule : IInitializer
{
Task<Result> configure();
}
//=============================================================================================
// Slots 관련 인터페이스
//
// author : kangms
//
//=============================================================================================
public interface ISlots
{
bool onInitSlots(int slotMaxCount);
ICollection getSlots();
List<EntityBase> getEntityBases();
string toBasicString();
}
//=============================================================================================
// SlotsWithType 관련 인터페이스
//
// author : kangms
//
//=============================================================================================
public interface ISlotsWithType<TSlotType> : ISlots
where TSlotType : notnull
{
ServerErrorCode onIsEquipable(TSlotType targetSlot);
ServerErrorCode onTryEquip(TSlotType targetSlot);
ServerErrorCode onTryEquipWithEntityBase(TSlotType targetSlot, EntityBase entityBase);
ServerErrorCode onIsUnequipable(TSlotType targetSlot);
ServerErrorCode onTryUnequip(TSlotType targetSlot);
ServerErrorCode onTryUnequipWithEntityBase(TSlotType targetSlot, out EntityBase? unequipedEntityBase);
EntityBase? onFindEntityBase(TSlotType targetSlot);
}
//=============================================================================================
// EntityAttribute 관련 인터페이스
//
// author : kangms
//
//=============================================================================================
public interface IEntityAttribute
{
EntityBase getOwner();
EntityAttributeBase? getEntityAttributeBase();
void onClear();
string toBasicString();
}
//=============================================================================================
// EntityDbTransact 관련 인터페이스
//
// author : kangms
//
//=============================================================================================
public interface IEntityDbTransact
{
EntityBase getOwner();
IEntityDbTransactAttribute? getEntityDbTransactAttribute();
void onClear();
string toBasicString();
}
public interface IEntityDbTransactAttribute
{
EntityBase getOwner();
void onClear();
string toBasicString();
}
//=============================================================================================
// IEntityAttributeTransactor 관련 인터페이스
//
// author : kangms
//
//=============================================================================================
public interface IEntityAttributeTransactor
{
bool cloneFromOriginEntityAttribute(EntityAttributeBase fromOriginEntityAttribute);
EntityAttributeBase? getOriginEntityAttribute();
EntityAttributeBase? getClonedEntityAttribute();
bool isReadOnly();
Task<(Result, DynamoDbDocBase?)> makeDocBase();
ENTITY_GUID getEntityGuid();
EntityBase getOwner();
string toBasicString();
}
//=============================================================================================
// EntityDeltaRecorder 관련 인터페이스
//
// author : kangms
//
//=============================================================================================
public interface IEntityDeltaRecorder
{
object getEntityDeltaType();
}
//=============================================================================================
// MergeWithEntityAttribute 관련 인터페이스
//
// author : kangms
//
//=============================================================================================
public interface IMergeWithEntityAttribute
{
// DBQWriteToAttributeAllWithTransactionRunner.onQueryResponseCommit()에서 호출 한다.
Result onMerge(EntityAttributeBase otherEntityAttribute);
}
//=============================================================================================
// DeleteWithEntityAttribute 관련 인터페이스
//
// author : kangms
//
//=============================================================================================
public interface IDeleteWithEntityAttribute
{
Result onDelete();
}
//=============================================================================================
// WithCommonResultFiller 관련 인터페이스
//
// author : kangms
//
//=============================================================================================
public interface IWithCommonResultFiller
{
void onFillCommonResult(EntityCommonResult commonResult, EntityAttributeBase origin, QueryBatchBase? queryBatch = null);
}
//=============================================================================================
// MergeWithInventory 관련 인터페이스
//
// author : kangms
//
//=============================================================================================
public interface IMergeWithInventory
{
Task<Result> onMerge(List<ReservedSlotOnInven> reservedSlotOnInvens, TransactionRunner transactionRunner);
}
//=============================================================================================
// Notify 관련 인터페이스
//
// author : chan
//
//=============================================================================================
public interface IInitNotifyPacket
{
void InitNotifyMessage();
}
//=============================================================================================
// Logic 관련 인터페이스
//
// author : kangms
//
//=============================================================================================
public interface ILogic
{
}
//=============================================================================================
// 서버 수준의 Logic 관련 인터페이스
//
// author : kangms
//
//=============================================================================================
public interface IServerLogic : ILogic, IActor
{
string getServerType();
string getServerName();
ServerConfig getServerConfig();
IModule getModule(MODULE_ID moduleId);
Result registerEntityTicker(EntityTicker entityTicker);
}
//=============================================================================================
// 서버 주요 지표 관련 인터페이스
//
// author : kangms
//
//=============================================================================================
public interface IWithServerMetrics
{
Task<Result> setupServerMetrics();
bool isSetupCompleted();
ServerMetricsCacheRequest getServerMetricsCacheRequest();
Task<Result> syncServerInfoToCache();
string toBasicString();
}
//=============================================================================================
// 관련 인터페이스
//
// author : chan
//
//=============================================================================================
public interface IRemoteTransaction
{
Task<Result> callRemoteChargeAIPoint(double beamDelta);
Task<Result> callNotifyCaliumEvent(string eventName, CurrencyType currencyType, double delta);
}

View File

@@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ServerBase;
public static class Constant
{
public static readonly int STOPWATCH_LOG_LIMIT_MSEC = 100;
public static readonly int LARGE_PACKET_CHECK_INTERVAL_MSEC = 300;
public static readonly int LARGE_PACKET_CHECK_WAIT_SEC = 10; //라지 패킷 대기시간 10초
public static readonly int LARGE_PACKET_CHECK_DEFAULT_SIZE = 256000; //라지 패킷 체크 기본 크기 (Byte)
public static readonly int PACKET_CHUNK_DEFAULT_SIZE = 60000; //패킷 청크 기본 크기 (Byte)
public static readonly bool IS_LARGE_PACKET_PROTOCOL_ACTIVE = true; //false면 라지 패킷 미사용 //이 값은 GameConfig같은 걸로 빼서 클라랑 같이 써야될수 있다.
public static readonly Int32 MAX_PACKET_SIZE = 256_000;
public static readonly UInt16 SERVER_INFO_NOTIFY_INTERVAL_MSEC = 5000;
}

View File

@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using DYNAMO_DB_TABLE_NAME = System.String;
namespace ServerBase;
public static class DynamoDbDefine
{
public static class TableNames
{
// 메인 테이블
public static DYNAMO_DB_TABLE_NAME Main = "Metaverse";
}
}

View File

@@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ServerBase;
//=============================================================================================
// Transaction ID Type 종류
//=============================================================================================
public enum TransactionIdType
{
None = 0, // 트랜잭션 대상이 아니다 !!!
PrivateContents, // 개인적인 컨텐츠
Trade, // 거래
BattleRoom, // 배틀 룸
}
public enum LocationTargetType
{
None = 0,
World = 1,
Instance = 2,
}

View File

@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Threading.Tasks;
namespace ServerBase;
public abstract class IAppender
{
public abstract void write(IFormatter logFormatter, BusinessLog log);
}//ILogAppender

View File

@@ -0,0 +1,60 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Threading.Tasks;
using NLog;
using NLog.Fluent;
using ServerCore; using ServerBase;
namespace ServerBase;
public class NLogAppender : IAppender
{
private readonly NLog.Logger m_logger = NLog.LogManager.GetLogger("BusinessLogger");
public NLogAppender(string xmlCofigFilePath = "")
{
if(true == xmlCofigFilePath.isNullOrWhiteSpace())
{
return;
}
reconfigureXML(xmlCofigFilePath);
}
public bool reconfigureXML(string xmlCofigFilePath)
{
var to_reload_configure = new NLog.Config.XmlLoggingConfiguration(xmlCofigFilePath);
if(null == to_reload_configure)
{
return false;
}
LogManager.Configuration = to_reload_configure;
LogManager.ReconfigExistingLoggers();
return true;
}
public bool isLoadedConfigXML()
{
if(null == LogManager.Configuration)
{
return false;
}
return true;
}
public override void write(IFormatter logFormatter, BusinessLog log)
{
m_logger?.Info(logFormatter.toLogString(log));
}
}

View File

@@ -0,0 +1,93 @@

using ServerCore; using ServerBase;
namespace ServerBase;
//========================================================================================================
// 비즈니스 로그 처리자
//
// 운영 담당 부서와 사업 담당 부서에서 필요로 하는 주요 속성들 정의 하여
// 필요로 하는 데이터 포멧에 맞게 출력 한다.
//========================================================================================================
public static class BusinessLogger
{
private static IAppender? m_appender;
private static IFormatter? m_formatter;
private static LogTransToOutputType m_output_type = LogTransToOutputType.TransToSingleLine;
//==========================================================================
// appender를 셋팅한다.
//==========================================================================
public static void setup(IAppender appender, IFormatter logFormatter, LogTransToOutputType outputType)
{
m_appender = appender;
m_formatter = logFormatter;
m_output_type = outputType;
}
//==========================================================================
// 단일 로그를 수집 한다.
//==========================================================================
public static ServerErrorCode collectLog(IWithLogActor actor, ILogInvoker invoker)
{
ArgumentNullReferenceCheckHelper.throwIfNull(actor, () => $"actor is null !!!");
ArgumentNullReferenceCheckHelper.throwIfNull(invoker, () => $"invoker is null !!!");
if (m_appender == null)
{
return ServerErrorCode.LogAppenderIsNull;
}
if(m_formatter == null)
{
return ServerErrorCode.LogAppenderIsNull;
}
if(true == invoker.hasLog())
{
var is_success = invoker.alloc(actor.toLogActor(), out var log);
if (is_success.isSuccess() && log != null)
{
m_appender.write(m_formatter, log);
}
}
return ServerErrorCode.Success;
}
//==========================================================================
// 복합 로그를 수집 한다.
//==========================================================================
public static ServerErrorCode collectLogs(LogAction logAction, IWithLogActor actor, List<ILogInvoker> invokers)
{
if (m_appender == null)
{
return ServerErrorCode.LogAppenderIsNull;
}
if (m_formatter == null)
{
return ServerErrorCode.LogFormatterIsNull;
}
List<BusinessLog> logs = new List<BusinessLog>();
var result_code = ILogInvoker.alloc(logAction.getLogActionType(), logAction.getTranId(), actor.toLogActor(), invokers, m_output_type, ref logs);
if(result_code.isFail())
{
return result_code;
}
foreach (var log in logs)
{
if (log != null && log.hasLog())
{
m_appender.write(m_formatter, log);
}
}
return ServerErrorCode.Success;
}
}

View File

@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ServerBase;
public abstract class IFormatter
{
public abstract string toLogString(BusinessLog log);
}//IFormatter

View File

@@ -0,0 +1,82 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json.Serialization;
using Newtonsoft.Json;
namespace ServerBase;
public class JsonText : IFormatter
{
private static readonly JsonSerializerSettings m_serialize_settings = new JsonSerializerSettings();
private static readonly ContractResolver m_resolver = new ContractResolver();
public JsonText()
{
m_serialize_settings.ContractResolver = m_resolver;
m_serialize_settings.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter());
}
public override string toLogString(BusinessLog log)
{
return JsonConvert.SerializeObject(log, m_serialize_settings);
}
//=============================================================================================
// Json을 만들때 무시할 property를 등록한다.
//=============================================================================================
private class ContractResolver : Newtonsoft.Json.Serialization.DefaultContractResolver
{
private readonly Dictionary<Type, HashSet<string>> m_ignores;
public ContractResolver()
{
m_ignores = new Dictionary<Type, HashSet<string>>();
}
public void ignoreProperty(Type type, params string[] jsonPropertyNames)
{
if (false == m_ignores.ContainsKey(type))
{
m_ignores[type] = new HashSet<string>();
}
foreach (var prop in jsonPropertyNames)
{
m_ignores[type].Add(prop);
}
}
public bool isIgnored(Type type, string jsonPropertyName)
{
if (false == m_ignores.ContainsKey(type))
{
return false;
}
return m_ignores[type].Contains(jsonPropertyName);
}
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
if (property.DeclaringType != null &&
property.PropertyName != null &&
isIgnored(property.DeclaringType, property.PropertyName))
{
property.ShouldSerialize = i => false;
property.Ignored = true;
}
return property;
}
}//ContractResolver
}//JsonText

View File

@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ServerBase;
//===================================================================================
// 로그 주체를 지칭한다
//===================================================================================
public abstract class ILogActor
{
public virtual bool fillup(IFormatter formatter)
{
return false;
}
}

View File

@@ -0,0 +1,243 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using Amazon.OpenSearchService.Model.Internal.MarshallTransformations;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using ServerCore; using ServerBase;
using LOG_ACTION_TYPE = System.String;
using LOG_DOMAIN_TYPE = System.String;
namespace ServerBase;
public enum LogTransToOutputType
{
TransToMultyLine = 0, // Body 갯수대로 로그 출력
TransToSingleLine = 1, // 한개의 Body로 로그 출력
}
//===================================================================================
// 로그 액션
//===================================================================================
public partial class LogAction
{
private readonly LOG_ACTION_TYPE m_log_action_type = string.Empty;
private Guid m_tran_id = Guid.Empty;
public LogAction(LOG_ACTION_TYPE logActionType)
{
m_log_action_type = logActionType;
m_tran_id = Guid.NewGuid();
}
public LogAction(LOG_ACTION_TYPE logActionType, Guid tranId)
{
m_log_action_type = logActionType;
m_tran_id = tranId;
}
}//LogAction
//===================================================================================
// 로그를 발생 시킨다
//===================================================================================
public abstract partial class ILogInvoker
{
private LogAction? m_log_action_nullable;
private readonly LOG_DOMAIN_TYPE m_log_domain_type = string.Empty;
public ILogInvoker()
{
}
public ILogInvoker(LOG_DOMAIN_TYPE logDomainType)
{
m_log_domain_type = logDomainType;
}
public ILogInvoker(LOG_DOMAIN_TYPE logDomainType, LogAction logAction)
{
m_log_action_nullable = logAction;
m_log_domain_type = logDomainType;
}
public ServerErrorCode alloc(ILogActor logActor, out BusinessLog? businessLog)
{
ArgumentNullReferenceCheckHelper.throwIfNull(logActor, () => $"logActor is null !!!");
businessLog = null;
if (null == m_log_action_nullable)
{
return ServerErrorCode.LogActionIsNull;
}
var header = new BusinessLog.LogHeader(m_log_action_nullable.getTranId().ToString(), logActor);
var body = new BusinessLog.LogBody(m_log_action_nullable.getLogActionType());
{
fillup(ref body);
}
businessLog = new BusinessLog(header, body);
return ServerErrorCode.Success;
}
public static ServerErrorCode alloc( LOG_ACTION_TYPE logActionType, Guid tran
, ILogActor actorLog, List<ILogInvoker> invokers
, LogTransToOutputType outputType, ref List<BusinessLog> outLogs )
{
if (logActionType.isNullOrWhiteSpace())
{
return ServerErrorCode.LogActionTypeInvalid;
}
var header = new BusinessLog.LogHeader(tran.ToString(), actorLog);
switch (outputType)
{
case LogTransToOutputType.TransToMultyLine:
{
foreach (var each in invokers)
{
var body = new BusinessLog.LogBody(logActionType);
{
if (false == each.hasLog())
{
continue;
}
each.fillup(ref body);
outLogs.Add(new BusinessLog(header, body));
}
}
}
break;
case LogTransToOutputType.TransToSingleLine:
{
var body = new BusinessLog.LogBody(logActionType);
{
foreach (var each in invokers)
{
if (false == each.hasLog())
{
continue;
}
each.fillup(ref body);
}
outLogs.Add(new BusinessLog(header, body));
}
}
break;
}
return ServerErrorCode.Success;
}
protected abstract void fillup(ref BusinessLog.LogBody body);
// 로그가 들어 있는지 검사한다.
public abstract bool hasLog();
public virtual string toBasicString()
{
return $"LogInvoker:{this.getTypeName()}";
}
//===================================================================================
// 사용자 정의 로그 정보 추상 클래스 이다.
//===================================================================================
public abstract class IInfo
{
[JsonProperty(Order = -2)]
private readonly LOG_DOMAIN_TYPE Domain = string.Empty;
// ILogInvoker 에 의해 생성될 경우 로그 정보 컨테이너에 적재되고
// 로그 정보를 Json 기반으로 직렬화 한다.
public IInfo(ILogInvoker parent)
{
Domain = parent.m_log_domain_type;
}
// 로그 정보 전달 객체로 활용 된다 !!!
public IInfo()
{
}
}//IInfo
}//ILogInvoker
public class BusinessLog
{
[JsonProperty]
private readonly LogHeader Header;
[JsonProperty]
private readonly LogBody Body;
public BusinessLog(LogHeader header, LogBody body)
{
Header = header;
Body = body;
}
public bool hasLog()
{
return Header != null && Body != null && Header.hasLog() && Body.hasLog();
}
public class LogHeader
{
[JsonProperty]
private readonly string TranId = string.Empty;
[JsonProperty]
private readonly ILogActor Actor;
public LogHeader(string tran_id, ILogActor actor_log)
{
TranId = tran_id;
Actor = actor_log;
}
public bool hasLog()
{
return Actor != null;
}
}
public class LogBody
{
[JsonProperty]
private readonly LOG_ACTION_TYPE Action = string.Empty;
[JsonProperty]
private readonly List<ILogInvoker.IInfo> Infos = new List<ILogInvoker.IInfo>();
public LogBody(LOG_ACTION_TYPE action)
{
Action = action;
}
public bool hasLog(bool allow_empty = false)
{
if (Action.isNullOrWhiteSpace())
{
return false;
}
return allow_empty ? true : Infos.Count != 0;
}
public void append(ILogInvoker.IInfo info)
{
Infos.Add(info);
}
}
}

View File

@@ -0,0 +1,48 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using ServerCore; using ServerBase;
namespace ServerBase;
public abstract class CacheBase
{
public DateTime CreatedDateTime { get; set; }
public DateTime UpdatedDateTime { get; set; }
public DateTime ExpireDateTime { get; set; }
public CacheBase()
{
applyUpdatedDateTime();
}
protected void applyCreatedDateTime()
{
CreatedDateTime = DateTimeHelper.Current;
applyUpdatedDateTime();
}
protected void applyExpireDateTime(TimeSpan elapsedTime)
{
ExpireDateTime = DateTimeHelper.Current + elapsedTime;
applyUpdatedDateTime();
}
protected void applyUpdatedDateTime()
{
UpdatedDateTime = DateTimeHelper.Current;
}
public string toJsonString()
{
return $"{JsonConvert.SerializeObject(this)}";
}
}

View File

@@ -0,0 +1,153 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using StackExchange.Redis;
using ServerCore;
using SERVER_METRICS_TRIGGER_TYPE = System.UInt32;
namespace ServerBase;
public class ServerInfo : IComparable<ServerInfo>
{
public ServerInfo()
{
}
public ServerInfo(ServerInfo serverInfo)
{
this.Name = serverInfo.Name;
this.Address = serverInfo.Address;
this.Port = serverInfo.Port;
this.Sessions = serverInfo.Sessions;
this.RoomCapacity = serverInfo.RoomCapacity;
this.Capacity = serverInfo.Capacity;
this.Reservation = serverInfo.Reservation;
this.ReturnCount = serverInfo.ReturnCount;
this.UgcNpcCount = serverInfo.UgcNpcCount;
this.Inspection = serverInfo.Inspection;
this.ReadyForDistroy = serverInfo.ReadyForDistroy;
this.LastUpdateTime = serverInfo.LastUpdateTime;
this.AwsInstanceID = serverInfo.AwsInstanceID;
this.Channel = serverInfo.Channel;
this.WorldId = serverInfo.WorldId;
}
public string Name = string.Empty;
public string Address = string.Empty;
public string AwsInstanceID = string.Empty;
public int Port = 0;
public int Sessions = 0;
public int RoomCapacity = 0;
public int Capacity = 0;
public int Reservation = 0;
public int ReturnCount = 0;
public int UgcNpcCount = 0;
public bool Inspection = false;
public bool ReadyForDistroy = false;
public DateTime LastUpdateTime;
public int Channel = 0;
public int WorldId = 0;
public int CompareTo(ServerInfo? other)
{
if (other == null)
return 1;
return Sessions.CompareTo(other.Sessions);
}
}
public class ServerMetricsCacheRequest : RedisRequestWithLambdaBase<ServerMetricsCacheRequest.FnConditionCheck>
{
public class CacheServerKey
{
public string key = string.Empty;
public long tick = 0;
public List<RedisValue> redisValue = new();
}
public delegate Task<Result> FnConditionCheck();
public delegate Task<IWithResult> FnTriggerHandler( params object[] handlerParams );
// for Metrics
public enum TriggerType
{
ServerMetrics_Init, //Return : ResultOnly, Params : { SERVER_NAME, ServerType, WORLD_META_ID }
ServerMetrics_AllGetAndFill, //Return : ResultValue<List<ServerInfo>>, Params : { WORLD_META_ID List } with ServerType{ Login, Channel, Indun }
ServerMetrics_ServerTypeGetAndFill, //Return : ResultValue<List<ServerInfo>>, Params : { ServerType, WORLD_META_ID }
ServerMetrics_ChannelTargetGetAndFill, //Return : ResultValue<ServerInfo>, Params : { WORLD_META_ID, ChannelNo } with ServerType{ Channel }
ServerMetrics_GetByServerName, //Return : ResultValue<ServerInfo>, Params : { SERVER_NAME }
ServerMetrics_ExpiredAllRemove, //Return : ResultOnly, Params : { WORLD_META_ID List } with ServerType{ Login, Channel, Indun }
ServerMetrics_RemoveByServerName, //Return : ResultOnly, Params : { SERVER_NAME }
ServerMetrics_UpdateToCache, //Return : ResultOnly, Params : { SERVER_NAME, ServerInfo }
}
private ConcurrentDictionary<SERVER_METRICS_TRIGGER_TYPE, FnTriggerHandler> m_trigger_handlers = new();
private ServerMetricsManager m_manager;
public ServerMetricsCacheRequest(ServerMetricsManager manager, RedisConnector redisConnector)
: base(redisConnector)
{
m_manager = manager;
}
public bool registerTriggerHandler(SERVER_METRICS_TRIGGER_TYPE triggerType, FnTriggerHandler handler)
{
return m_trigger_handlers.TryAdd(triggerType, handler);
}
public async Task<IWithResult> tryRunTriggerHandler(SERVER_METRICS_TRIGGER_TYPE triggerType, params object[] handlerParams)
{
var result = new Result();
var err_msg = string.Empty;
if (false == m_trigger_handlers.TryGetValue(triggerType, out var found_handler))
{
err_msg = $"Failed to TryGetValue() !!!, in tryRunTriggerHandler(), Not found FnTriggerHandler : triggerType:{triggerType}";
result.setFail(ServerErrorCode.ServerMetricsTriggerHandlerNotFound, err_msg);
return new ResultOnly(result);
}
return await found_handler.Invoke(handlerParams);
}
public ServerInfo makeServerInfo( string serverName
, string address, int port
, int sessions, int capacity, int reservation
, int returnCount, int ugcNpcCount
, int worldId = 0, int channel = 0, int roomCapacity = 0
, string awsInstanceId = "" )
{
var server_info = new ServerInfo();
server_info.Name = serverName;
server_info.Address = address;
server_info.Port = port;
server_info.Sessions = sessions;
server_info.Capacity = capacity;
server_info.Reservation = reservation;
server_info.ReturnCount = returnCount;
server_info.UgcNpcCount = ugcNpcCount;
server_info.RoomCapacity = roomCapacity;
//server_info.Inspection = AccountAuthorityManager.Instance.isInspection;
//server_info.ReadyForDistroy = AccountAuthorityManager.Instance.isReadyForDistroy;
server_info.LastUpdateTime = DateTime.Now;
server_info.AwsInstanceID = awsInstanceId;
server_info.Channel = channel;
server_info.WorldId = worldId;
return server_info;
}
public ServerMetricsManager getServerMetricsManager() => m_manager;
}

View File

@@ -0,0 +1,555 @@
using Newtonsoft.Json.Linq;
using ServerCore;
namespace ServerBase;
public class ManagerServerConfig
{
public int minSize = 1;
public int capacity = 2000;
}
public class RabbitmqConfig
{
public string HostName { get; private set; } = "localhost";
public string UserName { get; private set; } = "admin";
public string Password { get; private set; } = "admin";
public int Port { get; private set; } = -1;
public bool SSL { get; private set; } = false;
public void ReadConfig(JToken? jToken)
{
if (jToken == null)
return;
HostName = jToken["HostName"]?.Value<string>() ?? HostName;
UserName = jToken["UserName"]?.Value<string>() ?? UserName;
Password = jToken["Password"]?.Value<string>() ?? Password;
Port = jToken["Port"]?.Value<int>() ?? Port;
SSL = jToken["SSL"]?.Value<bool>() ?? SSL;
}
}
//=============================================================================================
// 계정 처리 룰
//=============================================================================================
public class AuthRule
{
//=========================================================================================
// 계정 처리 룰 플래그 종류
//=========================================================================================
public enum FlagType
{
None = 0,
TestUserAllow = 1, // TestUserCreateData, TestUserInitilData 메타를 참조하여 유저를 생성 및 초기화 한다.
BotIdAllow = 2, // Bot id를 허용 한다. (기본값:true(개발용))
TestIdAllow = 3, // Test id를 허용 한다. (기본값:true(개발용))
ClientStandaloneAllow = 4, // 클라이언트가 단독 로그인 시도를 허용 한다. (기본값:true(개발용))
ClientBySsoAccountAuthWithLauncherAllow = 5, // 클라이언트가 통합계정인증 및 런처를 통해 실행되어 로그인 시도를 허용 한다. (기본값:false(개발용)))
}
public bool TestUserAllow { get; set; } = true;
public bool BotIdAllow { get; set; } = true;
public bool TestIdAllow { get; set; } = true;
public bool ClientStandaloneAllow { get; set; } = true;
public bool ClientBySsoAccountAuthWithLauncherAllow { get; set; } = false;
// 접속 허용되는 PlatformType 목록
public List<PlatformType> PlatformTypeAllows { get; set; } = new List<PlatformType>();
}
//=============================================================================================
// 로드 밸런싱 룰
//=============================================================================================
public class LoadBalancingRule
{
//=========================================================================================
// 로드 밸런싱 룰 종류
//=========================================================================================
public enum RuleType
{
None = 0,
RoundRobin = 1, // 순서대로 돌아가며 접속을 유도 한다.
WeightedRoundRobin = 2, // 가중치가 높은 서버를 우선하여 순서대로 접속 유도 한다.
LeastConnection = 3, // 최소 연결 개수 서버를 우선 순위로 접속을 유도 한다.
WeightedLeastConnection = 4, // 가중치가 높은 서버를 우선하여 최소 연결 개수 서버를 접속 유도 한다.
}
//=========================================================================================
// 로드 밸런싱 룰 옵션 플래그 종류
//=========================================================================================
public enum RuleOptionFlagType
{
None = 0,
UserLanguageBased = 1, // 사용자 언어 서버를 우선 순위로 접속을 유도 한다.
}
public class Config
{
public RuleType Rule { get; set; } = RuleType.WeightedRoundRobin;
public UInt16 MinRate { get; set; } = 0;
public UInt16 MaxRate { get; set; } = 100;
public bool UserLanguageBased { get; set; } = false;
}
public Config AuthToGameRule { get; set; } = new() { Rule = RuleType.WeightedRoundRobin, UserLanguageBased = true };
public Config ChannelToInstanceDungeonRule { get; set; } = new() { Rule = RuleType.WeightedRoundRobin, UserLanguageBased = false };
public Config ChannelToChannelRule { get; set; } = new() { Rule = RuleType.WeightedRoundRobin, UserLanguageBased = false };
public Config InstanceDungeonToChannelRule { get; set; } = new() { Rule = RuleType.WeightedRoundRobin, UserLanguageBased = false };
public Config ToEtcServerRule { get; set; } = new() { Rule = RuleType.WeightedRoundRobin, UserLanguageBased = false };
}
//=========================================================================================
// NFT 처리 규칙
//=========================================================================================
public class NftRule
{
public bool NftDBAccess { get; set; } = false;
public string CPNftForOwnerAllGetUrl { get; set; } = string.Empty;
}
//=========================================================================================
// AI Chat 설정 정보
//=========================================================================================
public class AIChatConfig
{
public string BaseAddress { get; set; } = "https://caliverse-dev.caveduck.io";
public string PrivateKey { get; set; } = "/EbpXsuA4Wi3M9NV11dQj1mtw8FGB42txTUBVfyS2BrdLq7JEwGRFDtmzdDQNMMfByFEX8qJhkpbPZnepTbcCg==";
}
//=========================================================================================
// Echo System 설정 정보
//=========================================================================================
public class EchoSystemConfig
{
public string BaseAddress { get; set; } = "https://eco-system-dev-rollup-admin-api.caliverse.io";
public string SlackAddress { get; set; } = string.Empty;
}
//=========================================================================================
// Billing 설정 정보
//=========================================================================================
public class BillingConfig
{
public string BaseAddress { get; set; } = "https://dev-api.caliverse.io";
}
//=========================================================================================
// UGQ API Server 설정 정보
//=========================================================================================
public class UgqApiServerConfig
{
public string ApiServerAddress { get; set; } = "http://ec2-34-222-2-134.us-west-2.compute.amazonaws.com:11000";
public string UrlInGamePrefix { get; set; } = "/api/v1/InGame";
}
//=============================================================================================
// 서버 주요 설정 정보를 저장하는 클래스 이다.
// TODO: ServerConfig => ServerConfig 변경 한다 !!!
// ㄱ
//=============================================================================================
public partial class ServerConfig
{
public static readonly string ConfigDirectory = "Config";
private bool m_is_loaded_config = false;
private UInt16 m_app_param_port = 0;
private string m_config_file_path = string.Empty;
private JObject? m_loaded_config_nullable;
private string m_program_version_path = string.Empty;
private Dictionary<ProgramVersionType, string> m_program_versions = new();
private NetworkAddress m_listen_address = new();
private string m_region_id = string.Empty;
private UInt16 m_world_id = 0;
private UInt32 m_channel_no = 0;
private Dictionary<ServerUrlType, ServerUrl> m_server_urls = new();
// 언어별 url 링크
// private Dictionary<ServerUrlType, ServerUrlWithLang> m_server_url_with_langs = new();
public ServerConfig()
{ }
public virtual async Task<Result> tryLoadConfig()
{
var result = new Result();
var err_msg = string.Empty;
var config_file_path = getConfigFilePath();
try
{
if (false == File.Exists(config_file_path))
{
err_msg = $"Not exist Config File in path !!! : path:{config_file_path}";
result.setFail(ServerErrorCode.ServerConfigFileNotFound, err_msg);
ServerCore.Log.getLogger().error(result.toBasicString());
return result;
}
var loaded_json = JObject.Parse(File.ReadAllText(config_file_path));
result = await parseConfig(loaded_json);
if (result.isFail())
{
return result;
}
m_loaded_config_nullable = loaded_json;
m_is_loaded_config = true;
}
catch (Exception e)
{
err_msg = $"Exception !!!, Failed to perform !!!, in tryLoadConfig() : exception:{e}";
result.setFail(ServerErrorCode.DotNetException, err_msg);
ServerCore.Log.getLogger().error(result.toBasicString());
return result;
}
return result;
}
public virtual async Task<Result> parseConfig(JObject loadedJosn)
{
var err_msg = string.Empty;
var result = new Result();
LogDir = loadedJosn["LogDir"]?.Value<string>() ?? LogDir;
DumpDir = loadedJosn["DumpDir"]?.Value<string>() ?? DumpDir;
var listen = loadedJosn["ClientListen"];
if (listen != null)
{
ClientListenIp = listen["Ip"]?.Value<string>() ?? ClientListenIp;
ClientListenPort = listen["Port"]?.Value<UInt16>() ?? ClientListenPort;
}
MinWorkerThreadCount = loadedJosn["MinWorkerThreadCount"]?.Value<int>() ?? MinWorkerThreadCount;
MinIOCThreadCount = loadedJosn["MinIoThreadCount"]?.Value<int>() ?? MinIOCThreadCount;
AccountLoginBlockEnable = loadedJosn["AccountLoginBlockEnable"]?.Value<bool>() ?? AccountLoginBlockEnable;
SingleThreaded = loadedJosn["SingleThreaded"]?.Value<bool>() ?? SingleThreaded;
Rabbitmq.ReadConfig(loadedJosn["Rabbitmq"]);
OfflineMode = loadedJosn["OfflineMode"]?.Value<bool>() ?? false;
StandaloneMode = loadedJosn["StandaloneMode"]?.Value<bool>() ?? StandaloneMode;
ServiceType = (loadedJosn["ServiceType"]?.Value<string>() ?? ServiceType);
ServiceTypeNew = (loadedJosn["ServiceType"]?.Value<string>()?.toServiceType(global::ServiceType.None) ?? ServiceTypeNew);
SsoAccountDb = loadedJosn["SsoAccountDb"]?.Value<string>() ?? SsoAccountDb;
SsoAccountAuthJwtSecretKey = loadedJosn["SsoAccountAuthJwtSecretKey"]?.Value<string>() ?? SsoAccountAuthJwtSecretKey;
DefaultMaxUser = loadedJosn["DefaultMaxUser"]?.Value<int>() ?? DefaultMaxUser;
AccountNftDb = loadedJosn["AccountNftDb"]?.Value<string>() ?? AccountNftDb;
Redis = loadedJosn["Redis"]?.Value<string>() ?? Redis;
Dynamodb = loadedJosn["Dynamodb"]?.Value<string>() ?? Dynamodb;
SessionKeepAliveTimeSec = loadedJosn["SessionKeepAliveTimeSec"]?.Value<UInt16>() ?? SessionKeepAliveTimeSec;
EC2TemplateIMG = loadedJosn["EC2TemplateIMG"]?.Value<string>() ?? EC2TemplateIMG;
ControlAgentEnable = loadedJosn["ControlAgentEnable"]?.Value<bool>() ?? ControlAgentEnable;
PerformanceCheckEnable = loadedJosn["PerformanceCheckEnable"]?.Value<bool>() ?? PerformanceCheckEnable;
BattleSystemEnable = loadedJosn["BattleSystemEnable"]?.Value<bool>() ?? BattleSystemEnable;
var aws = loadedJosn["AWS"];
if (aws != null)
{
AWS = new AWSConf
{
Enable = aws["Enable"]?.Value<bool>() ?? false,
LocalDynamoDB = aws["LocalDynamoDB"]?.Value<bool>() ?? false,
AccessKey = aws["AccessKey"]?.Value<string>() ?? "",
SecretKey = aws["SecretKey"]?.Value<string>() ?? "",
Region = aws["Region"]?.Value<string>() ?? "",
MilestoneName = aws["MilestoneName"]?.Value<string>() ?? ""
};
if (AWS.Enable && !string.IsNullOrEmpty(AWS.Region))
{
setRegionId(AWS.Region);
}
// Cloud Watch Log
if (aws["CloudWatchLog"] is JObject cloud_watch_log)
{
AWS.CloudWatchLog = new CloudWatchLogConf
{
Enable = cloud_watch_log["Enable"]?.Value<bool>() ?? false,
CloudWatchLogGroup = cloud_watch_log["LogGroup"]?.Value<string>() ?? string.Empty,
CloudWatchLogNamePattern = cloud_watch_log["LogNamePattern"]?.Value<string>() ?? string.Empty,
CloudWatchLogLevel = cloud_watch_log["LogLevel"]?.Value<string>() ?? string.Empty
};
var error_code = cloud_watch_log.fillupCloudWatchLogLayout(out var layout);
if (error_code.isSuccess())
{
AWS.CloudWatchLog.CloudWatchLogLayout = layout;
}
}
// S3
if (aws["S3"] is JObject s3)
{
AWS.S3 = new S3Conf
{
ServiceFolderName = s3["ServiceFolderName"]?.Value<string>() ?? string.Empty,
MyhomeUgcInfoBucketName = s3["MyhomeUgcInfoBucketName"]?.Value<string>() ?? string.Empty,
BeaconAppProfileBucketName = s3["BeaconAppProfileBucketName"]?.Value<string>() ?? string.Empty,
};
}
}
var game_config = loadedJosn["GameConfig"];
if (game_config != null)
{
GameConfig = new GameConf();
GameConfig.ReservationWaitTimeMSec = game_config["ReservationWaitTimeMSec"]?.Value<int>() ?? 30_000;
GameConfig.LoginCacheExpiryTimeMSec = game_config["LoginCacheExpiryTimeMSec"]?.Value<int>() ?? 60 * 60 * 1000;
GameConfig.ServerSwitchCacheExpiryTimeMSec = game_config["ServerSwitchCacheExpiryTimeMSec"]?.Value<int>() ?? 5 * 60 * 1000;
}
var mongoDb = loadedJosn["MongoDb"];
if (mongoDb != null)
{
MongoDbConfig = new MongoDbConf();
MongoDbConfig.ConnectionString = mongoDb["ConnectionString"]?.Value<string>() ?? string.Empty;
MongoDbConfig.DatabaseName = mongoDb["DatabaseName"]?.Value<string>() ?? string.Empty;
MongoDbConfig.MinConnectionPoolSize = mongoDb["MinConnectionPoolSize"]?.Value<int>() ?? 0;
MongoDbConfig.MaxConnectionPoolSize = mongoDb["MaxConnectionPoolSize"]?.Value<int>() ?? 100;
MongoDbConfig.WaitQueueTimeoutSecs = mongoDb["WaitQueueTimeoutSecs"]?.Value<int>() ?? 120;
}
ClientProgramVersionCheck = loadedJosn["ClientProgramVersionCheck"]?.Value<bool>() ?? ClientProgramVersionCheck;
m_program_version_path = loadedJosn["ProgramVersionPath"]?.Value<string>() ?? string.Empty;
var program_version = loadedJosn["ProgramVersion"];
if (program_version != null)
{
if (m_program_version_path.isNullOrWhiteSpace())
{
err_msg = $"Not found ProgramVersionPath in ServerConfig !!!";
result.setFail(ServerErrorCode.ProgramVersionPathTokenNotFound, err_msg);
ServerCore.Log.getLogger().error(result.toBasicString());
return result;
}
var types = EnumHelper.getValuesWithoutScopeAll<ProgramVersionType>();
foreach (var type in types)
{
var version_file_name = program_version[type.ToString()]?.Value<string>() ?? string.Empty;
if (version_file_name.isNullOrWhiteSpace())
{
continue;
}
var full_path = $"{m_program_version_path}{version_file_name}";
if (false == m_program_versions.TryAdd(type, full_path))
{
err_msg = $"Already added ProgramVersionType !!! : {type}";
ServerCore.Log.getLogger().warn(result.toBasicString());
continue;
}
}
}
CheatCommandAlwaysAllow = loadedJosn["CheatCommandAlwaysAllow"]?.Value<bool>() ?? CheatCommandAlwaysAllow;
var server_api_url_catalog = loadedJosn["ServerApiUrlCatalog"] as JArray;
if (null != server_api_url_catalog)
{
foreach (var jtoken in server_api_url_catalog)
{
JObject jobject = (JObject)jtoken;
NullReferenceCheckHelper.throwIfNull(jobject, () => $"jobject is null !!!");
foreach (var property in jobject.Properties())
{
var server_url_type_string = property.Name;
// proto buffer에서 변환할 때, "_"을 제거하기 때문에, 여기서도 "_"를 제거하고 enum을 비교해야 한다.
server_url_type_string = server_url_type_string.Replace("_", "");
if (!Enum.TryParse<ServerUrlType>(server_url_type_string, ignoreCase: true, out var server_url_type))
{
server_url_type = ServerUrlType.None;
}
// var server_url_type = server_url_type_string.convertEnumTypeAndValueStringToEnum(ServerUrlType.None);
if (ServerUrlType.None == server_url_type)
{
err_msg = $"Invalid ServerUrlType in ServerConfig !!! : {server_url_type_string}";
result.setFail(ServerErrorCode.ServerUrlTypeInvalid, err_msg);
Log.getLogger().error(result.toBasicString());
return result;
}
var to_add_server_url = new ServerUrl();
to_add_server_url.ServerUrlType = server_url_type;
var value = property.Value;
// 언어별로 분리된 Link
// 클라이언트 측 요청으로 모든 언언어 타입의 url을 담아서 전송, 없으면 ko 전송
if (value is JObject jobj_value)
{
var langTypes = EnumHelper.getValuesWithoutScopeAll<LanguageType>();
var url_with_languages = langTypes.Select(langType =>
{
var url = jobj_value[langType.ToString()]?.Value<string>()
?? jobj_value[LanguageType.Ko.ToString()]?.Value<string>() ?? string.Empty;
return new ServerUrlWithLanguage { LangType = langType, TargetUrl = url };
});
to_add_server_url.ServerUrlWithLanguages.AddRange(url_with_languages);
ConditionValidCheckHelper.throwIfFalse(url_with_languages.Any(), $"ServerConfig ServerApiUrlCatalog: ko of {server_url_type_string} is null");
}
if (false == m_server_urls.TryAdd(server_url_type, to_add_server_url))
{
err_msg = $"Already registered ServerUrlType in ServerConfig !!! : {server_url_type_string}";
result.setFail(ServerErrorCode.ServerUrlTypeAlreadyRegistered, err_msg);
Log.getLogger().error(result.toBasicString());
return result;
}
}
}
}
var auth_rule = loadedJosn["AuthRule"];
if (null != auth_rule)
{
AuthRule.TestUserAllow = auth_rule["TestUserAllow"]?.Value<bool>() ?? AuthRule.TestUserAllow;
AuthRule.BotIdAllow = auth_rule["BotIdAllow"]?.Value<bool>() ?? AuthRule.BotIdAllow;
AuthRule.TestIdAllow = auth_rule["TestIdAllow"]?.Value<bool>() ?? AuthRule.TestIdAllow;
AuthRule.ClientStandaloneAllow = auth_rule["ClientStandaloneAllow"]?.Value<bool>() ?? AuthRule.ClientStandaloneAllow;
AuthRule.ClientBySsoAccountAuthWithLauncherAllow = auth_rule["ClientBySsoAccountAuthWithLauncherAllow"]?.Value<bool>() ?? AuthRule.ClientBySsoAccountAuthWithLauncherAllow;
AuthRule.PlatformTypeAllows = auth_rule["PlatformTypeAllows"]?.Value<string>()?.toPlatformTypeAllows() ?? AuthRule.PlatformTypeAllows;
}
var nft_rule = loadedJosn["NftRule"];
if (null != nft_rule)
{
NftRule.NftDBAccess = nft_rule["NftDBAccess"]?.Value<bool>() ?? NftRule.NftDBAccess;
NftRule.CPNftForOwnerAllGetUrl = nft_rule["CPNftForOwnerAllGetUrl"]?.Value<string>() ?? NftRule.CPNftForOwnerAllGetUrl;
}
var ai_chat = loadedJosn["AIChat"];
if (null != ai_chat)
{
AIChatConfig.BaseAddress = ai_chat["BaseAddress"]?.Value<string>() ?? string.Empty;
AIChatConfig.PrivateKey = ai_chat["PrivateKey"]?.Value<string>() ?? string.Empty;
}
var echo_system = loadedJosn["EchoSystem"];
if (null != echo_system)
{
EchoSystemConfig.BaseAddress = echo_system["BaseAddress"]?.Value<string>() ?? string.Empty;
EchoSystemConfig.SlackAddress = echo_system["SlackAddress"]?.Value<string>() ?? string.Empty;
}
var billing = loadedJosn["Billing"];
if (null != billing)
{
BillingConfig.BaseAddress = billing["BaseAddress"]?.Value<string>() ?? string.Empty;
}
var uqg_api_server = loadedJosn["Ugq"];
if (null != uqg_api_server)
{
UgqApiServerConfig.ApiServerAddress = uqg_api_server["ApiServerAddress"]?.Value<string>() ?? string.Empty;
UgqApiServerConfig.UrlInGamePrefix = uqg_api_server["UrlInGamePrefix"]?.Value<string>() ?? string.Empty;
}
var load_balancing_rule = loadedJosn["LoadBalancingRule"];
if (null != load_balancing_rule)
{
var auth_to_game_rule = load_balancing_rule["AuthToGameRule"];
if (null != auth_to_game_rule)
{
var rule_value = auth_to_game_rule["Rule"]?.Value<string>();
NullReferenceCheckHelper.throwIfNull(rule_value, () => $"rule_value is null !!!");
var rule_type = EnumHelper.convertEnumValueStringToEnum<LoadBalancingRule.RuleType>(rule_value, LoadBalancingRule.RuleType.None);
LoadBalancingRule.AuthToGameRule.Rule = rule_type != LoadBalancingRule.RuleType.None ? rule_type : LoadBalancingRule.AuthToGameRule.Rule;
LoadBalancingRule.AuthToGameRule.MinRate = auth_to_game_rule["MinRate"]?.Value<UInt16>() ?? LoadBalancingRule.AuthToGameRule.MinRate;
LoadBalancingRule.AuthToGameRule.MaxRate = auth_to_game_rule["MaxRate"]?.Value<UInt16>() ?? LoadBalancingRule.AuthToGameRule.MaxRate;
LoadBalancingRule.AuthToGameRule.UserLanguageBased = auth_to_game_rule["UserLanguageBased"]?.Value<bool>() ?? LoadBalancingRule.AuthToGameRule.UserLanguageBased;
}
var channel_to_instance_dungeon_rule = load_balancing_rule["ChannelToInstanceDungeonRule"];
if (null != channel_to_instance_dungeon_rule)
{
var rule_value = channel_to_instance_dungeon_rule["Rule"]?.Value<string>();
NullReferenceCheckHelper.throwIfNull(rule_value, () => $"rule_value is null !!!");
var rule_type = EnumHelper.convertEnumValueStringToEnum<LoadBalancingRule.RuleType>(rule_value, LoadBalancingRule.RuleType.None);
LoadBalancingRule.ChannelToInstanceDungeonRule.Rule = rule_type != LoadBalancingRule.RuleType.None ? rule_type : LoadBalancingRule.ChannelToInstanceDungeonRule.Rule;
LoadBalancingRule.ChannelToInstanceDungeonRule.MinRate = channel_to_instance_dungeon_rule["MinRate"]?.Value<UInt16>() ?? LoadBalancingRule.ChannelToInstanceDungeonRule.MinRate;
LoadBalancingRule.ChannelToInstanceDungeonRule.MaxRate = channel_to_instance_dungeon_rule["MaxRate"]?.Value<UInt16>() ?? LoadBalancingRule.ChannelToInstanceDungeonRule.MaxRate;
LoadBalancingRule.ChannelToInstanceDungeonRule.UserLanguageBased = channel_to_instance_dungeon_rule["UserLanguageBased"]?.Value<bool>() ?? LoadBalancingRule.ChannelToInstanceDungeonRule.UserLanguageBased;
}
var channel_to_channel_rule = load_balancing_rule["ChannelToChannelRule"];
if (null != channel_to_channel_rule)
{
var rule_value = channel_to_channel_rule["Rule"]?.Value<string>();
NullReferenceCheckHelper.throwIfNull(rule_value, () => $"rule_value is null !!!");
var rule_type = EnumHelper.convertEnumValueStringToEnum<LoadBalancingRule.RuleType>(rule_value, LoadBalancingRule.RuleType.None);
LoadBalancingRule.ChannelToChannelRule.Rule = rule_type != LoadBalancingRule.RuleType.None ? rule_type : LoadBalancingRule.ChannelToChannelRule.Rule;
LoadBalancingRule.ChannelToChannelRule.MinRate = channel_to_channel_rule["MinRate"]?.Value<UInt16>() ?? LoadBalancingRule.ChannelToChannelRule.MinRate;
LoadBalancingRule.ChannelToChannelRule.MaxRate = channel_to_channel_rule["MaxRate"]?.Value<UInt16>() ?? LoadBalancingRule.ChannelToChannelRule.MaxRate;
LoadBalancingRule.ChannelToChannelRule.UserLanguageBased = channel_to_channel_rule["UserLanguageBased"]?.Value<bool>() ?? LoadBalancingRule.ChannelToChannelRule.UserLanguageBased;
}
var to_etc_server_rule = load_balancing_rule["ToEtcServerRule"];
if (null != to_etc_server_rule)
{
var rule_value = to_etc_server_rule["Rule"]?.Value<string>();
NullReferenceCheckHelper.throwIfNull(rule_value, () => $"rule_value is null !!!");
var rule_type = EnumHelper.convertEnumValueStringToEnum<LoadBalancingRule.RuleType>(rule_value, LoadBalancingRule.RuleType.None);
LoadBalancingRule.ToEtcServerRule.Rule = rule_type != LoadBalancingRule.RuleType.None ? rule_type : LoadBalancingRule.ToEtcServerRule.Rule;
LoadBalancingRule.ToEtcServerRule.MinRate = to_etc_server_rule["MinRate"]?.Value<UInt16>() ?? LoadBalancingRule.ToEtcServerRule.MinRate;
LoadBalancingRule.ToEtcServerRule.MaxRate = to_etc_server_rule["MaxRate"]?.Value<UInt16>() ?? LoadBalancingRule.ToEtcServerRule.MaxRate;
LoadBalancingRule.ToEtcServerRule.UserLanguageBased = to_etc_server_rule["UserLanguageBased"]?.Value<bool>() ?? LoadBalancingRule.ToEtcServerRule.UserLanguageBased;
}
}
System.IO.Directory.CreateDirectory(LogDir);
System.IO.Directory.CreateDirectory(DumpDir);
return await Task.FromResult(result);
}
public Dictionary<ProgramVersionType, string> getVersionPaths()
{
return m_program_versions;
}
public string toBasicString()
{
return $"ServerConfigFile:{m_config_file_path}";
}
}

View File

@@ -0,0 +1,51 @@
using Newtonsoft.Json.Linq;
namespace ServerBase;
public class MetaverseBrokerConfig
{
public required string JwtSecretKey { get; init; }
public int ExpireMinutes { get; init; } = 1440;
public required string Issuer { get; init; } = string.Empty;
public required string Audience { get; init; } = string.Empty;
public required string MetaverseBrokerDb { get; init; }
public required string MetaverseBrokerDbLocal { get; init; }
public required string SsoAccountDb { get; init; }
}
// TODO: getset 파일로 이동할 것
public partial class ServerConfig
{
public MetaverseBrokerConfig? MetaverseBroker;
}
public class ServerConfigMetaverseBroker : ServerConfig
{
public override async Task<Result> parseConfig(JObject loadedJosn)
{
var result = await base.parseConfig(loadedJosn);
if (result.isSuccess())
{
var r = loadedJosn.ContainsKey("MetaverseBroker");
var jToken = loadedJosn["MetaverseBroker"];
if (jToken != null)
{
MetaverseBroker = new MetaverseBrokerConfig
{
JwtSecretKey = jToken["JwtSecretKey"]?.Value<string>() ?? string.Empty,
ExpireMinutes = jToken["ExpireMinutes"]?.Value<int>() ?? 1440,
Issuer = jToken["Issuer"]?.Value<string>() ?? string.Empty,
Audience = jToken["Audience"]?.Value<string>() ?? string.Empty,
MetaverseBrokerDb = jToken["MetaverseBrokerDb"]?.Value<string>() ?? string.Empty,
MetaverseBrokerDbLocal = jToken["MetaverseBrokerDbLocal"]?.Value<string>() ?? string.Empty,
SsoAccountDb = jToken["SsoAccountDb"]?.Value<string>() ?? string.Empty
};
return result;
}
}
result.setFail(ServerErrorCode.ServerConfigFileNotFound, "server config error : metaverse_broker not found");
return await Task.FromResult(result);
}
}

View File

@@ -0,0 +1,82 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection.Metadata;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using Amazon.DynamoDBv2.DocumentModel;
using Newtonsoft.Json.Linq;
namespace ServerBase;
public abstract partial class AttribBase
{
// AttribType은 DynamoDb Document 정보를 Read시 식별키로 사용되기 때문에 고유하게 정의되어야 한다 !!! - kangms
[JsonProperty("attrib_type")]
public readonly string AttribType;
private readonly bool m_is_save_to_json_in_db = false;
public AttribBase(string attribType, bool isSaveToJsonInDb = true)
{
AttribType = attribType;
m_is_save_to_json_in_db = isSaveToJsonInDb;
}
public bool isSaveToJsonInDb() => m_is_save_to_json_in_db;
public virtual string toJsonString()
{
return JsonConvert.SerializeObject(this);
}
public virtual Amazon.DynamoDBv2.DocumentModel.Document toDocument()
{
return DynamoDbClientHelper.classToDynamoDbDocument(this);
}
public virtual string toBasicString()
{
return $"AttribType:{AttribType}, {toJsonString()}";
}
}
public partial class AttribWrapper<TAttrib> : IAttribWrapper
where TAttrib : AttribBase, new()
{
private TAttrib m_attrib = new();
public AttribWrapper()
{}
public bool onCopyFromDynamoDbEntry(Amazon.DynamoDBv2.DocumentModel.Document doc)
{
if(false == doc.fillupAttribObject<TAttrib>(out var fillup_attrib))
{
return false;
}
if (fillup_attrib == null)
{
return false;
}
m_attrib = fillup_attrib;
return true;
}
public bool onCopyToDynamoDbEntry(ref Amazon.DynamoDBv2.DocumentModel.Document doc)
{
return doc.setAttribObject<TAttrib>(m_attrib);
}
public bool onFillupJsonStringToJObject(ref JObject jobject)
{
return jobject.setAttribObjectWithJsonString<TAttrib>(m_attrib);
}
}

View File

@@ -0,0 +1,275 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections.Concurrent;
using Amazon.DynamoDBv2;
using Amazon.DynamoDBv2.DocumentModel;
using Amazon.DynamoDBv2.Model;
using Amazon.Runtime;
using ServerCore;
using ServerBase;
using DYNAMO_DB_TABLE_NAME = System.String;
using DYNAMO_DB_TABLE_FULL_NAME = System.String;
namespace ServerBase;
public partial class DynamoDbClient : DynamoDbConnectorBase, IModule, IInitializer
{
public const string TTL_NAME = "TTL";
public const string SK_EMPTY = "empty";
public const string PK_GLOBAL = "global";
private readonly ModuleContext? m_module_context;
public DynamoDbClient()
: base()
{ }
public DynamoDbClient(ModuleContext moduleContext)
: base()
{
m_module_context = moduleContext;
}
public async Task<Result> onInit()
{
var err_msg = string.Empty;
var result = new Result();
var module_context = getModuleContext();
var config_param = module_context.getConfigParam() as ConfigParam;
NullReferenceCheckHelper.throwIfNull(config_param, () => $"config_param is null !!!");
result = connectToDb(config_param);
if (result.isFail())
{
err_msg = $"Failed to create DB Table !!! - {toBasicString()}";
Log.getLogger().fatal(result.toBasicString());
return result;
}
if (false == await createDBIfNotExists(false))
{
err_msg = $"Failed to createDBIfNotExists() !!! - {toBasicString()}";
result.setFail(ServerErrorCode.DynamoDbTableCreateFailed, err_msg);
Log.getLogger().fatal(result.toBasicString());
return result;
}
return result;
}
public async Task<Result> startModule()
{
var result = new Result();
return await Task.FromResult(result);
}
public async Task<Result> stopModule()
{
var result = new Result();
return await Task.FromResult(result);
}
public Result connectToDb(ConfigParam configParam)
{
var result = new Result();
var err_msg = string.Empty;
(var error_code, var to_load_table_names) = ServerConfigHelper.getDynamoDbTableNamesWithServiceType(configParam.ServiceType);
if (error_code.isFail())
{
err_msg = $"Failed to DynamoDbClient.getDynamoDbTableNameWithServiceType() !!! : {configParam.toBasicString()} - {toBasicString()}";
result.setFail(error_code, err_msg);
Log.getLogger().error(err_msg);
return result;
}
return connectToDb( to_load_table_names
, configParam.IsLocalDynamoDB, configParam.UrlOfDynamoDb
, configParam.AccessKeyOfAws, configParam.SecretKeyOfAws, configParam.RegionOfAws );
}
public Result connectToDb( ConcurrentDictionary<DYNAMO_DB_TABLE_NAME, DYNAMO_DB_TABLE_FULL_NAME> tableNames, bool localDynamoDb
, string dynamodbUrl
, string awsAccessKey, string awsSecretKey
, string awsRegion = "" )
{
var result = new Result();
var err_msg = string.Empty;
foreach( var each in tableNames )
{
var table_name = each.Key;
var table_full_name = each.Value;
if(false == addTableFullName(table_name, table_full_name))
{
err_msg = $"Failed to addTableFullName() !!! : tableName:{table_name}, tableFullName:{table_full_name} - {toBasicString()}";
result.setFail(ServerErrorCode.DynamoDbTableNameDuplicated, err_msg);
Log.getLogger().fatal(result.toBasicString());
return result;
}
}
var db_config = new AmazonDynamoDBConfig();
db_config.BufferSize = 4 * 1024 * 1024; // 4 MB
db_config.RetryMode = RequestRetryMode.Standard;
db_config.DisableLogging = false; // Logging is disabled by default. - Set to true to enable request-level logging.
db_config.ThrottleRetries = false; // Throttled requests are not automatically retried. - Set to false to automatically retry throttled requests.
if (localDynamoDb)
{
// DynamoDB-Local is running, so create a client
Log.getLogger().info($"Setting up a DynamoDB-Local client (DynamoDB Local seems to be running)");
db_config.ServiceURL = dynamodbUrl;
if (false == base.connectToDb(awsAccessKey, awsSecretKey, db_config))
{
err_msg = $"Failed to connect a DynamoDB-Local client !!! : {toBasicString()}";
result.setFail(ServerErrorCode.DynamoDbConnectFailed, err_msg);
Log.getLogger().error(result.toBasicString());
return result;
}
}
else
{
Log.getLogger().info($"Setting up a DynamoDB-Remote client : TargetRegion:{awsRegion}");
try
{
db_config.RegionEndpoint = Amazon.RegionEndpoint.GetBySystemName(awsRegion);
if (false == base.connectToDb(awsAccessKey, awsSecretKey, db_config))
{
err_msg = $"Failed to connect a DynamoDB-Remote client !!! : {toBasicString()}";
result.setFail(ServerErrorCode.DynamoDbConnectFailed, err_msg);
Log.getLogger().error(result.toBasicString());
return result;
}
}
catch (Exception e)
{
err_msg = $"Failed to connect a DynamoDB-Remote client !!! : Exception:{e}, {toBasicString()}";
result.setFail(ServerErrorCode.DynamoDbConnectFailed, err_msg);
Log.getLogger().fatal(result.toBasicString());
return result;
}
}
return result;
}
public Result connectToDb( string tableName, bool localDynamoDB, string dynamodbUrl
, string awsAccessKey, string awsSecretKey, string awsRegion = "")
{
var table_names = new ConcurrentDictionary<DYNAMO_DB_TABLE_NAME, DYNAMO_DB_TABLE_FULL_NAME>();
table_names[tableName] = tableName;
return connectToDb( table_names, localDynamoDB, dynamodbUrl
, awsAccessKey, awsSecretKey, awsRegion );
}
public async Task<bool> createDBIfNotExists(bool resetTable)
{
var err_msg = string.Empty;
var db_client = getDbClient();
if (db_client == null)
{
err_msg = $"Not created DynamoDbClient !!! - {toBasicString()}";
Log.getLogger().fatal(err_msg);
return false;
}
var loaded_table_full_names = getTableNames().Values.ToList();
if (resetTable == true)
{
if(false == await deleteTables(loaded_table_full_names))
{
err_msg = $"Failed to DynamoDbClient.deleteTables() !!! - {toBasicString()}";
Log.getLogger().fatal(err_msg);
return false;
}
}
List<AttributeDefinition> tableAttributes = new List<AttributeDefinition>();
tableAttributes.Add(new AttributeDefinition(PrimaryKey.PK_Define, ScalarAttributeType.S));
tableAttributes.Add(new AttributeDefinition(PrimaryKey.SK_Define, ScalarAttributeType.S));
List<KeySchemaElement> tableKeySchema = new List<KeySchemaElement>();
tableKeySchema.Add(new KeySchemaElement(PrimaryKey.PK_Define, KeyType.HASH));
tableKeySchema.Add(new KeySchemaElement(PrimaryKey.SK_Define, KeyType.RANGE));
foreach (var table_full_name in loaded_table_full_names)
{
if (await isExistTable(table_full_name) == false)
{
var ttl_update = new UpdateTimeToLiveRequest
{
TableName = table_full_name,
TimeToLiveSpecification = new TimeToLiveSpecification
{
AttributeName = TTL_NAME, // TTL로 사용할 속성 이름
Enabled = true // TTL 사용 여부
}
};
if(false == await createTable( db_client, table_full_name, ttl_update
, tableAttributes, tableKeySchema ))
{
err_msg = $"Failed to DynamoDbClient.addTable() !!! - {toBasicString()}";
Log.getLogger().fatal(err_msg);
return false;
}
}
if(false == addTable(table_full_name, Table.LoadTable(db_client, table_full_name)))
{
err_msg = $"Failed to DynamoDbClient.addTable() !!! : tableName:{table_full_name} - {toBasicString()}";
Log.getLogger().fatal(err_msg);
return false;
}
}
DynamoDbQueryOperationOptionConfig.configure();
return true;
}
public Table getTableByDoc<TDoc>()
where TDoc : DynamoDbDocBase, new()
{
return getTableByName((new TDoc()).TableName);
}
public Table getTableByDoc<TDoc>(TDoc doc)
where TDoc : DynamoDbDocBase
{
return getTableByName(doc.TableName);
}
public ModuleContext getModuleContext()
{
NullReferenceCheckHelper.throwIfNull(m_module_context, () => $"m_module_context is null !!!");
return m_module_context;
}
}

View File

@@ -0,0 +1,217 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Linq.Expressions;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Amazon.DynamoDBv2;
using Amazon.DynamoDBv2.DocumentModel;
using Amazon.DynamoDBv2.Model;
using ServerCore; using ServerBase;
using LINK_PKSK = System.String;
namespace ServerBase;
/*=============================================================================================
DynamoDb 정보 관리 흐름도
# 읽기는 Document Only, 쓰기는 Document + ItemRequest 로 처리는 하는 것이 적절하다 !!!
1. Document Only 시퀀스
* 읽기 => 쓰기
DB => Document => Attrib => Doc => EntityAttribute => Doc => Attrib => Document => DB
2. ItemRequest Only 시퀀스
* 읽기 => 쓰기
DB => ItemRequest => EntityAttribute => ItemRequest => DB
3. Document + ItemRequest 시퀀스
* 읽기 => 쓰기
DB => ItemRequest => Document => Attrib => Doc => Attrib => Document => ItemRequest => DB
- kangms
//===========================================================================================*/
//=============================================================================================
// DynamoDB의 쿼리의 종류를 정의 한다.
//
// - kangms
//=============================================================================================
public enum QueryType
{
None = 0,
// 일반 쿼리
Select, // 조회 한다.
Insert, // 존재하지 않은 경우만 추가 한다.
Update, // 존재하는 경우만 업데이트 한다.
Upsert, // 존재하지 않으면 추가하고, 있으면 업데이트 한다.
Delete, // 존재하는 경우 삭제 한다.
// 특수 쿼리
ExistsDelete, // 존재하지 않을 경우, 삭제되기 전 모든 속성을 반환 한다.
}
//=============================================================================================
// DynamoDB의 PrimaryKey를 설정하는 클래스 이다.
//
// - kangms
//=============================================================================================
public class PrimaryKey
{
public const string PK_Define = "PK";
public const string SK_Define = "SK";
[JsonProperty(PK_Define)]
public string PK { get; private set; } = string.Empty;
[JsonProperty(SK_Define)]
public string SK { get; private set; } = string.Empty;
public PrimaryKey()
{ }
public PrimaryKey(string partitionKey)
{
PK = partitionKey;
}
public PrimaryKey(string partitionKey, string sortKey)
{
PK = partitionKey;
SK = sortKey;
}
public bool isFilledPK()
{
if (true == PK.isNullOrWhiteSpace())
{
return false;
}
return true;
}
public bool isFilledSK()
{
if (true == SK.isNullOrWhiteSpace())
{
return false;
}
return true;
}
public void fillUpPrimaryKey(string partitionKey, string sortKey)
{
PK = partitionKey;
SK = sortKey;
}
public void fillUpPK(string partitionKey)
{
PK = partitionKey;
}
public void fillUpSK(string sortKey)
{
SK = sortKey;
}
public void setEmptySK()
{
SK = DynamoDbClient.SK_EMPTY;
}
public string toPKSK()
{
return $"{PK_Define}:{PK}-{SK_Define}:{SK})";
}
//=====================================================================================
// LINK_PKSK : "PK-SK"
//=====================================================================================
public static (Result, PrimaryKey?) parseLinkPKSK(LINK_PKSK linkPKSK)
{
var result = new Result();
var err_msg = string.Empty;
var pksk_parts = linkPKSK.Split('-');
if (pksk_parts.Length != 2)
{
err_msg = $"Invalid length of LinkPKSK !!! : 2 == length:{pksk_parts.Length}";
result.setFail(ServerErrorCode.DynamoDbDocLinkPkSkInvalid, err_msg);
Log.getLogger().error(result.toBasicString());
return (result, null);
}
return (result, new PrimaryKey(pksk_parts[0], pksk_parts[1]));
}
public string toBasicString()
{
return $"PrimaryKey({PK_Define}:{PK}, {SK_Define}:{SK})";
}
}
//=============================================================================================
// DynamoDB의 Document Model에서 쿼리할때 옵션을 설정하는 클래스 이다.
//
// - kangms
//=============================================================================================
public static class DynamoDbQueryOperationOptionConfig
{
private static GetItemOperationConfig m_get_item_config = new();
private static TransactWriteItemOperationConfig m_transact_write_config_for_exist_primary_key = new();
private static TransactWriteItemOperationConfig m_transact_write_config_for_not_exist_primary_key = new ();
// Select 쿼리후 반환되는 필드 선택 설정
private static TransactGetItemOperationConfig m_transact_get_config = new();
public static void configure()
{
{
m_get_item_config.ConsistentRead = true;
}
{
var expression = new Amazon.DynamoDBv2.DocumentModel.Expression();
expression.ExpressionStatement = $"attribute_exists(#{PrimaryKey.PK_Define}) AND attribute_exists(#{PrimaryKey.SK_Define})";
expression.ExpressionAttributeNames[$"#{PrimaryKey.PK_Define}"] = PrimaryKey.PK_Define;
expression.ExpressionAttributeNames[$"#{PrimaryKey.SK_Define}"] = PrimaryKey.SK_Define;
m_transact_write_config_for_exist_primary_key.ConditionalExpression = expression;
m_transact_write_config_for_exist_primary_key.ReturnValuesOnConditionCheckFailure = Amazon.DynamoDBv2.DocumentModel.ReturnValuesOnConditionCheckFailure.AllOldAttributes;
}
{
var expression = new Amazon.DynamoDBv2.DocumentModel.Expression();
expression.ExpressionStatement = $"attribute_not_exists(#{PrimaryKey.PK_Define}) AND attribute_not_exists(#{PrimaryKey.SK_Define})";
expression.ExpressionAttributeNames[$"#{PrimaryKey.PK_Define}"] = PrimaryKey.PK_Define;
expression.ExpressionAttributeNames[$"#{PrimaryKey.SK_Define}"] = PrimaryKey.SK_Define;
m_transact_write_config_for_not_exist_primary_key.ConditionalExpression = expression;
m_transact_write_config_for_not_exist_primary_key.ReturnValuesOnConditionCheckFailure = Amazon.DynamoDBv2.DocumentModel.ReturnValuesOnConditionCheckFailure.AllOldAttributes;
}
}
public static TransactWriteItemOperationConfig getTransactWriteConfigForExistKey() => m_transact_write_config_for_exist_primary_key;
public static TransactWriteItemOperationConfig getTransactWriteConfigForNotExistKey() => m_transact_write_config_for_not_exist_primary_key;
public static GetItemOperationConfig getItemConfig() => m_get_item_config;
}

View File

@@ -0,0 +1,59 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using ServerCore;
namespace ServerBase;
public partial class DynamoDbClient
{
public class ConfigParam : IConfigParam
{
public string ServiceType { get; set; } = string.Empty;
public bool IsLocalDynamoDB { get; set; } = false;
public string UrlOfDynamoDb { get; set; } = string.Empty;
public string AccessKeyOfAws { get; set; } = string.Empty;
public string SecretKeyOfAws { get; set; } = string.Empty;
public string RegionOfAws { get; set; } = string.Empty;
public Result tryReadFromJsonOrDefault(JObject jObject)
{
var result = new Result();
var err_msg = string.Empty;
try
{
ServiceType = jObject["ServiceType"]?.Value<string>() ?? ServiceType;
UrlOfDynamoDb = jObject["Dynamodb"]?.Value<string>() ?? UrlOfDynamoDb;
var aws = jObject["AWS"];
NullReferenceCheckHelper.throwIfNull(aws, () => $"aws is null !!!");
IsLocalDynamoDB = aws["LocalDynamoDB"]?.Value<bool>() ?? IsLocalDynamoDB;
AccessKeyOfAws = aws["AccessKey"]?.Value<string>() ?? AccessKeyOfAws;
SecretKeyOfAws = aws["SecretKey"]?.Value<string>() ?? SecretKeyOfAws;
RegionOfAws = aws["Region"]?.Value<string>() ?? RegionOfAws;
return result;
}
catch (Exception e)
{
var error_code = ServerErrorCode.TryCatchException;
err_msg = $"Exception !!!, Failed to perform in tryReadFromJsonOrDefault() !!! : errorCode:{error_code}, exception:{e} - {this.getTypeName()}";
result.setFail(error_code, err_msg);
Log.getLogger().error(err_msg);
return result;
}
}
public string toBasicString()
{
return $"ConfigParam: ServiceType:{ServiceType}, UrlOfDynamoDb:{UrlOfDynamoDb}, isLocalDynamoDb:{IsLocalDynamoDB}, RegionOfAWS:{RegionOfAws}";
}
};
}

View File

@@ -0,0 +1,766 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.CompilerServices;
using System.Globalization;
using static System.Runtime.InteropServices.JavaScript.JSType;
using Amazon.DynamoDBv2;
using Amazon.DynamoDBv2.DocumentModel;
using Amazon.DynamoDBv2.Model;
using Amazon.Runtime;
using Microsoft.AspNetCore.Server.IIS.Core;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Google.Protobuf.WellKnownTypes;
using ServerCore; using ServerBase;
using DYNAMO_DB_TABLE_FULL_NAME = System.String;
using DYNAMO_DB_TABLE_NAME = System.String;
namespace ServerBase;
//=============================================================================================
// DynamoDb Document 기반 시간 설정
//
// - kangms
//=============================================================================================
public class DbTimestamp
{
public bool IsSet { get; set; } = false;
public DateTime ProcessedTime { get; set; } = DateTimeHelper.MinTime;
public void setProcessedTime(DateTime currentTime)
{
ProcessedTime = currentTime;
IsSet = true;
}
public void reset()
{
ProcessedTime = DateTimeHelper.MinTime;
IsSet = false;
}
}
//=============================================================================================
// DynamoDb Document 기반 Query Context
//
// - kangms
//=============================================================================================
public partial class DynamoDbDocumentQueryContext : IQueryContext
{
private readonly DYNAMO_DB_TABLE_FULL_NAME m_table_full_name;
private readonly Document m_document;
private QueryType m_query_type;
// TransactionCanceledException 관련 설정
private DynamoDbQueryExceptionNotifier.ExceptionHandler? m_exception_handler_nullable;
public DynamoDbDocumentQueryContext( DYNAMO_DB_TABLE_FULL_NAME tableFullName
, Document document, QueryType queryType )
{
m_table_full_name = tableFullName;
m_document = document;
m_query_type = queryType;
}
public DynamoDbDocumentQueryContext( DYNAMO_DB_TABLE_FULL_NAME tableFullName
, Document document, QueryType queryType
, DynamoDbQueryExceptionNotifier.ExceptionHandler? exceptionHandler )
{
m_table_full_name = tableFullName;
m_document = document;
m_query_type = queryType;
m_exception_handler_nullable = exceptionHandler;
}
public bool isValid()
{
if (true == m_table_full_name.isNullOrWhiteSpace())
{
Log.getLogger().error($"TableFullName is Null or WhiteSpace !!! - {toBasicString()}");
return false;
}
if (null == m_document)
{
Log.getLogger().error($"DynamoDbDocument is null !!! - {toBasicString()}");
return false;
}
if (QueryType.None == m_query_type)
{
Log.getLogger().error($"DynamoDbDocument QueryType invalid !!! - {toBasicString()}");
return false;
}
return true;
}
public DYNAMO_DB_TABLE_FULL_NAME getTableFullName()
{
ConditionValidCheckHelper.throwIfFalseWithCondition(() => false == m_table_full_name.isNullOrWhiteSpace(), () => $"Invalid TableFullName !!!, Null of WhiteSpace !!! - {m_document.toPKSK()}");
return m_table_full_name;
}
public string toBasicString()
{
return $"{this.getTypeName()}, TableFullName:{m_table_full_name}, {m_query_type}, {m_document?.toBasicString()}";
}
}
//=============================================================================================
// PK(Partition Key) : Primary Key 내의 Partition Key + 해당 정보의 Domain 식별키
// SK(Sort Key) : Primary Key 내의 Sort Key, 해당 정보의 고유 식별키
// DocType : DynamoDbDocBase 상속 객체명
// Attrib ... : AttribBase 상속 객체명
// CreatedDateTime : Item Document 생성 시간
// UpdateedDateTime : Item Document 마지막 업데이트 시간 (생성, 삭제, 복구시 동일 시간으로 반영)
// DeletedDateTime : Item Document 삭제 시간
// RestoredDateTime : Item Document 복구 시간
// - kangms
//=============================================================================================
public abstract partial class DynamoDbDocBase : IRowData
{
// 관계된 Table Name, 특화된 테이블명이 있다면 재정의해야 한다. - kangms
public virtual DYNAMO_DB_TABLE_NAME TableName
{
get {
return DynamoDbDefine.TableNames.Main;
}
}
public const string CreatedDateTime = "CreatedDateTime";
public const string UpdatedDateTime = "UpdatedDateTime";
public const string DeletedDateTime = "DeletedDateTime";
public const string RestoredDateTime = "RestoredDateTime";
private PrimaryKey m_key = new();
// DocType은 DynamoDb Document 정보를 Read시 식별키로 사용되기 때문에 고유하게 정의되어야 한다 !!! - kangms
[JsonProperty("DocType")]
private readonly string m_doc_type;
public const string ATTRIBUTE_PATH = "attribute_path";
private ConcurrentDictionary<System.Type, IAttribWrapper> m_attrib_wrappers = new();
private DbTimestamp m_created_datetime = new();
private DbTimestamp m_updated_datetime = new();
private DbTimestamp m_deleted_datetime = new();
private DbTimestamp m_restored_datetime = new();
private string m_combination_key_for_pk = string.Empty;
private string m_combination_key_for_sk = string.Empty;
private Result m_initialize_result = new();
private bool m_is_enable_ttl = false;
private Int64 m_ttl_seconds = default;
private QueryType m_query_type = QueryType.None;
private DynamoDbQueryExceptionNotifier.ExceptionHandler? m_exception_handler_nullable;
public DynamoDbDocBase(string docType)
{
m_doc_type = docType;
}
// 필독 !!!
// 최초 등록시 TTL 설정을 적용해야 한다.
// TTL 설정이 적용되면 TTL 시간이 다시 적용됨을 유의 !!! - kangms
public DynamoDbDocBase(string docType, Int64 ttlSeconds)
{
m_doc_type = docType;
enableTTL(ttlSeconds);
}
public void SetTtlSeconds(Int64 ttlSeconds)
{
m_ttl_seconds = ttlSeconds;
}
public bool isNotSetupQueryType()
{
if(QueryType.None == m_query_type)
{
return true;
}
return false;
}
protected bool appendAttribWrapper(IAttribWrapper wrapper)
{
var result = getInitializeResult();
if (false == m_attrib_wrappers.TryAdd(wrapper.getAttribType(), wrapper))
{
var error_code = ServerErrorCode.DynamoDbDocAttribTypeDuplicated;
var err_msg = $"Failed to TryAdd AttribWrapper !!!, duplicated AttribType : AttribType:{wrapper.getAttribType()} - {toBasicString()}";
result.setFail(error_code, err_msg);
Log.getLogger().error(err_msg);
return false;
}
return true;
}
public virtual async Task<Result> newDoc4Query()
{
var result = getInitializeResult();
if(result.isFail())
{
return result;
}
result = await onPrepareCRUD();
if (result.isFail())
{
return result;
}
var current_time = DateTimeHelper.Current;
setCreatedDateTime(current_time);
changeUpdatedDateTime(current_time);
m_query_type = QueryType.Insert;
return result;
}
public virtual async Task<Result> updateDoc4Query()
{
var result = getInitializeResult();
if (result.isFail())
{
return result;
}
result = await onPrepareCRUD();
if (result.isFail())
{
return result;
}
changeUpdatedDateTime(DateTimeHelper.Current);
m_query_type = QueryType.Update;
return result;
}
public virtual async Task<Result> upsertDoc4Query()
{
var result = getInitializeResult();
if (result.isFail())
{
return result;
}
result = await onPrepareCRUD();
if (result.isFail())
{
return result;
}
var current_time = DateTimeHelper.Current;
setCreatedDateTime(current_time);
changeUpdatedDateTime(current_time);
m_query_type = QueryType.Upsert;
return result;
}
public virtual async Task<Result> deleteDoc4Query()
{
var result = getInitializeResult();
if (result.isFail())
{
return result;
}
result = await onPrepareCRUD();
if (result.isFail())
{
return result;
}
setDeletedDateTime(DateTimeHelper.Current);
m_query_type = QueryType.Delete;
return result;
}
public virtual async Task<Result> onCopyFromDocument(Amazon.DynamoDBv2.DocumentModel.Document fromDocument)
{
return await Task.Run(() =>
{
var result = new Result();
var err_msg = string.Empty;
var doc_type = fromDocument.getDocType();
var pk = fromDocument.getPK();
var sk = fromDocument.getSK();
try
{
result = getInitializeResult();
if (result.isFail())
{
return result;
}
var error_code = fillUpPrimaryKey(pk, sk);
if (error_code.isFail())
{
err_msg = $"Failed to fillUpPrimaryKey() !!! : {error_code}";
result.setFail(error_code, err_msg);
return result;
}
var ttl_name = DynamoDbClient.TTL_NAME;
if (fromDocument.Contains(ttl_name))
{
fromDocument[ttl_name].AsString();
}
if (false == fillUpDocType(doc_type))
{
err_msg = $"Failed to fillUpDocType() !!! : docType:{doc_type} - PK:{pk}, SK:{sk}";
result.setFail(ServerErrorCode.DynamoDbDocTypeNotMatch, err_msg);
return result;
}
foreach (var each in m_attrib_wrappers)
{
var attrib_wrapper = each.Value;
NullReferenceCheckHelper.throwIfNull(attrib_wrapper, () => $"attrib_wrapper is null !!! - docType:{doc_type}, PK:{pk}, SK:{sk}");
if (false == attrib_wrapper.onCopyFromDynamoDbEntry(fromDocument))
{
err_msg = $"Failed to onCopyFromDynamoDbEntry() !!! : attribType:{attrib_wrapper.getAttribType()} - docType:{doc_type}, PK:{pk}, SK:{sk}";
result.setFail(ServerErrorCode.DynamoDbDocAttribWrapperCopyFailed, err_msg);
return result;
}
}
if (false == fromDocument.getTimestampByDB_TIMESTAMP(DynamoDbDocBase.CreatedDateTime, out var created_time))
{
err_msg = $"Failed to getTimestampByDB_TIMESTAMP() !!!, CreatedDateTime - docType:{doc_type}, PK:{pk}, SK:{sk}";
Log.getLogger().error(err_msg);
}
else
{
setCreatedDateTime(created_time);
}
if (false == fromDocument.getTimestampByDB_TIMESTAMP(DynamoDbDocBase.UpdatedDateTime, out var updated_time))
{
err_msg = $"Failed to getTimestampByDB_TIMESTAMP() !!!, UpdatedDateTime - docType:{doc_type}, PK:{pk}, SK:{sk}";
Log.getLogger().error(err_msg);
}
else
{
setUpdatedDateTime(updated_time);
}
if (false == fromDocument.getTimestampByDB_TIMESTAMP(DynamoDbDocBase.DeletedDateTime, out var deleteded_time))
{
err_msg = $"Failed to getTimestampByDB_TIMESTAMP() !!!, DeletedDateTime - docType:{doc_type}, PK:{pk}, SK:{sk}";
Log.getLogger().debug(err_msg);
}
else
{
setDeletedDateTime(deleteded_time);
}
if (false == fromDocument.getTimestampByDB_TIMESTAMP(DynamoDbDocBase.RestoredDateTime, out var restored_time))
{
err_msg = $"Failed to getTimestampByDB_TIMESTAMP() !!!, RestoredDateTime - docType:{doc_type}, PK:{pk}, SK:{sk}";
Log.getLogger().debug(err_msg);
}
else
{
setRestoredDateTime(restored_time);
}
}
catch (Exception e)
{
err_msg = $"Exception !!!, Failed to perform in DynamoDbDocBase.onCopyFromDocument() !!! : exception:{e} - docType:{doc_type}, PK:{pk}, SK:{sk}";
result.setFail(ServerErrorCode.DynamoDbDocCopyFailedFromDocument, err_msg);
Log.getLogger().fatal(result.toBasicString());
return result;
}
return result;
});
}
public virtual async Task<(Result, Amazon.DynamoDBv2.DocumentModel.Document)> onCopyToDocument()
{
return await Task.Run(() =>
{
var toDocument = new Amazon.DynamoDBv2.DocumentModel.Document();
var result = new Result();
var err_msg = string.Empty;
var doc_type = getDocType();
var pk = onMakePK();
var sk = onMakeSK();
try
{
result = getInitializeResult();
if (result.isFail())
{
return (result, toDocument);
}
var error_code = fillUpPrimaryKey(onMakePK(), onMakeSK());
if (error_code.isFail())
{
err_msg = $"Failed to fillUpPrimaryKey() !!! : {error_code}";
result.setFail(error_code, err_msg);
return (result, toDocument);
}
if (false == fillUpDocType(getDocType()))
{
return (result, toDocument);
}
toDocument[PrimaryKey.PK_Define] = getPK();
toDocument[PrimaryKey.SK_Define] = getSK();
if (isEnableTTL())
{
toDocument[DynamoDbClient.TTL_NAME] = getTTLSeconds();
}
toDocument["DocType"] = getDocType();
foreach (var each in m_attrib_wrappers)
{
each.Value.onCopyToDynamoDbEntry(ref toDocument);
}
if(true == getCreatedDateTime().IsSet)
{
toDocument[DynamoDbDocBase.CreatedDateTime] = getCreatedDateTime().ProcessedTime;
}
if (true == getUpdatedDateTime().IsSet)
{
toDocument[DynamoDbDocBase.UpdatedDateTime] = getUpdatedDateTime().ProcessedTime;
}
if (true == getDeletedDateTime().IsSet)
{
toDocument[DynamoDbDocBase.DeletedDateTime] = getDeletedDateTime().ProcessedTime;
}
if (true == getRestoredDateTime().IsSet)
{
toDocument[DynamoDbDocBase.RestoredDateTime] = getRestoredDateTime().ProcessedTime;
}
return (result, toDocument);
}
catch(Exception e)
{
err_msg = $"Exception !!!, Failed to perform in DynamoDbDocBase.onCopyToDocument() !!! : exception:{e} - docType:{doc_type}, PK:{pk}, SK:{sk}";
result.setFail(ServerErrorCode.DynamoDbDocCopyFailedToDocument, err_msg);
Log.getLogger().fatal(err_msg);
return (result, toDocument);
}
});
}
public void copyTimestampsFromOriginDocBase(DynamoDbDocBase originDocBase)
{
setCreatedDateTime(originDocBase.getCreatedDateTime().ProcessedTime);
setUpdatedDateTime(originDocBase.getUpdatedDateTime().ProcessedTime);
setDeletedDateTime(originDocBase.getDeletedDateTime().ProcessedTime);
setRestoredDateTime(originDocBase.getRestoredDateTime().ProcessedTime);
}
protected abstract string onGetPrefixOfPK();
protected virtual string onMakePK()
{
return $"{onGetPrefixOfPK()}{getCombinationKeyForPK()}";
}
protected virtual string onGetPrefixOfSK() { return ""; }
protected virtual string onMakeSK() { return getCombinationKeyForSK(); }
protected ServerErrorCode fillUpPrimaryKey(string partitionKey, string sortKey)
{
var err_msg = string.Empty;
var error_code = onCheckAndSetPK(partitionKey);
if (error_code.isFail())
{
return error_code;
}
if(0 < sortKey.Length)
{
if(true == sortKey.isEmptySK())
{
getPrimaryKey().setEmptySK();
}
else
{
error_code = onCheckAndSetSK(sortKey);
if (error_code.isFail())
{
err_msg = $"Failed to onCheckAndSetSK() !!!, param sortKey exist : sorkKey:{sortKey}, {error_code.toBasicString()} - {toBasicString()}";
Log.getLogger().fatal(err_msg);
return error_code;
}
}
}
return ServerErrorCode.Success;
}
protected virtual ServerErrorCode onCheckAndSetPK(string partitionKey)
{
var error_code = onFillupCombinationKeyForPK(partitionKey, out var combination_key_for_pk);
if (error_code.isFail())
{
return error_code;
}
getPrimaryKey().fillUpPK(partitionKey);
setCombinationKeyForPK(combination_key_for_pk);
return ServerErrorCode.Success;
}
public virtual ServerErrorCode onFillupCombinationKeyForPK(string partitionKey, out string fillupKey)
{
fillupKey = string.Empty;
var prefix_of_pk = onGetPrefixOfPK();
if (true == prefix_of_pk.isNullOrWhiteSpace())
{
fillupKey = partitionKey;
}
else
{
var combination_key_for_pk = partitionKey.Replace(onGetPrefixOfPK(), "");
if (0 >= combination_key_for_pk.Length)
{
var err_msg = $"PK combination 0 is length !!! : PrimaryKey:{partitionKey} - {toBasicString()}";
Log.getLogger().fatal(err_msg);
return ServerErrorCode.DynamoDbDocPkInvalid;
}
fillupKey = combination_key_for_pk;
}
return ServerErrorCode.Success;
}
protected virtual ServerErrorCode onCheckAndSetSK(string sortKey)
{
return ServerErrorCode.FunctionNotImplemented;
}
public virtual ServerErrorCode onFillupCombinationKeyForSK(string sortKey, out string fillupKey)
{
fillupKey = string.Empty;
var prefix_of_sk = onGetPrefixOfSK();
if(true == prefix_of_sk.isNullOrWhiteSpace())
{
fillupKey = sortKey;
}
else
{
var combination_key_for_sk = sortKey.Replace(prefix_of_sk, "");
if (0 >= combination_key_for_sk.Length)
{
var err_msg = $"SK combination 0 is length !!! : SortKey:{sortKey} - {toBasicString()}";
Log.getLogger().fatal(err_msg);
return ServerErrorCode.DynamoDbDocSkInvalid;
}
fillupKey = combination_key_for_sk;
}
return ServerErrorCode.Success;
}
public virtual ServerErrorCode onApplyPKSK()
{
return fillUpPrimaryKey(onMakePK(), onMakeSK());
}
protected Int64 getTTLSeconds() => m_ttl_seconds;
protected virtual Task<Result> onPrepareCRUD()
{
return Task.FromResult(new Result());
}
private bool fillUpDocType(string docType)
{
var err_msg = string.Empty;
if ( false == m_doc_type.Equals(docType, StringComparison.OrdinalIgnoreCase) )
{
err_msg = $"DocType Not matched !!! : currDocType:{m_doc_type} == reqDocType:{docType} - {toBasicString()}";
Log.getLogger().fatal(err_msg);
return false;
}
return true;
}
public TAttrib? getAttrib<TAttrib>()
where TAttrib : AttribBase
{
var err_msg = string.Empty;
var to_find_attrib_type = typeof(TAttrib);
if (false == m_attrib_wrappers.TryGetValue(to_find_attrib_type, out var lookup_attrib))
{
err_msg = $"Not found AttribType !!! : toFindAttribType:{to_find_attrib_type.Name} - {toBasicString()}";
Log.getLogger().fatal(err_msg);
return null;
}
var found_attrib = lookup_attrib.getAttribBase() as TAttrib;
if (null == found_attrib)
{
err_msg = $"Failed to cast AttribType : toCastAttribType:{to_find_attrib_type.Name} == lookupAttribType:{lookup_attrib.getAttribType().Name} - {toBasicString()}";
Log.getLogger().fatal(err_msg);
return null;
}
return found_attrib;
}
protected void setCreatedDateTime(DateTime currentTime)
{
m_created_datetime.setProcessedTime(currentTime);
}
protected void setUpdatedDateTime(DateTime currentTime)
{
m_updated_datetime.setProcessedTime(currentTime);
}
protected void changeUpdatedDateTime(DateTime currentTime)
{
m_updated_datetime.setProcessedTime(currentTime);
}
protected void setDeletedDateTime(DateTime currentTime)
{
m_deleted_datetime.setProcessedTime(currentTime);
}
protected void setRestoredDateTime(DateTime currentTime)
{
m_restored_datetime.setProcessedTime(currentTime);
}
public virtual string toAttribAllString()
{
var attrib_string = string.Empty;
foreach(var attrib_wrapper in m_attrib_wrappers.Values)
{
attrib_string += attrib_wrapper.getAttribBase().toBasicString();
attrib_string += ", ";
}
var found_index = attrib_string.LastIndexOf(", ");
if(found_index != -1)
{
attrib_string.Remove(found_index);
}
return attrib_string;
}
protected bool isEnableTTL() => m_is_enable_ttl;
public void desableTTL()
{
m_ttl_seconds = 0;
m_is_enable_ttl = false;
}
public void enableTTL(long ttlSeconds)
{
m_ttl_seconds = ttlSeconds;
m_is_enable_ttl = true;
}
public string toDocTypePKSK()
{
return $"{this.getTypeName()}:{getPrimaryKey().toBasicString()}";
}
public string toJsonStringOfAttribs()
{
var json_object = new JObject();
foreach (var each in m_attrib_wrappers)
{
if(false == each.Value.onFillupJsonStringToJObject(ref json_object))
{
var err_msg = $"Failed to onFillupJsonStringToDynamoDbEntry() !!! : {each.Value.getAttribType().Name} - {toBasicString()}";
Log.getLogger().fatal(err_msg);
}
}
return JsonConvert.SerializeObject(json_object, Formatting.Indented);
}
public virtual string toBasicString()
{
return $"{this.getTypeName()}: tableName:{TableName}, {getPrimaryKey().toBasicString()}"
+ $", {getCreatedDateTime().ProcessedTime.toString("o")}"
+ $", {getUpdatedDateTime().ProcessedTime.toString("o")}"
+ $", {getDeletedDateTime().ProcessedTime.toString("o")}"
+ $", {getRestoredDateTime().ProcessedTime.toString("o")}";
}
}

View File

@@ -0,0 +1,66 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Amazon.DynamoDBv2;
using ServerCore; using ServerBase;
namespace ServerBase;
//=============================================================================================
// DynamoDb ItemRequest 기반 Query Context
//
// - kangms
//=============================================================================================
public partial class DynamoDbItemRequestQueryContext : IQueryContext
{
private readonly AmazonDynamoDBRequest m_db_request;
private QueryType m_query_type;
// TransactionCanceledException 관련 설정
private DynamoDbQueryExceptionNotifier.ExceptionHandler? m_exception_handler_nullable;
public DynamoDbItemRequestQueryContext( AmazonDynamoDBRequest dbRequest, QueryType queryType )
{
m_db_request = dbRequest;
m_query_type = queryType;
}
public DynamoDbItemRequestQueryContext( AmazonDynamoDBRequest dbRequest, QueryType queryType
, DynamoDbQueryExceptionNotifier.ExceptionHandler? exceptionHandler )
{
m_db_request = dbRequest;
m_query_type = queryType;
m_exception_handler_nullable = exceptionHandler;
}
public bool isValid()
{
if (null == m_db_request)
{
Log.getLogger().error($"DynamoDbDocument is null !!! - {toBasicString()}");
return false;
}
if (QueryType.None == m_query_type)
{
Log.getLogger().error($"DynamoDbDocument QueryType invalid !!! - {toBasicString()}");
return false;
}
return true;
}
public string toBasicString()
{
return $"{this.getTypeName()}, {m_query_type}, {m_db_request?.toBasicString()}";
}
}

View File

@@ -0,0 +1,77 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ServerCore; using ServerBase;
namespace ServerBase;
public class DynamoDbQueryExceptionNotifier
{
public const string Success = "None"; // 쿼리 성공
public const string ConditionalCheckFailed = "ConditionalCheckFailed"; // 쿼리 실패 : 조건들이 충족하지 못해 실패 했다.
public const string ItemCollectionSizeLimitExceeded = "ItemCollectionSizeLimitExceeded"; // 쿼리 실패 : 아이템 제한 개수 초과로 실패 했다.
public class ExceptionHandler
{
public ExceptionHandler(string reasonCode, ServerErrorCode returnToErrorCode)
{
ReasonCode = reasonCode;
ReturnToErrorCode = returnToErrorCode;
}
public bool hasReasonCode(string reasonCode)
{
return ReasonCode.Equals(reasonCode, StringComparison.OrdinalIgnoreCase);
}
public string ReasonCode { get; set; }
public ServerErrorCode ReturnToErrorCode { get; set; }
}
private Int32 m_query_seq_no = 0;
private readonly Dictionary<Int32, ExceptionHandler> m_exception_handlers = new();
public DynamoDbQueryExceptionNotifier()
{
}
public bool registerExceptionHandler(ExceptionHandler? exceptionHandler)
{
m_query_seq_no++;
if(null == exceptionHandler)
{
return false;
}
if(true == m_exception_handlers.ContainsKey(m_query_seq_no))
{
return false;
}
m_exception_handlers[m_query_seq_no] = exceptionHandler;
return true;
}
public ExceptionHandler? findExceptionHandler(Int32 querySeqNo, string reasonCode)
{
if(false == m_exception_handlers.TryGetValue(querySeqNo, out var exception_handler))
{
return null;
}
if(false == exception_handler.hasReasonCode(reasonCode))
{
return null;
}
return exception_handler;
}
}

View File

@@ -0,0 +1,61 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using ServerCore;
namespace ServerBase;
public partial class MongoDbConnector : MongoDbConnectorBase, IModule, IInitializer
{
public class ConfigParam : IConfigParam
{
public string ServiceType { get; set; } = string.Empty;
public string DatabaseName { get; set; } = string.Empty;
public string ConnectionString { get; set; } = string.Empty;
public int MinConnectionPoolSize { get; set; } = 0;
public int MaxConnectionPoolSize { get; set; } = 100;
public int WaitQueueTimeoutSecs { get; set; } = 120;
public Result tryReadFromJsonOrDefault(JObject jObject)
{
var result = new Result();
var err_msg = string.Empty;
try
{
ServiceType = jObject["ServiceType"]?.Value<string>() ?? ServiceType;
var mongo_db = jObject["MongoDb"];
NullReferenceCheckHelper.throwIfNull(mongo_db, () => $"mongo_db is null !!!");
ConnectionString = mongo_db["ConnectionString"]?.Value<string>() ?? ConnectionString;
DatabaseName = mongo_db["DatabaseName"]?.Value<string>() ?? DatabaseName;
MinConnectionPoolSize = mongo_db["MinConnectionPoolSize"]?.Value<int>() ?? MinConnectionPoolSize;
MaxConnectionPoolSize = mongo_db["MaxConnectionPoolSize"]?.Value<int>() ?? MaxConnectionPoolSize;
WaitQueueTimeoutSecs = mongo_db["WaitQueueTimeoutSecs"]?.Value<int>() ?? WaitQueueTimeoutSecs;
return result;
}
catch (Exception e)
{
var error_code = ServerErrorCode.TryCatchException;
err_msg = $"Exception !!!, Failed to perform in tryReadFromJsonOrDefault() !!! : errorCode:{error_code}, exception:{e} - {this.getTypeName()}";
result.setFail(error_code, err_msg);
Log.getLogger().error(err_msg);
return result;
}
}
public string toBasicString()
{
return $"ConfigParam: ServiceType:{ServiceType}, ConnectionStringOfMongoDb:{ConnectionString}, MinConnectionPoolSize:{MinConnectionPoolSize}, MaxConnectionPoolSize:{MaxConnectionPoolSize}, WaitQueueTimeoutSecs:{WaitQueueTimeoutSecs}";
}
};
}

View File

@@ -0,0 +1,62 @@
using MongoDB.Driver;
using ServerCore;
namespace ServerBase;
public partial class MongoDbConnector : MongoDbConnectorBase, IModule, IInitializer
{
private readonly ModuleContext m_module_context;
public MongoDbConnector(ModuleContext moduleContext)
: base()
{
m_module_context = moduleContext;
}
public async Task<Result> onInit()
{
var err_msg = string.Empty;
var result = new Result();
var module_context = getModuleContext();
var config_param = module_context.getConfigParam() as ConfigParam;
NullReferenceCheckHelper.throwIfNull(config_param, () => $"config_param is null !!!");
(var is_success, var db_list) = await initAndVerifyDb( config_param.ConnectionString
, config_param.MinConnectionPoolSize
, config_param.MaxConnectionPoolSize
, config_param.WaitQueueTimeoutSecs );
if(false == is_success)
{
err_msg = $"Failed to initAndVerifyDb() !!! : {config_param.toBasicString()}";
result.setFail(ServerErrorCode.MongoDbInitAndVefifyDbFailed, err_msg);
Log.getLogger().error(result.toBasicString());
return result;
}
return result;
}
public async Task<Result> startModule()
{
var result = new Result();
return await Task.FromResult(result);
}
public async Task<Result> stopModule()
{
var result = new Result();
return await Task.FromResult(result);
}
public ModuleContext getModuleContext() => m_module_context;
public string toBasicString()
{
return $"{m_module_context.toBasicString()} - {this.getTypeName()}";
}
}

View File

@@ -0,0 +1,213 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Transactions;
using MySqlConnector;
using ServerCore; using ServerBase;
namespace ServerBase;
public partial class MySqlDbConnector : IDisposable
{
// MySql 접속 재시도 간격 시간 밀리초)
private Int32 MYSQL_CONNECT_RETRY_INTERVAL_MSEC = 3000;
// MySql 접속 재시도 간격 활성화 조건 실패 횟수
private Int32 MYSQL_CONNECT_RETRY_INTERVAL_ENABLE_CONDITION_FAILED_COUNT = 3;
// private Int16 m_curr_retry_connect_count = 0;
private string m_connection_string = string.Empty;
private MySqlConnection? m_connection;
private ConnectionState m_last_connection_state = ConnectionState.Closed;
private delegate int MyDelegate(int a, int b);
public MySqlDbConnector()
{
}
public async Task<ServerErrorCode> initMySql(string connectionString)
{
var err_msg = string.Empty;
var error_code = createConnection(connectionString);
if (error_code.isFail())
{
return error_code;
}
error_code = await openMySql();
if (error_code.isFail())
{
Log.getLogger().error($"Failed to initMySql() !!! : errorCode:{error_code} - connectString:{m_connection_string}");
return error_code;
}
return ServerErrorCode.Success;
}
public ServerErrorCode createConnection(string connectionString)
{
try
{
if(null != m_connection)
{
Dispose();
}
m_connection_string = connectionString;
m_connection = new MySqlConnection(connectionString);
// 연결 상태 변경에 대한 이벤트를 받기 위해 등록 한다. - kangms
m_connection.StateChange += new StateChangeEventHandler(onStateChange);
}
catch (Exception e)
{
Log.getLogger().error($"Exception !!!, new MySqlConnection() !!! : message:{e}, connectString:{m_connection_string}");
return ServerErrorCode.MySqlConnectionCreateFailed;
}
return ServerErrorCode.Success;
}
public async Task<ServerErrorCode> openMySql()
{
if (m_connection == null)
{
return ServerErrorCode.MySqlConnectionCreateFailed;
}
try
{
await m_connection.OpenAsync();
}
catch (Exception e)
{
Log.getLogger().error($"Exception to open MySqlConnection !!! : errDesc:{e.Message} - connectString:{m_connection_string}");
return ServerErrorCode.MySqlConnectionOpenFailed;
}
return ServerErrorCode.Success;
}
public void Dispose()
{
// 객체 소멸시점 MySqlConnection 이 ConnectionPool 에 반환될 수 있도록
// MySqlConnection.Dispose() 를 반드시 호출해 주어야 한다. - kangms
if (m_connection != null)
{
m_connection.Dispose();
m_connection = null;
}
}
public void disposeMySql()
{
Dispose();
}
//=========================================================================================
// MySqlDbConnector.onStateChange() 함수가 호출되는 시점은
// 1. Self Disconnect : MySqlConnection.Dispose() 이 호출되는 시점 (Using 으로 생성후 Using 범위를 벗어나는 순간, 즉 GC 호출 시점)
// 2. Other Disconnect : 실제로 MySql 서버와 끊어진 경우
// 3. 기타 : 비정상적인 상황
//=========================================================================================
private void onStateChange(object sender, StateChangeEventArgs e)
{
if (m_connection == null)
{
Log.getLogger().error($"Failed to onStateChange(), Connection is null or CurrentState is null !!! - connectString:{m_connection_string}");
return;
}
switch (e.CurrentState)
{
case ConnectionState.Closed:
Log.getLogger().debug($"Closed MySqlConnector !!! - connectString:{m_connection_string}");
break;
case ConnectionState.Open:
Log.getLogger().debug($"Opened MySqlConnector !!! - connectString:{m_connection_string}");
break;
}
m_last_connection_state = e.CurrentState;
}
public async Task<ServerErrorCode> retryOpenMySql()
{
Log.getLogger().info($"Retry db connect start !!! - connectString:{m_connection_string}");
var error_code = ServerErrorCode.Success;
for (var i = 0; i < MYSQL_CONNECT_RETRY_INTERVAL_ENABLE_CONDITION_FAILED_COUNT; i++)
{
await Task.Delay(MYSQL_CONNECT_RETRY_INTERVAL_MSEC);
error_code = createConnection(m_connection_string);
if (error_code.isFail())
{
Log.getLogger().error($"Failed to createConnection() when retryOpenMySql() !!! : errorCode:{error_code}, retryCount:{i} - connectString:{m_connection_string}");
continue;
}
error_code = await openMySql();
if (error_code.isFail())
{
Log.getLogger().error($"Failed to openMySql() when retryOpenMySql() !!! : errorCode:{error_code}, retryCount:{i} - connectString:{m_connection_string}");
}
else
{
Log.getLogger().info($"Success Retry db connect !!! : retryCount:{i} - connectString:{m_connection_string}");
return ServerErrorCode.Success;
}
}
return error_code;
}
public async Task<ServerErrorCode> querySQL(string queryString, Func<MySqlDataReader, ServerErrorCode> dataReader)
{
var error_code = ServerErrorCode.Success;
try
{
using (var cmd = new MySqlCommand(queryString, m_connection))
using (var reader = await cmd.ExecuteReaderAsync())
{
if (true == await reader.ReadAsync())
{
if (null != dataReader)
{
error_code = dataReader.Invoke(reader);
if (error_code.isFail())
{
Log.getLogger().error($"Failed to dataReader.Invoke() when querySQL() !!! : errorCode:{error_code}, queryString:{queryString} - connectString:{m_connection_string}");
return error_code;
}
}
}
}
}
catch(Exception e)
{
error_code = ServerErrorCode.MySqlDbQueryException;
var err_msg = $"Exception !!!, Failed to query for Read when querySQL() !!! : errorCode:{error_code}, exception:{e}, queryString:{queryString} - connectString:{m_connection_string}";
Log.getLogger().fatal(err_msg);
return error_code;
}
return error_code;
}
}

610
ServerBase/DB/QueryBatch.cs Normal file
View File

@@ -0,0 +1,610 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
using NLog;
using Amazon.Runtime;
using Amazon.DynamoDBv2;
using Amazon.DynamoDBv2.Model;
using Amazon.DynamoDBv2.DataModel;
using Amazon.DynamoDBv2.DocumentModel;
using Amazon;
using ServerCore; using ServerBase;
using LOG_ACTION_TYPE = System.String;
namespace ServerBase;
//==============================================================================================
// 배치 쿼리를 전담 처리해 주는 추상 클래스 이다.
//==============================================================================================
public abstract partial class QueryBatchBase
{
public enum QueryResultType
{
Success = 0, // 쿼리를 성공 했다.
NotCalledQueryFunc = 1, // 쿼리 함수가 호출되지 않았다.
CalledQueryFunc = 2, // 쿼리 함수를 호출 했다.
QueryFailed = 3, // 쿼리를 실패 했다.
}
private DynamoDbClient m_dynamo_db_connector;
private IQueryRunner m_query_runner;
private bool m_is_use_transact = false;
private readonly string m_trans_id = string.Empty;
private UInt16 m_retry_delay_msec = 0;
private UInt16 m_retry_count = 0;
private readonly List<QueryExecutorBase> m_querys = new();
private IWithLogActor m_log_actor;
private readonly LogAction m_log_action;
private readonly List<ILogInvoker> m_business_logs = new();
public QueryBatchBase( IWithLogActor logActor, LOG_ACTION_TYPE logActionType
, DynamoDbClient dbConnector
, IQueryRunner queryRunner
, bool isUseTransact = true, string transId = ""
, UInt16 retryCount = 0, UInt16 retryDelayMSec = 0)
{
m_log_actor = logActor;
m_dynamo_db_connector = dbConnector;
m_query_runner = queryRunner;
m_is_use_transact = isUseTransact;
m_trans_id = string.IsNullOrEmpty(transId) ? Guid.NewGuid().ToString("N") : transId;
m_log_action = new LogAction(logActionType);
m_retry_count = retryCount;
m_retry_delay_msec = retryDelayMSec;
}
public QueryBatchBase( IWithLogActor logActor, LogAction logAction
, DynamoDbClient dbConnector
, IQueryRunner queryRunner
, bool isUseTransact = true, string transId = ""
, UInt16 retryCount = 0, UInt16 retryDelayMSec = 0)
{
m_log_actor = logActor;
m_dynamo_db_connector = dbConnector;
m_query_runner = queryRunner;
m_is_use_transact = isUseTransact;
m_trans_id = string.IsNullOrEmpty(transId) ? Guid.NewGuid().ToString("N") : transId;
m_log_action = logAction;
m_retry_count = retryCount;
m_retry_delay_msec = retryDelayMSec;
}
public bool addQuery( QueryExecutorBase queryContext
, QueryExecutorBase.FnCommit? fnCommit = null
, QueryExecutorBase.FnRollback? fnRollback = null )
{
queryContext.bindCallback(fnCommit, fnRollback);
queryContext.setQueryBatch(this);
if(false == queryContext.onAddQueryRequests())
{
return false;
}
m_querys.Add(queryContext);
return true;
}
//=========================================================================================
// DB 쿼리 주요 실행 함수들...
//=========================================================================================
public async Task<Result> prepareQueryWithStopwatch()
{
Stopwatch? stopwatch = null;
var event_tid = string.Empty;
var server_logic = ServerLogicApp.getServerLogicApp();
ArgumentNullException.ThrowIfNull(server_logic);
var server_config = server_logic.getServerConfig();
ArgumentNullException.ThrowIfNull(server_config);
if (true == server_config.PerformanceCheckEnable)
{
event_tid = string.IsNullOrEmpty(getTransId()) ? System.Guid.NewGuid().ToString("N") : getTransId();
stopwatch = Stopwatch.StartNew();
}
var result = await prepareQuery();
if (null != stopwatch)
{
var elapsed_msec = stopwatch.ElapsedMilliseconds;
stopwatch.Stop();
if (elapsed_msec > Constant.STOPWATCH_LOG_LIMIT_MSEC)
{
Log.getLogger().debug($"QueryBatch - prepareQuery Stopwatch Stop : ETID:{event_tid}, ElapsedMSec:{elapsed_msec}");
}
}
return result;
}
private async Task<Result> prepareQuery()
{
var result = new Result();
foreach(var query in m_querys)
{
result = await query.onPrepareQuery();
if (result.isFail())
{
return result;
}
}
return result;
}
public async Task<Result> doQueryWithStopwatch()
{
Stopwatch? stopwatch = null;
var event_tid = string.Empty;
var server_logic = ServerLogicApp.getServerLogicApp();
ArgumentNullException.ThrowIfNull(server_logic);
var server_config = server_logic.getServerConfig();
ArgumentNullException.ThrowIfNull(server_config);
if (true == server_config.PerformanceCheckEnable)
{
event_tid = string.IsNullOrEmpty(getTransId()) ? System.Guid.NewGuid().ToString("N") : getTransId();
stopwatch = Stopwatch.StartNew();
}
var result = await doQuery();
if (null != stopwatch)
{
var elapsed_msec = stopwatch.ElapsedMilliseconds;
stopwatch.Stop();
if (elapsed_msec > Constant.STOPWATCH_LOG_LIMIT_MSEC)
{
Log.getLogger().debug($"QueryBatch - doQuery Stopwatch Stop : ETID:{event_tid}, ElapsedMSec:{elapsed_msec}");
}
}
return result;
}
private async Task<Result> doQuery()
{
var result = new Result();
var err_msg = string.Empty;
try
{
foreach (var query in m_querys)
{
result = await query.onQuery();
if (result.isFail())
{
Log.getLogger().error(result.toBasicString());
break;
}
}
}
catch(System.Exception e)
{
err_msg = $"Exception !!!, doQuery(), Exception:{e} - {toBasicString()}";
result.setFail(ServerErrorCode.DynamoDbQueryException, err_msg);
Log.getLogger().error(result.toBasicString());
}
finally
{
if (result.isSuccess())
{
result = await commitQueryAllWithStopWatch();
}
else
{
await rollbackQueryAll(result);
}
onSendMsgByHandler(result);
}
return result;
}
//=====================================================================================
// 1. 등록된 쿼리들을 등록 순서대로 처리해 준다.
// 2. Non-Transaction 쿼리에 오류가 발생할 경우 이후 처리할 쿼리는 모두 실행하지 않는다.
// 3. TransactionGetItem 쿼리에 오류가 발생할 경우 TransactionGetItemRequest에
// 등록된 모든 쿼리는 처리되지 않는다.
// 4. TransactionWriteItem 쿼리에 오류가 발생할 경우 TransactionWriteItemRequest에
// 등록된 모든 쿼리는 처리되지 않는다.
//=====================================================================================
private async Task<Result> commitQueryAllWithStopWatch()
{
Stopwatch? stopwatch = null;
var event_tid = string.Empty;
var server_logic = ServerLogicApp.getServerLogicApp();
ArgumentNullException.ThrowIfNull(server_logic);
var server_config = server_logic.getServerConfig();
ArgumentNullException.ThrowIfNull(server_config);
if (true == server_config.PerformanceCheckEnable)
{
event_tid = string.IsNullOrEmpty(getTransId()) ? System.Guid.NewGuid().ToString("N") : getTransId();
stopwatch = Stopwatch.StartNew();
}
var result = await commitQueryAll();
if (null != stopwatch)
{
var elapsed_msec = stopwatch.ElapsedMilliseconds;
stopwatch.Stop();
if (elapsed_msec > 100)
{
Log.getLogger().debug($"QueryBatch - commitQueryAll Stopwatch Stop : ETID:{event_tid}, ElapsedMSec:{elapsed_msec}");
}
}
return result;
}
private async Task<Result> commitQueryAll()
{
var result = new Result();
var try_count = 0;
while(true)
{
(result, bool is_retry_query) = await commitQueryRunner();
if (true == is_retry_query)
{
try_count++;
if (m_retry_count <= try_count)
{
break;
}
await Task.Delay(m_retry_delay_msec);
}
else
{
break;
}
}
if (result.isFail())
{
await rollbackQueryAll(result);
}
else
{
foreach (var query in m_querys)
{
if (QueryBatchBase.QueryResultType.NotCalledQueryFunc == await query.doFnCommit())
{
await query.onQueryResponseCommit();
}
}
}
return result;
}
private async Task<(Result, bool)> commitQueryRunner()
{
var result = new Result();
var err_msg = string.Empty;
try
{
result = await onCommitQueryRunner();
}
catch (TransactionCanceledException e)
{
var error_code = ServerErrorCode.DynamoDbTransactionCanceledException;
err_msg = $"Failed to commitQueryRunner() !!!, Transaction Canceled, implies a client issue, fix before retrying !!! : errorCode:{error_code}, {e.toExceptionString()} - {toBasicString()}";
result.setFail(error_code, err_msg);
Log.getLogger().error(result.toBasicString());
(var dispatch_result, var exception_custom_result) = await onDispatchTransactionCancelException(e);
if (dispatch_result.isFail())
{
err_msg = $"Failed to onDispatchTransactionCancelException() !!! : {dispatch_result.toBasicString()}, {e.toExceptionString()} - {toBasicString()}";
Log.getLogger().error(err_msg);
}
else
{
// 재정의된 오류 코드가 설정된 경우
if (exception_custom_result.isFail())
{
// 재정의한 예외 결과를 dispatch 했다면 오류 로그로 설정해 준다. !!!
err_msg = $"QueryBatch.onDispatchTransactionCancelException() !!! : exceptionResult:{exception_custom_result.getResultString()} - {toBasicString()}";
result.setFail(exception_custom_result.getErrorCode(), exception_custom_result.getResultString());
Log.getLogger().error(result.toBasicString());
}
}
}
catch (TransactionConflictException e)
{
var error_code = ServerErrorCode.DynamoDbTransactionConflictException;
err_msg = $"TransactionConflictException !!!, Failed to perform in commitQueryRunner() !!!, Transaction conflict occurred !!! : errorCode:{error_code}, {e.toExceptionString()} - {toBasicString()}";
result.setFail(error_code, err_msg);
Log.getLogger().error(result.toBasicString());
}
catch (AmazonDynamoDBException e)
{
var error_code = ServerErrorCode.DynamoDbAmazonDynamoDbException;
err_msg = $"AmazonDynamoDBException !!!, Failed to perform in commitQueryRunner() !!! : errorCode:{error_code}, {e.toExceptionString()} - {toBasicString()}";
result.setFail(error_code, err_msg);
Log.getLogger().error(err_msg);
}
catch (AmazonServiceException e)
{
var error_code = ServerErrorCode.DynamoDbAmazonServiceException;
err_msg = $"AmazonServiceException !!!, Failed to perform in commitQueryRunner() !!! : errorCode:{error_code}, {e.toExceptionString()} - {toBasicString()}";
result.setFail(error_code, err_msg);
Log.getLogger().error(result.toBasicString());
return (result, true);
}
catch (System.Exception e)
{
var error_code = ServerErrorCode.TryCatchException;
err_msg = $"Exception !!!, Failed to perform in commitQueryRunner() !!! : errorCode:{error_code}, exception:{e} - {toBasicString()}";
result.setFail(error_code, err_msg);
Log.getLogger().error(result.toBasicString());
}
return (result, false);
}
private async Task rollbackQueryAll(Result errorResult)
{
await onRollbackQueryRunnner();
foreach (var query in m_querys)
{
if (QueryBatchBase.QueryResultType.NotCalledQueryFunc == await query.doFnRollback(errorResult))
{
await query.onQueryResponseRollback(errorResult);
}
}
}
protected abstract Task<Result> onCommitQueryRunner();
protected abstract Task onRollbackQueryRunnner();
public virtual void onSendMsgByHandler(Result errorResult)
{
return;
}
//==============================================================================================
// 배치쿼리중에서 특정 쿼리를 찾는다.
//==============================================================================================
public TQuery? getQuery<TQuery>()
where TQuery : QueryExecutorBase
{
return m_querys.Find(x => x is TQuery == true) as TQuery;
}
public List<TQuery> getQuerys<TQuery>()
where TQuery : QueryExecutorBase
{
var querys = new List<TQuery>();
foreach (var each in m_querys)
{
if (each is TQuery query)
{
querys.Add(query);
}
}
return querys;
}
public void appendBusinessLogs(List<ILogInvoker> logs)
{
foreach(var log in logs)
{
m_business_logs.Add(log);
}
}
public void appendBusinessLog(ILogInvoker log)
{
m_business_logs.Add(log);
}
private bool hasBusinessLog()
{
var log_actor = getLogActor();
if (null != log_actor && 0 < m_business_logs.Count)
{
return true;
}
return false;
}
public void sendBusinessLog()
{
if (false == hasBusinessLog())
{
return;
}
BusinessLogger.collectLogs(m_log_action, m_log_actor, m_business_logs);
}
public string toBasicString()
{
return $"{this.getTypeName()}, LogActionType:{m_log_action.getLogActionType()}, TransId:{m_trans_id}";
}
protected abstract Task<(Result, Result)> onDispatchTransactionCancelException(TransactionCanceledException exception);
}//QueryBatchBase
//==============================================================================================
// 배치 쿼리를 전담 처리해 주는 제네릭 클래스 이다.
//==============================================================================================
public partial class QueryBatch<TQueryRunner> : QueryBatchBase
where TQueryRunner : IQueryRunner, new()
{
public QueryBatch( IWithLogActor logActor
, LOG_ACTION_TYPE logActionType
, DynamoDbClient dbClient
, bool isUseTransact = true, string transId = ""
, UInt16 retryCount = 3, UInt16 retryDelayMSec = 1000, string eventTid = "" )
: base( logActor, logActionType, dbClient, new TQueryRunner()
, isUseTransact, transId, retryCount, retryDelayMSec)
{
getQueryRunner().setQueryBatch(this);
}
public QueryBatch( IWithLogActor logActor
, LogAction logAction
, DynamoDbClient dbClient
, bool isUseTransact = true, string transId = ""
, UInt16 retryCount = 3, UInt16 retryDelayMSec = 1000, string eventTid = "" )
: base( logActor, logAction.getLogActionType(), dbClient, new TQueryRunner()
, isUseTransact, transId, retryCount, retryDelayMSec)
{
getQueryRunner().setQueryBatch(this);
}
protected override async Task<Result> onCommitQueryRunner()
{
var query_runner = getQueryRunner();
var result = await query_runner.onTryCommitQueryRunner();
if (result.isFail())
{
return result;
}
return await getQueryRunner().onCommitQueryRunner();
}
protected override async Task onRollbackQueryRunnner()
{
await getQueryRunner().onRollbackQueryRunner();
}
protected override async Task<(Result, Result)> onDispatchTransactionCancelException(TransactionCanceledException exception)
{
await Task.CompletedTask;
var log_actor = getLogActor();
ArgumentNullException.ThrowIfNull(log_actor, $"log_actor is null !!!");
ArgumentNullException.ThrowIfNull(exception, $"exception is null !!! - {log_actor.getTypeName()}");
return await getQueryRunner().onHandleTransactionCancelException(exception);
}
}//QueryBatch<TQueryRunner>
//==============================================================================================
// 배치 쿼리 + 모든 쿼리 처리후 TMsg 를 결과로 전송해 주는 제네릭 클래스 이다.
//==============================================================================================
public class QueryBatch<TQueryRunner, TMsg> : QueryBatch<TQueryRunner>
where TQueryRunner : IQueryRunner, new()
where TMsg : class, new()
{
protected readonly TMsg m_reserved_msg ; // 전송하고자 하는 패킷
protected readonly object m_sender; // 전송객체 : setError(ErrorCode error), sendPacketAsync(TMsg msg) 맴버함수가 있어야 한다. !!!
public QueryBatch( IWithLogActor logActor
, LOG_ACTION_TYPE logActionType
, DynamoDbClient dbClient
, object sender
, TMsg? msg = null
, bool isUseTransact = true )
: base(logActor, logActionType, dbClient, isUseTransact )
{
if (msg == null)
{
m_reserved_msg = new TMsg();
}
else
{
m_reserved_msg = msg;
}
m_sender = sender;
}
public QueryBatch( IWithLogActor logActor
, LogAction logAction
, DynamoDbClient dbClient
, object sender
, TMsg? msg = null
, bool isUseTransact = true )
: base(logActor, logAction.getLogActionType(), dbClient, isUseTransact )
{
if (msg == null)
{
m_reserved_msg = new TMsg();
}
else
{
m_reserved_msg = msg;
}
m_sender = sender;
}
public TMsg Msg
{
get { return m_reserved_msg; }
}
private void setErrorToMsg(Result errorResult)
{
}
public override void onSendMsgByHandler(Result errorResult)
{
if (m_reserved_msg != null && m_sender != null)
{
setErrorToMsg(errorResult);
}
}
}//QueryBatch<TQueryRunner, TMsg>

View File

@@ -0,0 +1,254 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using Amazon.DynamoDBv2;
using Amazon.DynamoDBv2.DocumentModel;
using Amazon.DynamoDBv2.Model;
namespace ServerBase;
//==============================================================================================
// DB 쿼리 종류별 재정의 클래스들 이다.
//==============================================================================================
public partial class DBRequestGetBatch : QueryDbRequesterWithItemRequestBase<BatchGetItemRequest>
{
public override async Task<Result> doDbRequestAsync()
{
var result = new Result();
var query_batch = getQueryBatch();
ArgumentNullException.ThrowIfNull(query_batch, $"query_batch is null !!!");
var query_runner = query_batch.getQueryRunner() as QueryRunnerWithItemRequest;
ArgumentNullException.ThrowIfNull(query_runner, $"query_runner is null !!!");
var db_client = query_batch.getDynamoDbConnector().getDbClient();
ArgumentNullException.ThrowIfNull(db_client, $"db_client is null !!!");
//=====================================================================================
// Batch 기반 DB Get Request 처리 : Non-Transaction
//=====================================================================================
var response = await db_client.BatchGetItemAsync(getDbRequestGenericType());
ArgumentNullException.ThrowIfNull(response, $"response is null !!!");
await response.logDetail();
query_runner.addDbResponse(response);
return result;
}
}
public partial class DBRequestWriteBatch : QueryDbRequesterWithItemRequestBase<BatchWriteItemRequest>
{
public override async Task<Result> doDbRequestAsync()
{
var result = new Result();
var query_batch = getQueryBatch();
ArgumentNullException.ThrowIfNull(query_batch, $"query_batch is null !!!");
var query_runner = query_batch.getQueryRunner() as QueryRunnerWithItemRequest;
ArgumentNullException.ThrowIfNull(query_runner, $"query_runner is null !!!");
var db_client = query_batch.getDynamoDbConnector().getDbClient();
ArgumentNullException.ThrowIfNull(db_client, $"db_client is null !!!");
//=====================================================================================
// Batch 기반 DB Write Request 처리 : Non-Transaction
//=====================================================================================
var response = await db_client.BatchWriteItemAsync(getDbRequestGenericType());
ArgumentNullException.ThrowIfNull(response, $"response is null !!!");
await response.logDetail();
query_runner.addDbResponse(response);
return result;
}
}
public partial class DBRequestUpdate : QueryDbRequesterWithItemRequestBase<UpdateItemRequest>
{
public override async Task<Result> doDbRequestAsync()
{
var result = new Result();
var query_batch = getQueryBatch();
ArgumentNullException.ThrowIfNull(query_batch, $"query_batch is null !!!");
var query_runner = query_batch.getQueryRunner() as QueryRunnerWithItemRequest;
ArgumentNullException.ThrowIfNull(query_runner, $"query_runner is null !!!");
var db_client = query_batch.getDynamoDbConnector().getDbClient();
ArgumentNullException.ThrowIfNull(db_client, $"db_client is null !!!");
//=====================================================================================
// Update 기반 DB Update Request 처리 : Non-Transaction
//=====================================================================================
var response = await db_client.UpdateItemAsync(getDbRequestGenericType());
ArgumentNullException.ThrowIfNull(response, $"response is null !!!");
await response.logDetail();
query_runner.addDbResponse(response);
return result;
}
}
public partial class DBRequestGetWithTransact : QueryDbRequesterWithItemRequestBase<TransactGetItemsRequest>
{
public override async Task<Result> doDbRequestAsync()
{
var result = new Result();
var query_batch = getQueryBatch();
ArgumentNullException.ThrowIfNull(query_batch, $"query_batch is null !!!");
var query_runner = query_batch.getQueryRunner() as QueryRunnerWithItemRequest;
ArgumentNullException.ThrowIfNull(query_runner, $"query_runner is null !!!");
var db_client = query_batch.getDynamoDbConnector().getDbClient();
ArgumentNullException.ThrowIfNull(db_client, $"db_client is null !!!");
//=====================================================================================
// Transaction 기반 DB Get Request 처리
//=====================================================================================
var response = await db_client.TransactGetItemsAsync(getDbRequestGenericType());
ArgumentNullException.ThrowIfNull(response, $"response is null !!!");
await response.logDetail(getDbRequestGenericType());
query_runner.addDbResponse(response);
return result;
}
}
public partial class DBRequestWriteWithTransact : QueryDbRequesterWithItemRequestBase<TransactWriteItemsRequest>
{
public override async Task<Result> doDbRequestAsync()
{
var result = new Result();
var query_batch = getQueryBatch();
ArgumentNullException.ThrowIfNull(query_batch, $"query_batch is null !!!");
var query_runner = query_batch.getQueryRunner() as QueryRunnerWithItemRequest;
ArgumentNullException.ThrowIfNull(query_runner, $"query_runner is null !!!");
var db_client = query_batch.getDynamoDbConnector().getDbClient();
ArgumentNullException.ThrowIfNull(db_client, $"db_client is null !!!");
//=====================================================================================
// Transaction 기반 DB Write Request 처리
//=====================================================================================
var response = await db_client.TransactWriteItemsAsync(getDbRequestGenericType());
ArgumentNullException.ThrowIfNull(response, $"response is null !!!");
await response.logDetail();
query_runner.addDbResponse(response);
return result;
}
}
//==============================================================================================
// DB 쿼리 Document 전용 재정의 클래스들 이다.
//==============================================================================================
public partial class DBRequestGetWithDocumentBatchGet : QueryDbRequesterWithDocumentBatchGetBase<DocumentBatchGet>
{
public override async Task<Result> doDbRequestAsync()
{
await Task.CompletedTask;
var result = new Result();
var query_batch = getQueryBatch();
ArgumentNullException.ThrowIfNull(query_batch, $"query_batch is null !!!");
var query_runner = query_batch.getQueryRunner() as QueryRunnerWithDocument;
ArgumentNullException.ThrowIfNull(query_runner, $"query_runner is null !!!");
var doc_batch_get = query_runner.getOrNewMultiTableDocumentBatchGet();
ArgumentNullException.ThrowIfNull(doc_batch_get, $"doc_batch_get is null !!!");
doc_batch_get.AddBatch(getDbRequestGenericType());
return result;
}
}
public partial class DBRequestWriteWithDocumentBatchWrite : QueryDbRequesterWithDocumentBatchWriteBase<DocumentBatchWrite>
{
public override async Task<Result> doDbRequestAsync()
{
await Task.CompletedTask;
var result = new Result();
var query_batch = getQueryBatch();
ArgumentNullException.ThrowIfNull(query_batch, $"query_batch is null !!!");
var query_runner = query_batch.getQueryRunner() as QueryRunnerWithDocument;
ArgumentNullException.ThrowIfNull(query_runner, $"query_runner is null !!!");
var doc_batch_write = query_runner.getOrNewMultiTableDocumentBatchWrite();
ArgumentNullException.ThrowIfNull(doc_batch_write, $"doc_batch_write is null !!!");
doc_batch_write.AddBatch(getDbRequestGenericType());
return result;
}
}
public partial class DBRequestGetWithDocumentTransactGet : QueryDbRequesterWithDocumentTransactGetBase<DocumentTransactGet>
{
public override async Task<Result> doDbRequestAsync()
{
await Task.CompletedTask;
var result = new Result();
var query_batch = getQueryBatch();
ArgumentNullException.ThrowIfNull(query_batch, $"query_batch is null !!!");
var query_runner = query_batch.getQueryRunner() as QueryRunnerWithDocument;
ArgumentNullException.ThrowIfNull(query_runner, $"query_runner is null !!!");
var doc_transact_get = query_runner.getOrNewMultiTableDocumentTransactGet();
ArgumentNullException.ThrowIfNull(doc_transact_get, $"doc_transact_get is null !!!");
doc_transact_get.AddTransactionPart(getDbRequestGenericType());
return result;
}
}
public partial class DBRequestWriteWithDocumentTransactWrite : QueryDbRequesterWithDocumentTransactWriteBase<DocumentTransactWrite>
{
public override async Task<Result> doDbRequestAsync()
{
await Task.CompletedTask;
var result = new Result();
var query_batch = getQueryBatch();
ArgumentNullException.ThrowIfNull(query_batch, $"query_batch is null !!!");
var query_runner = query_batch.getQueryRunner() as QueryRunnerWithDocument;
ArgumentNullException.ThrowIfNull(query_runner, $"query_runner is null !!!");
var doc_transact_write = query_runner.getOrNewMultiTableDocumentTransactWrite();
ArgumentNullException.ThrowIfNull(doc_transact_write, $"doc_transact_write is null !!!");
doc_transact_write.AddTransactionPart(getDbRequestGenericType());
return result;
}
}

View File

@@ -0,0 +1,133 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Amazon.DynamoDBv2;
using Amazon.DynamoDBv2.DocumentModel;
using ServerCore; using ServerBase;
namespace ServerBase;
//=============================================================================================
// DB 쿼리 ItemRequest 타입 구현 하기위한 추상 + 제네릭 클래스 이다.
//=============================================================================================
public abstract partial class QueryDbRequesterWithItemRequestBase<TDbRequestType> : IQueryDbRequester
where TDbRequestType : AmazonDynamoDBRequest, new()
{
private QueryBatchBase? m_query_batch;
private readonly TDbRequestType m_db_request;
private DynamoDbQueryExceptionNotifier m_exception_notifier = new();
public QueryDbRequesterWithItemRequestBase()
{
m_db_request = new TDbRequestType();
}
public abstract Task<Result> doDbRequestAsync();
public DynamoDbQueryExceptionNotifier getDynamoDbQueryExceptionNotifier() => m_exception_notifier;
}
//=============================================================================================
// DB 쿼리 DocumentBatchWrite 타입 구현 하기위한 추상 + 제네릭 클래스 이다.
//=============================================================================================
public abstract partial class QueryDbRequesterWithDocumentBatchWriteBase<TDbRequestType> : IQueryDbRequester, IWithDocumentModel
where TDbRequestType : DocumentBatchWrite
{
private QueryBatchBase? m_query_batch;
private TDbRequestType? m_db_request;
private Table? m_table;
private DynamoDbQueryExceptionNotifier m_exception_notifier = new();
public void onCreateDocumentModel(Table table)
{
m_table = table;
var db_request_type = table.CreateBatchWrite() as TDbRequestType;
NullReferenceCheckHelper.throwIfNull(db_request_type, () => $"db_request_type is null !!!");
m_db_request = db_request_type;
}
public abstract Task<Result> doDbRequestAsync();
public DynamoDbQueryExceptionNotifier getDynamoDbQueryExceptionNotifier() => m_exception_notifier;
}
//=============================================================================================
// DB 쿼리 DocumentBatchGet 타입 구현 하기위한 추상 + 제네릭 클래스 이다.
//=============================================================================================
public abstract partial class QueryDbRequesterWithDocumentBatchGetBase<TDbRequestType> : IQueryDbRequester, IWithDocumentModel
where TDbRequestType : DocumentBatchGet
{
private QueryBatchBase? m_query_batch;
private TDbRequestType? m_db_request;
private Table? m_table;
private DynamoDbQueryExceptionNotifier m_exception_notifier = new();
public void onCreateDocumentModel(Table table)
{
m_table = table;
m_db_request = table.CreateBatchGet() as TDbRequestType;
}
public abstract Task<Result> doDbRequestAsync();
public DynamoDbQueryExceptionNotifier getDynamoDbQueryExceptionNotifier() => m_exception_notifier;
}
//=============================================================================================
// DB 쿼리 DocumentTransactWrite 타입 구현 하기위한 추상 + 제네릭 클래스 이다.
//=============================================================================================
public abstract partial class QueryDbRequesterWithDocumentTransactWriteBase<TDbRequestType> : IQueryDbRequester, IWithDocumentModel
where TDbRequestType : DocumentTransactWrite
{
private QueryBatchBase? m_query_batch = null;
private TDbRequestType? m_db_request;
private Table? m_table;
private DynamoDbQueryExceptionNotifier m_exception_notifier = new();
public void onCreateDocumentModel(Table table)
{
m_table = table;
m_db_request = table.CreateTransactWrite() as TDbRequestType;
}
public abstract Task<Result> doDbRequestAsync();
public DynamoDbQueryExceptionNotifier getDynamoDbQueryExceptionNotifier() => m_exception_notifier;
}
//=============================================================================================
// DB 쿼리 DocumentTransactGet 타입 구현 하기위한 추상 + 제네릭 클래스 이다.
//=============================================================================================
public abstract partial class QueryDbRequesterWithDocumentTransactGetBase<TDbRequestType> : IQueryDbRequester, IWithDocumentModel
where TDbRequestType : DocumentTransactGet
{
private QueryBatchBase? m_query_batch = null;
private TDbRequestType? m_db_request;
private Table? m_table;
private DynamoDbQueryExceptionNotifier m_exception_notifier = new();
public void onCreateDocumentModel(Table table)
{
m_table = table;
m_db_request = table.CreateTransactGet() as TDbRequestType;
}
public abstract Task<Result> doDbRequestAsync();
public DynamoDbQueryExceptionNotifier getDynamoDbQueryExceptionNotifier() => m_exception_notifier;
}

View File

@@ -0,0 +1,189 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Amazon.DynamoDBv2;
using Amazon.DynamoDBv2.Model;
using ServerCore; using ServerBase;
namespace ServerBase;
//==============================================================================================
// 쿼리 내용을 작성하는 기반 클래스
//==============================================================================================
public abstract partial class QueryExecutorBase
{
private readonly string m_query_name;
public delegate Task<QueryBatchBase.QueryResultType> FnCommit(QueryExecutorBase query);
public delegate Task<QueryBatchBase.QueryResultType> FnRollback(QueryExecutorBase query, Result errorResult);
private FnCommit? m_fn_commit; // 쿼리 성공시 호출될 콜백함수
private FnRollback? m_fn_rollback; // 쿼리 실패시 호출될 콜백함수
private QueryBatchBase? m_parent; // 배치쿼리
public QueryExecutorBase(string queryName)
{
m_query_name = queryName;
}
public void bindCallback(FnCommit? fnCommit, FnRollback? fnRollback)
{
m_fn_commit = fnCommit;
m_fn_rollback = fnRollback;
}
//=====================================================================================
// DB 쿼리를 작성한다.
//=====================================================================================
public virtual bool onAddQueryRequests() => true;
//=====================================================================================
// DB 쿼리 직전에 준비해야 할 로직들을 작성한다.
//=====================================================================================
public abstract Task<Result> onPrepareQuery();
//=====================================================================================
// onPrepareQuery()를 성공할 경우 호출된다.
//=====================================================================================
public abstract Task<Result> onQuery();
//=====================================================================================
// DB 쿼리를 성공하고, doFnCommit()가 QueryResultType.NotCalledQueryFunc를 반환할 경우 호출된다.
//=====================================================================================
public abstract Task onQueryResponseCommit();
//=====================================================================================
// DB 쿼리를 실패하고, doFnRollback()가 QueryResultType.NotCalledQueryFunc를 반환할 경우 호출된다.
//=====================================================================================
public abstract Task onQueryResponseRollback(Result errorResult);
//=====================================================================================
// DB commit 성공후 호출된다.
//=====================================================================================
public async Task<QueryBatchBase.QueryResultType> doFnCommit()
{
if (null != m_fn_commit)
{
await m_fn_commit(this);
return QueryBatchBase.QueryResultType.CalledQueryFunc;
}
return QueryBatchBase.QueryResultType.NotCalledQueryFunc;
}
//=====================================================================================
// DB commit 실패후 호출된다.
//=====================================================================================
public async Task<QueryBatchBase.QueryResultType> doFnRollback(Result errorResult)
{
if (null != m_fn_rollback)
{
await m_fn_rollback(this, errorResult);
return QueryBatchBase.QueryResultType.CalledQueryFunc;
}
return QueryBatchBase.QueryResultType.NotCalledQueryFunc;
}
public void appendBusinessLog(ILogInvoker log)
{
if(null == m_parent)
{
var err_msg = $"Failed to QueryContextBase.appendBusinessLog() !!!, m_parent is null - {log.toBasicString()}, {toBasicString()}";
Log.getLogger().error(err_msg);
return;
}
m_parent?.appendBusinessLog(log);
}
public TQuery? getQuery<TQuery>()
where TQuery : QueryExecutorBase
{
if (this is TQuery query)
{
return query;
}
if (null == m_parent)
{
var err_msg = $"Failed to QueryContextBase.getQuery<{nameof(TQuery)}>() !!!, m_parent is null - {toBasicString()}";
Log.getLogger().error(err_msg);
return null;
}
return m_parent.getQuery<TQuery>();
}
public List<TQuery>? getQuerys<TQuery>()
where TQuery : QueryExecutorBase
{
if (null == m_parent)
{
var err_msg = $"Failed to QueryContextBase.getQuerys<{nameof(TQuery)}>() !!!, m_parent is null - {toBasicString()}";
Log.getLogger().error(err_msg);
return null;
}
return m_parent.getQuerys<TQuery>();
}
public virtual EntityBase getOwner()
{
var query_batch = getQueryBatch();
NullReferenceCheckHelper.throwIfNull(query_batch, () => $"query_batch is null !!!");
var owner = query_batch.getLogActor() as EntityBase;
NullReferenceCheckHelper.throwIfNull(owner, () => $"owner is null !!!");
return owner;
}
public virtual string toBasicString()
{
return $"QueryName:{getQueryName()}, ";
}
}
//==============================================================================================
// 배치 쿼리시 마지막 스텝의 콜백을 받기 위한 empty 쿼리
//==============================================================================================
public class QueryFinal : QueryExecutorBase
{
public QueryFinal()
: base(nameof(QueryFinal))
{ }
public override async Task<Result> onPrepareQuery()
{
await Task.CompletedTask;
var result = new Result();
return result;
}
public override async Task<Result> onQuery()
{
await Task.CompletedTask;
var result = new Result();
return result;
}
public override async Task onQueryResponseCommit()
{
await Task.CompletedTask;
}
public override async Task onQueryResponseRollback(Result errorResult)
{
await Task.CompletedTask;
}
}//QueryFinal

View File

@@ -0,0 +1,158 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Amazon.Runtime;
using Amazon.DynamoDBv2.Model;
using Amazon.DynamoDBv2;
using Amazon.DynamoDBv2.DocumentModel;
using ServerCore; using ServerBase;
namespace ServerBase;
//==============================================================================================
// QueryBatch 클래스의 주요 절차들을 처리해 주는 클래스 이다.
//==============================================================================================
public static class QueryHelper
{
//==============================================================================================
// 1. DB 쿼리를 요청한다.
// 2. 배치쿼리를 요청한다.
//==============================================================================================
public static async Task<Result> sendQueryAndBusinessLog(QueryBatchBase batch)
{
var result = new Result();
var err_msg = string.Empty;
if (batch == null)
{
err_msg = $"Failed to DBQuery !!!, QueryBatchBase is null";
result.setFail(ServerErrorCode.FunctionInvalidParam, err_msg);
return result;
}
Stopwatch? stopwatch = null;
var event_tid = string.Empty;
var server_logic = ServerLogicApp.getServerLogicApp();
ArgumentNullException.ThrowIfNull(server_logic);
var server_config = server_logic.getServerConfig();
ArgumentNullException.ThrowIfNull(server_config);
if (true == server_config.PerformanceCheckEnable)
{
event_tid = string.IsNullOrEmpty(batch.getTransId()) ? System.Guid.NewGuid().ToString("N") : batch.getTransId();
stopwatch = Stopwatch.StartNew();
}
result = await sendQuery(batch);
if(result.isSuccess())
{
batch.sendBusinessLog();
}
if (null != stopwatch)
{
var elapsed_msec = stopwatch.ElapsedMilliseconds;
stopwatch.Stop();
if (elapsed_msec > Constant.STOPWATCH_LOG_LIMIT_MSEC)
{
Log.getLogger().debug($"QueryBatch - Total Stopwatch Stop : ETID:{event_tid}, ElapsedMSec:{elapsed_msec}, LogAction:{batch.getLogAction().getLogActionType()}");
}
}
return result;
}
//==============================================================================================
// 1. DB 쿼리를 요청한다.
// 2. 배치쿼리를 요청한다.
//==============================================================================================
private static async Task<Result> sendQuery(QueryBatchBase batch)
{
var result = new Result();
var err_msg = string.Empty;
if (batch == null)
{
err_msg = $"Failed to sendQuery !!!, QueryBatchBase is null";
result.setFail(ServerErrorCode.FunctionInvalidParam, err_msg);
return result;
}
if (false == batch.hasQuery())
{
err_msg = $"Not has DBQuery !!!";
result.setFail(ServerErrorCode.DynamoDbQueryNoRequested, err_msg);
return result;
}
result = await batch.prepareQueryWithStopwatch();
if (result.isFail())
{
return result;
}
result = await batch.doQueryWithStopwatch();
if (result.isFail())
{
return result;
}
return result;
}
}
//==============================================================================================
// 쿼리 관련 확장 함수들을 정의하는 클래스 이다.
//==============================================================================================
public static class QueryExtension
{
public static bool isSuccess(this CancellationReason reason)
{
return true == reason.Code.Equals(DynamoDbQueryExceptionNotifier.Success, StringComparison.OrdinalIgnoreCase);
}
public static string toExceptionString(this TransactionCanceledException e)
{
return $"[TransactionCanceledException] - exception:{e}";
}
public static string toExceptionString(this AmazonDynamoDBException e)
{
var err_msg = $"[AmazonDynamoDBException] - occurred:{e.Message}, ";
err_msg += (e.InnerException != null) ? $" Inner Exception:{e.InnerException.Message}," : "";
err_msg += $" exception:{e}";
return err_msg;
}
public static string toExceptionString(this AmazonServiceException e)
{
return $"[AmazonServiceException] - exception:{e}";
}
public static bool isEqualQuery<TDbRequestWithDocument>(this IQueryDbRequester dbRequester)
where TDbRequestWithDocument : IQueryDbRequester
{
if(dbRequester.GetType() == typeof(TDbRequestWithDocument))
{
return true;
}
return false;
}
}

View File

@@ -0,0 +1,69 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Amazon.DynamoDBv2.DocumentModel;
using Amazon.DynamoDBv2.Model;
namespace ServerBase;
//=============================================================================================
// DB 쿼리를 실행해 주는 인터페이스 이다.
//=============================================================================================
public interface IQueryRunner
{
void setQueryBatch(QueryBatchBase queryBatch);
Task<Result> onTryCommitQueryRunner();
Task<Result> onCommitQueryRunner();
Task onRollbackQueryRunner();
Task<(Result, Result)> onHandleTransactionCancelException(TransactionCanceledException exception);
Task<(Result, IQueryContext?)> tryCreateQueryContext(IRowData rowData, QueryType toApplyQueryType = QueryType.None);
Task<Result> tryRegisterQueryContext(IQueryContext queryContext);
}
//=============================================================================================
// DB Row 형태의 정보를 관리하는 인터페이스 이다.
//=============================================================================================
public interface IRowData
{
string toBasicString();
}
//=============================================================================================
// DB 쿼리를 요청하는 인터페이스 이다.
//=============================================================================================
public interface IQueryDbRequester
{
Task<Result> doDbRequestAsync();
void setQueryBatch(QueryBatchBase queryBatch);
Int32 getQueryCount();
DynamoDbQueryExceptionNotifier getDynamoDbQueryExceptionNotifier();
}
//=============================================================================================
// Document 기반으로 생성해 주는 인터페이스 이다.
//=============================================================================================
public interface IWithDocumentModel
{
void onCreateDocumentModel(Table table);
}
//=============================================================================================
// DB 쿼리 정보를 담는 인터페이스 이다.
//=============================================================================================
public interface IQueryContext
{
QueryType getQueryType();
string toBasicString();
}

View File

@@ -0,0 +1,644 @@
using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Amazon.DynamoDBv2.DocumentModel;
using StackExchange.Redis;
using Amazon.DynamoDBv2.Model;
using ServerCore; using ServerBase;
namespace ServerBase;
//=============================================================================================
// Amazon.DynamoDBv2.DocumentModel 기반 쿼리를 실행해 주는 클래스 이다.
//
// 1. 선택된 테이블에서 쿼리가 가능 하다.
// 2. Get(Read)과 Write는 각각 트랜젝션이 보장 되므로 주의 한다
// 3. DocumentTransactGet + DocumentTransactWrite 실행시 동일한 PK에 대한 트랜잭션 보장은 되지 않는다.
// 4. 쿼리는 Get(Read), Write 는 최초 등록된 순서대로 실행 된다.
//=============================================================================================
public partial class QueryRunnerWithDocument : IQueryRunner
{
private QueryBatchBase? m_query_batch;
private Dictionary<Table, Dictionary<Type, IQueryDbRequester>> m_reserved_db_requesters = new();
private List<IQueryDbRequester> m_to_try_db_requesters = new();
private IQueryDbRequester? m_executing_query_nullable;
private MultiTableDocumentBatchGet? m_batch_get;
private MultiTableDocumentBatchWrite? m_batch_write;
private MultiTableDocumentTransactGet? m_transact_get;
private MultiTableDocumentTransactWrite? m_transact_write;
public QueryRunnerWithDocument()
{ }
public async Task<Result> onTryCommitQueryRunner()
{
var result = new Result();
foreach (var db_requester in m_to_try_db_requesters)
{
result = await db_requester.doDbRequestAsync();
if(result.isFail())
{
return result;
}
}
return result;
}
public async Task<Result> onCommitQueryRunner()
{
var result = new Result();
foreach (var db_requester in m_to_try_db_requesters)
{
m_executing_query_nullable = db_requester;
var err_msg = $"DBRequesterType:{db_requester.getTypeName()}, QueryCount:{db_requester.getQueryCount()}";
Log.getLogger().info(err_msg);
if (true == db_requester.isEqualQuery<DBRequestGetWithDocumentBatchGet>())
{
ArgumentNullException.ThrowIfNull(m_batch_get, $"m_batch_get is null !!!");
await m_batch_get.ExecuteAsync();
}
else if (true == db_requester.isEqualQuery<DBRequestWriteWithDocumentBatchWrite>())
{
ArgumentNullException.ThrowIfNull(m_batch_write, $"m_batch_write is null !!!");
await m_batch_write.ExecuteAsync();
}
else if (true == db_requester.isEqualQuery<DBRequestGetWithDocumentTransactGet>())
{
ArgumentNullException.ThrowIfNull(m_transact_get, $"m_transact_get is null !!!");
await m_transact_get.ExecuteAsync();
}
else if (true == db_requester.isEqualQuery<DBRequestWriteWithDocumentTransactWrite>())
{
ArgumentNullException.ThrowIfNull(m_transact_write, $"m_transact_write is null !!!");
await m_transact_write.ExecuteAsync();
}
else
{
err_msg = $"Not found Query with Document !!!";
result.setFail(ServerErrorCode.DynamoDbQueryNotFoundDocumentQuery, err_msg);
return result;
}
}
return result;
}
public async Task onRollbackQueryRunner()
{
await Task.CompletedTask;
m_reserved_db_requesters.Clear();
m_to_try_db_requesters.Clear();
m_batch_get?.Batches.Clear();
m_batch_write?.Batches.Clear();
m_transact_get?.TransactionParts.Clear();
m_transact_write?.TransactionParts.Clear();
}
public async Task<Result> pushQueryContexts(List<DynamoDbDocumentQueryContext> toQueryConexts)
{
ArgumentNullException.ThrowIfNull(toQueryConexts, $"toQueryConexts is null !!!");
var result = new Result();
var err_msg = string.Empty;
foreach (var query_context in toQueryConexts)
{
result = await pushQueryContext(query_context);
if(result.isFail())
{
return result;
}
}
return result;
}
protected async Task<Result> pushQueryContext(DynamoDbDocumentQueryContext queryContext)
{
var result = new Result();
var err_msg = string.Empty;
var query_batch = getQueryBatch();
NullReferenceCheckHelper.throwIfNull(query_batch, () => $"query_batch is null !!! - {queryContext.toBasicString()}");
var dynamo_db_connector = query_batch.getDynamoDbConnector();
var table = dynamo_db_connector.getTableByFullName(queryContext.getTableFullName());
if (false == queryContext.isValid())
{
err_msg = $"DynamoDbDocumentQueryContext invalid !!! - {queryContext.toBasicString()}";
result.setFail(ServerErrorCode.DynamoDbDocumentIsNullInQueryContext, err_msg);
Log.getLogger().error(result.toBasicString());
return result;
}
var document = queryContext.getDocument();
NullReferenceCheckHelper.throwIfNull(document, () => $"document is null !!! - {queryContext.toBasicString()}");
if (false == document.isValid())
{
err_msg = $"DynamoDbDocument invalid !!! : {document.toBasicString()} - {queryContext.toBasicString()}";
result.setFail(ServerErrorCode.DynamoDbDocumentIsInvalid, err_msg);
Log.getLogger().error(result.toBasicString());
return result;
}
var query_type = queryContext.getQueryType();
switch (query_type)
{
case QueryType.Insert:
{
pushInsertQuery(table, queryContext);
break;
}
case QueryType.Update:
{
result = await pushUpdateQuery(table, queryContext);
if(result.isFail())
{
return result;
}
break;
}
case QueryType.Upsert:
{
result = await pushUpdateQuery(table, queryContext, true);
if (result.isFail())
{
return result;
}
break;
}
case QueryType.Delete:
{
pushDeleteQuery(table, queryContext);
break;
}
case QueryType.ExistsDelete:
{
pushDeleteQueryWithExistAttribute(table, queryContext);
break;
}
default:
err_msg = $"Invalid QueryType getQueryType() !!! : queryType:{query_type} - {queryContext.toBasicString()}, {document.toBasicString()}";
result.setFail(ServerErrorCode.DynamoDbDocumentQueryContextTypeInvalid, err_msg);
Log.getLogger().error(result.toBasicString());
return result;
}
var msg = $"{this.getTypeName()} : {queryContext.getQueryType().convertEnumToEnumTypeAndValueString()} : {document.toBasicString()}";
Log.getLogger().info(msg);
return await Task.FromResult(result);
}
public void pushSelectQuery(Table table, DynamoDbDocumentQueryContext queryContext)
{
ArgumentNullException.ThrowIfNull(table, $"table is null !!!");
ArgumentNullException.ThrowIfNull(queryContext, $"queryContext is null !!!");
var query_batch = getQueryBatch();
ArgumentNullException.ThrowIfNull(query_batch, $"query_batch is null !!!");
var to_select_ducument = queryContext.getDocument();
ArgumentNullException.ThrowIfNull(to_select_ducument, $"to_select_ducument is null !!!");
IQueryDbRequester db_requster;
if (true == query_batch.isUseTransact())
{
var get_with_transact_request = getOrAddDbRequesterWithDocument<DBRequestGetWithDocumentTransactGet>(table);
ArgumentNullException.ThrowIfNull(get_with_transact_request, $"get_with_transact_request is null !!!");
var generic_type = get_with_transact_request.getDbRequestGenericType();
ArgumentNullException.ThrowIfNull(generic_type, $"generic_type is null !!!");
generic_type.AddKey(to_select_ducument);
db_requster = get_with_transact_request;
}
else
{
var get_with_batch_request = getOrAddDbRequesterWithDocument<DBRequestGetWithDocumentBatchGet>(table);
ArgumentNullException.ThrowIfNull(get_with_batch_request, $"get_with_batch_request is null !!!");
var generic_type = get_with_batch_request.getDbRequestGenericType();
ArgumentNullException.ThrowIfNull(generic_type, $"generic_type is null !!!");
generic_type.AddKey(to_select_ducument);
db_requster = get_with_batch_request;
}
registerExceptionHandler(db_requster, queryContext.getExceptionHandler());
}
public void pushInsertQuery(Table table, DynamoDbDocumentQueryContext queryContext)
{
ArgumentNullException.ThrowIfNull(table, $"table is null !!!");
ArgumentNullException.ThrowIfNull(queryContext, $"queryContext is null !!!");
var query_batch = getQueryBatch();
ArgumentNullException.ThrowIfNull(query_batch, $"query_batch is null !!!");
var db_connector = query_batch.getDynamoDbConnector();
ArgumentNullException.ThrowIfNull(db_connector, $"db_connector is null !!!");
var to_insert_ducument = queryContext.getDocument();
ArgumentNullException.ThrowIfNull(to_insert_ducument, $"to_insert_ducument is null !!!");
IQueryDbRequester db_requster;
if (true == query_batch.isUseTransact())
{
var write_with_transact_request = getOrAddDbRequesterWithDocument<DBRequestWriteWithDocumentTransactWrite>(table);
ArgumentNullException.ThrowIfNull(write_with_transact_request, $"write_with_transact_request is null !!!");
var generic_type = write_with_transact_request.getDbRequestGenericType();
ArgumentNullException.ThrowIfNull(generic_type, $"generic_type is null !!!");
generic_type.AddDocumentToPut(to_insert_ducument, DynamoDbQueryOperationOptionConfig.getTransactWriteConfigForNotExistKey());
db_requster = write_with_transact_request;
}
else
{
var write_with_batch_request = getOrAddDbRequesterWithDocument<DBRequestWriteWithDocumentBatchWrite>(table);
ArgumentNullException.ThrowIfNull(write_with_batch_request, $"write_with_batch_request is null !!!");
var generic_type = write_with_batch_request.getDbRequestGenericType();
ArgumentNullException.ThrowIfNull(generic_type, $"generic_type is null !!!");
generic_type.AddDocumentToPut(to_insert_ducument);
db_requster = write_with_batch_request;
}
registerExceptionHandler(db_requster, queryContext.getExceptionHandler());
}
public async Task<Result> pushUpdateQuery(Table table, DynamoDbDocumentQueryContext queryContext, bool isUpsert = false)
{
ArgumentNullException.ThrowIfNull(queryContext, $"queryContext is null !!!");
var result = new Result();
var err_msg = string.Empty;
var query_batch = getQueryBatch();
ArgumentNullException.ThrowIfNull(query_batch, $"query_batch is null !!!");
var dynamo_db_connector = query_batch.getDynamoDbConnector();
var to_update_ducument = queryContext.getDocument();
ArgumentNullException.ThrowIfNull(to_update_ducument, $"to_update_ducument is null !!!");
IQueryDbRequester db_requster;
if (true == query_batch.isUseTransact())
{
var write_with_transact_request = getOrAddDbRequesterWithDocument<DBRequestWriteWithDocumentTransactWrite>(table);
ArgumentNullException.ThrowIfNull(write_with_transact_request, $"write_with_transact_request is null !!!");
var generic_type = write_with_transact_request.getDbRequestGenericType();
ArgumentNullException.ThrowIfNull(generic_type, $"generic_type is null !!!");
if (true == isUpsert)
{
generic_type.AddDocumentToUpdate( to_update_ducument );
}
else
{
generic_type.AddDocumentToUpdate( to_update_ducument, DynamoDbQueryOperationOptionConfig.getTransactWriteConfigForExistKey() );
}
db_requster = write_with_transact_request;
}
else
{
if (true == isUpsert)
{
result = await to_update_ducument.tryFillupTimestampsForUpsert(dynamo_db_connector, table.TableName);
if(result.isFail())
{
err_msg = $"Failed to tryFillupTimestampsForUpsert() !!! : {result.toBasicString()} - {queryContext.toBasicString()}, {to_update_ducument.toBasicString()}";
Log.getLogger().error(err_msg);
return result;
}
}
else
{
(result, var primary_key) = to_update_ducument.toPrimaryKey();
if(result.isFail())
{
return result;
}
NullReferenceCheckHelper.throwIfNull(primary_key, () => $"primary_key is null !!!");
(result, _) = await dynamo_db_connector.isExistPrimaryKey(table.TableName, primary_key, true);
if (result.isFail())
{
return result;
}
}
var write_with_batch_request = getOrAddDbRequesterWithDocument<DBRequestWriteWithDocumentBatchWrite>(table);
ArgumentNullException.ThrowIfNull(write_with_batch_request, $"write_with_batch_request is null !!!");
var generic_type = write_with_batch_request.getDbRequestGenericType();
ArgumentNullException.ThrowIfNull(generic_type, $"generic_type is null !!!");
generic_type.AddDocumentToPut(to_update_ducument);
db_requster = write_with_batch_request;
}
registerExceptionHandler(db_requster, queryContext.getExceptionHandler());
return result;
}
public void pushDeleteQuery(Table table, DynamoDbDocumentQueryContext queryContext)
{
ArgumentNullException.ThrowIfNull(table, $"table is null !!!");
ArgumentNullException.ThrowIfNull(queryContext, $"queryContext is null !!!");
var query_batch = getQueryBatch();
ArgumentNullException.ThrowIfNull(query_batch, $"query_batch is null !!!");
var to_delete_ducument = queryContext.getDocument();
ArgumentNullException.ThrowIfNull(to_delete_ducument, $"to_delete_ducument is null !!!");
IQueryDbRequester db_requster;
if (true == query_batch.isUseTransact())
{
var write_with_transact_request = getOrAddDbRequesterWithDocument<DBRequestWriteWithDocumentTransactWrite>(table);
ArgumentNullException.ThrowIfNull(write_with_transact_request, $"write_with_transact_request is null !!!");
var generic_type = write_with_transact_request.getDbRequestGenericType();
ArgumentNullException.ThrowIfNull(generic_type, $"generic_type is null !!!");
generic_type.AddItemToDelete(to_delete_ducument);
db_requster = write_with_transact_request;
}
else
{
var write_with_batch_request = getOrAddDbRequesterWithDocument<DBRequestWriteWithDocumentBatchWrite>(table);
ArgumentNullException.ThrowIfNull(write_with_batch_request, $"write_with_batch_request is null !!!");
var generic_type = write_with_batch_request.getDbRequestGenericType();
ArgumentNullException.ThrowIfNull(generic_type, $"generic_type is null !!!");
generic_type.AddItemToDelete(to_delete_ducument);
db_requster = write_with_batch_request;
}
registerExceptionHandler(db_requster, queryContext.getExceptionHandler());
}
public void pushDeleteQueryWithExistAttribute(Table table, DynamoDbDocumentQueryContext queryContext)
{
ArgumentNullException.ThrowIfNull(table, $"table is null !!!");
ArgumentNullException.ThrowIfNull(queryContext, $"queryContext is null !!!");
var query_batch = getQueryBatch();
ArgumentNullException.ThrowIfNull(query_batch, $"query_batch is null !!!");
var db_connector = query_batch.getDynamoDbConnector();
var to_delete_ducument = queryContext.getDocument();
ArgumentNullException.ThrowIfNull(to_delete_ducument, $"to_delete_ducument is null !!!");
IQueryDbRequester db_requster;
if (true == query_batch.isUseTransact())
{
var write_with_transact_request = getOrAddDbRequesterWithDocument<DBRequestWriteWithDocumentTransactWrite>(table);
ArgumentNullException.ThrowIfNull(write_with_transact_request, $"write_with_transact_request is null !!!");
var generic_type = write_with_transact_request.getDbRequestGenericType();
ArgumentNullException.ThrowIfNull(generic_type, $"generic_type is null !!!");
generic_type.AddItemToDelete(to_delete_ducument, DynamoDbQueryOperationOptionConfig.getTransactWriteConfigForExistKey());
db_requster = write_with_transact_request;
}
else
{
var write_with_batch_request = getOrAddDbRequesterWithDocument<DBRequestWriteWithDocumentBatchWrite>(table);
ArgumentNullException.ThrowIfNull(write_with_batch_request, $"write_with_batch_request is null !!!");
var generic_type = write_with_batch_request.getDbRequestGenericType();
ArgumentNullException.ThrowIfNull(generic_type, $"generic_type is null !!!");
db_requster = write_with_batch_request;
}
registerExceptionHandler(db_requster, queryContext.getExceptionHandler());
}
public async Task<Result> tryRegisterQueryContext(IQueryContext queryContext)
{
var result = new Result();
var doc_query_context = queryContext as DynamoDbDocumentQueryContext;
ArgumentNullReferenceCheckHelper.throwIfNull(doc_query_context, () => $"doc_query_context is null !!!");
result = await pushQueryContext(doc_query_context);
if(result.isFail())
{
return result;
}
return result;
}
public async Task<(Result, IQueryContext?)> tryCreateQueryContext(IRowData rowData, QueryType toApplyQueryType = QueryType.None)
{
var result = new Result();
var doc_base = rowData as DynamoDbDocBase;
ArgumentNullReferenceCheckHelper.throwIfNull(doc_base, () => $"doc_base is null !!! - {rowData.getTypeName()}");
if(QueryType.None != toApplyQueryType)
{
doc_base.setQueryType(toApplyQueryType);
}
(result, var query_context) = await doc_base.toDocumentQueryContext<DynamoDbDocBase>();
if(result.isFail())
{
return (result, null);
}
NullReferenceCheckHelper.throwIfNull(query_context, () => $"query_context is null !!!");
return (result, query_context);
}
public MultiTableDocumentBatchGet getOrNewMultiTableDocumentBatchGet()
{
if(null == m_batch_get)
{
m_batch_get = new MultiTableDocumentBatchGet();
}
return m_batch_get;
}
public MultiTableDocumentBatchWrite getOrNewMultiTableDocumentBatchWrite()
{
if (null == m_batch_write)
{
m_batch_write = new MultiTableDocumentBatchWrite();
}
return m_batch_write;
}
public MultiTableDocumentTransactGet getOrNewMultiTableDocumentTransactGet()
{
if (null == m_transact_get)
{
m_transact_get = new MultiTableDocumentTransactGet();
}
return m_transact_get;
}
public MultiTableDocumentTransactWrite getOrNewMultiTableDocumentTransactWrite()
{
if (null == m_transact_write)
{
m_transact_write = new MultiTableDocumentTransactWrite();
}
return m_transact_write;
}
public TDbRequester? getOrAddDbRequesterWithDocument<TDbRequester>(Table table)
where TDbRequester : class, IQueryDbRequester, new()
{
ArgumentNullException.ThrowIfNull(table, $"table is null !!!");
var db_requester = getDbRequester<TDbRequester>(table);
if (null != db_requester)
{
return db_requester;
}
var query_batch = getQueryBatch();
ArgumentNullException.ThrowIfNull(query_batch, $"query_batch is null !!!");
db_requester = new TDbRequester();
db_requester.setQueryBatch(query_batch);
var db_requester_with_document = db_requester as IWithDocumentModel;
ArgumentNullException.ThrowIfNull(db_requester_with_document, $"db_requester_with_document is null !!!");
db_requester_with_document.onCreateDocumentModel(table);
var db_requester_type = typeof(TDbRequester);
m_reserved_db_requesters[table][db_requester_type] = db_requester;
m_to_try_db_requesters.Add(db_requester);
return db_requester;
}
public TDbRequester? getDbRequester<TDbRequester>(Table table)
where TDbRequester : class, IQueryDbRequester
{
if(false == m_reserved_db_requesters.TryGetValue(table, out var found_db_requesters))
{
found_db_requesters = new Dictionary<Type, IQueryDbRequester>();
m_reserved_db_requesters.Add(table, found_db_requesters);
}
var db_requester_type = typeof(TDbRequester);
if(false == found_db_requesters.TryGetValue(db_requester_type, out var found_db_requester))
{
return null;
}
return found_db_requester as TDbRequester;
}
private void registerExceptionHandler(IQueryDbRequester queryDbRequester, DynamoDbQueryExceptionNotifier.ExceptionHandler? exceptionHandler)
{
var exception_notifier = queryDbRequester.getDynamoDbQueryExceptionNotifier();
ArgumentNullException.ThrowIfNull(exception_notifier, $"exception_notifier is null !!!");
exception_notifier.registerExceptionHandler(exceptionHandler);
}
public async Task<(Result, Result)> onHandleTransactionCancelException(TransactionCanceledException exception)
{
await Task.CompletedTask;
ArgumentNullException.ThrowIfNull(exception, $"exception is null !!!");
var result = new Result();
var exception_result = new Result();
var err_msg = string.Empty;
var exception_notifier = getDynamoDbQueryExceptionNotifierOfExecutingQuery();
if(null == exception_notifier)
{
err_msg = $"Not found ExceptionNotifier !!!";
result.setFail(ServerErrorCode.DynamoDbQueryExceptionNotifierNotFound, err_msg);
Log.getLogger().error(result.toBasicString());
return (result, exception_result);
}
// 트랜잭션 취소 원인에 따라 등록된 ServerErrorCode로 재설정 한다.
// 성공일 경우에는 ServerErrorCode를 설정 하지 않는다 !!!
var query_count = exception.CancellationReasons.Count;
for (var query_seq_index = 0; query_seq_index < query_count; ++query_seq_index)
{
var reason = exception.CancellationReasons[query_seq_index];
// 성공일 경우 패스 !!!
if(true == reason.isSuccess())
{
continue;
}
var query_seq_no = query_seq_index + 1;
err_msg = $"TransactionCanceledException Reason : QuerySeq:{query_seq_no}/{query_count}, Code:{reason.Code}, Message:{reason.Message}, Item:{reason.Item}";
Log.getLogger().fatal(err_msg);
var exception_handler = exception_notifier.findExceptionHandler(query_seq_no, reason.Code);
if (null != exception_handler)
{
exception_result.setFail(exception_handler.ReturnToErrorCode, err_msg);
}
}
return (result, exception_result);
}
private DynamoDbQueryExceptionNotifier getDynamoDbQueryExceptionNotifierOfExecutingQuery()
{
ArgumentNullException.ThrowIfNull(m_executing_query_nullable, $"m_executing_query_nullable is null !!!");
return m_executing_query_nullable.getDynamoDbQueryExceptionNotifier();
}
}

View File

@@ -0,0 +1,611 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Amazon.Runtime;
using Amazon.DynamoDBv2;
using Amazon.DynamoDBv2.Model;
using StackExchange.Redis;
using ServerCore; using ServerBase;
namespace ServerBase;
//=============================================================================================
// Amazon.DynamoDBv2.Model 기반 쿼리를 실행해 주는 클래스 이다.
//
// 1. 복수개의 테이블에 쿼리가 가능 하다.
// 2. 세부적인 Attribute 단위 쿼리가 가능 하다.
// 3. TransactGetItemsRequest(Read)과 TransactWriteItemsRequest(Write) 각각 트랜젝션이 보장 되므로 주의 한다.
// 4. TransactGetItemsRequest + TransactWriteItemsRequest 실행시 동일한 PK에 대한 트랜잭션 보장은 되지 않는다.
// 5. 쿼리는 Get(Read), Write 는 최초 등록된 순서대로 실행 된다.
// 6. 주요 코어 처리 객체
// BatchGetItemRequest : 배치 기반 읽기 쿼리
// BatchWriteItemRequest : 배치 기반 쓰기 쿼리
// UpdateItemRequest : 특정 대상을 위한 Upsert 쿼리
// TransactGetItemsRequest : 배치 + 트랜잭션 기반 읽기 쿼리
// TransactWriteItemsRequest : 배치 + 트랜잭션 기반 쓰기 쿼리
//=============================================================================================
public partial class QueryRunnerWithItemRequest : IQueryRunner
{
private QueryBatchBase? m_query_batch;
private readonly List<IQueryDbRequester> m_db_requesters = new();
private IQueryDbRequester? m_executing_query_nullable;
private readonly List<AmazonWebServiceResponse> m_db_responses = new();
public QueryRunnerWithItemRequest()
{ }
public async Task<Result> onTryCommitQueryRunner()
{
await Task.CompletedTask;
var result = new Result();
return result;
}
public async Task<Result> onCommitQueryRunner()
{
var result = new Result();
foreach (var db_requester in m_db_requesters)
{
m_executing_query_nullable = db_requester;
var err_msg = $"DBRequesterType:{db_requester.getTypeName()}, QueryCount:{db_requester.getQueryCount()}";
Log.getLogger().info(err_msg);
result = await db_requester.doDbRequestAsync();
if (result.isFail())
{
break;
}
}
return result;
}
public async Task onRollbackQueryRunner()
{
await Task.CompletedTask;
m_db_requesters.Clear();
m_db_responses.Clear();
}
protected async Task<Result> pushQueryContext(DynamoDbItemRequestQueryContext queryContext)
{
ArgumentNullException.ThrowIfNull(queryContext, $"queryContext is null !!!");
var result = new Result();
var err_msg = string.Empty;
if (false == queryContext.isValid())
{
err_msg = $"DynamoDbDocumentQueryContext invalid !!! - {queryContext.toBasicString()}";
result.setFail(ServerErrorCode.DynamoDbDocumentIsNullInQueryContext, err_msg);
Log.getLogger().error(result.toBasicString());
return result;
}
var item_request = queryContext.getAmazonDynamoDBRequest();
NullReferenceCheckHelper.throwIfNull(item_request, () => $"item_request is null !!!");
if (false == item_request.isValid())
{
err_msg = $"DynamoDbDocument invalid !!! : {item_request.toBasicString()}";
result.setFail(ServerErrorCode.DynamoDbDocumentIsInvalid, err_msg);
Log.getLogger().error(result.toBasicString());
return result;
}
var query_type = queryContext.getQueryType();
switch (query_type)
{
case QueryType.Insert:
{
pushInsertQuery(queryContext);
break;
}
case QueryType.Update:
{
result = pushUpdateQuery(queryContext, false);
if (result.isFail())
{
return result;
}
break;
}
case QueryType.Upsert:
{
result = pushUpdateQuery(queryContext, true);
if (result.isFail())
{
return result;
}
break;
}
case QueryType.Delete:
{
pushDeleteQuery(queryContext);
break;
}
default:
err_msg = $"Invalid QueryType getQueryType() !!! : queryType:{query_type} - {queryContext.toBasicString()}, {item_request.toBasicString()}";
result.setFail(ServerErrorCode.DynamoDbItemRequestQueryContextTypeInvalid, err_msg);
Log.getLogger().error(result.toBasicString());
return result;
}
var msg = $"{this.getTypeName()} : {queryContext.getQueryType().convertEnumToEnumTypeAndValueString()} : {item_request.toBasicString()}";
Log.getLogger().info(msg);
return await Task.FromResult(result);
}
public void pushSelectQuery(DynamoDbItemRequestQueryContext queryContext)
{
var query_batch = getQueryBatch();
ArgumentNullException.ThrowIfNull(query_batch, $"query_batch is null !!!");
var to_select_db_item_request = queryContext.getAmazonDynamoDBRequest() as GetItemRequest;
ArgumentNullException.ThrowIfNull(to_select_db_item_request, $"to_select_db_item_request is null !!!");
IQueryDbRequester db_requster;
if (true == query_batch.isUseTransact())
{
var transactGetItem = new TransactGetItem()
{
Get = new Get()
{
TableName = to_select_db_item_request.TableName,
Key = to_select_db_item_request.Key
}
};
var get_with_transact_request = getOrAddDbRequester<DBRequestGetWithTransact>();
ArgumentNullException.ThrowIfNull(get_with_transact_request, $"get_with_transact_request is null !!!");
var generic_type = get_with_transact_request.getDbRequestGenericType();
ArgumentNullException.ThrowIfNull(generic_type, $"generic_type is null !!!");
generic_type.TransactItems.Add(transactGetItem);
db_requster = get_with_transact_request;
}
else
{
var get_batch_request = getOrAddDbRequester<DBRequestGetBatch>();
ArgumentNullException.ThrowIfNull(get_batch_request, $"get_batch_request is null !!!");
var generic_type = get_batch_request.getDbRequestGenericType();
ArgumentNullException.ThrowIfNull(generic_type, $"generic_type is null !!!");
generic_type.RequestItems.TryGetValue(to_select_db_item_request.TableName, out var get_requests);
if (null == get_requests)
{
get_requests = new KeysAndAttributes();
generic_type.RequestItems.Add(to_select_db_item_request.TableName, get_requests);
}
get_requests.Keys.Add(to_select_db_item_request.Key);
db_requster = get_batch_request;
}
registerExceptionHandler(db_requster, queryContext.getExceptionHandler());
}
public void pushInsertQuery(DynamoDbItemRequestQueryContext queryContext)
{
var query_batch = getQueryBatch();
ArgumentNullException.ThrowIfNull(query_batch, $"query_batch is null !!!");
var to_insert_db_item_request = queryContext.getAmazonDynamoDBRequest() as PutItemRequest;
ArgumentNullException.ThrowIfNull(to_insert_db_item_request, $"to_insert_db_item_request is null !!!");
IQueryDbRequester db_requster;
to_insert_db_item_request.fillupTimestamps();
if (true == query_batch.isUseTransact())
{
var transactWriteItem = new TransactWriteItem()
{
Put = new Put()
{
TableName = to_insert_db_item_request.TableName,
Item = to_insert_db_item_request.Item,
ReturnValuesOnConditionCheckFailure = Amazon.DynamoDBv2.ReturnValuesOnConditionCheckFailure.ALL_OLD,
ConditionExpression = to_insert_db_item_request.ConditionExpression,
}
};
var write_with_transact_request = getOrAddDbRequester<DBRequestWriteWithTransact>();
ArgumentNullException.ThrowIfNull(write_with_transact_request, $"write_with_transact_request is null !!!");
var generic_type = write_with_transact_request.getDbRequestGenericType();
ArgumentNullException.ThrowIfNull(generic_type, $"generic_type is null !!!");
generic_type.TransactItems.Add(transactWriteItem);
db_requster = write_with_transact_request;
}
else
{
var write_batch_request = getOrAddDbRequester<DBRequestWriteBatch>();
ArgumentNullException.ThrowIfNull(write_batch_request, $"write_batch_request is null !!!");
var generic_type = write_batch_request.getDbRequestGenericType();
ArgumentNullException.ThrowIfNull(generic_type, $"generic_type is null !!!");
generic_type.RequestItems.TryGetValue(to_insert_db_item_request.TableName, out var write_requests);
if (null == write_requests)
{
write_requests = new List<WriteRequest>();
generic_type.RequestItems.Add(to_insert_db_item_request.TableName, write_requests);
}
write_requests.Add( new WriteRequest {
PutRequest = new PutRequest {
Item = to_insert_db_item_request.Item
}
}
);
db_requster = write_batch_request;
}
registerExceptionHandler(db_requster, queryContext.getExceptionHandler());
}
public Result pushUpdateQuery(DynamoDbItemRequestQueryContext queryContext, bool isUpsert = false)
{
var query_batch = getQueryBatch();
ArgumentNullException.ThrowIfNull(query_batch, $"query_batch is null !!!");
var to_update_db_item_request = queryContext.getAmazonDynamoDBRequest() as UpdateItemRequest;
ArgumentNullException.ThrowIfNull(to_update_db_item_request, $"to_update_db_item_request is null !!!");
var result = new Result();
var err_msg = string.Empty;
if(to_update_db_item_request.UpdateExpression.isNullOrWhiteSpace())
{
err_msg = $"UpdateItemRequest.UpdateExpression is Empty !!! - {queryContext.toBasicString()}, {to_update_db_item_request.toBasicString()}";
result.setFail(ServerErrorCode.DynamoDbItemRequestUpdateExpressionEmpty, err_msg);
Log.getLogger().error(err_msg);
return result;
}
result = to_update_db_item_request.tryFillupTimestamps(isUpsert);
if (result.isFail())
{
err_msg = $"Failed to tryFillupTimestamps() !!! : {result.toBasicString()} - {queryContext.toBasicString()}, {to_update_db_item_request.toBasicString()}";
Log.getLogger().error(err_msg);
return result;
}
IQueryDbRequester db_requster;
if (true == query_batch.isUseTransact())
{
var transactWriteItem = new TransactWriteItem()
{
Update = new Update()
{
TableName = to_update_db_item_request.TableName,
Key = to_update_db_item_request.Key,
ExpressionAttributeNames = to_update_db_item_request.ExpressionAttributeNames,
ExpressionAttributeValues = to_update_db_item_request.ExpressionAttributeValues,
ConditionExpression = to_update_db_item_request.ConditionExpression,
UpdateExpression = to_update_db_item_request.UpdateExpression,
ReturnValuesOnConditionCheckFailure = Amazon.DynamoDBv2.ReturnValuesOnConditionCheckFailure.ALL_OLD,
}
};
var write_with_transact_request = getOrAddDbRequester<DBRequestWriteWithTransact>();
ArgumentNullException.ThrowIfNull(write_with_transact_request, $"write_with_transact_request is null !!!");
var generic_type = write_with_transact_request.getDbRequestGenericType();
ArgumentNullException.ThrowIfNull(generic_type, $"generic_type is null !!!");
generic_type.TransactItems.Add(transactWriteItem);
db_requster = write_with_transact_request;
}
else
{
var update_request = getAfterAddDbRequester<DBRequestUpdate>();
ArgumentNullException.ThrowIfNull(update_request, $"update_request is null !!!");
var generic_type = update_request.getDbRequestGenericType();
ArgumentNullException.ThrowIfNull(generic_type, $"generic_type is null !!!");
generic_type.TableName = to_update_db_item_request.TableName;
generic_type.Key = to_update_db_item_request.Key;
generic_type.ExpressionAttributeNames = to_update_db_item_request.ExpressionAttributeNames;
generic_type.ExpressionAttributeValues = to_update_db_item_request.ExpressionAttributeValues;
generic_type.ConditionExpression = to_update_db_item_request.ConditionExpression;
generic_type.UpdateExpression = to_update_db_item_request.UpdateExpression;
generic_type.ReturnValues = to_update_db_item_request.ReturnValues;
db_requster = update_request;
}
registerExceptionHandler(db_requster, queryContext.getExceptionHandler());
return result;
}
public void pushDeleteQuery(DynamoDbItemRequestQueryContext queryContext)
{
var query_batch = getQueryBatch();
ArgumentNullException.ThrowIfNull(query_batch, $"query_batch is null !!!");
var to_delete_db_item_request = queryContext.getAmazonDynamoDBRequest() as DeleteItemRequest;
ArgumentNullException.ThrowIfNull(to_delete_db_item_request, $"to_delete_db_item_request is null !!!");
IQueryDbRequester db_requster;
if (true == query_batch.isUseTransact())
{
var transactWriteItem = new TransactWriteItem()
{
Delete = new Delete()
{
TableName = to_delete_db_item_request.TableName,
Key = to_delete_db_item_request.Key,
ReturnValuesOnConditionCheckFailure = Amazon.DynamoDBv2.ReturnValuesOnConditionCheckFailure.ALL_OLD,
}
};
var write_with_transact_request = getOrAddDbRequester<DBRequestWriteWithTransact>();
ArgumentNullException.ThrowIfNull(write_with_transact_request, $"write_with_transact_request is null !!!");
var generic_type = write_with_transact_request.getDbRequestGenericType();
ArgumentNullException.ThrowIfNull(generic_type, $"generic_type is null !!!");
generic_type.TransactItems.Add(transactWriteItem);
db_requster = write_with_transact_request;
}
else
{
var write_batch_request = getOrAddDbRequester<DBRequestWriteBatch>();
ArgumentNullException.ThrowIfNull(write_batch_request, $"write_batch_request is null !!!");
var generic_type = write_batch_request.getDbRequestGenericType();
ArgumentNullException.ThrowIfNull(generic_type, $"generic_type is null !!!");
generic_type.RequestItems.TryGetValue(to_delete_db_item_request.TableName, out var write_requests);
if (null == write_requests)
{
write_requests = new List<WriteRequest>();
generic_type.RequestItems.Add(to_delete_db_item_request.TableName, write_requests);
}
write_requests.Add( new WriteRequest {
DeleteRequest = new DeleteRequest {
Key = to_delete_db_item_request.Key
}
}
);
db_requster = write_batch_request;
}
registerExceptionHandler(db_requster, queryContext.getExceptionHandler());
}
public async Task<Result> tryRegisterQueryContext(IQueryContext queryContext)
{
var query_batch = getQueryBatch();
NullReferenceCheckHelper.throwIfNull(query_batch, () => $"query_batch is null !!!");
var result = new Result();
DynamoDbItemRequestQueryContext? item_request_query_context;
if(queryContext is DynamoDbDocumentQueryContext document_query_context)
{
(result, item_request_query_context) = document_query_context.getDocument().toItemRequestQueryContext(document_query_context.getTableFullName(), queryContext.getQueryType());
if(result.isFail())
{
return result;
}
}
else
{
item_request_query_context = queryContext as DynamoDbItemRequestQueryContext;
}
ArgumentNullReferenceCheckHelper.throwIfNull(item_request_query_context, () => $"item_request_query_context is null !!!");
result = await pushQueryContext(item_request_query_context);
if (result.isFail())
{
return result;
}
return result;
}
public async Task<(Result, IQueryContext?)> tryCreateQueryContext(IRowData rowData, QueryType toApplyQueryType = QueryType.None)
{
var result = new Result();
var err_msg = string.Empty;
var query_batch = getQueryBatch();
NullReferenceCheckHelper.throwIfNull(query_batch, () => $"query_batch is null !!!");
var dynamo_db_connector = query_batch.getDynamoDbConnector();
var doc_base = rowData as DynamoDbDocBase;
ArgumentNullReferenceCheckHelper.throwIfNull(doc_base, () => $"doc_base is null !!! - {rowData.getTypeName()}");
if (QueryType.None != toApplyQueryType)
{
doc_base.setQueryType(toApplyQueryType);
}
var query_type = doc_base.getQueryType();
if (QueryType.None == query_type)
{
err_msg = $"Invalid QueryType !!! : QueryType.None != DocBase.QueryType:{query_type} - {doc_base.toBasicString()}";
result.setFail(ServerErrorCode.DbQueryTypeInvalid, err_msg);
return (result, null);
}
(result, var document) = await doc_base.onCopyToDocument();
if(result.isFail())
{
return (result, null);
}
NullReferenceCheckHelper.throwIfNull(document, () => $"document is null !!! - {doc_base.toBasicString()}");
(result, var item_request_query_context) = document.toItemRequestQueryContext(dynamo_db_connector.getTableFullName(doc_base.TableName), doc_base.getQueryType());
if (result.isFail())
{
return (result, null);
}
return (result, item_request_query_context);
}
public TDbRequester? getOrAddDbRequester<TDbRequester>()
where TDbRequester : class, IQueryDbRequester, new()
{
var db_requester = getDbRequester<TDbRequester>();
if (null != db_requester)
{
return db_requester;
}
db_requester = new TDbRequester();
var batch = getQueryBatch();
ArgumentNullException.ThrowIfNull(batch, $"batch is null !!!");
db_requester.setQueryBatch(batch);
m_db_requesters.Add(db_requester);
return getDbRequester<TDbRequester>();
}
public TDbRequester? getAfterAddDbRequester<TDbRequester>()
where TDbRequester : class, IQueryDbRequester, new()
{
var db_requester = new TDbRequester();
var batch = getQueryBatch();
ArgumentNullException.ThrowIfNull(batch, $"batch is null !!!");
db_requester.setQueryBatch(batch);
m_db_requesters.Add(db_requester);
return db_requester;
}
public TDbResponseType? getDbResponse<TDbResponseType>()
where TDbResponseType : AmazonWebServiceResponse
{
foreach (var db_response in m_db_responses)
{
var response_type = db_response as TDbResponseType;
if (null != response_type)
{
return response_type;
}
}
return null;
}
public TDbRequester? getDbRequester<TDbRequester>()
where TDbRequester : class, IQueryDbRequester
{
foreach (var db_requester in m_db_requesters)
{
var to_return_type = db_requester as TDbRequester;
if (null != to_return_type)
{
return to_return_type;
}
}
return null;
}
private void registerExceptionHandler(IQueryDbRequester queryDbRequester, DynamoDbQueryExceptionNotifier.ExceptionHandler? exceptionHandler)
{
var exception_notifier = queryDbRequester.getDynamoDbQueryExceptionNotifier();
ArgumentNullException.ThrowIfNull(exception_notifier, $"exception_notifier is null !!!");
exception_notifier.registerExceptionHandler(exceptionHandler);
}
public async Task<(Result, Result)> onHandleTransactionCancelException(TransactionCanceledException exception)
{
await Task.CompletedTask;
ArgumentNullException.ThrowIfNull(exception, $"exception is null !!!");
var result = new Result();
var exception_resullt = new Result();
var err_msg = string.Empty;
var exception_notifier = getDynamoDbQueryExceptionNotifierOfExecutingQuery();
if (null == exception_notifier)
{
err_msg = $"Not found ExceptionNotifier !!!";
result.setFail(ServerErrorCode.DynamoDbQueryExceptionNotifierNotFound, err_msg);
Log.getLogger().error(result.toBasicString());
return (result, exception_resullt);
}
// 트랜잭션 취소 원인에 따라 등록된 ServerErrorCode로 재설정 한다.
var query_count = exception.CancellationReasons.Count;
for (var query_seq_index = 0; query_seq_index < query_count; ++query_seq_index)
{
var reason = exception.CancellationReasons[query_seq_index];
// 성공일 경우 패스 !!!
if (true == reason.isSuccess())
{
continue;
}
var query_seq_no = query_seq_index + 1;
err_msg = $"TransactionCanceledException Reason : QuerySeq:{query_seq_no}/{query_count}, Code:{reason.Code}, Message:{reason.Message}, Item:{reason.Item}";
Log.getLogger().fatal(err_msg);
var exception_handler = exception_notifier.findExceptionHandler(query_seq_no, reason.Code);
if (null != exception_handler)
{
exception_resullt.setFail(exception_handler.ReturnToErrorCode, err_msg);
}
}
return (result, exception_resullt);
}
private DynamoDbQueryExceptionNotifier getDynamoDbQueryExceptionNotifierOfExecutingQuery()
{
ArgumentNullException.ThrowIfNull(m_executing_query_nullable, $"m_executing_query_nullable is null !!!");
return m_executing_query_nullable.getDynamoDbQueryExceptionNotifier();
}
}

View File

@@ -0,0 +1,194 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections.Concurrent;
using Amazon.Runtime;
using Amazon.DynamoDBv2;
using Amazon.DynamoDBv2.Model;
using Amazon.DynamoDBv2.DocumentModel;
using ServerCore; using ServerBase;
namespace ServerBase;
//==============================================================================================
// QueryContextBase 의 GetSet 함수들
//==============================================================================================
public abstract partial class QueryExecutorBase
{
public void setQueryBatch(QueryBatchBase parent) => m_parent = parent;
public QueryBatchBase? getQueryBatch() => m_parent;
public string getQueryName() => m_query_name;
}
//==============================================================================================
// QueryBatchBase 의 GetSet 함수들
//==============================================================================================
public abstract partial class QueryBatchBase
{
public bool isUseTransact() => m_is_use_transact;
public string getTransId() => m_trans_id;
public bool hasQuery() => 0 < m_querys.Count;
public Int32 getQueryCount() => m_querys.Count;
public IWithLogActor getLogActor() => m_log_actor;
public void setLogActor(IWithLogActor logActor) => m_log_actor = logActor;
public IQueryRunner getQueryRunner() => m_query_runner;
public DynamoDbClient getDynamoDbConnector() => m_dynamo_db_connector;
public LogAction getLogAction() => m_log_action;
public List<ILogInvoker> getLogInvokers() => m_business_logs;
}
//==============================================================================================
// QueryRunnerWithItemRequest 의 GetSet 함수들
//==============================================================================================
public partial class QueryRunnerWithItemRequest : IQueryRunner
{
public QueryBatchBase? getQueryBatch() => m_query_batch;
public void setQueryBatch(QueryBatchBase queryBatch) => m_query_batch = queryBatch;
public List<IQueryDbRequester> getDbRequesters() => m_db_requesters;
public List<AmazonWebServiceResponse> getDbResponses() => m_db_responses;
public void addDbResponse(AmazonWebServiceResponse dbResponse)
{
m_db_responses.Add(dbResponse);
}
}
//==============================================================================================
// QueryRunnerWithDocument 의 GetSet 함수들
//==============================================================================================
public partial class QueryRunnerWithDocument : IQueryRunner
{
public QueryBatchBase? getQueryBatch() => m_query_batch;
public void setQueryBatch(QueryBatchBase queryBatch) => m_query_batch = queryBatch;
}
//==============================================================================================
// QueryDbRequesterWithItemRequestBase<TDbRequestType> 의 GetSet 함수들
//==============================================================================================
public abstract partial class QueryDbRequesterWithItemRequestBase<TDbRequestType> : IQueryDbRequester
{
public TDbRequestType getDbRequestGenericType() => m_db_request;
public void setQueryBatch(QueryBatchBase queryBatch) => m_query_batch = queryBatch;
public QueryBatchBase? getQueryBatch() => m_query_batch;
public Int32 getQueryCount()
{
NullReferenceCheckHelper.throwIfNull(m_query_batch, () => $"m_query_batch is null !!!");
return m_query_batch.getQueryCount();
}
}
//==============================================================================================
// QueryDbRequesterWithDocumentBatchWriteBase<TDbRequestType> 의 GetSet 함수들
//==============================================================================================
public abstract partial class QueryDbRequesterWithDocumentBatchWriteBase<TDbRequestType> : IQueryDbRequester, IWithDocumentModel
where TDbRequestType : DocumentBatchWrite
{
public TDbRequestType? getDbRequestGenericType() => m_db_request;
public Table? getTable() => m_table;
public void setQueryBatch(QueryBatchBase queryBatch) => m_query_batch = queryBatch;
public QueryBatchBase? getQueryBatch() => m_query_batch;
public Int32 getQueryCount()
{
NullReferenceCheckHelper.throwIfNull(m_query_batch, () => $"m_query_batch is null !!!");
return m_query_batch.getQueryCount();
}
}
//==============================================================================================
// QueryDbRequesterWithDocumentBatchGetBase<TDbRequestType> 의 GetSet 함수들
//==============================================================================================
public abstract partial class QueryDbRequesterWithDocumentBatchGetBase<TDbRequestType> : IQueryDbRequester, IWithDocumentModel
where TDbRequestType : DocumentBatchGet
{
public TDbRequestType? getDbRequestGenericType() => m_db_request;
public Table? getTable() => m_table;
public void setQueryBatch(QueryBatchBase queryBatch) => m_query_batch = queryBatch;
public QueryBatchBase? getQueryBatch() => m_query_batch;
public Int32 getQueryCount()
{
NullReferenceCheckHelper.throwIfNull(m_query_batch, () => $"m_query_batch is null !!!");
return m_query_batch.getQueryCount();
}
}
//==============================================================================================
// QueryDbRequesterWithDocumentTransactWriteBase<TDbRequestType> 의 GetSet 함수들
//==============================================================================================
public abstract partial class QueryDbRequesterWithDocumentTransactWriteBase<TDbRequestType> : IQueryDbRequester, IWithDocumentModel
where TDbRequestType : DocumentTransactWrite
{
public TDbRequestType? getDbRequestGenericType() => m_db_request;
public Table? getTable() => m_table;
public void setQueryBatch(QueryBatchBase queryBatch) => m_query_batch = queryBatch;
public QueryBatchBase? getQueryBatch() => m_query_batch;
public Int32 getQueryCount()
{
NullReferenceCheckHelper.throwIfNull(m_query_batch, () => $"m_query_batch is null !!!");
return m_query_batch.getQueryCount();
}
}
//==============================================================================================
// QueryDbRequesterWithDocumentTransactGetBase<TDbRequestType> 의 GetSet 함수들
//==============================================================================================
public abstract partial class QueryDbRequesterWithDocumentTransactGetBase<TDbRequestType> : IQueryDbRequester, IWithDocumentModel
where TDbRequestType : DocumentTransactGet
{
public TDbRequestType? getDbRequestGenericType() => m_db_request;
public Table? getTable() => m_table;
public void setQueryBatch(QueryBatchBase queryBatch) => m_query_batch = queryBatch;
public QueryBatchBase? getQueryBatch() => m_query_batch;
public Int32 getQueryCount()
{
NullReferenceCheckHelper.throwIfNull(m_query_batch, () => $"m_query_batch is null !!!");
return m_query_batch.getQueryCount();
}
}

View File

@@ -0,0 +1,8 @@
<Project>
<PropertyGroup>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<BaseIntermediateOutputPath>..\..\obj\AnyCPU\$(MSBuildProjectName)\</BaseIntermediateOutputPath>
<BaseOutputPath>..\..\bin\</BaseOutputPath>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,46 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ServerCore; using ServerBase;
using SESSION_ID = System.Int32;
using META_ID = System.UInt32;
using ENTITY_GUID = System.String;
using ACCOUNT_ID = System.String;
using OWNER_GUID = System.String;
using USER_GUID = System.String;
using CHARACTER_GUID = System.String;
using ITEM_GUID = System.String;
namespace ServerBase;
public abstract partial class EntityActionBase : IInitializer
{
private EntityBase m_owner;
public EntityActionBase(EntityBase owner)
{
m_owner = owner;
}
public abstract Task<Result> onInit();
public abstract void onClear();
public virtual async Task onTick()
{
await Task.CompletedTask;
}
public virtual string toBasicString()
{
return $"{this.getTypeName()}";
}
}

View File

@@ -0,0 +1,72 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using ServerCore; using ServerBase;
namespace ServerBase;
public class EntityAggregator<TKey, TCounterType>
where TKey : ITuple
where TCounterType : struct
{
private readonly ConcurrentDictionary<TKey, TCounterType> m_counters = new();
public EntityAggregator()
{
}
public void reset()
{
m_counters.Clear();
}
public void incCounter(TKey key, TCounterType toIncValue, Func<TCounterType, TCounterType, TCounterType> fnUpdate)
{
var add_counter = delegate (TKey key)
{
return toIncValue;
};
var update_recorder = delegate (TKey key, TCounterType currValue)
{
return fnUpdate(currValue, toIncValue);
};
m_counters.AddOrUpdate(key, add_counter, update_recorder);
}
public void decCounter(TKey key, TCounterType toDecValue, Func<TCounterType, TCounterType, TCounterType> fnUpdate)
{
var add_counter = delegate (TKey key)
{
return toDecValue;
};
var update_recorder = delegate (TKey key, TCounterType currValue)
{
return fnUpdate(currValue, toDecValue);
};
m_counters.AddOrUpdate(key, add_counter, update_recorder);
}
public TCounterType getCounter(TKey key, TCounterType defaultValue)
{
if(false == m_counters.TryGetValue(key, out var found_counter))
{
return defaultValue;
}
return found_counter;
}
public ConcurrentDictionary<TKey, TCounterType> getCounters() => m_counters;
}

View File

@@ -0,0 +1,397 @@
using System;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Threading;
using Newtonsoft.Json;
using NeoSmart;
using NeoSmart.AsyncLock;
using ServerCore; using ServerBase;
using SESSION_ID = System.Int32;
using WORLD_ID = System.UInt32;
using META_ID = System.UInt32;
using ENTITY_GUID = System.String;
using ACCOUNT_ID = System.String;
using OWNER_GUID = System.String;
using USER_GUID = System.String;
using CHARACTER_GUID = System.String;
using ITEM_GUID = System.String;
namespace ServerBase;
public abstract partial class EntityAttributeBase : IInitializer
{
public enum StateType
{
None = 0,
Created, // 신규 정보로 추가 한다. delta count == Stack count !!!
Modified, // +, - : 증가, 감소
Removed, // 제거
}
public class AttributeState : Flags<StateType>
{
public AttributeState()
{
reset();
}
public void tryApplyModifyFlag(bool isTrue)
{
setFlag(StateType.Modified, isTrue);
}
}
#region
private EntityBase m_owner;
#endregion
private bool m_is_origin = true;
private DynamoDbDocBase? m_origin_doc_base_nullable;
private DynamoDbDocBase? m_try_pending_doc_base_nullable;
// CommonResult에 적용 되는가?
private readonly bool m_is_applicable_to_common_result;
private AttributeState m_attribute_state = new();
private bool m_is_upsert = false;
private bool m_is_delete = false;
private EntityRecorder? m_recorder_nullable;
#region Owner Contents [OwnerEntityType, EntityBase]
private OwnerEntityType m_owner_entity_type = OwnerEntityType.None;
private EntityBase? m_entity_of_owner_entity_type = null;
#endregion
private ReaderWriterLockSlim m_rw_lock = new();
private AsyncLock m_async_lock = new();
//=========================================================================================
// Owner 객체와 OwnerEntityType 객체가 동일한 경우
//=========================================================================================
public EntityAttributeBase(EntityBase owner, bool isApplicableCommonResult = false)
{
m_owner = owner;
setEntityOfOwnerEntityType(owner.onGetOwnerEntityType(), owner);
m_is_applicable_to_common_result = isApplicableCommonResult;
}
//=========================================================================================
// Owner 객체와 OwnerEntityType 객체가 다른 경우
//=========================================================================================
public EntityAttributeBase(EntityBase owner, EntityBase entityOfOwnerEntityType, bool isApplicableCommonResult = false)
{
if(false == entityOfOwnerEntityType.isValidOwnerEntityType())
{
throw new ArgumentException($"argument exception isValidOwnerEntityType() !!! : {entityOfOwnerEntityType.toBasicString()} - {toBasicString()}");
}
m_owner = owner;
setEntityOfOwnerEntityType(entityOfOwnerEntityType.onGetOwnerEntityType(), entityOfOwnerEntityType);
m_is_applicable_to_common_result = isApplicableCommonResult;
}
public virtual async Task<Result> onInit()
{
var result = new Result();
return await Task.FromResult(result);
}
public abstract void onClear();
public virtual void newEntityAttribute()
{
getAttributeState().setFlag(StateType.Created, true);
}
public virtual void deleteEntityAttribute()
{
getAttributeState().reset();
m_is_upsert = false;
getAttributeState().setFlag(StateType.Removed, true);
}
public virtual void modifiedEntityAttribute(bool isUpsert = false)
{
if (true == getAttributeState().hasFlag(StateType.Created))
{
return;
}
getAttributeState().setFlag(StateType.Modified, true);
if(true == isUpsert)
{
m_is_upsert = isUpsert;
}
}
public abstract IEntityAttributeTransactor onNewEntityAttributeTransactor();
public abstract EntityAttributeBase onCloned();
protected void deepCopyFromBase(EntityAttributeBase other)
{
m_attribute_state.deepCopy(other.getAttributeState());
}
public virtual DynamoDbDocBase? onCreateDocBase()
{
var err_msg = $"Not implementation EntityAttribute.onCreateDocBase() !!! : {this.getTypeName()} - {getOwner().toBasicString()}";
Log.getLogger().error(err_msg);
throw new NotImplementedException(err_msg);
}
public virtual async Task<(Result, DynamoDbDocBase?)> toDocBase(bool isForQuery = true)
{
var result = new Result();
var err_msg = $"Not implementation EntityAttribute.toDocBase() !!! : {this.getTypeName()} - {getOwner().toBasicString()}";
result.setFail(ServerErrorCode.NotImplemented, err_msg);
Log.getLogger().warn(result.toBasicString());
return await Task.FromResult<(Result, DynamoDbDocBase?)>((result, null));
}
public virtual async Task<Result> fillupDocBase<TDoc>(TDoc copyToDocBase)
where TDoc : DynamoDbDocBase
{
var result = new Result();
var err_msg = string.Empty;
var owner = getOwner();
var parent = owner.getRootParent();
var copy_to_doc = copyToDocBase as ICopyDocFromEntityAttribute;
if(null == copy_to_doc)
{
err_msg = $"Failed to cast ICopyDocFromEntityAttribute !!! : {copyToDocBase.getTypeName()} - {owner.toBasicString()}";
result.setFail(ServerErrorCode.ClassDoesNotImplementInterfaceInheritance, err_msg);
Log.getLogger().error(result.toBasicString());
return result;
}
if(false == copy_to_doc.copyDocFromEntityAttribute(this))
{
err_msg = $"Failed to copyDocFromEntityAttribute() !!!, to:{copyToDocBase.getTypeName()}, from:{this.getTypeName()} - {toSummaryString()}, {owner.toBasicString()}";
result.setFail(ServerErrorCode.DynamoDbDocCopyToEntityAttributeFailed, err_msg);
Log.getLogger().error(result.toBasicString());
return result;
}
return await Task.FromResult(result);
}
public async Task<Result> fillupDoc4QueryWithAttribute<TDoc>(TDoc docBase)
where TDoc : DynamoDbDocBase
{
var result = new Result();
var err_msg = string.Empty;
var owner = getOwner();
var parent = owner.getRootParent();
result = await fillupDocBase<TDoc>(docBase);
if (result.isFail())
{
err_msg = $"Failed to fillupDocBase() !!! : {result.toBasicString()} - {owner.toBasicString()}";
Log.getLogger().error(err_msg);
return result;
}
(result, _) = await applyDoc4Query(docBase);
if(result.isFail())
{
return result;
}
return result;
}
protected async Task<(Result, DynamoDbDocBase?)> applyDoc4Query(DynamoDbDocBase docBase)
{
var result = new Result();
var err_msg = string.Empty;
var owner = getOwner();
var parent = owner.getRootParent();
bool is_apply = false;
if (true == getAttributeState().hasFlag(StateType.Created))
{
result = await docBase.newDoc4Query();
if (result.isFail())
{
return (result, null);
}
is_apply = true;
}
else
{
if (true == getAttributeState().hasFlag(StateType.Modified))
{
if (true == m_is_upsert)
{
result = await docBase.upsertDoc4Query();
if (result.isFail())
{
return (result, null);
}
}
else
{
result = await docBase.updateDoc4Query();
if (result.isFail())
{
return (result, null);
}
}
m_is_upsert = false;
is_apply = true;
}
if (true == getAttributeState().hasFlag(StateType.Removed))
{
result = await docBase.deleteDoc4Query();
if (result.isFail())
{
return (result, null);
}
is_apply = true;
setDelete();
}
}
if (false == is_apply)
{
err_msg = $"Invalid AttributeState !!!, all false state - {owner.toBasicString()}";
result.setFail(ServerErrorCode.DynamoDbDocAttributeStateNotSet, err_msg);
return (result, null);
}
getAttributeState().reset();
return (result, docBase);
}
public void enterReadLock()
{
m_rw_lock.EnterReadLock();
}
public void exitReadLock()
{
m_rw_lock.ExitReadLock();
}
public void enterWriteLock()
{
m_rw_lock.EnterWriteLock();
}
public void exitWriteLock()
{
m_rw_lock.ExitWriteLock();
}
public bool hasQueryFlag()
{
return 0 < getAttributeState().getData().Count;
}
#region DB
//=================================================================================================
// EntityAttribute로 Db에 저장할 OwnerEntity를 반환해 준다.
//=================================================================================================
public virtual (Result, OwnerEntityType, OWNER_GUID) onToOwnerEntity4Db(EntityBase ownerEntity)
{
var result = new Result();
result.setFail(ServerErrorCode.NotImplemented, $"Not implemented onToOwnerEntity4Db() !!! - {getOwner().toBasicString()}");
return (result, OwnerEntityType.None, string.Empty);
}
//=========================================================================================
// DB 에 저장되어 있는 정보가 있는 경우 호출되어야 한다.
//=========================================================================================
public void syncOriginDocBaseWithNewDoc<EntityAttributeType>(DynamoDbDocBase newDoc)
where EntityAttributeType : EntityAttributeBase
{
if(true == isOrigin())
{
m_origin_doc_base_nullable = newDoc;
return;
}
var owner = getOwner();
var origin_entity_attribute = owner.getOriginEntityAttribute<EntityAttributeType>();
NullReferenceCheckHelper.throwIfNull(origin_entity_attribute, () => $"origin_entity_attribute is null !!! - {owner.toBasicString()}");
origin_entity_attribute.syncOriginDocBaseWithNewDoc<EntityAttributeType>(newDoc);
}
public DynamoDbDocBase? getOriginDocBase<EntityAttributeType>()
where EntityAttributeType : EntityAttributeBase
{
if (true == isOrigin())
{
return m_origin_doc_base_nullable;
}
var owner = getOwner();
var origin_entity_attribute = owner.getOriginEntityAttribute<EntityAttributeType>();
NullReferenceCheckHelper.throwIfNull(origin_entity_attribute, () => $"origin_entity_attribute is null !!! - {owner.toBasicString()}");
return origin_entity_attribute.getOriginDocBase<EntityAttributeType>();
}
#endregion DB
#region
public virtual string toSummaryString()
{
return $"{this.getTypeName()}, {JsonConvert.SerializeObject(this)}";
}
public virtual string toBasicString()
{
return $"{this.getTypeName()}, isOrigin:{isOrigin()}, hasQueryFlag:{hasQueryFlag()}";
}
#endregion
}

View File

@@ -0,0 +1,113 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ServerCore; using ServerBase;
using SESSION_ID = System.Int32;
using WORLD_ID = System.UInt32;
using META_ID = System.UInt32;
using ENTITY_GUID = System.String;
using ACCOUNT_ID = System.String;
using OWNER_GUID = System.String;
using USER_GUID = System.String;
using CHARACTER_GUID = System.String;
using ITEM_GUID = System.String;
namespace ServerBase;
public abstract partial class EntityAttributeTransactorBase<TEntityAttribute> : IEntityAttributeTransactor
where TEntityAttribute : EntityAttributeBase
{
public enum TransactionState
{
None = 0,
New, // 생성
Modify, // 변경
Remove, // 제거
Completed // 처리 완료
}
private EntityBase m_owner;
private TEntityAttribute? m_cloned_entity_attribute_nullable;
private TransactionState m_transaction_state = TransactionState.None;
private readonly EntityRecorder m_entity_recorder;
private TEntityAttribute? m_origin_entity_attribute_nullable;
public EntityAttributeTransactorBase(EntityBase owner)
{
m_owner = owner;
m_entity_recorder = new EntityRecorder(owner);
}
public bool cloneFromOriginEntityAttribute(EntityAttributeBase fromOriginEntityAttribute)
{
m_cloned_entity_attribute_nullable = fromOriginEntityAttribute.onCloned() as TEntityAttribute;
if(null == m_cloned_entity_attribute_nullable)
{
var err_msg = $"m_cloned_entity_attribute_nullable is null !!! : toCloneTarget:{typeof(TEntityAttribute).Name} != OriginEntityAttribute:{fromOriginEntityAttribute.getTypeName()} - {getOwner().toBasicString()}";
Log.getLogger().error(err_msg);
return false;
}
m_cloned_entity_attribute_nullable.setCloned();
m_cloned_entity_attribute_nullable.bindEntityRecorder(m_entity_recorder);
m_origin_entity_attribute_nullable = fromOriginEntityAttribute as TEntityAttribute;
return true;
}
public bool isReadOnly()
{
NullReferenceCheckHelper.throwIfNull(m_cloned_entity_attribute_nullable, () => $"m_cloned_entity_attribute_nullable is null !!! - {getOwner().toBasicString()}");
if(true == m_cloned_entity_attribute_nullable.getAttributeState().isEmptyFlags())
{
return true;
}
return false;
}
public async Task<(Result, DynamoDbDocBase?)> makeDocBase()
{
NullReferenceCheckHelper.throwIfNull(m_cloned_entity_attribute_nullable, () => $"m_cloned_entity_attribute_nullable is null !!! - {getOwner().toBasicString()}");
(var result, var doc) = await m_cloned_entity_attribute_nullable.toDocBase();
if(result.isFail())
{
return (result, null);
}
return (result, doc);
}
public virtual void onClear()
{
m_cloned_entity_attribute_nullable?.onClear();
m_entity_recorder.reset();
}
public ENTITY_GUID getEntityGuid() => getOwner().getEntityGuid();
public virtual string toBasicString()
{
return $"{this.getTypeName()}, {m_cloned_entity_attribute_nullable?.toBasicString()}, {getOwner().toBasicString()}";
}
}

View File

@@ -0,0 +1,82 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Google.Protobuf.WellKnownTypes;
using Newtonsoft.Json.Linq;
namespace ServerBase;
public class EntityRecorder
{
private readonly EntityBase m_owner;
private readonly ConcurrentDictionary<object, NormalDeltaRecorder> m_normal_delta_recorders = new();
public EntityRecorder(EntityBase owner)
{
m_owner = owner;
}
public void applyDeltaCounter(object entityDeltaType, double deltaCount, double totalCount)
{
var add_recorder = delegate (object deltaType)
{
return new NormalDeltaRecorder(entityDeltaType, deltaCount, totalCount);
};
var update_recorder = delegate (object key, NormalDeltaRecorder value)
{
value.incDeltaCount(deltaCount);
value.setTotalCount(totalCount);
return value;
};
m_normal_delta_recorders.AddOrUpdate(entityDeltaType, add_recorder, update_recorder);
}
public NormalDeltaRecorder? findDeltaCounter(object deltaType)
{
m_normal_delta_recorders.TryGetValue(deltaType, out var found_delta_counter);
return found_delta_counter;
}
public void reset()
{
m_normal_delta_recorders.Clear();
}
}
public class NormalDeltaRecorder
{
private object m_entity_delta_type;
private double m_delta_count;
private double m_total_count;
public NormalDeltaRecorder(object deltaType, double deltaCount, double totalCount)
{
m_entity_delta_type = deltaType;
m_delta_count = deltaCount;
m_total_count = totalCount;
}
public void setDeltaCount(double deltaCount) => m_delta_count = deltaCount;
public double getDeltaCount() => m_delta_count;
public void incDeltaCount(double deltaCount) => m_delta_count += deltaCount;
public void decDeltaCount(double deltaCount) => m_delta_count -= deltaCount;
public void setTotalCount(double totalCount) => m_total_count = totalCount;
public double getTotalCount() => m_total_count;
public object getEntityDeltaType() => m_entity_delta_type;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,75 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using AsyncStateMachine;
using ServerCore; using ServerBase;
using SESSION_ID = System.Int32;
using META_ID = System.UInt32;
using ENTITY_GUID = System.String;
using ACCOUNT_ID = System.String;
using OWNER_GUID = System.String;
using USER_GUID = System.String;
using CHARACTER_GUID = System.String;
using ITEM_GUID = System.String;
namespace ServerBase;
public abstract partial class EntityHFSMBase : HFSMBase<EntityStateTriggerType, EntityStateType>, IWithEntityOwner
{
private readonly EntityBase m_owner;
public EntityHFSMBase(EntityBase owner)
: base()
{
m_owner = owner;
}
public async Task<Result> loadHfsm()
{
var result = new Result();
var err_msg = string.Empty;
if (false == await initHfsm(EntityStateType.Created, setupHfsm))
{
err_msg = $"\"Failed to create Entity HFSM !!!";
result.setFail(ServerErrorCode.EntityBaseHfsmInitFailed, err_msg);
return result;
}
return result;
}
protected abstract bool setupHfsm(HFSMBase<EntityStateTriggerType, EntityStateType> initHFSM);
public override void onSubscribeStateChangeNotify()
{
var sm = getStateMachine();
NullReferenceCheckHelper.throwIfNull(sm, () => $"sm is null !!!");
sm.Observable.Subscribe(onTransitionedState);
}
protected virtual void onTransitionedState(AsyncStateMachine.Transition<EntityStateTriggerType, EntityStateType> transition)
{
if (false == transition.isChangedState())
{
return;
}
var owner = getOwner();
var debug_msg = $"EntityHFSM transited State !!! : {transition.toBasicString()} - {owner.toBasicString()}";
Log.getLogger().debug(debug_msg);
}
}

View File

@@ -0,0 +1,61 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ServerCore; using ServerBase;
using SESSION_ID = System.Int32;
using WORLD_ID = System.UInt32;
using META_ID = System.UInt32;
using ENTITY_GUID = System.String;
using ACCOUNT_ID = System.String;
using OWNER_GUID = System.String;
using USER_GUID = System.String;
using CHARACTER_GUID = System.String;
using ITEM_GUID = System.String;
namespace ServerBase;
public abstract partial class EntityStateBase
{
private readonly EntityBase m_owner;
private DateTime m_start_time = DateTime.MinValue;
private DateTime m_end_time = DateTime.MinValue;
public EntityStateBase(EntityBase owner)
{
m_owner = owner;
}
public virtual async Task onEnter()
{
await Task.CompletedTask;
m_start_time = DateTimeHelper.Current;
}
public virtual async Task onTick()
{
await Task.CompletedTask;
}
public virtual async Task onExit()
{
await Task.CompletedTask;
m_end_time = DateTimeHelper.Current;
}
public DateTime getStartTime() { return m_start_time; }
public DateTime getEndTime() { return m_end_time; }
public UInt64 getElapsedMilliSeconds()
{
return (UInt64)(m_end_time - m_start_time).TotalMilliseconds;
}
}

View File

@@ -0,0 +1,107 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ServerCore; using ServerBase;
namespace ServerBase;
public abstract class EntityTicker : EntityBase, ITaskTicker
{
private double m_tick_interval_milliseconds;
private PeriodicTaskTimer? m_timer_nullable;
private readonly CancellationTokenSource m_cancel_token;
private readonly bool m_is_cancel_by_self;
public EntityTicker( EntityType entityType
, double onTickIntervalMilliseconds, CancellationTokenSource? toCancelToken )
: base(entityType)
{
m_tick_interval_milliseconds = onTickIntervalMilliseconds;
if(null == toCancelToken)
{
m_cancel_token = new();
m_is_cancel_by_self = true;
}
else
{
m_cancel_token = toCancelToken;
m_is_cancel_by_self = false;
}
}
public EntityTicker(EntityType entityType, EntityBase parent, Int32 onTickIntervalMilliseconds, CancellationTokenSource toCancelToken)
: base(entityType, parent)
{
m_tick_interval_milliseconds = onTickIntervalMilliseconds;
if(null == toCancelToken)
{
m_cancel_token = new();
m_is_cancel_by_self = true;
}
else
{
m_cancel_token = toCancelToken;
m_is_cancel_by_self = false;
}
}
public override async Task<Result> onInit()
{
return await onCreateTask();
}
public virtual async Task<Result> onCreateTask()
{
await Task.CompletedTask;
var result = new Result();
try
{
m_timer_nullable = new PeriodicTaskTimer( GetType().Name
, getOnTickIntervalMilliseconds()
, getCancelToken(), onTaskTick );
}
catch(Exception e)
{
var err_msg = $"Exception !!!, new PeriodicTaskTimer() : exception:{e} - {toBasicString()}";
result.setFail(ServerErrorCode.TryCatchException, err_msg);
return result;
}
return result;
}
public abstract Task onTaskTick();
public async Task onTaskWait()
{
if (m_timer_nullable != null)
await m_timer_nullable.getTask();
}
public void cancel()
{
if(true == m_is_cancel_by_self)
{
m_timer_nullable?.cancelTimer();
}
}
public double getOnTickIntervalMilliseconds() => m_tick_interval_milliseconds;
public CancellationTokenSource getCancelToken() => m_cancel_token;
public PeriodicTaskTimer? getPeriodicTaskTimer() => m_timer_nullable;
}

View File

@@ -0,0 +1,77 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NeoSmart.AsyncLock;
using ServerCore; using ServerBase;
using MASTER_GUID = System.String;
namespace ServerBase;
public class EntityTransactionRunnerWithScopLock : IDisposable
{
private readonly EntityBase m_owner;
private readonly MASTER_GUID m_transact_master_guid;
public EntityTransactionRunnerWithScopLock(EntityBase owner, string transactMasterGuid = "")
{
m_owner = owner;
m_transact_master_guid = transactMasterGuid;
}
public void Dispose()
{
if (m_transact_master_guid.isNullOrWhiteSpace())
{
return;
}
var master_guid = m_owner.getMasterGuid();
if (m_transact_master_guid != master_guid)
{
Log.getLogger().error($"Not matched MasterGuid !!! - attachedMasterGuid:{master_guid} == settedMasterGuid:{m_transact_master_guid}");
return;
}
m_owner.detachMasterGuid();
}
public async Task<T> tryInvokeWithAsyncLock<T>(Func<Task<T>> func)
{
var owner = getOwner();
NullReferenceCheckHelper.throwIfNull(owner, () => $"owner is null !!!");
var result = default(T);
var err_msg = string.Empty;
using (var releaser = await m_owner.getAsyncLock())
{
err_msg = $"AsyncLock() Start !!! - {m_owner.toBasicString()}";
Log.getLogger().debug(err_msg);
if(false == m_transact_master_guid.isNullOrWhiteSpace())
{
ConditionValidCheckHelper.throwIfFalseWithCondition( () => true == m_owner.attachMasterGuid(m_transact_master_guid)
, () => $"Failed to attach Master !!! : transactMasterGuid:{m_transact_master_guid} - {owner.toBasicString()}");
}
result = await func();
err_msg = $"AsyncLock() End !!! - {m_owner.toBasicString()}";
Log.getLogger().debug(err_msg);
}
return result;
}
public EntityBase getOwner() => m_owner;
}

View File

@@ -0,0 +1,847 @@
using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.ConstrainedExecution;
using NLog;
using Amazon.DynamoDBv2.Model;
using Newtonsoft.Json;
using Pipelines.Sockets.Unofficial.Buffers;
using StackExchange.Redis;
using Google.Protobuf.Collections;
using ServerControlCenter;
using Amazon.S3.Model;
using ServerCore; using ServerBase;
using SESSION_ID = System.Int32;
using WORLD_ID = System.UInt32;
using META_ID = System.UInt32;
using TRANSACTION_NAME = System.String;
using TRANSACTION_GUID = System.String;
using ENTITY_GUID = System.String;
using ACCOUNT_ID = System.String;
using OWNER_GUID = System.String;
using USER_GUID = System.String;
using CHARACTER_GUID = System.String;
using ITEM_GUID = System.String;
namespace ServerBase;
public class ReservedSlotOnInven
{
public class ReservedSlot
{
private Int16 m_reserved_count = 0;
private EntityBase? m_equip_entity_nullable;
private EntityBase? m_unequip_entity_nullable;
public ReservedSlot()
{ }
public void incReservedCount(Int16 reservedItemCount)
{
m_reserved_count += reservedItemCount;
}
public void decReservedCount(Int16 reservedItemCount)
{
m_reserved_count -= reservedItemCount;
}
public Int16 getReservedCount() => m_reserved_count;
public void setEquipEntity(EntityBase? entityBase) => m_equip_entity_nullable = entityBase;
public EntityBase? getEquipEntity() => m_equip_entity_nullable;
public void setUnequipEntity(EntityBase? entityBase) => m_unequip_entity_nullable = entityBase;
public EntityBase? getUnequipEntity() => m_unequip_entity_nullable;
}
private EntityType m_entity_inven_type = EntityType.None;
private readonly Dictionary<string, ReservedSlot> m_reserved_slots = new();
public ReservedSlotOnInven(EntityType entityInvenType)
{
m_entity_inven_type = entityInvenType;
}
public void addOrInc(string slotKey, Int16 incReservedCount)
{
if(false == m_reserved_slots.TryGetValue(slotKey, out var found_reserved_slot))
{
found_reserved_slot = new ReservedSlot();
m_reserved_slots.Add(slotKey, found_reserved_slot);
}
found_reserved_slot.incReservedCount(incReservedCount);
}
public void addOrDec(string slotKey, Int16 decReservedCount)
{
if (false == m_reserved_slots.TryGetValue(slotKey, out var found_reserved_slot))
{
found_reserved_slot = new ReservedSlot();
m_reserved_slots.Add(slotKey, found_reserved_slot);
}
found_reserved_slot.decReservedCount(decReservedCount);
}
public Dictionary<string, ReservedSlot> getReservedSlots() => m_reserved_slots;
public EntityType getEntityType() => m_entity_inven_type;
}
public partial class TransactionRunner : IDisposable
{
private readonly EntityBase m_owner;
private readonly string m_trans_id = string.Empty;
private readonly TransactionIdType m_transaction_id_type = TransactionIdType.None;
private readonly ConcurrentDictionary<Type, ConcurrentDictionary<ENTITY_GUID, IEntityAttributeTransactor>> m_entity_attribute_transactors = new();
private readonly ConcurrentQueue<IEntityAttributeTransactor> m_to_db_query_transactors = new(); // Db Query용 (순서 보장 중요)
private readonly ConcurrentDictionary<EntityBase, ConcurrentDictionary<EntityType, ReservedSlotOnInven>> m_reserved_inven_slots_of_owners = new();
private readonly ConcurrentDictionary<string, EntityCommonResult> m_entity_common_results = new();
//IRemoteTransaction
private readonly List<Func<Task>> m_remote_transaction_funcs = new();
private string m_transaction_name;
private bool m_is_binding = false;
public TransactionRunner(EntityBase owner, TransactionIdType idType, string transactionName)
{
m_owner = owner;
m_trans_id = System.Guid.NewGuid().ToString("N");
m_transaction_id_type = idType;
m_transaction_name = transactionName;
}
public TransactionRunner(EntityBase owner, string transId, TransactionIdType idType, string transactionName)
{
m_owner = owner;
m_trans_id = transId;
m_transaction_id_type = idType;
m_transaction_name = transactionName;
}
public Result beginTransaction()
{
var result = new Result();
var err_msg = string.Empty;
result = m_owner.bindTransactionRunner(this);
if(result.isFail())
{
return result;
}
setBinding();
return result;
}
public void clearTransaction()
{
m_entity_attribute_transactors.Clear();
m_reserved_inven_slots_of_owners.Clear();
m_to_db_query_transactors.Clear();
}
// override IDisposable.Dispose
public void Dispose()
{
var err_msg = string.Empty;
if (false == isBinding())
{
return;
}
var owner = getOwner();
NullReferenceCheckHelper.throwIfNull(owner, () => $"owner is null !!!");
if (false == m_owner.unbindTransactionRunner(this))
{
err_msg = $"Failed to unbindTransactionRunner() !!! : {toBasicString()} - {owner.toBasicString()}";
Log.getLogger().error(err_msg);
return;
}
}
public void resetTransaction(string transactionName)
{
clearTransaction();
m_transaction_name = transactionName;
}
public async Task<Result> onCommitResults(QueryBatchBase? queryBatch = null)
{
await Task.CompletedTask;
var owner = getOwner();
NullReferenceCheckHelper.throwIfNull(owner, () => $"owner is null !!!");
var result = new Result();
var err_msg = string.Empty;
var transactors = getEntityAttributeTransactorAll();
// 메모리를 동기화 한다.
foreach (var to_sync_merge in transactors)
{
var origin_entity_attribute = to_sync_merge.getOriginEntityAttribute();
NullReferenceCheckHelper.throwIfNull(origin_entity_attribute, () => $"origin_entity_attribute is null !!! - {owner.toBasicString()}");
var to_merge_attribute = origin_entity_attribute as IMergeWithEntityAttribute;
if (null == to_merge_attribute)
{
err_msg = $"Failed to cast IMergeWithEntityAttribute !!! : OriginEntityAttribute:{origin_entity_attribute.toBasicString()} - {owner.toBasicString()}";
Log.getLogger().error(err_msg);
continue;
}
var cloned_entity_attribute = to_sync_merge.getClonedEntityAttribute();
if (null == cloned_entity_attribute)
{
err_msg = $"getClonedEntityAttribute() is null !!! : OriginEntityAttribute:{origin_entity_attribute.toBasicString()} - {owner.toBasicString()}";
Log.getLogger().error(err_msg);
continue;
}
var comon_result_filler = cloned_entity_attribute as IWithCommonResultFiller;
if (null != comon_result_filler)
{
(result, var entity_common_result) = getOrNewEntityCommonResult(cloned_entity_attribute);
if (result.isFail())
{
Log.getLogger().fatal(result.toBasicString());
}
else
{
NullReferenceCheckHelper.throwIfNull(entity_common_result, () => $"entity_common_result is null !!! - {owner.toBasicString()}");
comon_result_filler.onFillCommonResult(entity_common_result, origin_entity_attribute, queryBatch);
}
}
result = to_merge_attribute.onMerge(cloned_entity_attribute);
if (result.isFail())
{
err_msg = $"Failed to onMerge() !!! : {result.toBasicString()} - {owner.toBasicString()}";
Log.getLogger().debug(err_msg);
continue;
}
}
var reserved_iven_slots_of_owners = getReservedInvenSlotAllOfOwners();
foreach(var each in reserved_iven_slots_of_owners)
{
var inven_owner = each.Key;
var reserved_inven_slots = each.Value;
var to_merge_inventory = inven_owner as IMergeWithInventory;
if (null != to_merge_inventory)
{
result = await to_merge_inventory.onMerge(reserved_inven_slots.Values.ToList<ReservedSlotOnInven>(), this);
if (result.isFail())
{
err_msg = $"Failed to IMergeWithInventory.onMerge() !!! : {result.toBasicString()} - {inven_owner.toBasicString()}, {owner.toBasicString()}";
Log.getLogger().fatal(err_msg);
continue;
}
}
}
reserved_iven_slots_of_owners.Clear();
await tryStartRemoteTransaction();
return result;
}
public async Task<Result> onCommitResults4DbQuery(List<IEntityAttributeTransactor> dbQueriedTransactors, QueryBatchBase? queryBatch = null)
{
await Task.CompletedTask;
var owner = getOwner();
NullReferenceCheckHelper.throwIfNull(owner, () => $"owner is null !!!");
NullReferenceCheckHelper.throwIfNull(dbQueriedTransactors, () => $"dbQueriedTransactors is null !!! - {owner.toBasicString()}");
var result = new Result();
var err_msg = string.Empty;
foreach (var to_sync_merge in dbQueriedTransactors)
{
var origin_entity_attribute = to_sync_merge.getOriginEntityAttribute();
NullReferenceCheckHelper.throwIfNull(origin_entity_attribute, () => $"origin_entity_attribute is null !!! - {owner.toBasicString()}");
var to_merge_attribute = origin_entity_attribute as IMergeWithEntityAttribute;
if (null == to_merge_attribute)
{
err_msg = $"Failed to cast IMergeWithEntityAttribute !!! : OriginEntityAttribute:{origin_entity_attribute.toBasicString()} - {owner.toBasicString()}";
Log.getLogger().error(err_msg);
continue;
}
var cloned_entity_attribute = to_sync_merge.getClonedEntityAttribute();
if (null == cloned_entity_attribute)
{
err_msg = $"getClonedEntityAttribute() is null !!! : OriginEntityAttribute:{origin_entity_attribute.toBasicString()} - {owner.toBasicString()}";
Log.getLogger().error(err_msg);
continue;
}
if(true == m_entity_attribute_transactors.TryGetValue(origin_entity_attribute.GetType(), out var found_transactors))
{
// DB 처리후 여기서 origin_entity_attribute Merge를 하므로
// onCommitResults() 함수로 중복된 Merge를 시도할 수 있으므로 m_entity_attribute_transactors 에서 제거 한다. - kangms
found_transactors.Remove(origin_entity_attribute.getOwner().getEntityGuid(), out _);
}
var comon_result_filler = cloned_entity_attribute as IWithCommonResultFiller;
if (null != comon_result_filler)
{
(result, var entity_common_result) = getOrNewEntityCommonResult(cloned_entity_attribute);
if (result.isFail())
{
Log.getLogger().fatal(result.toBasicString());
}
else
{
NullReferenceCheckHelper.throwIfNull(entity_common_result, () => $"entity_common_result is null !!! - {owner.toBasicString()}");
comon_result_filler.onFillCommonResult(entity_common_result, origin_entity_attribute, queryBatch);
}
}
result = to_merge_attribute.onMerge(cloned_entity_attribute);
if (result.isFail())
{
err_msg = $"Failed to onMerge() !!! : {result.toBasicString()} - {owner.toBasicString()}";
Log.getLogger().error(err_msg);
return result;
}
}
var reserved_iven_slots_of_owners = getReservedInvenSlotAllOfOwners();
foreach (var each in reserved_iven_slots_of_owners)
{
var inven_owner = each.Key;
var reserved_inven_slots = each.Value;
var to_merge_inventory = inven_owner as IMergeWithInventory;
if (null != to_merge_inventory)
{
result = await to_merge_inventory.onMerge(reserved_inven_slots.Values.ToList<ReservedSlotOnInven>(), this);
if (result.isFail())
{
err_msg = $"Failed to IMergeWithInventory.onMerge() !!! : {result.toBasicString()} - {inven_owner.toBasicString()}, {owner.toBasicString()}";
Log.getLogger().error(err_msg);
return result;
}
}
}
reserved_iven_slots_of_owners.Clear();
await tryStartRemoteTransaction();
return result;
}
public ConcurrentDictionary<ENTITY_GUID, IEntityAttributeTransactor> getEntityAttributeTransactorAll(Type targetEntityAttribute)
{
var err_msg = string.Empty;
var entity_attribute_transactors = new List<IEntityAttributeTransactor>();
if(false == m_entity_attribute_transactors.TryGetValue(targetEntityAttribute, out var found_transactors))
{
return new ConcurrentDictionary<ENTITY_GUID, IEntityAttributeTransactor>();
}
return found_transactors;
}
public ConcurrentDictionary<ENTITY_GUID, IEntityAttributeTransactor> getEntityAttributeTransactorAll<TEntityAttributeType>()
where TEntityAttributeType : EntityAttributeBase
{
var err_msg = string.Empty;
var entity_attribute_transactors = new List<IEntityAttributeTransactor>();
var target_type = typeof(TEntityAttributeType);
if(false == m_entity_attribute_transactors.TryGetValue(target_type, out var found_transactors))
{
return new ConcurrentDictionary<ENTITY_GUID, IEntityAttributeTransactor>();
}
return found_transactors;
}
public List<IEntityAttributeTransactor> getEntityAttributeTransactorAll()
{
var err_msg = string.Empty;
var entity_attribute_transactors = new List<IEntityAttributeTransactor>();
foreach (var each_attribute_type in m_entity_attribute_transactors)
{
foreach (var each_attribute_transactor in each_attribute_type.Value)
{
var transactor_entity_guid = each_attribute_transactor.Key;
var transactor = each_attribute_transactor.Value;
if (null == transactor)
{
continue;
}
entity_attribute_transactors.Add(transactor);
}
}
return entity_attribute_transactors;
}
public List<IEntityAttributeTransactor> getEntityAttributeTransactorAll4DbQuery()
{
var err_msg = string.Empty;
var transactors = new List<IEntityAttributeTransactor>();
while (false == m_to_db_query_transactors.IsEmpty)
{
if (true == m_to_db_query_transactors.TryDequeue(out var transactor))
{
transactors.Add(transactor);
}
}
return transactors;
}
public TEntityAttribute? getEntityAttribute<TEntityAttribute>(TEntityAttribute fromOriginEntityAttributeBase)
where TEntityAttribute : EntityAttributeBase
{
var owner = getOwner();
NullReferenceCheckHelper.throwIfNull(owner, () => $"owner is null !!!");
ArgumentNullReferenceCheckHelper.throwIfNull(fromOriginEntityAttributeBase, () => $"fromOriginEntityAttributeBase is null !!! - {owner.toBasicString()}");
var to_add_type = fromOriginEntityAttributeBase.GetType();
var entity_guid = fromOriginEntityAttributeBase.getOwner().getEntityGuid();
ConcurrentDictionary<ENTITY_GUID, IEntityAttributeTransactor>? found_transactors;
lock (m_entity_attribute_transactors)
{
if (false == m_entity_attribute_transactors.TryGetValue(to_add_type, out found_transactors))
{
found_transactors = new();
m_entity_attribute_transactors[to_add_type] = found_transactors;
}
}
IEntityAttributeTransactor found_transactor;
var add_transactor = delegate (ENTITY_GUID entityGuid)
{
found_transactor = fromOriginEntityAttributeBase.onNewEntityAttributeTransactor();
if (false == found_transactor.cloneFromOriginEntityAttribute(fromOriginEntityAttributeBase))
{
var err_msg = $"Failed to cloneFromOriginEntityAttribute() !!! - {toBasicString()}, {owner.toBasicString()}";
Log.getLogger().error(err_msg);
return null;
}
m_to_db_query_transactors.Enqueue(found_transactor);
return found_transactor;
};
found_transactor = found_transactors.GetOrAdd(entity_guid, add_transactor!);
if (null == found_transactor)
{
var err_msg = $"Failed to GetOrAdd() !!! : {fromOriginEntityAttributeBase.toBasicString()} - {toBasicString()}, {owner.toBasicString()}";
Log.getLogger().error(err_msg);
}
else
{
return found_transactor.getClonedEntityAttribute() as TEntityAttribute;
}
return fromOriginEntityAttributeBase as TEntityAttribute;
}
public TEntityAttribute? getEntityAttributeWithReadOnly<TEntityAttribute>(TEntityAttribute fromOriginEntityAttributeBase)
where TEntityAttribute : EntityAttributeBase
{
var owner = getOwner();
NullReferenceCheckHelper.throwIfNull(owner, () => $"owner is null !!!");
ArgumentNullReferenceCheckHelper.throwIfNull(fromOriginEntityAttributeBase, () => $"fromOriginEntityAttributeBase is null !!! - {owner.toBasicString()}");
var to_add_type = fromOriginEntityAttributeBase.GetType();
var entity_guid = fromOriginEntityAttributeBase.getOwner().getEntityGuid();
ConcurrentDictionary<ENTITY_GUID, IEntityAttributeTransactor>? found_transactors;
lock (m_entity_attribute_transactors)
{
m_entity_attribute_transactors.TryGetValue(to_add_type, out found_transactors);
IEntityAttributeTransactor? found_transactor = null;
if (null != found_transactors)
{
found_transactors.TryGetValue(entity_guid, out found_transactor);
}
if (null != found_transactor)
{
return found_transactor.getClonedEntityAttribute() as TEntityAttribute;
}
return fromOriginEntityAttributeBase as TEntityAttribute;
}
}
public Result tryIncReserveSlotCount(EntityBase invenOwner, EntityType entityInvenType, string slotType)
{
var owner = getOwner();
NullReferenceCheckHelper.throwIfNull(owner, () => $"owner is null !!!");
ArgumentNullReferenceCheckHelper.throwIfNull(invenOwner, () => $"invenOwner is null !!! - {owner.toBasicString()}");
var result = new Result();
ConcurrentDictionary<EntityType, ReservedSlotOnInven>? found_reserved_inven_slots;
lock (m_reserved_inven_slots_of_owners)
{
if (false == m_reserved_inven_slots_of_owners.TryGetValue(invenOwner, out found_reserved_inven_slots))
{
found_reserved_inven_slots = new ConcurrentDictionary<EntityType, ReservedSlotOnInven>();
m_reserved_inven_slots_of_owners[invenOwner] = found_reserved_inven_slots;
}
}
var add_recorder = delegate (EntityType invenType)
{
var reserved_slot = new ReservedSlotOnInven(entityInvenType);
reserved_slot.addOrInc(slotType, 1);
return reserved_slot;
};
var update_recorder = delegate (EntityType invenType, ReservedSlotOnInven currValue)
{
if (false == currValue.getReservedSlots().TryGetValue(slotType, out var found_reserved_slot))
{
found_reserved_slot = new ReservedSlotOnInven.ReservedSlot();
currValue.getReservedSlots().Add(slotType, found_reserved_slot);
}
found_reserved_slot.incReservedCount(1);
return currValue;
};
found_reserved_inven_slots.AddOrUpdate(entityInvenType, add_recorder, update_recorder);
return result;
}
public Result tryDecToReserveSlotCount(EntityBase invenOwner, EntityType entityInvenType, string slotType)
{
var owner = getOwner();
NullReferenceCheckHelper.throwIfNull(owner, () => $"owner is null !!!");
ArgumentNullReferenceCheckHelper.throwIfNull(invenOwner, () => $"invenOwner is null !!! - {owner.toBasicString()}");
var result = new Result();
ConcurrentDictionary<EntityType, ReservedSlotOnInven>? found_reserved_inven_slots;
lock (m_reserved_inven_slots_of_owners)
{
if (false == m_reserved_inven_slots_of_owners.TryGetValue(invenOwner, out found_reserved_inven_slots))
{
found_reserved_inven_slots = new ConcurrentDictionary<EntityType, ReservedSlotOnInven>();
m_reserved_inven_slots_of_owners[invenOwner] = found_reserved_inven_slots;
}
}
var add_recorder = delegate (EntityType invenType)
{
var reserved_slot = new ReservedSlotOnInven(entityInvenType);
reserved_slot.addOrDec(slotType, 1);
return reserved_slot;
};
var update_recorder = delegate (EntityType invenType, ReservedSlotOnInven currValue)
{
if (false == currValue.getReservedSlots().TryGetValue(slotType, out var found_reserved_slot))
{
found_reserved_slot = new ReservedSlotOnInven.ReservedSlot();
currValue.getReservedSlots().Add(slotType, found_reserved_slot);
}
found_reserved_slot.decReservedCount(1);
return currValue;
};
found_reserved_inven_slots.AddOrUpdate(entityInvenType, add_recorder, update_recorder);
return result;
}
public Result tryEquipToReserveSlotWithEntityBase(EntityBase invenOwner, EntityType entityInvenType, string slotType, EntityBase? entityBase)
{
var owner = getOwner();
NullReferenceCheckHelper.throwIfNull(owner, () => $"owner is null !!!");
ArgumentNullReferenceCheckHelper.throwIfNull(invenOwner, () => $"invenOwner is null !!! - {owner.toBasicString()}");
var result = new Result();
var err_msg = string.Empty;
ConcurrentDictionary<EntityType, ReservedSlotOnInven>? found_reserved_inven_slots;
lock (m_reserved_inven_slots_of_owners)
{
if (false == m_reserved_inven_slots_of_owners.TryGetValue(invenOwner, out found_reserved_inven_slots))
{
found_reserved_inven_slots = new ConcurrentDictionary<EntityType, ReservedSlotOnInven>();
m_reserved_inven_slots_of_owners[invenOwner] = found_reserved_inven_slots;
}
}
var slot_key = slotType;
var add_recorder = delegate (EntityType invenType)
{
var reserved_slot_on_inven = new ReservedSlotOnInven(entityInvenType);
reserved_slot_on_inven.addOrInc(slot_key, 1);
reserved_slot_on_inven.getReservedSlots()[slot_key].setEquipEntity(entityBase);
return reserved_slot_on_inven;
};
var update_recorder = delegate (EntityType invenType, ReservedSlotOnInven currValue)
{
if (false == currValue.getReservedSlots().TryGetValue(slot_key, out var found_reserved_slot))
{
found_reserved_slot = new ReservedSlotOnInven.ReservedSlot();
currValue.getReservedSlots()[slot_key] = found_reserved_slot;
}
var equip_entity = found_reserved_slot.getEquipEntity();
if (null != equip_entity)
{
err_msg = $"Already reserved equip item !!! : reservedEquipItem:{equip_entity.toBasicString()} - {entityBase?.toBasicString()}, {toBasicString()}, {owner.toBasicString()}";
result.setFail(ServerErrorCode.SlotsAlreadyReservedEquip, err_msg);
Log.getLogger().warn(result.toBasicString());
return currValue;
}
found_reserved_slot.incReservedCount(1);
found_reserved_slot.setEquipEntity(entityBase);
return currValue;
};
found_reserved_inven_slots.AddOrUpdate(entityInvenType, add_recorder, update_recorder);
return result;
}
public Result tryUnequipToReserveSlotWithEntityBase(EntityBase invenOwner, EntityType entityInvenType, string slotType, EntityBase? entityBase)
{
var owner = getOwner();
NullReferenceCheckHelper.throwIfNull(owner, () => $"owner is null !!!");
ArgumentNullReferenceCheckHelper.throwIfNull(invenOwner, () => $"invenOwner is null !!! - {owner.toBasicString()}");
var result = new Result();
var err_msg = string.Empty;
var slot_key = slotType;
ConcurrentDictionary<EntityType, ReservedSlotOnInven>? found_reserved_inven_slots = null;
lock (m_reserved_inven_slots_of_owners)
{
if (false == m_reserved_inven_slots_of_owners.TryGetValue(invenOwner, out found_reserved_inven_slots))
{
found_reserved_inven_slots = new ConcurrentDictionary<EntityType, ReservedSlotOnInven>();
m_reserved_inven_slots_of_owners[invenOwner] = found_reserved_inven_slots;
}
}
var add_recorder = delegate (EntityType invenType)
{
var reserved_slot_on_inven = new ReservedSlotOnInven(entityInvenType);
reserved_slot_on_inven.addOrDec(slot_key, 1);
reserved_slot_on_inven.getReservedSlots()[slot_key].setUnequipEntity(entityBase);
return reserved_slot_on_inven;
};
var update_recorder = delegate (EntityType invenType, ReservedSlotOnInven currValue)
{
if (false == currValue.getReservedSlots().TryGetValue(slot_key, out var found_reserved_slot))
{
found_reserved_slot = new ReservedSlotOnInven.ReservedSlot();
currValue.getReservedSlots()[slot_key] = found_reserved_slot;
}
var unequip_entity = found_reserved_slot.getUnequipEntity();
if (null != unequip_entity)
{
err_msg = $"Already reserved unequip item !!! : reservedUnequipItem:{unequip_entity.toBasicString()} - {entityBase?.toBasicString()}, {toBasicString()}, {owner.toBasicString()}";
result.setFail(ServerErrorCode.SlotsAlreadyReservedUnequip, err_msg);
Log.getLogger().warn(result.toBasicString());
return currValue;
}
found_reserved_slot.decReservedCount(1);
found_reserved_slot.setUnequipEntity(entityBase);
return currValue;
};
found_reserved_inven_slots.AddOrUpdate(entityInvenType, add_recorder, update_recorder);
return result;
}
public Result tryEquipToReserveSlot(EntityBase invenOwner, EntityType entityInvenType, string slotType)
{
return tryEquipToReserveSlotWithEntityBase(invenOwner, entityInvenType, slotType, null);
}
public Result tryUnequipToReserveSlot(EntityBase invenOwner, EntityType entityInvenType, string slotType)
{
return tryUnequipToReserveSlotWithEntityBase(invenOwner, entityInvenType, slotType, null);
}
public ReservedSlotOnInven.ReservedSlot? findReservedSlot(EntityBase invenOwner, EntityType entityInvenType, string slotType)
{
if(false == m_reserved_inven_slots_of_owners.TryGetValue(invenOwner, out var found_reserved_inven_slots))
{
return null;
}
if (true == found_reserved_inven_slots.TryGetValue(entityInvenType, out var found_reserved_slot_on_inven))
{
if(found_reserved_slot_on_inven.getReservedSlots().TryGetValue(slotType, out var found_reserved_slot))
{
return found_reserved_slot;
}
}
return null;
}
public (Result, EntityCommonResult?) getOrNewEntityCommonResult(EntityAttributeBase entityAttribute)
{
var owner = getOwner();
NullReferenceCheckHelper.throwIfNull(owner, () => $"owner is null !!!");
ArgumentNullReferenceCheckHelper.throwIfNull(entityAttribute, () => $"entityAttribute is null !!! - {owner.toBasicString()}");
var entity_of_owner_entity_type = entityAttribute.getEntityOfOwnerEntityType();
NullReferenceCheckHelper.throwIfNull(entity_of_owner_entity_type, () => $"entity_of_owner_entity_type is null !!! - {owner.toBasicString()}");
var result = new Result();
var err_msg = string.Empty;
var owner_guid = entity_of_owner_entity_type.onGetDbGuid();
if(owner_guid.isNullOrWhiteSpace())
{
err_msg = $"Invlid OwnerGuid of onGetDbGuid() !!! : entityAttribute:{entityAttribute.toBasicString()} - {owner.toBasicString()}";
result.setFail(ServerErrorCode.OwnerGuidInvalid, err_msg);
Log.getLogger().fatal(result.toBasicString());
return (result, null);
}
if (false == m_entity_common_results.TryGetValue(owner_guid, out var found_entity_common_result))
{
found_entity_common_result = new EntityCommonResult();
found_entity_common_result.EntityType = entity_of_owner_entity_type.getEntityType();
found_entity_common_result.EntityGuid = owner_guid;
found_entity_common_result.Money = new MoneyResult();
found_entity_common_result.Exp = new ExpResult();
found_entity_common_result.Item = new ItemResult();
m_entity_common_results[owner_guid] = found_entity_common_result;
}
return (result, found_entity_common_result);
}
public CommonResult getCommonResult()
{
var common_result = new CommonResult();
foreach(var each in m_entity_common_results)
{
common_result.EntityCommonResults.Add(each.Value);
}
return common_result;
}
public void addRemoteChargeAIPoint(IRemoteTransaction remoteTransaction, double beamDelta)
{
var fn_buy_cart = new Func<Task>(() =>
{
return remoteTransaction.callRemoteChargeAIPoint(beamDelta);
});
m_remote_transaction_funcs.Add(fn_buy_cart);
}
public void addNotifyCaliumEvent(IRemoteTransaction remoteTransaction, string eventName, CurrencyType currencyType, double delta)
{
var fn = new Func<Task>(() => remoteTransaction.callNotifyCaliumEvent(eventName, currencyType, delta));
m_remote_transaction_funcs.Add(fn);
}
public async Task tryStartRemoteTransaction()
{
foreach(var remote_transaction_funcs in m_remote_transaction_funcs)
{
await remote_transaction_funcs();
}
}
public string toBasicString()
{
return $"TransactionRunnger - transId:{m_trans_id}, transactionIdType:{m_transaction_id_type}, transactionName:{m_transaction_name}";
}
}

View File

@@ -0,0 +1,46 @@
using Org.BouncyCastle.Bcpg.OpenPgp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MODULE_ID = System.UInt32;
using SORT_ORDER_NO = System.Int32;
namespace ServerBase;
public class ModuleContext
{
private readonly MODULE_ID m_module_id;
private readonly SORT_ORDER_NO m_sort_start_order_no;
private readonly SORT_ORDER_NO m_sort_stop_order_no;
private readonly IConfigParam m_config_param;
public ModuleContext( MODULE_ID moduleId
, SORT_ORDER_NO sortStartOrderNo, SORT_ORDER_NO sortStopOrderNo
, IConfigParam configParam )
{
m_module_id = moduleId;
m_sort_start_order_no = sortStartOrderNo;
m_sort_stop_order_no = sortStopOrderNo;
m_config_param = configParam;
}
public IConfigParam getConfigParam() => m_config_param;
public SORT_ORDER_NO getSortStartOrderNo() => m_sort_start_order_no;
public SORT_ORDER_NO getSortStopOrderNo() => m_sort_stop_order_no;
public MODULE_ID getModuleId() => m_module_id;
public string toBasicString()
{
return $"ModuleId:{m_module_id}, ConfigParam:{m_config_param.toBasicString()}";
}
}

View File

@@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ServerCore; using ServerBase;
namespace ServerBase;
public class ResultOnly : IWithResult
{
public Result Result { get; set; }
public ResultOnly(Result result)
{
Result = result;
}
public bool isResultOnly() => true;
}
public class ResultValue<TResultValue> : IWithResultValue<TResultValue>
{
public Result Result { get; set; }
public TResultValue ValueOfResult { get; }
public ResultValue(Result result, TResultValue value)
{
Result = result;
ValueOfResult = value;
}
public bool isResultOnly() => false;
}

View File

@@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ServerCore; using ServerBase;
namespace ServerBase;
public abstract class SimpleEventTriggerBase : IEventTask
{
private bool m_is_completed = false;
public void setCompleted()
{
m_is_completed = true;
}
public bool isCompleted()
{
return m_is_completed;
}
public abstract Task<Result> onTriggerEffect();
public virtual string toBasicString()
{
return $"{this.getTypeName()}, IsCompleted:{isCompleted()}";
}
}

View File

@@ -0,0 +1,283 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Amazon.Util.Internal;
using Grpc.Core;
using Microsoft.AspNetCore.Mvc.TagHelpers;
using ServerCore; using ServerBase;
namespace ServerBase;
public static class DataCopyHelper
{
#region : Doc => Cache
public static async Task<Result> copyCacheFromDocs<TCacheType>(TCacheType toCacheBase, List<DynamoDbDocBase> fromDocBases)
where TCacheType : CacheBase
{
var result = new Result();
var err_msg = string.Empty;
var to_copy = toCacheBase as ICopyCacheFromDoc;
if (null == to_copy)
{
err_msg = $"Failed to cast ICopyCacheFromDoc !!! : {typeof(TCacheType).Name} !!!";
result.setFail(ServerErrorCode.ClassDoesNotImplementInterfaceInheritance, err_msg);
Log.getLogger().error(result.toBasicString());
return result;
}
foreach (var doc in fromDocBases)
{
if (false == to_copy.copyCacheFromDoc(doc))
{
err_msg = $"Failed to copyCacheFromDoc() !!!, to:{typeof(TCacheType).Name}, from:{doc.getTypeName()}";
result.setFail(ServerErrorCode.DynamoDbDocCopyToCacheFailed, err_msg);
Log.getLogger().error(result.toBasicString());
return result;
}
}
return await Task.FromResult(result);
}
#endregion
#region : Doc => EntityAttribute
public static async Task<Result> copyEntityAttributeFromDocs<TEntityAttributeType>(TEntityAttributeType toEntityAttributeBase, List<DynamoDbDocBase> fromDocBases)
where TEntityAttributeType : EntityAttributeBase
{
await Task.CompletedTask;
var result = new Result();
var err_msg = string.Empty;
var to_copy = toEntityAttributeBase as ICopyEntityAttributeFromDoc;
if (null == to_copy)
{
err_msg = $"Failed to cast ICopyEntityAttributeFromDoc !!! : {typeof(TEntityAttributeType).Name}";
result.setFail(ServerErrorCode.ClassDoesNotImplementInterfaceInheritance, err_msg);
Log.getLogger().error(result.toBasicString());
return result;
}
foreach(var doc in fromDocBases)
{
if (false == to_copy.copyEntityAttributeFromDoc(doc))
{
err_msg = $"Failed to copyEntityAttributeFromDoc() !!!, to:{typeof(TEntityAttributeType).Name}, from:{doc.getTypeName()}";
result.setFail(ServerErrorCode.DynamoDbDocCopyToEntityAttributeFailed, err_msg);
Log.getLogger().error(result.toBasicString());
return result;
}
}
return result;
}
#endregion
#region : Cache => EntityAttribute
public static async Task<Result> copyEntityAttributeFromCaches<TEntityAttributeType>(TEntityAttributeType toEntityAttribBase, List<CacheBase> fromCacheBases)
where TEntityAttributeType : EntityAttributeBase
{
var result = new Result();
var err_msg = string.Empty;
var to_copy = toEntityAttribBase as ICopyEntityAttributeFromCache;
if (null == to_copy)
{
err_msg = $"Failed to cast ICopyEntityAttributeFromCache !!! : {typeof(TEntityAttributeType).Name} !!!";
result.setFail(ServerErrorCode.ClassDoesNotImplementInterfaceInheritance, err_msg);
Log.getLogger().error(result.toBasicString());
return result;
}
foreach (var cache in fromCacheBases)
{
if (false == to_copy.copyEntityAttributeFromCache(cache))
{
err_msg = $"Failed to copyEntityAttributeFromCache() !!!, to:{typeof(TEntityAttributeType).Name}, from:{cache.getTypeName()}";
result.setFail(ServerErrorCode.CacheCopyToEntityAttributeFailed, err_msg);
Log.getLogger().error(result.toBasicString());
return result;
}
}
return await Task.FromResult(result);
}
#endregion
#region : Cache => Doc
public static async Task<Result> copyDocFromCache<TDoc>(TDoc toDoc, List<CacheBase> fromCacheBases)
where TDoc : DynamoDbDocBase
{
var result = new Result();
var err_msg = string.Empty;
var to_copy = toDoc as ICopyDocFromCache;
if (null == to_copy)
{
err_msg = $"Failed to cast ICopyDocFromCache !!! : {typeof(TDoc).Name}";
result.setFail(ServerErrorCode.ClassDoesNotImplementInterfaceInheritance, err_msg);
Log.getLogger().error(result.toBasicString());
return result;
}
foreach (var cache in fromCacheBases)
{
if (false == to_copy.copyDocFromCache(cache))
{
err_msg = $"Failed to copyDocFromCache() !!!, to:{typeof(TDoc).Name}, from:{cache.getTypeName()}";
result.setFail(ServerErrorCode.CacheCopyToEntityAttributeFailed, err_msg);
Log.getLogger().error(result.toBasicString());
return result;
}
}
return await Task.FromResult(result);
}
#endregion
#region : EntityAttribute => Cache
public static async Task<Result> copyCacheFromEntityAttributes<TCacheType>(TCacheType toCacheBase, List<EntityAttributeBase> fromEntityAttributeBases)
where TCacheType : CacheBase
{
var result = new Result();
var err_msg = string.Empty;
var to_copy = toCacheBase as ICopyCacheFromEntityAttribute;
if (null == to_copy)
{
err_msg = $"Failed to cast ICopyCacheFromEntityAttribute !!! : {typeof(TCacheType).Name} !!!";
result.setFail(ServerErrorCode.ClassDoesNotImplementInterfaceInheritance, err_msg);
Log.getLogger().error(result.toBasicString());
return result;
}
foreach(var attribute in fromEntityAttributeBases)
{
if (false == to_copy.copyCacheFromEntityAttribute(attribute))
{
err_msg = $"Failed to copyCacheFromEntityAttributes() !!!, to:{typeof(TCacheType).Name}, from:{fromEntityAttributeBases.getTypeName()}";
result.setFail(ServerErrorCode.EntityAttributeCopyToCacheFailed, err_msg);
Log.getLogger().error(result.toBasicString());
return result;
}
}
return await Task.FromResult(result);
}
#endregion
#region : EntityAttribute => Doc
public static async Task<Result> copyDocFromEntityAttributes<TDoc>(TDoc toDocBase, List<EntityAttributeBase> fromEntityAttributeBases)
where TDoc : DynamoDbDocBase
{
var result = new Result();
var err_msg = string.Empty;
var to_copy = toDocBase as ICopyDocFromEntityAttribute;
if (null == to_copy)
{
err_msg = $"Failed to cast ICopyDocFromEntityAttribute !!! : {fromEntityAttributeBases.getTypeName()}";
result.setFail(ServerErrorCode.ClassDoesNotImplementInterfaceInheritance, err_msg);
Log.getLogger().error(result.toBasicString());
return result;
}
foreach (var entity_attribute in fromEntityAttributeBases)
{
if (false == to_copy.copyDocFromEntityAttribute(entity_attribute))
{
err_msg = $"Failed to copyDocFromEntityAttribute() !!!, to:{typeof(TDoc).Name}, from:{fromEntityAttributeBases.getTypeName()}";
result.setFail(ServerErrorCode.EntityAttributeCopyToDynamoDbDocFailed, err_msg);
Log.getLogger().error(result.toBasicString());
return result;
}
}
return await Task.FromResult(result);
}
#endregion
#region : EntityAttribute => EntityAttributeTransactor
public static async Task<Result> copyEntityAttributeTransactorFromEntityAttribute<TEntityAttribute>(EntityAttributeTransactorBase<TEntityAttribute> toEntityAttributeTransactorBase, EntityAttributeBase fromEntityAttribute)
where TEntityAttribute : EntityAttributeBase
{
var result = new Result();
var err_msg = string.Empty;
var to_copy = toEntityAttributeTransactorBase as ICopyEntityAttributeTransactorFromEntityAttribute;
if (null == to_copy)
{
err_msg = $"Failed to cast ICopyEntityAttributeTransactorBaseFromEntityAttribute !!! : {fromEntityAttribute.getTypeName()}";
result.setFail(ServerErrorCode.ClassDoesNotImplementInterfaceInheritance, err_msg);
Log.getLogger().error(result.toBasicString());
return result;
}
if (false == to_copy.copyEntityAttributeTransactorFromEntityAttribute(fromEntityAttribute))
{
err_msg = $"Failed to copyEntityAttributeTransactorBaseFromEntityAttribute() !!!, to:{toEntityAttributeTransactorBase.getTypeName()}, from:{fromEntityAttribute.getTypeName()}";
result.setFail(ServerErrorCode.EntityAttributeCopyToEntityAttributeTransactorFailed, err_msg);
Log.getLogger().error(result.toBasicString());
return result;
}
return await Task.FromResult(result);
}
#endregion
#region : EntityAttributeTransactor => Doc
public static async Task<Result> copyDocFromEntityAttributeTransactor<TDynamoDbDocBase, TEntityAttribute>(TDynamoDbDocBase toDoc, EntityAttributeTransactorBase<TEntityAttribute> fromEntityAttributeTransactorBase)
where TDynamoDbDocBase : DynamoDbDocBase
where TEntityAttribute : EntityAttributeBase
{
var result = new Result();
var err_msg = string.Empty;
var to_copy = toDoc as ICopyDocFromEntityAttributeTransactor;
if (null == to_copy)
{
err_msg = $"Failed to cast ICopyDocFromEntityAttributeTransactor !!! : {fromEntityAttributeTransactorBase.getTypeName()}";
result.setFail(ServerErrorCode.ClassDoesNotImplementInterfaceInheritance, err_msg);
Log.getLogger().error(result.toBasicString());
return result;
}
if (false == to_copy.copyDocFromEntityAttributeTransactor(fromEntityAttributeTransactorBase))
{
err_msg = $"Failed to copyDocFromEntityAttributeTransactor() !!!, to:{typeof(TDynamoDbDocBase).Name}, from:{fromEntityAttributeTransactorBase.getTypeName()}";
result.setFail(ServerErrorCode.EntityAttributeTransactorCopyToDynamoDbDocFailed, err_msg);
Log.getLogger().error(result.toBasicString());
return result;
}
return await Task.FromResult(result);
}
#endregion
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,617 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Amazon.DynamoDBv2.Model;
using Amazon.DynamoDBv2;
using Amazon.Runtime.Internal;
using Amazon.DynamoDBv2.DocumentModel;
using NLog.Conditions;
using ServerCore; using ServerBase;
using DYNAMO_DB_TABLE_FULL_NAME = System.String;
namespace ServerBase;
public static class DynamoDbItemRequestHelper
{
public enum AttribValueAction
{
None = 0,
Increase, // Atomic 기반 증가
Decrease, // Atomic 기반 감소
Update // 값의 변경
}
public class AttribAction
{
public AttribValueAction ValueAction { get; set; } = AttribValueAction.None;
public AttributeValue Value { get; set; } = new();
}
public static (Result, DynamoDbItemRequestQueryContext?) makeUpdateItemRequestWithDoc<TAttrib>( DynamoDbDocBase targetDoc
, Dictionary<string, AttribAction> toChangeAttibValues
, DynamoDbQueryExceptionNotifier.ExceptionHandler? exceptionHandler = null )
where TAttrib : AttribBase
{
ConditionValidCheckHelper.throwIfFalseWithCondition(() => 0 < toChangeAttibValues.Count, () => $"Invalid toChangeAttibValues.Count !!! : 0 < {toChangeAttibValues.Count}");
var server_logic = ServerLogicApp.getServerLogicApp();
NullReferenceCheckHelper.throwIfNull(server_logic, () => $"server_logic client is null !!! ");
var db_connector = server_logic.getDynamoDbClient();
NullReferenceCheckHelper.throwIfNull(db_connector, () => $"db_connector client is null !!! ");
var result = new Result();
var primary_key = targetDoc.getPrimaryKey();
var query_builder = new DynamoDbItemRequestHelper.UpdateItemRequestBuilder(db_connector.getTableFullName(targetDoc.TableName));
query_builder.withKeys(primary_key.toKeyWithAttributeValue());
var attrib_path_json_string = targetDoc.toJsonStringOfAttribs();
var update_expression = "SET ";
var expression_idx = 0;
var expression_attribute_names = new Dictionary<string, string>();
var expression_attribute_values = new Dictionary<string, AttributeValue>();
var update_item_request = query_builder.getItemRequest();
foreach (var each in toChangeAttibValues)
{
var attrib_key = each.Key;
var attrib_value = each.Value;
var target_key = JsonHelper.getJsonPropertyName<TAttrib>(attrib_key);
(var is_success, var attribute_expression) = DynamoDbClientHelper.toAttributeExpressionFromJson(attrib_path_json_string, target_key);
if (false == is_success)
{
var err_msg = $"Failed to DynamoDbClientHelper.toAttributeExpressionFromJson() !!! : attribPath:{attrib_path_json_string}, targetKey:{target_key} - {primary_key.toBasicString()}";
result.setFail(ServerErrorCode.AttribPathMakeFailed, err_msg);
Log.getLogger().error(result.toBasicString());
continue;
}
var attribute_names = DynamoDbClientHelper.toExpressionAttributeNamesFromJson(attrib_path_json_string, target_key);
foreach (var name in attribute_names)
{
expression_attribute_names.TryAdd(name.Key, name.Value);
}
if (expression_idx > 0) update_expression += ", ";
var data = attrib_value.Value;
if (AttribValueAction.Increase == attrib_value.ValueAction)
{
var start_key = $":start_{expression_idx}";
var incr_key = $":incr_{expression_idx}";
update_expression += $"{attribute_expression} = if_not_exists({attribute_expression}, {start_key}) + {incr_key}";
expression_attribute_values.Add(incr_key, data);
expression_attribute_values.TryAdd(start_key, new AttributeValue { N = "0" });
}
else if(AttribValueAction.Decrease== attrib_value.ValueAction)
{
var start_key = $":start_{expression_idx}";
var decr_key = $":decr_{expression_idx}";
update_expression += $"{attribute_expression} = if_not_exists({attribute_expression}, {start_key}) - {decr_key}";
expression_attribute_values.Add(decr_key, data);
expression_attribute_values.TryAdd(start_key, new AttributeValue { N = "0" });
}
else if (AttribValueAction.Update == attrib_value.ValueAction)
{
var new_value_key = $":newValue_{expression_idx}";
update_expression += $"{attribute_expression} = {new_value_key}";
expression_attribute_values.Add(new_value_key, data);
}
expression_idx++;
}
query_builder.withExpressionAttributeNames(expression_attribute_names);
query_builder.withUpdateExpression(update_expression);
query_builder.withExpressionAttributeValues(expression_attribute_values);
query_builder.withReturnValues(ReturnValue.ALL_NEW);
(result, var builded_update_item_request) = query_builder.build();
if(result.isFail())
{
return (result, null);
}
NullReferenceCheckHelper.throwIfNull(builded_update_item_request, () => $"builded_update_item_request is null !!! - {primary_key.toBasicString()}");
var exception_handler = new DynamoDbQueryExceptionNotifier.ExceptionHandler(DynamoDbQueryExceptionNotifier.ConditionalCheckFailed, ServerErrorCode.LackOfTotalCalium);
return (result, builded_update_item_request.createItemRequestQueryContext(QueryType.Update, exceptionHandler));
}
//=========================================================================================
// UpdateItemRequest Builder
//=========================================================================================
public class UpdateItemRequestBuilder
{
private readonly UpdateItemRequest m_request;
private Document? m_document;
private readonly bool m_is_upsert;
public UpdateItemRequestBuilder(DYNAMO_DB_TABLE_FULL_NAME tableFullName, bool isUpsert = false)
{
ConditionValidCheckHelper.throwIfFalseWithCondition(() => false == tableFullName.isNullOrWhiteSpace(), () => $"Invalid TableFullName !!!, Null or WhiteSpace !!!");
m_request = new UpdateItemRequest
{
TableName = tableFullName,
Key = new Dictionary<string, AttributeValue>(),
AttributeUpdates = new Dictionary<string, AttributeValueUpdate>(),
UpdateExpression = string.Empty,
};
m_is_upsert = isUpsert;
}
public UpdateItemRequest getItemRequest() => m_request;
public UpdateItemRequestBuilder withDocument(Document document)
{
m_document = document;
return this;
}
public UpdateItemRequestBuilder withReturnValues(ReturnValue returnValue)
{
m_request.ReturnValues = returnValue;
return this;
}
public UpdateItemRequestBuilder withConditionExpression(string conditionExpression)
{
m_request.ConditionExpression = conditionExpression;
return this;
}
public UpdateItemRequestBuilder withExpressionAttributeNames(Dictionary<string, string> expressionAttributeNames)
{
m_request.ExpressionAttributeNames = expressionAttributeNames;
return this;
}
public UpdateItemRequestBuilder withExpressionAttributeValues(Dictionary<string, AttributeValue> expressionAttributeValues)
{
m_request.ExpressionAttributeValues = expressionAttributeValues;
return this;
}
public UpdateItemRequestBuilder withKeys(Dictionary<string, AttributeValue> keyValue)
{
m_request.Key = keyValue;
return this;
}
public UpdateItemRequestBuilder addKey(string keyName, string keyValue)
{
m_request.Key[keyName] = new AttributeValue { S = keyValue };
return this;
}
public UpdateItemRequestBuilder addKey(string keyName, int keyValue)
{
m_request.Key[keyName] = new AttributeValue { N = keyValue.ToString() };
return this;
}
public UpdateItemRequestBuilder addKey(string keyName, long keyValue)
{
m_request.Key[keyName] = new AttributeValue { N = keyValue.ToString() };
return this;
}
public UpdateItemRequestBuilder addKey(string keyName, float keyValue)
{
m_request.Key[keyName] = new AttributeValue { N = keyValue.ToString() };
return this;
}
public UpdateItemRequestBuilder addKey(string keyName, double keyValue)
{
m_request.Key[keyName] = new AttributeValue { N = keyValue.ToString() };
return this;
}
public UpdateItemRequestBuilder addKey(string keyName, bool keyValue)
{
m_request.Key[keyName] = new AttributeValue { BOOL = keyValue };
return this;
}
public UpdateItemRequestBuilder addKey(string keyName, AttributeValue keyValue)
{
m_request.Key[keyName] = keyValue;
return this;
}
public UpdateItemRequestBuilder withReturnConsumedCapacity(ReturnConsumedCapacity returnConsumedCapacity)
{
m_request.ReturnConsumedCapacity = returnConsumedCapacity;
return this;
}
public UpdateItemRequestBuilder withReturnItemCollectionMetrics(ReturnItemCollectionMetrics returnItemCollectionMetrics)
{
m_request.ReturnItemCollectionMetrics = returnItemCollectionMetrics;
return this;
}
public UpdateItemRequestBuilder withUpdateExpression(string updateExpression)
{
m_request.UpdateExpression = updateExpression;
return this;
}
public UpdateItemRequestBuilder withConditionCheck(string conditionExpression)
{
m_request.ConditionExpression = conditionExpression;
return this;
}
public (Result, UpdateItemRequest?) build()
{
var result = new Result();
if(null != m_document)
{
result = m_document.tryFillupUpdateItemRequest(m_request, m_is_upsert);
if(result.isFail())
{
return (result, null);
}
}
else
{
if ( false == m_is_upsert
&& null != m_request.ConditionExpression
&& m_request.ConditionExpression.isNullOrWhiteSpace() )
{
m_request.ConditionExpression = $"attribute_exists({PrimaryKey.PK_Define}) AND attribute_exists({PrimaryKey.SK_Define})";
}
}
return (result, m_request);
}
}
//=========================================================================================
// GetItemRequest Builder
//=========================================================================================
public class GetItemRequestBuilder
{
private readonly GetItemRequest m_request;
public GetItemRequestBuilder(string tableName)
{
m_request = new GetItemRequest
{
TableName = tableName,
Key = new Dictionary<string, AttributeValue>(),
ExpressionAttributeNames = new Dictionary<string, string>()
};
}
public GetItemRequestBuilder withKeys(Dictionary<string, AttributeValue> keyValue)
{
m_request.Key = keyValue;
return this;
}
public GetItemRequestBuilder addKey(string keyName, string keyValue)
{
m_request.Key[keyName] = new AttributeValue { S = keyValue };
return this;
}
public GetItemRequestBuilder addKey(string keyName, int keyValue)
{
m_request.Key[keyName] = new AttributeValue { N = keyValue.ToString() };
return this;
}
public GetItemRequestBuilder addKey(string keyName, long keyValue)
{
m_request.Key[keyName] = new AttributeValue { N = keyValue.ToString() };
return this;
}
public GetItemRequestBuilder addKey(string keyName, double keyValue)
{
m_request.Key[keyName] = new AttributeValue { N = keyValue.ToString() };
return this;
}
public GetItemRequestBuilder addKey(string keyName, float keyValue)
{
m_request.Key[keyName] = new AttributeValue { N = keyValue.ToString() };
return this;
}
public GetItemRequestBuilder addKey(string keyName, bool keyValue)
{
m_request.Key[keyName] = new AttributeValue { BOOL = keyValue };
return this;
}
public GetItemRequestBuilder withConsistentRead(bool isConsistentRead)
{
m_request.ConsistentRead = isConsistentRead;
return this;
}
public GetItemRequestBuilder withProjectionExpression(string projectExpression)
{
m_request.ProjectionExpression = projectExpression;
return this;
}
public GetItemRequestBuilder withExpressionAttributeNames(Dictionary<string, string> expressionAttributeNames)
{
m_request.ExpressionAttributeNames = expressionAttributeNames;
return this;
}
public GetItemRequestBuilder withReturnConsumedCapacity(ReturnConsumedCapacity returnConsumedCapacity)
{
m_request.ReturnConsumedCapacity = returnConsumedCapacity;
return this;
}
public GetItemRequest build()
{
return m_request;
}
}
//=========================================================================================
// PutItemRequest Builder
//=========================================================================================
public class PutItemRequestBuilder
{
private readonly PutItemRequest m_request;
public PutItemRequestBuilder(string tableName)
{
m_request = new PutItemRequest
{
TableName = tableName,
Item = new Dictionary<string, AttributeValue>(),
ExpressionAttributeNames = new Dictionary<string, string>(),
ExpressionAttributeValues = new Dictionary<string, AttributeValue>()
};
}
public PutItemRequestBuilder withItem(Dictionary<string, AttributeValue> attributeValues)
{
m_request.Item = attributeValues;
return this;
}
public PutItemRequestBuilder addItem(string keyName, string keyValue)
{
m_request.Item[keyName] = new AttributeValue { S = keyValue };
return this;
}
public PutItemRequestBuilder addItem(string keyName, int keyValue)
{
m_request.Item[keyName] = new AttributeValue { N = keyValue.ToString() };
return this;
}
public PutItemRequestBuilder addItem(string keyName, long keyValue)
{
m_request.Item[keyName] = new AttributeValue { N = keyValue.ToString() };
return this;
}
public PutItemRequestBuilder addItem(string keyName, double keyValue)
{
m_request.Item[keyName] = new AttributeValue { N = keyValue.ToString() };
return this;
}
public PutItemRequestBuilder addItem(string keyName, float keyValue)
{
m_request.Item[keyName] = new AttributeValue { N = keyValue.ToString() };
return this;
}
public PutItemRequestBuilder addItem(string keyName, bool keyValue)
{
m_request.Item[keyName] = new AttributeValue { BOOL = keyValue };
return this;
}
public PutItemRequestBuilder withConditionalOperator(ConditionalOperator conditionalOperator)
{
m_request.ConditionalOperator = conditionalOperator;
return this;
}
public PutItemRequestBuilder withConditionalCapacity(ReturnConsumedCapacity returnConsumedCapacity)
{
m_request.ReturnConsumedCapacity = returnConsumedCapacity;
return this;
}
public PutItemRequestBuilder withConditionExpression(string conditionExpression)
{
m_request.ConditionExpression = conditionExpression;
return this;
}
public PutItemRequestBuilder withExpressionAttributeNames(Dictionary<string, string> expressAttributeNames)
{
m_request.ExpressionAttributeNames = expressAttributeNames;
return this;
}
public PutItemRequestBuilder withExpressionAttributeValues(Dictionary<string, AttributeValue> expressAttributeValues)
{
m_request.ExpressionAttributeValues = expressAttributeValues;
return this;
}
public PutItemRequestBuilder withReturnConsumedCapacity(ReturnConsumedCapacity retrunConsumedCapacity)
{
m_request.ReturnConsumedCapacity = retrunConsumedCapacity;
return this;
}
public PutItemRequestBuilder withReturnItemCollectionMetrics(ReturnItemCollectionMetrics returnItemCollectionMetrics)
{
m_request.ReturnItemCollectionMetrics = returnItemCollectionMetrics;
return this;
}
public PutItemRequest build()
{
return m_request;
}
}
//=========================================================================================
// DeleteItemRequest Builder
//=========================================================================================
public class DeleteItemRequestBuilder
{
private readonly DeleteItemRequest m_request;
public DeleteItemRequestBuilder(string tableName)
{
m_request = new DeleteItemRequest
{
TableName = tableName,
Key = new Dictionary<string, AttributeValue>(),
ExpressionAttributeNames = new Dictionary<string, string>(),
ExpressionAttributeValues = new Dictionary<string, AttributeValue>()
};
}
public DeleteItemRequestBuilder withKeys(Dictionary<string, AttributeValue> keys)
{
m_request.Key = keys;
return this;
}
public DeleteItemRequestBuilder addKey(string attributeName, string attributeValue)
{
m_request.Key[attributeName] = new AttributeValue { S = attributeValue };
return this;
}
public DeleteItemRequestBuilder addKey(string attributeName, int attributeValue)
{
m_request.Key[attributeName] = new AttributeValue { N = attributeValue.ToString() };
return this;
}
public DeleteItemRequestBuilder addKey(string attributeName, long attributeValue)
{
m_request.Key[attributeName] = new AttributeValue { N = attributeValue.ToString() };
return this;
}
public DeleteItemRequestBuilder addKey(string attributeName, double attributeValue)
{
m_request.Key[attributeName] = new AttributeValue { N = attributeValue.ToString() };
return this;
}
public DeleteItemRequestBuilder addKey(string attributeName, float attributeValue)
{
m_request.Key[attributeName] = new AttributeValue { N = attributeValue.ToString() };
return this;
}
public DeleteItemRequestBuilder addKey(string attributeName, bool attributeValue)
{
m_request.Key[attributeName] = new AttributeValue { BOOL = attributeValue };
return this;
}
public DeleteItemRequestBuilder withConditionalOperator(ConditionalOperator conditionalOperator)
{
m_request.ConditionalOperator = conditionalOperator;
return this;
}
public DeleteItemRequestBuilder withReturnValues(ReturnValue returnValue)
{
m_request.ReturnValues = returnValue;
return this;
}
public DeleteItemRequestBuilder withConditionExpression(string conditionExpression)
{
m_request.ConditionExpression = conditionExpression;
return this;
}
public DeleteItemRequestBuilder withExpressionAttributeNames(Dictionary<string, string> expressionAttributeNames)
{
m_request.ExpressionAttributeNames = expressionAttributeNames;
return this;
}
public DeleteItemRequestBuilder withExpressionAttributeValues(Dictionary<string, AttributeValue> expressionAttributeValues)
{
m_request.ExpressionAttributeValues = expressionAttributeValues;
return this;
}
public DeleteItemRequestBuilder withReturnConsumedCapacity(ReturnConsumedCapacity returnConsumedCapacity)
{
m_request.ReturnConsumedCapacity = returnConsumedCapacity;
return this;
}
public DeleteItemRequestBuilder withReturnItemCollectionMetrics(ReturnItemCollectionMetrics returnItemCollectionMetrics)
{
m_request.ReturnItemCollectionMetrics = returnItemCollectionMetrics;
return this;
}
public DeleteItemRequest build()
{
return m_request;
}
}
public static DynamoDbItemRequestQueryContext createItemRequestQueryContext( this AmazonDynamoDBRequest itemRequest
, QueryType queryType
, DynamoDbQueryExceptionNotifier.ExceptionHandler? exceptionHandler = null)
{
return new DynamoDbItemRequestQueryContext(itemRequest, queryType, exceptionHandler);
}
public static bool isValid(this AmazonDynamoDBRequest itemRequest)
{
//if (document.getPK() == string.Empty || document.getSK() == string.Empty || document.getDocType() == string.Empty)
//{
// Log.getLogger().error($"PK or SK or DocType is empty !!! : {document.toBasicString()}");
// return false;
//}
return true;
}
}

View File

@@ -0,0 +1,256 @@
using System.Diagnostics;
using Amazon.DynamoDBv2;
using Amazon.DynamoDBv2.DocumentModel;
using Amazon.DynamoDBv2.Model;
using ServerCore; using ServerBase;
namespace ServerBase;
public static class DynamoDbStopwatchHelper
{
public static Search queryWithStopwatch(this Table table, QueryOperationConfig queryOperationConfig, string eventTid)
{
Stopwatch? stopwatch = null;
var server_logic = ServerLogicApp.getServerLogicAppWithNull();
var server_config = server_logic?.getServerConfig();
if (true == server_config?.PerformanceCheckEnable)
{
eventTid = string.IsNullOrEmpty(eventTid) ? System.Guid.NewGuid().ToString("N") : eventTid;
stopwatch = Stopwatch.StartNew();
}
var search = table.Query(queryOperationConfig);
if (null != stopwatch)
{
var elapsed_msec = stopwatch.ElapsedMilliseconds;
stopwatch.Stop();
if (elapsed_msec > Constant.STOPWATCH_LOG_LIMIT_MSEC)
{
Log.getLogger().debug($"QueryBatch - Table Query Stopwatch Stop : ETID:{eventTid}, ElapsedMSec:{elapsed_msec} - {table.toBasicString()}");
}
Monitor.It.setDelayTimeForDBResponse(elapsed_msec);
}
return search;
}
public static async Task putItemAsyncWithStopwatch(this Table table, Document document, string eventTid)
{
Stopwatch? stopwatch = null;
var server_logic = ServerLogicApp.getServerLogicAppWithNull();
var server_config = server_logic?.getServerConfig();
if (true == server_config?.PerformanceCheckEnable)
{
eventTid = string.IsNullOrEmpty(eventTid) ? System.Guid.NewGuid().ToString("N") : eventTid;
stopwatch = Stopwatch.StartNew();
}
await table.PutItemAsync(document);
if (null != stopwatch)
{
var elapsed_msec = stopwatch.ElapsedMilliseconds;
stopwatch.Stop();
if (elapsed_msec > Constant.STOPWATCH_LOG_LIMIT_MSEC)
{
Log.getLogger().debug($"QueryBatch - Table PutItemAsync Stopwatch Stop : ETID:{eventTid}, ElapsedMSec:{elapsed_msec} - {table.toBasicString()}");
}
Monitor.It.setDelayTimeForDBResponse(elapsed_msec);
}
}
public static async Task<Document?> upsertItemAsyncWithStopwatch(this Table table, Document document, string eventTid)
{
Stopwatch? stopwatch = null;
var server_logic = ServerLogicApp.getServerLogicAppWithNull();
var server_config = server_logic?.getServerConfig();
if (true == server_config?.PerformanceCheckEnable)
{
eventTid = string.IsNullOrEmpty(eventTid) ? System.Guid.NewGuid().ToString("N") : eventTid;
stopwatch = Stopwatch.StartNew();
}
var updated_document = await table.UpdateItemAsync(document);
if (null != stopwatch)
{
var elapsed_msec = stopwatch.ElapsedMilliseconds;
stopwatch.Stop();
if (elapsed_msec > Constant.STOPWATCH_LOG_LIMIT_MSEC)
{
Log.getLogger().debug($"QueryBatch - Table UpsertItemAsync Stopwatch Stop : ETID:{eventTid}, ElapsedMSec:{elapsed_msec} - {table.toBasicString()}");
}
Monitor.It.setDelayTimeForDBResponse(elapsed_msec);
}
return updated_document;
}
public static async Task<Document?> updateItemAsyncWithStopwatch(this Table table, Document document, UpdateItemOperationConfig operationConfig, string eventTid)
{
Stopwatch? stopwatch = null;
var server_logic = ServerLogicApp.getServerLogicAppWithNull();
var server_config = server_logic?.getServerConfig();
if (true == server_config?.PerformanceCheckEnable)
{
eventTid = string.IsNullOrEmpty(eventTid) ? System.Guid.NewGuid().ToString("N") : eventTid;
stopwatch = Stopwatch.StartNew();
}
var updated_document = await table.UpdateItemAsync(document, operationConfig);
if (null != stopwatch)
{
var elapsed_msec = stopwatch.ElapsedMilliseconds;
stopwatch.Stop();
if (elapsed_msec > Constant.STOPWATCH_LOG_LIMIT_MSEC)
{
Log.getLogger().debug($"QueryBatch - Table UpdateItemAsync Stopwatch Stop : ETID:{eventTid}, ElapsedMSec:{elapsed_msec} - {table.toBasicString()}");
}
Monitor.It.setDelayTimeForDBResponse(elapsed_msec);
}
return updated_document;
}
public static async Task deleteItemAsyncWithStopwatch(this Table table, Document document, string eventTid)
{
Stopwatch? stopwatch = null;
var server_logic = ServerLogicApp.getServerLogicAppWithNull();
var server_config = server_logic?.getServerConfig();
if (true == server_config?.PerformanceCheckEnable)
{
eventTid = string.IsNullOrEmpty(eventTid) ? System.Guid.NewGuid().ToString("N") : eventTid;
stopwatch = Stopwatch.StartNew();
}
await table.DeleteItemAsync(document);
if (null != stopwatch)
{
var elapsed_msec = stopwatch.ElapsedMilliseconds;
stopwatch.Stop();
if (elapsed_msec > Constant.STOPWATCH_LOG_LIMIT_MSEC)
{
Log.getLogger().debug($"QueryBatch - Table DeleteItemAsync Stopwatch Stop : ETID:{eventTid}, ElapsedMSec:{elapsed_msec} - {table.toBasicString()}");
}
Monitor.It.setDelayTimeForDBResponse(elapsed_msec);
}
}
public static async Task executeAsyncWithStopwatch(this DocumentBatchWrite batch, string eventTid)
{
Stopwatch? stopwatch = null;
var server_logic = ServerLogicApp.getServerLogicAppWithNull();
var server_config = server_logic?.getServerConfig();
if (true == server_config?.PerformanceCheckEnable)
{
eventTid = string.IsNullOrEmpty(eventTid) ? System.Guid.NewGuid().ToString("N") : eventTid;
stopwatch = Stopwatch.StartNew();
}
await batch.ExecuteAsync();
if (null != stopwatch)
{
var elapsed_msec = stopwatch.ElapsedMilliseconds;
stopwatch.Stop();
if (elapsed_msec > Constant.STOPWATCH_LOG_LIMIT_MSEC)
{
Log.getLogger().debug($"QueryBatch - DocumentBatchWrite ExecuteAsync Stopwatch Stop : ETID:{eventTid}, ElapsedMSec:{elapsed_msec}");
}
Monitor.It.setDelayTimeForDBResponse(elapsed_msec);
}
}
public static async Task executeAsyncWithStopwatch(this DocumentTransactWrite transactWrite, string eventTid)
{
Stopwatch? stopwatch = null;
var server_logic = ServerLogicApp.getServerLogicAppWithNull();
var server_config = server_logic?.getServerConfig();
if (true == server_config?.PerformanceCheckEnable)
{
eventTid = string.IsNullOrEmpty(eventTid) ? System.Guid.NewGuid().ToString("N") : eventTid;
stopwatch = Stopwatch.StartNew();
}
await transactWrite.ExecuteAsync();
if (null != stopwatch)
{
var elapsed_msec = stopwatch.ElapsedMilliseconds;
stopwatch.Stop();
if (elapsed_msec > Constant.STOPWATCH_LOG_LIMIT_MSEC)
{
Log.getLogger().debug($"QueryBatch - DocumentTransactWrite ExecuteAsync Stopwatch Stop : ETID:{eventTid}, ElapsedMSec:{elapsed_msec}");
}
Monitor.It.setDelayTimeForDBResponse(elapsed_msec);
}
}
public static async Task<UpdateItemResponse> updateItemAsyncWithStopwatch(this AmazonDynamoDBClient dbClient, UpdateItemRequest updateItemRequest, string eventTid)
{
Stopwatch? stopwatch = null;
var server_logic = ServerLogicApp.getServerLogicAppWithNull();
var server_config = server_logic?.getServerConfig();
if (true == server_config?.PerformanceCheckEnable)
{
eventTid = string.IsNullOrEmpty(eventTid) ? System.Guid.NewGuid().ToString("N") : eventTid;
stopwatch = Stopwatch.StartNew();
}
var updated_item_response = await dbClient.UpdateItemAsync(updateItemRequest);
if (null != stopwatch)
{
var elapsed_msec = stopwatch.ElapsedMilliseconds;
stopwatch.Stop();
if (elapsed_msec > Constant.STOPWATCH_LOG_LIMIT_MSEC)
{
Log.getLogger().debug($"QueryBatch - DocumentTransactWrite ExecuteAsync Stopwatch Stop : ETID:{eventTid}, ElapsedMSec:{elapsed_msec} - {updateItemRequest.toBasicString()}");
}
Monitor.It.setDelayTimeForDBResponse(elapsed_msec);
}
return updated_item_response;
}
}

View File

@@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ServerBase;
public static class EntityHelper
{
public static Pos makePos( float x, float y, float z
, int angle )
{
var pos = new Pos();
pos.X = x;
pos.Y = y;
pos.Z = z;
pos.Angle = angle;
return pos;
}
}

View File

@@ -0,0 +1,46 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ServerBase;
//=============================================================================================
// 오류 관련 각종 지원 함수
//
// author : kangms
//
//=============================================================================================
public static class ErrorHelper
{
public static bool isSuccess(this ServerErrorCode error)
{
if(ServerErrorCode.Success == error)
{
return true;
}
return false;
}
public static bool isFail(this ServerErrorCode error)
{
if (ServerErrorCode.Success != error)
{
return true;
}
return false;
}
public static string toBasicString(this ServerErrorCode error)
{
return $"ErrorCode:{error.ToString()}";
}
}

View File

@@ -0,0 +1,86 @@
using System.Text;
using System.Text.Json;
using System.IdentityModel.Tokens.Jwt;
using ServerCore;
using ServerBase;
namespace ServerBase;
public static class HttpClientHelper
{
public static async Task<(bool, string)> sendHttpRequest( string httpMethod
, string url
, string? jwt
, string? bodyJson
, string mediaType
, string userAgentName
, string userAgentVersion
, short timeOutSec = 5 )
{
validateUserAgentArgs(mediaType, userAgentName, userAgentVersion);
Log.getLogger().debug($"Request Message To WebServer. url : {url}, httpMethod : {httpMethod}, jwt : {jwt}, bodyJson : {bodyJson}");
var requestMessage = new HttpRequestMessage(new HttpMethod(httpMethod), url);
if (!string.IsNullOrEmpty(jwt))
{
requestMessage.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", jwt);
}
if (!string.IsNullOrEmpty(bodyJson))
{
requestMessage.Content = new StringContent(bodyJson, Encoding.UTF8, mediaType);
}
requestMessage.Headers.UserAgent.Add(new System.Net.Http.Headers.ProductInfoHeaderValue(userAgentName, userAgentVersion));
try
{
using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(timeOutSec)))
{
var resMsg = await SharedHttpClient.It.sendAsync(requestMessage, cts.Token);
if (resMsg == null)
{
Log.getLogger().error($"resMsg is null, sendAsync() returned null - url:{url}");
return (false, string.Empty);
}
var readMsg = await resMsg.Content.ReadAsStringAsync();
Log.getLogger().debug($"Response Message From sendAsync() : readMsg:{readMsg} - url:{url}");
if (!resMsg.IsSuccessStatusCode)
{
return (false, readMsg);
}
return (true, readMsg);
}
}
catch (OperationCanceledException e)
{
Log.getLogger().error($"OperationCanceledException !!!, Request timed out !!! : exception:{e} - url:{url}");
return (false, "Request Timeout");
}
catch (Exception e)
{
Log.getLogger().error($"Exception !!!, Failed to perform in sendHttpRequest() : exception:{e} - - url:{url}");
return (false, string.Empty);
}
}
private static void validateUserAgentArgs(string mediaType, string userAgentName, string userAgentVersion)
{
if (mediaType.isNullOrWhiteSpace())
throw new ArgumentException("mediaType must be a non-empty string.", nameof(mediaType));
if (userAgentName.isNullOrWhiteSpace())
throw new ArgumentException("userAgentName must be a non-empty string.", nameof(userAgentName));
if (userAgentVersion.isNullOrWhiteSpace())
throw new ArgumentException("userAgentVersion must be a non-empty string.", nameof(userAgentVersion));
}
}

View File

@@ -0,0 +1,97 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Linq;
using System.Reflection.Metadata.Ecma335;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using MySqlConnector;
using ServerCore; using ServerBase;
namespace ServerBase;
//=============================================================================================
// MySqlConnector 관련 각종 지원 함수
//
// author : kangms
//
//=============================================================================================
public static class MySqlConnectorHelper
{
public static async Task<Result> simpleTryConnectToDb(string connectionString)
{
var result = new Result();
var err_msg = string.Empty;
using (var db_connector = new MySqlDbConnector())
{
var error_cdoe = await db_connector.initMySql(connectionString);
if (error_cdoe.isFail())
{
err_msg = $"Failed to initMySql() !!! : errCode:{error_cdoe} - connectString:{connectionString}";
result.setFail(error_cdoe, err_msg);
}
}
return result;
}
public static async Task<Result> simpleQueryExecuteForReaderAsync( string queryString, Func<MySqlDataReader, ServerErrorCode> readFunc
, string connectionString
, Int16 queryTimeoutSec = 15
, Int16 retryIntervalMSec = 1000, Int16 retryCount = 3 )
{
var result = new Result();
var err_msg = string.Empty;
using ( var db_connector = new MySqlDbConnector() )
{
for(var i = 0; i <= retryCount; i++)
{
var error_cdoe = await db_connector.initMySql(connectionString);
if (error_cdoe.isSuccess())
{
error_cdoe = await db_connector.querySQL(queryString, readFunc);
if(error_cdoe.isSuccess())
{
// 성공하면 즉시 반환 한다 !!!
result.setSuccess();
return result;
}
}
// Db와 연결이 끊어진 경우는 재시도 설정에 따라 추가 처리 한다.
if (ConnectionState.Closed == db_connector.getLastConnectionState())
{
err_msg = $"Failed to retry Open MySql !!! : errCode:{error_cdoe}";
result.setFail(error_cdoe, err_msg);
if (i > 0)
{
await Task.Delay(retryIntervalMSec);
err_msg = $"Retry Query !!! : retriedCount:{i}, queryString:{queryString} - retryableCount:{retryCount}";
Log.getLogger().error(err_msg);
}
}
// 기타 오류 발생시 실패로 처리 한다.
else
{
err_msg = $"Failed to simpleQueryExecuteForReaderAsync() !!! : errCode:{error_cdoe}, queryString:{queryString} - retryCount:{retryCount}";
Log.getLogger().error(err_msg);
result.setFail(error_cdoe, err_msg);
break;
}
}
}
return result;
}
}

View File

@@ -0,0 +1,16 @@
using MySqlConnector;
using ServerCore; using ServerBase;
namespace ServerBase;
public static class MySqlHelper
{
public static string makeConnectionString(string server, string id, string passward, string database)
{
return $"Server={server};User ID={id};Password={passward};Database={database}";
}
}

View File

@@ -0,0 +1,138 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NLog.LayoutRenderers.Wrappers;
using ServerCore; using ServerBase;
namespace ServerBase;
public static class ProgramVersionHelper
{
public static ServerErrorCode checkClientProgramVersion( ServerConfig config
, ServerProgramVersion serverProgramVersion
, ClientProgramVersion clientVersion )
{
ArgumentNullException.ThrowIfNull(config, $"config is null !!!");
ArgumentNullException.ThrowIfNull(serverProgramVersion, $"serverProgramVersion is null !!!");
var err_msg = string.Empty;
if (null == clientVersion)
{
err_msg = $"ClientProgramVersion is null !!!";
Log.getLogger().error(err_msg);
return ServerErrorCode.ClientProgramVersionIsNull;
}
var version_paths = config.getVersionPaths();
ArgumentNullException.ThrowIfNull(version_paths, $"version_paths is null !!!");
foreach ( var each in version_paths )
{
var version_type = each.Key;
switch(version_type)
{
case ProgramVersionType.MetaSchemaVersion:
if (serverProgramVersion.MetaSchemaVersion != clientVersion.MetaSchemaVersion)
{
err_msg = $"Not match MetaSchema Version !!! : serverMetaSchemaVersion:{serverProgramVersion.MetaSchemaVersion} == clientMetaSchemaVersion:{clientVersion.MetaSchemaVersion}";
Log.getLogger().error(err_msg);
return ServerErrorCode.MetaSchemaVersionNotMatch;
}
break;
case ProgramVersionType.MetaDataVersion:
if (serverProgramVersion.MetaDataVersion != clientVersion.MetaDataVersion)
{
err_msg = $"Not match MetaData Version !!! : serverMetaDataVersion:{serverProgramVersion.MetaDataVersion} == clientMetaDataVersion:{clientVersion.MetaDataVersion}";
Log.getLogger().error(err_msg);
return ServerErrorCode.MetaDataVersionNotMatch;
}
break;
case ProgramVersionType.PacketVersion:
if (serverProgramVersion.PacketVersion != clientVersion.PacketVersion)
{
err_msg = $"Not match Packet Version !!! : serverPacketVersion:{serverProgramVersion.PacketVersion} == clientPacketVersion:{clientVersion.PacketVersion}";
Log.getLogger().error(err_msg);
return ServerErrorCode.PacketVersionNotMatch;
}
break;
case ProgramVersionType.ResourceVersion:
if (serverProgramVersion.ResourceVersion != clientVersion.ResourceVersion)
{
err_msg = $"Not match ResourceVersion Version !!! : serverPacketVersion:{serverProgramVersion.ResourceVersion} == clientResourceVersion:{clientVersion.ResourceVersion}";
Log.getLogger().error(err_msg);
return ServerErrorCode.ResourceVersionNotMatch;
}
break;
}
if (config.ClientMinimumRequiredLogicVersion > clientVersion.LogicVersion)
{
err_msg = $"Not match ClientLogic Version !!! : ClientMinimumRequiredLogicVersion:{config.ClientMinimumRequiredLogicVersion} <= clientLogicVersion:{clientVersion.LogicVersion}";
Log.getLogger().error(err_msg);
return ServerErrorCode.ClientLogicVersionNotMatch;
}
}
return ServerErrorCode.Success;
}
public static Result appendVersion( this ServerProgramVersion programVersion
, ProgramVersionType versionType
, Int32 buildVersion, Int32 revisionVersion )
{
var result = new Result();
switch(versionType)
{
case ProgramVersionType.MetaSchemaVersion:
programVersion.MetaSchemaVersion = (ulong)revisionVersion;
break;
case ProgramVersionType.MetaDataVersion:
programVersion.MetaDataVersion = (ulong)revisionVersion;
break;
case ProgramVersionType.DbSchemaVersion:
programVersion.DbSchemaVersion = (ulong)revisionVersion;
break;
case ProgramVersionType.PacketVersion:
programVersion.PacketVersion = (ulong)revisionVersion;
break;
case ProgramVersionType.ResourceVersion:
programVersion.ResourceVersion = (ulong)revisionVersion;
break;
case ProgramVersionType.ConfigVersion:
programVersion.ConfigVersion = (ulong)revisionVersion;
break;
case ProgramVersionType.LogicVersion:
var logic_version = new LogicVersion();
programVersion.LogicVersion = logic_version;
logic_version.Revision = revisionVersion;
break;
default:
var err_msg = $"Invaoid ProgramVersionType !!! : ProgramVersionType:{versionType}";
Log.getLogger().error(err_msg);
return result;
}
return result;
}
}

View File

@@ -0,0 +1,187 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using Google.Protobuf;
using Google.Protobuf.Collections;
using Google.Protobuf.Reflection;
using Google.Protobuf.WellKnownTypes;
using Microsoft.AspNetCore.DataProtection.KeyManagement;
using RabbitMQ.Client.Events;
using ServerCore; using ServerBase;
namespace ServerBase;
//=============================================================================================
// Protobuf 관련 각종 지원 함수
//
// author : kangms
//
//=============================================================================================
public static class ProtoHelper
{
public static string toBasicString(this IMessage _this)
{
// IM : IMessage
return $"IM: {_this.ToString()}";
}
public static string toBasicString(this FieldDescriptor _this)
{
// FD : FieldDescriptor
return $"FD: {_this.ToString()}";
}
public static string toJson(this IMessage message)
{
return JsonFormatter.Default.Format(message);
}
public static List<T> toList<T>(this RepeatedField<T> repeatedField)
{
return new List<T>(repeatedField);
}
public static RepeatedField<T> toRepeatedField<T>(this List<T> list)
{
var repeated_field = new RepeatedField<T>();
repeated_field.AddRange(list);
return repeated_field;
}
public static Dictionary<TKey, TValue> toDictionary<TKey, TValue>(this MapField<TKey, TValue> mapField)
where TKey : notnull
where TValue : notnull
{
var dictionary = new Dictionary<TKey, TValue>();
foreach (var each in mapField)
{
dictionary.Add(each.Key, each.Value);
}
return dictionary;
}
public static MapField<TKey, TValue> toMapField<TKey, TValue>(this Dictionary<TKey, TValue> dictionary)
where TKey : notnull
where TValue : notnull
{
var map_field = new MapField<TKey, TValue>();
foreach (var each in dictionary)
{
map_field.Add(each.Key, each.Value);
}
return map_field;
}
private static IMessage? parsePacketActionType(this IMessage rootMsg)
{
var found_oneof = rootMsg.Descriptor.Oneofs.First<OneofDescriptor>();
if (null == found_oneof)
{
return null;
}
var curr_oneof_field = found_oneof.Accessor.GetCaseFieldDescriptor(rootMsg);
if (null == curr_oneof_field)
{
return null;
}
var packet_action_type = (IMessage)curr_oneof_field.Accessor.GetValue(rootMsg);
if (null == packet_action_type)
{
return null;
}
return packet_action_type;
}
private static IMessage? parsePacketCommandType(this IMessage childMsg)
{
var found_oneof = childMsg.Descriptor.Oneofs.First<OneofDescriptor>();
if (null == found_oneof)
{
return null;
}
var curr_oneof_field = found_oneof.Accessor.GetCaseFieldDescriptor(childMsg);
if (null == curr_oneof_field)
{
return null;
}
var packet_command_type = (IMessage)curr_oneof_field.Accessor.GetValue(childMsg);
if (null == packet_command_type)
{
return null;
}
return packet_command_type;
}
private static bool parsePacketCommandMsgHook(this IMessage message)
{
var message_hook_property = message.GetType().GetProperty("MessageHook");
if (message_hook_property != null)
{
var msgHookValue = message_hook_property.GetValue(message);
if (msgHookValue is bool)
{
return (bool)msgHookValue;
}
}
return false;
}
public static bool makeProudNetPacketCommand(this IMessage message, out ProudNetPacketCommand? packetCommand)
{
packetCommand = null;
var packet_action_type = message.parsePacketActionType();
if (null == packet_action_type)
{
Log.getLogger().fatal($"Not found Packet Action Type !!! : {message.toBasicString()}");
return false;
}
var packet_command_type = parsePacketCommandType(packet_action_type);
if (null == packet_command_type)
{
Log.getLogger().fatal($"Not found Packet Command Type !!! : {message.toBasicString()}");
return false;
}
var msg_hook = parsePacketCommandMsgHook(packet_command_type);
packetCommand = new ProudNetPacketCommand(packet_action_type.GetType(), packet_command_type.GetType());
return true;
}
public static bool makeRabbitMqPacketCommand(this IMessage message, BasicDeliverEventArgs ea, out RabbitMqPacketCommand? packetCommand)
{
packetCommand = null;
var packet_command_type = message.parsePacketCommandType();
if (null == packet_command_type)
{
ServerCore.Log.getLogger().fatal($"Not found Packet Command Type !!! : {message.toBasicString()}");
return false;
}
packetCommand = new RabbitMqPacketCommand(ea.Exchange, packet_command_type.GetType());
return true;
}
}

View File

@@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Google.Protobuf;
using Nettention.Proud;
using ServerCore; using ServerBase;
using SESSION_ID = System.Int32;
namespace ServerBase;
//=============================================================================================
// ProudNet 관련 각종 지원 함수
//
// author : kangms
//
//=============================================================================================
public static class ProudNetHelper
{
public static void convertP2PGroupToByteArray(ByteArray byArray, P2PGroupType p2PGroupType)
{
using (var stream = new MemoryStream())
{
using (var output = new CodedOutputStream(stream))
{
p2PGroupType.WriteTo(output);
}
byArray.AddRange(stream.ToArray());
}
}
}

View File

@@ -0,0 +1,88 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ServerCore; using ServerBase;
namespace ServerBase;
//=============================================================================================
// Result 관련 확장 함수
//
// author : kangms
//
//=============================================================================================
public static class ResultHelper
{
public static bool isSuccess(this Result result)
{
ArgumentNullReferenceCheckHelper.throwIfNull(result, () => $"result is null !!!");
if (true == result.ErrorCode.isSuccess())
{
return true;
}
return false;
}
public static bool isFail(this Result result)
{
ArgumentNullReferenceCheckHelper.throwIfNull(result, () => $"result is null !!!");
if (true == result.ErrorCode.isFail())
{
return true;
}
return false;
}
public static void setSuccess(this Result result, string resultString = "")
{
ArgumentNullReferenceCheckHelper.throwIfNull(result, () => $"result is null !!!");
result.set(ServerErrorCode.Success, resultString);
}
public static void setFail(this Result result, ServerErrorCode errorCode, string resultString = "")
{
ArgumentNullReferenceCheckHelper.throwIfNull(result, () => $"result is null !!!");
result.set(errorCode, resultString);
}
public static string getResultString(this Result result)
{
ArgumentNullReferenceCheckHelper.throwIfNull(result, () => $"result is null !!!");
return result.ResultString;
}
public static ServerErrorCode getErrorCode(this Result result)
{
ArgumentNullReferenceCheckHelper.throwIfNull(result, () => $"result is null !!!");
return result.ErrorCode;
}
public static void set(this Result result, ServerErrorCode errorCode, string resultString)
{
ArgumentNullReferenceCheckHelper.throwIfNull(result, () => $"result is null !!!");
result.ErrorCode = errorCode;
result.ResultString = resultString;
}
public static string toBasicString(this Result result)
{
ArgumentNullReferenceCheckHelper.throwIfNull(result, () => $"result is null !!!");
return $"ErrorInfo: errCode:{result.getErrorCode()}, errDesc:{result.getResultString()}";
}
}

View File

@@ -0,0 +1,212 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections.Concurrent;
using NLog.Layouts;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Microsoft.AspNetCore.Components;
using Axion.Collections.Concurrent;
using Amazon.DynamoDBv2.Model;
using ServerCore; using ServerBase;
using DYNAMO_DB_TABLE_NAME = System.String;
using DYNAMO_DB_TABLE_FULL_NAME = System.String;
namespace ServerBase;
public static partial class ServerConfigHelper
{
private static ServerConfig? m_server_config;
private static Flags<AuthRule.FlagType> m_auth_rules_flags = new();
private static Dictionary<Tuple<ServerType, ServerType>, LoadBalancingRule.Config> m_load_balancing_rule_configs = new();
public static void init(ServerConfig serverConfig)
{
m_server_config = serverConfig;
initAuthRule(serverConfig);
initLoadBalancingRule(serverConfig);
}
public static int getDefaultMaxUser()
{
NullReferenceCheckHelper.throwIfNull(m_server_config, () => $"m_server_config is null !!!");
return m_server_config.DefaultMaxUser;
}
private static void initAuthRule(ServerConfig serverConfig)
{
var auth_rule = serverConfig.AuthRule;
m_auth_rules_flags.setFlag(AuthRule.FlagType.BotIdAllow, auth_rule.BotIdAllow);
m_auth_rules_flags.setFlag(AuthRule.FlagType.TestIdAllow, auth_rule.TestIdAllow);
m_auth_rules_flags.setFlag(AuthRule.FlagType.ClientStandaloneAllow, auth_rule.ClientStandaloneAllow);
m_auth_rules_flags.setFlag(AuthRule.FlagType.ClientBySsoAccountAuthWithLauncherAllow, auth_rule.ClientBySsoAccountAuthWithLauncherAllow);
}
public static string getSsoAccountDbConnectionString(this ServerConfig serverConfig)
{
return serverConfig.SsoAccountDb;
}
private static void initLoadBalancingRule(ServerConfig serverConfig)
{
var load_balancing_rule = serverConfig.LoadBalancingRule;
m_load_balancing_rule_configs.Add(Tuple.Create<ServerType, ServerType>(ServerType.Login, ServerType.Channel), load_balancing_rule.AuthToGameRule);
m_load_balancing_rule_configs.Add(Tuple.Create<ServerType, ServerType>(ServerType.Channel, ServerType.Indun), load_balancing_rule.ChannelToInstanceDungeonRule);
m_load_balancing_rule_configs.Add(Tuple.Create<ServerType, ServerType>(ServerType.Indun, ServerType.Channel), load_balancing_rule.InstanceDungeonToChannelRule);
m_load_balancing_rule_configs.Add(Tuple.Create<ServerType, ServerType>(ServerType.Channel, ServerType.Channel), load_balancing_rule.ChannelToChannelRule);
}
public static LoadBalancingRule.Config? getLoadBalancingRuleType(ServerType fromServerType, ServerType toServerType)
{
m_load_balancing_rule_configs.TryGetValue(Tuple.Create<ServerType, ServerType>(fromServerType, toServerType), out var rule_config);
if(null == rule_config)
{
rule_config = m_server_config?.LoadBalancingRule.ToEtcServerRule;
}
return rule_config;
}
public static string geAccountNftDbConnectionString(this ServerConfig serverConfig)
{
return serverConfig.AccountNftDb;
}
public static List<PlatformType> toPlatformTypeAllows(this string platformTypeAllows)
{
var platform_type_allows = new List<PlatformType>();
var splitted_value = platformTypeAllows.Split(",");
for(int i = 0; i < splitted_value.Length; i++)
{
var enum_string = splitted_value[i];
var platform_type = EnumHelper.convertEnumValueStringToEnum(enum_string, PlatformType.None);
if(PlatformType.None != platform_type)
{
if(true == platform_type_allows.Exists(x => x == platform_type))
{
continue;
}
platform_type_allows.Add(platform_type);
}
}
return platform_type_allows;
}
public static ServiceType toServiceType( this string serviceType, ServiceType defaultType)
{
return EnumHelper.convertEnumValueStringToEnum(serviceType, defaultType);
}
public static DYNAMO_DB_TABLE_NAME makeDynamoDbTableFullName(string tableName, ServiceType serviceType)
{
ConditionValidCheckHelper.throwIfFalseWithCondition(() => false == tableName.isNullOrWhiteSpace(), () => $"Invalid tableName !!!");
return tableName + "-" + serviceType.ToString();
}
public static (ServerErrorCode, ConcurrentDictionary<DYNAMO_DB_TABLE_NAME, DYNAMO_DB_TABLE_FULL_NAME>) getDynamoDbTableNamesWithServiceType( string serviceType )
{
var target_table_names = new ConcurrentDictionary<DYNAMO_DB_TABLE_NAME, DYNAMO_DB_TABLE_FULL_NAME>();
// 서비스 타입 체크
var enum_service_type = serviceType.convertEnumTypeAndValueStringToEnum<ServiceType>(ServiceType.None);
if ( enum_service_type != ServiceType.Dev
&& enum_service_type != ServiceType.Qa
&& enum_service_type != ServiceType.Stage
&& enum_service_type != ServiceType.Live )
{
return (ServerErrorCode.ServiceTypeInvalid, target_table_names);
}
//=========================================================================================
// 기본 메인 테이블
//=========================================================================================
{
var main_table_name = DynamoDbDefine.TableNames.Main;
var table_full_name = ServerConfigHelper.makeDynamoDbTableFullName(main_table_name, enum_service_type);
if(false == target_table_names.TryAdd(main_table_name, table_full_name)) { return (ServerErrorCode.DynamoDbTableNameDuplicated, target_table_names); }
}
//=========================================================================================
// 기타 추가 DB
//=========================================================================================
return (ServerErrorCode.Success, target_table_names);
}
public static string toClientListenIP(this ServerConfig _this)
{
return AwsHelper.getAwsPublicIPv4OrEthernetIPv4();
}
public static UInt16 toClientListenPort(this ServerConfig _this)
{
if (0 >= _this.ClientListenPort)
{
return _this.getAppParamPort();
}
return _this.ClientListenPort;
}
public static ServerErrorCode fillupCloudWatchLogLayout(this JObject jsonObject, out JsonLayout jsonLayout)
{
JObject? json_object_with_layout = null;
if (JTokenType.Object == jsonObject.Type)
{
json_object_with_layout = jsonObject["Layout"] as JObject;
}
if ( null == json_object_with_layout )
{
jsonLayout = new JsonLayout
{
Attributes =
{
new JsonAttribute("logTime", "${date:universalTime=true:format=yyyy-MM-ddTHH\\:mm\\:ss.fffZ}"),
new JsonAttribute("server", "${event-properties:server}"),
new JsonAttribute("ip/port", "${event-properties:ip/port}"),
new JsonAttribute("threadId", "${threadid}"),
new JsonAttribute("level", "${level:upperCase=true}"),
new JsonAttribute("message", "${message}"),
new JsonAttribute("function", "${event-properties:memberName}"),
new JsonAttribute("filePath", "${event-properties:filePath}"),
new JsonAttribute("lineNumber", "${event-properties:lineNumber}"),
}
};
}
else
{
jsonLayout = new JsonLayout();
var ref_attributes = jsonLayout.Attributes;
foreach (var property in json_object_with_layout.Properties())
{
if (JTokenType.Object != jsonObject.Type)
{
return ServerErrorCode.JsonTypeInvalid;
}
ref_attributes.Add(new JsonAttribute(property.Name, property.Value.ToString()));
}
}
return ServerErrorCode.Success;
}
}

View File

@@ -0,0 +1,151 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ServerCore; using ServerBase;
namespace ServerBase;
public static class ServerHelper
{
public static Mutex m_server_running_mutex = new Mutex();
public static bool isValidServerType(this ServerType type)
{
switch (type)
{
case ServerType.Channel:
case ServerType.Login:
case ServerType.Indun:
case ServerType.Chat:
case ServerType.Auth:
case ServerType.Manager:
break;
default:
{
Log.getLogger().error($"Invalid ServerType !!! : ServerType:{type}");
return false;
}
}
return true;
}
public static LocationTargetType toLocationTargetType(this string serverType)
{
var server_type = EnumHelper.convertEnumValueStringToEnum<ServerType>(serverType, ServerType.None);
switch (server_type)
{
case ServerType.Channel:
return LocationTargetType.World;
case ServerType.Indun:
return LocationTargetType.Instance;
}
return LocationTargetType.None;
}
public static ServerType toServerType(this string serverType)
{
return EnumHelper.convertEnumValueStringToEnum<ServerType>(serverType, ServerType.None);
}
public static ServerType fillupServerType(string serverName)
{
var server_type = serverName.Split(":")[0];
return EnumHelper.convertEnumValueStringToEnum<ServerType>(server_type, ServerType.None);
}
public static string toServerTypeString(this ServerType serverType)
{
return serverType.ToString();
}
public static string toServerName(this ServerType type, string ip, UInt16 port, int worldId = 0, int channel = 0)
{
var server_type = type.ToString();
var server_name = string.Empty;
if (false == type.isValidServerType())
{
server_name = makeServerNameByNetworkAddress(type, ip, port);
Log.getLogger().error($"Failed to convert ServerName !!! : {server_name}");
return server_name;
}
switch (type)
{
case ServerType.Channel:
{
server_name = makeGameServerName(type, worldId, channel);
}
break;
default:
{
server_name = makeServerNameByNetworkAddress(type, ip, port);
}
break;
}
return server_name;
}
public static string makeGameServerName(ServerType type, int worldId, int channel)
{
return ($"{type.ToString()}:{ServerHelper.makeWorldIdToString(worldId)}:{ServerHelper.makeChannelToString(channel)}");
}
public static string makeServerNameByNetworkAddress(ServerType type, string ip, UInt16 port)
{
return ($"{type.ToString()}:{ip}_{port.ToString()}");
}
public static string makeWorldIdToString(int worldId)
{
return worldId.ToString("000");
}
public static string makeChannelToString(int channel)
{
return channel.ToString("000");
}
public static bool isRunningServerWithListenPort(this Process currProcess, UInt16 listenPort)
{
return isExistProcess(currProcess.ProcessName + Convert.ToString(listenPort));
}
public static bool isExistProcess(string serverName)
{
m_server_running_mutex = new Mutex(true, serverName, out bool create_new);
return create_new == true ? false : true;
}
public static NetworkAddress toNetworkAddress(this ServerInfo serverInfo)
{
var network_address = new NetworkAddress();
network_address.IP = serverInfo.Address;
network_address.Port = (UInt16)serverInfo.Port;
return network_address;
}
public static ClientToLoginMessage.Types.GameServerInfo toGameServerInfo(this NetworkAddress networkAddress)
{
var game_server_info = new ClientToLoginMessage.Types.GameServerInfo();
game_server_info.GameServerAddr = networkAddress.IP;
game_server_info.GameServerPort = networkAddress.Port;
return game_server_info;
}
}

View File

@@ -0,0 +1,93 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using StackExchange.Redis;
using ServerCore;
using MODULE_ID = System.UInt32;
namespace ServerBase;
public static class ServerLogicHelper
{
public static RabbitMQConnectorBase getRabbitMqConnector(this IServerLogic logic)
{
var found_module = logic.getModule((MODULE_ID)ModuleId.RabbitMqConnector);
var casted_rabbit_mq_connector = found_module as RabbitMQConnectorBase;
NullReferenceCheckHelper.throwIfNull(casted_rabbit_mq_connector, () => $"casted_rabbit_mq_connector is null !!! - {logic.toBasicString()}");
return casted_rabbit_mq_connector;
}
public static RedisConnector getRedisConnector(this IServerLogic logic)
{
var found_module = logic.getModule((MODULE_ID)ModuleId.RedisConnector);
var casted_redis_connector = found_module as RedisConnector;
NullReferenceCheckHelper.throwIfNull(casted_redis_connector, () => $"casted_redis_connector is null !!! - {logic.toBasicString()}");
return casted_redis_connector;
}
public static IDatabase getRedisDb(this IServerLogic logic)
{
var redis_database = logic.getRedisConnector().getDatabase();
NullReferenceCheckHelper.throwIfNull(redis_database, () => $"redis_database is null !!! - {logic.toBasicString()}");
return redis_database;
}
public static RedisWithLuaScriptExecutor getRedisWithLuaScriptExecutor(this IServerLogic logic)
{
var found_module = logic.getModule((MODULE_ID)ModuleId.RedisWithLuaScriptExecutor);
var casted_redis_with_lua_script_executor = found_module as RedisWithLuaScriptExecutor;
NullReferenceCheckHelper.throwIfNull(casted_redis_with_lua_script_executor, () => $"casted_redis_with_lua_script_executor is null !!! - {logic.toBasicString()}");
return casted_redis_with_lua_script_executor;
}
public static DynamoDbClient getDynamoDbClient(this IServerLogic logic)
{
var found_module = logic.getModule((MODULE_ID)ModuleId.DynamoDbConnector);
var casted_dynamo_db_client = found_module as DynamoDbClient;
NullReferenceCheckHelper.throwIfNull(casted_dynamo_db_client, () => $"casted_dynamo_db_client is null !!! - {logic.toBasicString()}");
return casted_dynamo_db_client;
}
public static MongoDbConnector getMongoDbConnector(this IServerLogic logic)
{
var found_module = logic.getModule((MODULE_ID)ModuleId.MongoDbConnector);
var casted_mongo_db_connector = found_module as MongoDbConnector;
NullReferenceCheckHelper.throwIfNull(casted_mongo_db_connector, () => $"casted_mongo_db_connector is null !!! - {logic.toBasicString()}");
return casted_mongo_db_connector;
}
public static S3Connector getS3Connector(this IServerLogic logic)
{
var found_module = logic.getModule((MODULE_ID)ModuleId.S3Connector);
var casted_s3_connector = found_module as S3Connector;
NullReferenceCheckHelper.throwIfNull(casted_s3_connector, () => $"casted_s3_connector is null !!! - {logic.toBasicString()}");
return casted_s3_connector;
}
public static ListenSessionBase getListenSessionBase(this ServerLogicBase logicBase)
{
var found_module = logicBase.getModule<IModule>((MODULE_ID)ModuleId.ProudNetListener);
var listen_session_base = found_module as ListenSessionBase;
NullReferenceCheckHelper.throwIfNull(listen_session_base, () => $"listen_session_base is null !!! - {logicBase.toBasicString()}");
return listen_session_base;
}
public static ListenSessionBase getListenSessionBaseByModuleId(this ServerLogicBase logicBase, ModuleId moduleId)
{
var found_module = logicBase.getModule<IModule>((MODULE_ID)moduleId);
var listen_session_base = found_module as ListenSessionBase;
NullReferenceCheckHelper.throwIfNull(listen_session_base, () => $"listen_session_base is null !!! - {logicBase.toBasicString()}");
return listen_session_base;
}
}

View File

@@ -0,0 +1,172 @@

using ServerCore;
using MODULE_ID = System.UInt32;
using WORLD_META_ID = System.UInt32;
namespace ServerBase;
public static class ServerMetricsHelper
{
public static async Task<(Result, ServerInfo?)> getServerInfoByChannel( this IWithServerMetrics serverMetrics
, WORLD_META_ID worldMetaId, int channelNo )
{
var result = new Result();
ServerInfo? found_server_info = null;
var server_metrics_request = serverMetrics.getServerMetricsCacheRequest();
NullReferenceCheckHelper.throwIfNull(server_metrics_request, () => $"server_metrics_request is null - {serverMetrics.toBasicString()}");
var handler_type = (uint)ServerMetricsCacheRequest.TriggerType.ServerMetrics_ChannelTargetGetAndFill;
var with_result = await server_metrics_request.tryRunTriggerHandler(handler_type, worldMetaId, channelNo);
if (with_result.Result.isFail())
{
return (with_result.Result, null);
}
var result_value_server_info = with_result as ResultValue<ServerInfo>;
NullReferenceCheckHelper.throwIfNull(result_value_server_info, () => $"result_value_server_info is null - {serverMetrics.toBasicString()}");
if (null != result_value_server_info.ValueOfResult)
{
found_server_info = result_value_server_info.ValueOfResult;
}
if (null != found_server_info)
{
return (result, found_server_info);
}
return (result, null);
}
public static async Task<(Result, ServerInfo?)> getServerInfoByServerName( this IWithServerMetrics serverMetrics
, string serverName )
{
var result = new Result();
ServerInfo? found_server_info = null;
var server_metrics_request = serverMetrics.getServerMetricsCacheRequest();
NullReferenceCheckHelper.throwIfNull(server_metrics_request, () => $"server_metrics_request is null - {serverMetrics.toBasicString()}");
var handler_type = (uint)ServerMetricsCacheRequest.TriggerType.ServerMetrics_GetByServerName;
var with_result = await server_metrics_request.tryRunTriggerHandler(handler_type, serverName);
if (with_result.Result.isFail())
{
return (with_result.Result, null);
}
var result_value_server_info = with_result as ResultValue<ServerInfo>;
NullReferenceCheckHelper.throwIfNull(result_value_server_info, () => $"result_value_server_info is null - {serverMetrics.toBasicString()}");
if (null != result_value_server_info.ValueOfResult)
{
found_server_info = result_value_server_info.ValueOfResult;
}
if (null != found_server_info)
{
return (result, found_server_info);
}
return (result, null);
}
public static async Task<(Result, List<ServerInfo>)> getServerInfosByServerType( this IWithServerMetrics serverMetrics
, ServerType serverType, WORLD_META_ID worldMetaId = 0)
{
var result = new Result();
var server_metrics_request = serverMetrics.getServerMetricsCacheRequest();
NullReferenceCheckHelper.throwIfNull(server_metrics_request, () => $"server_metrics_request is null - {serverMetrics.toBasicString()}");
var handler_type = (uint)ServerMetricsCacheRequest.TriggerType.ServerMetrics_ServerTypeGetAndFill;
var with_result = await server_metrics_request.tryRunTriggerHandler(handler_type, serverType, worldMetaId);
if (with_result.Result.isFail())
{
return (with_result.Result, new List<ServerInfo>());
}
if (with_result.isResultOnly())
{
return (result, new List<ServerInfo>());
}
var result_value_server_info = with_result as ResultValue<List<ServerInfo>>;
NullReferenceCheckHelper.throwIfNull(result_value_server_info, () => $"result_value_server_info is null - {serverMetrics.toBasicString()}");
return (result, result_value_server_info.ValueOfResult);
}
public static async Task<Result> syncServerInfoToCache( this IWithServerMetrics serverMetrics
, string serverName
, string listenIp, ushort listenPort
, int currentConnectedUserCount
, int maxUserCount
, int reservationCount = 0, int returnUserCount = 0
, int ugcNpcCount = 0
, string awsInstanceId = ""
, int worldId = 0, int channelNo = 0, int roomCapcity = 0 )
{
var err_msg = string.Empty;
var result = new Result();
try
{
var server_metrics_request = serverMetrics.getServerMetricsCacheRequest();
var server_info = server_metrics_request.makeServerInfo( serverName
, listenIp, listenPort
, currentConnectedUserCount, maxUserCount
, reservationCount, returnUserCount
, ugcNpcCount
, worldId, channelNo, roomCapcity
, awsInstanceId );
var handler_type = (uint)ServerMetricsCacheRequest.TriggerType.ServerMetrics_UpdateToCache;
var with_result = await server_metrics_request.tryRunTriggerHandler(handler_type, serverName, server_info);
if (with_result.Result.isFail())
{
err_msg = $"Failed to sync ServerInfo with Cache !!! : {with_result.Result.toBasicString()} - {serverMetrics.toBasicString()}";
Log.getLogger().error(err_msg);
return with_result.Result;
}
return result;
}
catch (Exception e)
{
var error_code = ServerErrorCode.TryCatchException;
err_msg = $"Exception !!!, Failed to sync ServerInfo with Cache !!! : errorCode:{error_code}, exception:{e} - {serverMetrics.toBasicString()}";
result.setFail(error_code, err_msg);
Log.getLogger().error(err_msg);
return result;
}
}
public static ServerMetricsCacheRequest getServerMetricsCacheRequest(this IWithServerMetrics serverMetrics)
{
return ServerMetricsManager.It.getServerMetricsCacheRequest();
}
public static bool isSetupCompleted(this ServerLogicBase serverLogic)
{
return ServerMetricsManager.It.isSetupCompleted();
}
public static async Task<Result> setupDefaultServerMetrics<TRedisConnector>(this IWithServerMetrics serverMetrics, MODULE_ID toRedisConnectorModuleId)
where TRedisConnector : RedisConnector
{
await Task.CompletedTask;
var result = new Result();
var server_logic = serverMetrics as IServerLogic;
NullReferenceCheckHelper.throwIfNull(server_logic, () => $"server_logic is null");
var found_redis_connector_module = server_logic.getModule(toRedisConnectorModuleId);
var casted_redis_connector = found_redis_connector_module as RedisConnector;
NullReferenceCheckHelper.throwIfNull(casted_redis_connector, () => $"casted_redis_connector is null - moudleId:{toRedisConnectorModuleId}, moduleType:{typeof(TRedisConnector)} - {server_logic.toBasicString()}");
return ServerMetricsManager.It.setup(casted_redis_connector);
}
}

View File

@@ -0,0 +1,33 @@
using ServerCore;
namespace ServerBase;
public static class TickerHelper
{
public static async Task<Result> createTimeEventForMinuteTicker(this IServerLogic serverLogic)
{
var result = new Result();
// base ticker 등록
var entity_ticker_initializers = new Initializers();
entity_ticker_initializers.appendInitializer(new TimeEventForMinuteTicker((double)ConstValue.default_1_min_to_sec * ConstValue.default_1_sec_to_milisec, null));
await entity_ticker_initializers.init("EntityTickers");
// ticker 등록
foreach (var initializer in entity_ticker_initializers.getInitializers())
{
var entity_ticker = initializer as EntityTicker;
NullReferenceCheckHelper.throwIfNull(entity_ticker, () => $"entity_ticker is null !!! - {serverLogic.toBasicString()}");
result = serverLogic.registerEntityTicker(entity_ticker);
if (result.isFail())
{
return result;
}
}
return result;
}
}

View File

@@ -0,0 +1,58 @@
using Amazon.Runtime.Internal;
using ServerControlCenter;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ServerCore; using ServerBase;
using TRANSACTION_NAME = System.String;
using TRANSACTION_GUID = System.String;
namespace ServerBase;
public class TransactionRunnerHelper
{
public static async Task<Result> runTransactionRunnerWithLogic( EntityBase owner
, TransactionIdType transactionIdType, TRANSACTION_NAME transactionName
, Func<Task<Result>> fnTransactLogic )
{
ArgumentNullReferenceCheckHelper.throwIfNull(owner, () => $"owner is null !!!");
ArgumentNullReferenceCheckHelper.throwIfNull(fnTransactLogic, () => $"fnTransactLogic is null !!! - {owner.toBasicString()}");
using (var runner = new TransactionRunner(owner, transactionIdType, transactionName))
{
var result = runner.beginTransaction();
if (result.isFail())
{
return result;
}
return await fnTransactLogic.Invoke();
}
}
public static async Task<Result> runTransactionRunnerWithLogic( EntityBase owner, TRANSACTION_GUID transGuid
, TransactionIdType transactionIdType, TRANSACTION_NAME transactionName
, Func<Task<Result>> fnTransactLogic )
{
ArgumentNullReferenceCheckHelper.throwIfNull(owner, () => $"owner is null !!!");
ArgumentNullReferenceCheckHelper.throwIfNull(fnTransactLogic, () => $"fnTransactLogic is null !!! - {owner.toBasicString()}");
using (var runner = new TransactionRunner(owner, transGuid, transactionIdType, transactionName))
{
var result = runner.beginTransaction();
if (result.isFail())
{
return result;
}
return await fnTransactLogic.Invoke();
}
}
}

View File

@@ -0,0 +1,63 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Amazon.S3.Model;
using ServerCore; using ServerBase;
namespace ServerBase;
public static class VersionHelper
{
public static (Result result, int buildVersion,int revisionVersion) tryReadVersion(string filePath)
{
var result = new Result();
var build_version = 0;
var revision_version = 0;
try
{
if (File.Exists(filePath))
{
using (var stream = new StreamReader(filePath))
{
while (stream.Peek() >= 0)
{
var line = stream.ReadLine();
if (line == null)
break;
if (line.IndexOf("Build:") >= 0)
{
var version = line.Replace("Build:", "");
build_version = int.Parse(version);
}
else if (line.IndexOf("Revision:") >= 0)
{
var version = line.Replace("Revision:", "");
revision_version = int.Parse(version);
}
}
}
}
}
catch (Exception e)
{
var err_msg = $"Exception !!!, Failed to perform in tryReadVersion() !!! : exception:{e} - filePath:{filePath}";
result.setFail(ServerErrorCode.TryCatchException, err_msg);
Log.getLogger().error(result.toBasicString());
return (result, 0, 0);
}
return (result, build_version, revision_version);
}
}

View File

@@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ServerCore;
using SESSION_ID = System.Int32;
using META_ID = System.UInt32;
using ENTITY_GUID = System.String;
using ACCOUNT_ID = System.String;
using OWNER_GUID = System.String;
using USER_GUID = System.String;
using CHARACTER_GUID = System.String;
using ITEM_GUID = System.String;
namespace ServerBase;
public static class ServerLogicApp
{
private static IServerLogic? m_server_logic_base;
public static void setServerLogicApp(IServerLogic serverLogic) => m_server_logic_base = serverLogic;
public static IServerLogic getServerLogicApp()
{
NullReferenceCheckHelper.throwIfNull(m_server_logic_base, () => $"m_server_logic_base is null !!!");
return m_server_logic_base;
}
public static IServerLogic? getServerLogicAppWithNull() => m_server_logic_base;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,71 @@
using ServerCore; using ServerBase;
namespace ServerBase;
public class DailyTimeTask
{
public Func<Task> m_task { get; init; } = null!;
public DateTime m_time { get; init; } = DateTimeHelper.MinTime;
public bool m_is_first_run { get; set; } = false;
}
public class DailyTimeEventManager
{
private static readonly Lazy<DailyTimeEventManager> m_instance = new(() => new DailyTimeEventManager());
public static DailyTimeEventManager Instance => m_instance.Value;
private Dictionary<string, DailyTimeTask> m_tasks { get; set; } = new();
public Result tryAddTask(string taskName, DateTime time, Func<Task> task, bool isFirstRun = false)
{
var result = new Result();
var is_add = m_tasks.TryAdd(taskName, new DailyTimeTask { m_task = task, m_time = time, m_is_first_run = isFirstRun});
if (! is_add)
{
var err_msg = $"fail to add daily time event !! already added - taskName: {taskName} / time: {time}";
result.setFail(ServerErrorCode.DailyTimeEventAdditionFailed, err_msg);
Log.getLogger().error(result.toBasicString());
}
return result;
}
public async Task runTimeEvents()
{
try
{
foreach (var task in m_tasks)
{
if (!checkEventTime(task.Value.m_time) && task.Value.m_is_first_run) continue;
m_tasks[task.Key].m_is_first_run = true;
// fire and forget
_ = Task.Run(task.Value.m_task);
}
}
catch (Exception e)
{
var err_msg = $"fail to run daily time event !! - {e}";
Log.getLogger().error(err_msg);
}
await Task.CompletedTask;
}
private bool checkEventTime(DateTime time)
{
var current = DateTimeHelper.Current;
// Hour check
if (current.Hour != time.Hour) return false;
// Minute check
if (current.Minute != time.Minute) return false;
// seconds check
if (current.Second != time.Second) return false;
return true;
}
}

View File

@@ -0,0 +1,46 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using StackExchange.Redis;
using ServerCore;
namespace ServerBase;
public class ServerMetricsManager : Singleton<ServerMetricsManager>
{
public static readonly int KEEP_SERVER_UPDATE_INTERVAL_MSEC = (10 * 1000);
private ServerMetricsCacheRequest? m_server_Metrics_handler;
private ConcurrentDictionary<string, ServerMetricsCacheRequest.CacheServerKey> m_cache_server_keys = new();
private bool m_is_setup_completed = false;
public Result setup(RedisConnector redisConnector)
{
var result = new Result();
m_server_Metrics_handler = new ServerMetricsHandler(this, redisConnector);
m_is_setup_completed = true;
return result;
}
public ServerMetricsCacheRequest getServerMetricsCacheRequest()
{
NullReferenceCheckHelper.throwIfNull(m_server_Metrics_handler, () => $"found_request is null !!!");
return m_server_Metrics_handler;
}
public bool isSetupCompleted() => m_is_setup_completed;
public ConcurrentDictionary<string, ServerMetricsCacheRequest.CacheServerKey> getCacheServers() => m_cache_server_keys;
}

View File

@@ -0,0 +1,64 @@

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Intrinsics.X86;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using ServerCore;
namespace ServerBase;
public abstract partial class RabbitMqConnector : RabbitMQConnectorBase, IRabbitMqSession, IModule
{
public class ConfigParam : IConfigParam
{
public string ServiceType { get; set; } = string.Empty;
public string ServiceName { get; set; } = string.Empty;
public string HostName { get; set; } = string.Empty;
public string UserName { get; set; } = string.Empty;
public string Password { get; set; } = string.Empty;
public ushort Port { get; set; } = 0;
public bool UseSSL { get; set; } = false;
public RabbitMqConnector.FnServerMessageRecvFromConsumer? fnServerMessageRecvFromConsumer { get; set; }
public Result tryReadFromJsonOrDefault(JObject jObject)
{
var result = new Result();
var err_msg = string.Empty;
try
{
var rabbit_mq = jObject["Rabbitmq"];
NullReferenceCheckHelper.throwIfNull(rabbit_mq, () => $"rabbit_mq is null !!!");
HostName = rabbit_mq["HostName"]?.Value<string>() ?? HostName;
UserName = rabbit_mq["UserName"]?.Value<string>() ?? UserName;
Password = rabbit_mq["Password"]?.Value<string>() ?? Password;
Port = rabbit_mq["Port"]?.Value<ushort>() ?? Port;
UseSSL = rabbit_mq["SSL"]?.Value<bool>() ?? UseSSL;
return result;
}
catch (Exception e)
{
var error_code = ServerErrorCode.TryCatchException;
err_msg = $"Exception !!!, Failed to perform in tryReadFromJsonOrDefault() !!! : errorCode:{error_code}, exception:{e} - {this.getTypeName()}";
result.setFail(error_code, err_msg);
Log.getLogger().error(err_msg);
return result;
}
}
public string toBasicString()
{
return $"ConfigParam: ServiceType:{ServiceType}, ServiceName:{ServiceName}, HostAddress:{HostName}, Port:{Port}, UserName:{UserName}, Password:{Password}, UseSsl:{UseSSL}";
}
}
};

View File

@@ -0,0 +1,336 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;
using Nettention.Proud;
using RabbitMQ.Client.Events;
using RabbitMQ.Client;
using Google.Protobuf;
using Google.Protobuf.WellKnownTypes;
using System.Reflection;
using Newtonsoft.Json;
using NLog;
using MongoDB.Bson.IO;
using ServerCore;
namespace ServerBase;
public abstract partial class RabbitMqConnector : RabbitMQConnectorBase, IRabbitMqSession, IModule, IWithPacketNamespaceVerifier
{
public delegate Task FnServerMessageRecvFromConsumer(BasicDeliverEventArgs ea, IMessage message);
public FnServerMessageRecvFromConsumer? _fnServerMessageRecvFromConsumer;
private readonly PacketReceiver m_packet_receiver;
private ModuleContext m_module_context;
public RabbitMqConnector( ModuleContext moduleContext )
: base( getConfigParamsOrThrow(moduleContext).serviceName
, getConfigParamsOrThrow(moduleContext).address
, getConfigParamsOrThrow(moduleContext).port
, getConfigParamsOrThrow(moduleContext).userName
, getConfigParamsOrThrow(moduleContext).password
, getConfigParamsOrThrow(moduleContext).useSSL )
{
m_module_context = moduleContext;
m_packet_receiver = new PacketReceiver(this);
}
private static (string serviceName, string address, int port, string userName, string password, bool useSSL) getConfigParamsOrThrow(ModuleContext context)
{
if (context.getConfigParam() is not ConfigParam param)
throw new InvalidCastException("IConfigParam must be of type RabbitMqConnector.ConfigParam !!!");
return (
param.ServiceName ?? throw new InvalidOperationException("ServiceName is null !!!"),
param.HostName ?? throw new InvalidOperationException("HostName is null !!!"),
param.Port,
param.UserName ?? throw new InvalidOperationException("Username is null !!!"),
param.Password ?? throw new InvalidOperationException("Password is null !!!"),
param.UseSSL
);
}
public bool isValidPacketNamespace(string toCheckNamespace, IPacketCommand packetCommand)
{
var packet_namespace = ".PacketHandler";
if (null != toCheckNamespace
&& true == toCheckNamespace.Contains(packet_namespace))
{
return true;
}
else
{
ServerCore.Log.getLogger().error($"Invalid PacketNamespace !!!, not included Namespace : {packet_namespace} ⊆ {toCheckNamespace}, packetCommnad:{packetCommand.toBasicString()}");
}
return false;
}
public async Task<Result> startModule()
{
var err_msg = string.Empty;
var result = new Result();
var module_context = getModuleContext();
var config_param = module_context.getConfigParam() as ConfigParam;
NullReferenceCheckHelper.throwIfNull(config_param, () => $"config_param is null !!!");
var fn_receiver = config_param.fnServerMessageRecvFromConsumer;
NullReferenceCheckHelper.throwIfNull(fn_receiver, () => $"fn_receiver is null !!!");
result = onCreateExchangeChannel();
if(result.isFail())
{
err_msg = $"Failed to onCreateExchangeChannel() in startModule() !!! - {config_param.toBasicString()}";
result.setFail(ServerErrorCode.RabbitMqChannelCreateFailed, err_msg);
Log.getLogger().error(result.toBasicString());
return result;
}
var is_success = await startMessageReceiverByConsumer(fn_receiver);
if(false == is_success)
{
err_msg = $"Failed to startMessageReceiverByConsumer() in startModule() !!! - {config_param.toBasicString()}";
result.setFail(ServerErrorCode.RabbitMqConsumerStartFailed, err_msg);
Log.getLogger().error(result.toBasicString());
return result;
}
return result;
}
public async Task<Result> stopModule()
{
var result = new Result();
return await Task.FromResult(result);
}
public async Task<bool> startMessageReceiverByConsumer(RabbitMqConnector.FnServerMessageRecvFromConsumer fnFromConsumer)
{
if(null == fnFromConsumer)
{
return false;
}
var result = await m_packet_receiver.registerRecvHandlerAll();
if (false == result)
{
Log.getLogger().error("rabbitMq registerRecvHandler error");
return result;
}
_fnServerMessageRecvFromConsumer = fnFromConsumer;
return base.startConsumer();
}
public async Task<bool> onRecvProtocol<T>(BasicDeliverEventArgs ea, T recvProtocol)
where T : Google.Protobuf.IMessage
{
Log.getLogger().info($"receive:{recvProtocol.ToString()} - {toBasicString()}");
Stopwatch? stopwatch = null;
var event_tid = string.Empty;
var server_logic = ServerLogicApp.getServerLogicApp();
var server_config = server_logic.getServerConfig();
if (true == server_config.PerformanceCheckEnable)
{
event_tid = System.Guid.NewGuid().ToString("N");
stopwatch = Stopwatch.StartNew();
}
var receiver = getPacketReceiver();
if (false == receiver.fillupRabbitMqPacketCommand(ea, recvProtocol, out var fillupPacketCommand))
{
Log.getLogger().error($"Invalid received Packet !!! : {recvProtocol.ToString()}");
return false;
}
if (fillupPacketCommand == null)
{
return false;
}
var found_handler = receiver.findRecvHandler(fillupPacketCommand);
if (null == found_handler)
{
Log.getLogger().error($"Unacceptable Packet !!! : {fillupPacketCommand.toBasicString()} - {toBasicString()}");
return false;
}
var check_by_exchange_type = onCheckByExchangeType(ea, recvProtocol);
if (false == check_by_exchange_type) return false;
//var task = Task.Factory.StartNew(async () =>
var task = Task.Run(async () =>
{
var result = await found_handler.onCheckValid(this, recvProtocol);
if (result.isFail())
{
Log.getLogger().error($"Failed to check Valid !!! : {fillupPacketCommand.toBasicString()}");
}
result = await found_handler.onProcessPacket(this, recvProtocol);
if (result.isFail())
{
Log.getLogger().error($"Failed to process Packet !!! : {fillupPacketCommand.toBasicString()}");
}
if (null != stopwatch)
{
var elapsed_msec = stopwatch.ElapsedMilliseconds;
stopwatch.Stop();
Log.getLogger().debug($"{GetType()} MQSSPacket Stopwatch Stop : ETID:{event_tid}, ElapsedMSec:{elapsed_msec} - {recvProtocol.toBasicString()}");
}
});
return await Task.FromResult(true);
}
protected override async void onRecvJsonMessageFromConsumer(object? sender, BasicDeliverEventArgs ea)
{
var body = ea.Body.ToArray();
var bodyStr = Encoding.UTF8.GetString(body);
ServerMessage? message = null;
try
{
message = ServerMessage.Parser.ParseJson(bodyStr);
}
catch (Exception e)
{
Log.getLogger().error($"consumer parsing error bodyStr : {bodyStr}, stackTrace : {e.StackTrace}");
return;
}
if(message == null)
{
Log.getLogger().error($"message is null, bodyStr : {bodyStr}");
return;
}
if(_fnServerMessageRecvFromConsumer == null)
{
Log.getLogger().error("_fnServerMessageRecvFromConsumer is null");
return;
}
await _fnServerMessageRecvFromConsumer.Invoke(ea, message);
}
protected override void onRecvProtoMessageFromConsumer(object? sender, BasicDeliverEventArgs ea)
{
}
protected virtual bool onCheckByExchangeType<T>(BasicDeliverEventArgs ea, T recvProtocol)
where T : Google.Protobuf.IMessage
{
return true;
}
public Task<object>? SendKick( string destServer, string name, Int32 delayMS
, Action<Task<object>>? callback )
{
int reqId = nextReqId();
Task<object>? waitTask = null;
if (callback != null)
{
CancellationTokenSource cancelTokenSrc = new CancellationTokenSource(delayMS);
waitTask = registerCompletionSource(reqId, cancelTokenSrc.Token, callback);
if (waitTask == null)
{
return null;
}
}
ServerMessage message = new ServerMessage();
message.KickReq = new ServerMessage.Types.KickReq();
message.KickReq.ReqId = reqId;
message.KickReq.Name = name;
SendMessage(destServer, message);
return waitTask;
}
public void SendMessage(string to, ServerMessage message)
{
IConnection? con = getConnection();
if(con == null)
{
Log.getLogger().error("GetConnection return null");
return;
}
using (var channel = con.CreateModel())
{
Stopwatch? stopwatch = null;
var event_tid = string.Empty;
var server_logic = ServerLogicApp.getServerLogicApp();
var server_config = server_logic.getServerConfig();
if (true == server_config.PerformanceCheckEnable)
{
event_tid = System.Guid.NewGuid().ToString("N");
stopwatch = Stopwatch.StartNew();
}
channel.QueueDeclare( queue: to,
durable: true,
exclusive: false,
autoDelete: true,
arguments: null );
message.MessageTime = Google.Protobuf.WellKnownTypes.Timestamp.FromDateTime(DateTime.UtcNow);
message.MessageSender = getServiceName();
string messageJson = JsonFormatter.Default.Format(message);
var body = Encoding.UTF8.GetBytes(messageJson);
channel.BasicPublish( exchange: "",
routingKey: to,
basicProperties: null,
body: body );
Log.getLogger().info($"send to MQS !!!, msg:{messageJson} - receiver:{to}");
if (null != stopwatch)
{
var elapsed_msec = stopwatch.ElapsedMilliseconds;
stopwatch.Stop();
Log.getLogger().debug($"{GetType()} SMQSPacket Stopwatch Stop : ETID:{event_tid}, ElapsedMSec:{stopwatch.ElapsedMilliseconds} - {message.toBasicString()}");
}
}
}
protected abstract Result onCreateExchangeChannel();
public PacketReceiver getPacketReceiver() => m_packet_receiver;
public ModuleContext getModuleContext() => m_module_context;
public virtual string toBasicString()
{
return $"{this.getTypeName()}";
}
}

View File

@@ -0,0 +1,69 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Amazon.DynamoDBv2.Model;
using ServerCore; using ServerBase;
namespace ServerBase;
/*
public static partial class MetaHelper
{
public abstract class MetaHelperBase
{
private bool m_is_initialized = false;
public MetaHelperBase()
{
}
public bool init()
{
if (m_is_initialized == true)
{
return true;
}
m_is_initialized = true;
return onInit();
}
protected abstract bool onInit();
public bool isInitialized()
{
return m_is_initialized;
}
public string? getMetaHelperName()
{
return GetType().FullName;
}
}
public abstract class MetaHelperBase<T>
: MetaHelperBase where T : class, IMetaData
{
public MetaHelperBase()
: base()
{
}
//protected T MetaDatas
//{
// get { return TableData.It.getMetaDatas<T>(); }
//}
}//IHelper<T>
}
*/

View File

@@ -0,0 +1,51 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace ServerBase;
public static class ContentLoader
{
public static T? loadFile<T>(string dataDir, string fileName) where T : ContentTableBase<T>
{
try
{
string exactPath = Path.GetFullPath(dataDir);
string data = File.ReadAllText(Path.Combine(dataDir, fileName));
return Newtonsoft.Json.JsonConvert.DeserializeObject<T>(data);
}
catch (Exception ex)
{
throw new Exception($"content load fail. dataDir: {dataDir}, fileName: {fileName}", ex);
}
}
public static T? loadMultipleFiles<T>(string dataDir, string filePattern) where T : ContentTableBase<T>
{
var files = Directory.GetFiles(dataDir, filePattern, SearchOption.TopDirectoryOnly);
List<T> tables = new List<T>();
foreach (string file in files)
{
string data = File.ReadAllText(file);
T? json = Newtonsoft.Json.JsonConvert.DeserializeObject<T>(data);
if (json != null)
tables.Add(json);
}
T? oneTable = null;
foreach (var table in tables)
{
if (oneTable == null)
oneTable = table;
else
oneTable.merge(table);
}
return oneTable;
}
}

View File

@@ -0,0 +1,11 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace ServerBase;
public class ContentTableBase<T>
{
public virtual void merge(T table) { }
}

View File

@@ -0,0 +1,8 @@

namespace ServerBase;
public interface IMetaData
{
string toBasicString();
}

View File

@@ -0,0 +1,37 @@
using System.Reflection;
using ServerCore; using ServerBase;
namespace ServerBase;
public class MetaValidator
{
public void validate<T>(IReadOnlyList<T> list, ValidatorErrorCollection errors)
{
var thisAssembly = Assembly.GetExecutingAssembly();
var methods = thisAssembly.GetTypes()
.SelectMany(x => x.GetMethods(BindingFlags.Public | BindingFlags.Static))
.Where(x => x.GetCustomAttributes<MetaValidatorAttribute>().Any())
.Where(x => x.GetParameters()?[0]?.ParameterType == typeof(T))
.ToList();
if (methods.Count == 0)
{
Log.getLogger().error($"not found validateFunc. type: {typeof(T).Name}");
return;
}
var validateFunc = MethodInfoExtentions.compile<Action<T, ValidatorErrorCollection>>(methods[0]);
errors.CurrentName = typeof(T).Name;
// var invokeParams = new object[1];
foreach (var (item, i) in list.Select((value, i) => (value, i)))
{
errors.CurrentArrayIndex = i;
validateFunc.Invoke(item!, errors);
}
}
}

View File

@@ -0,0 +1,11 @@

namespace ServerBase;
[AttributeUsage(AttributeTargets.Method)]
public class MetaValidatorAttribute : Attribute
{
}

View File

@@ -0,0 +1,41 @@
using System.Linq.Expressions;
using System.Reflection;
namespace ServerBase;
public static class MethodInfoExtentions
{
public static TDelegate compile<TDelegate>(MethodInfo mi)
{
ParameterExpression? @this = null;
if (!mi.IsStatic)
{
@this = Expression.Parameter(mi.DeclaringType!, "this");
}
var parameters = new List<ParameterExpression>();
if (@this != null)
{
parameters.Add(@this);
}
foreach (var parameter in mi.GetParameters())
{
parameters.Add(Expression.Parameter(parameter.ParameterType, parameter.Name));
}
Expression? call = null;
if (@this != null)
{
call = Expression.Call(@this, mi, parameters.Skip(1));
}
else
{
call = Expression.Call(mi, parameters);
}
return Expression.Lambda<TDelegate>(call, parameters).Compile();
}
}

View File

@@ -0,0 +1,69 @@
using System.Text;
using ServerCore;
namespace ServerBase;
public class ValidattionError
{
public string Name { get; set; } = "";
public int ArrayIndex { get; set; }
public string Message { get; set; } = "";
public string toDetailString()
{
return $"[{Name}, array: {ArrayIndex}] {Message}";
}
public string toSimpleString()
{
return $"[array: {ArrayIndex}] {Message}";
}
}
public class ValidatorErrorCollection
{
public string CurrentName { get; set; } = "";
public int CurrentArrayIndex { get; set; }
public Dictionary<string, List<ValidattionError>> Errors = new();
public bool HasError => Errors.Count > 0;
public void add(string message)
{
Errors.TryGetValue(CurrentName, out var list);
if (list == null)
{
list = new List<ValidattionError>();
Errors.Add(CurrentName, list);
}
list.Add(new ValidattionError
{
Name = CurrentName,
ArrayIndex = CurrentArrayIndex,
Message = message
});
}
public void log()
{
foreach (var item in Errors)
{
StringBuilder sb = new();
sb.AppendLine($"[{item.Key}]");
foreach (var error in item.Value)
{
sb.AppendLine($"\t {error.toSimpleString()}");
}
sb.AppendLine($"[{item.Key}] validaton error. errorCount: {item.Value.Count}");
Log.getLogger().error(sb.ToString());
}
}
}

View File

@@ -0,0 +1,88 @@
using Google.Protobuf.WellKnownTypes;
using ServerCore;
namespace ServerBase;
public class Monitor : Singleton<Monitor>
{
// 접속중인 유저수 카운터
public Counter UserCurrentCount { get; set; } = new Counter();
// 1분 동안 최대 로그인 처리 횟수
private long MaxLoginCounterForMin { get; set; } = 0;
private void checkMaxLoginCounter(long loginCounter)
{
MaxLoginCounterForMin = Math.Max(MaxLoginCounterForMin, loginCounter);
NamedPipeMonitor.setContentDetail("분당 최대 로그인 처리 수", Value.ForNumber(MaxLoginCounterForMin));
}
// 1분당 로그인 처리 수
private (DateTime setTime, Counter counter) LoginCounter { get; set; } = new ValueTuple<DateTime, Counter>();
public void incLoginCounter()
{
var current = DateTimeHelper.Current;
if (current - LoginCounter.setTime > TimeSpan.FromSeconds(60))
{
LoginCounter = new(current, new Counter());
}
LoginCounter.counter.incCount();
checkMaxLoginCounter(LoginCounter.counter.getCount());
}
// DB 최대 응답 지연 시간
private long MaxDelayTimeForDBResponse { get; set; }
public void setDelayTimeForDBResponse(long delayTimeMs)
{
MaxDelayTimeForDBResponse = Math.Max(MaxDelayTimeForDBResponse, delayTimeMs);
NamedPipeMonitor.setContentDetail("DB 응답 최대 딜레이 시간", Value.ForNumber(MaxDelayTimeForDBResponse));
if(delayTimeMs >= 1000) incDelayTimeForDBResponseOverSec();
}
// 1초 이상 DB 응답 지연 횟수
private Counter DelayTimeForDBResponseOverSec { get; set; } = new Counter();
private void incDelayTimeForDBResponseOverSec()
{
DelayTimeForDBResponseOverSec.incCount();
NamedPipeMonitor.setContentDetail("1초 이상 DB 응답 딜레이 수", Value.ForNumber(DelayTimeForDBResponseOverSec.getCount()));
}
// Current Room 갯수
private Counter CurrentRoomCounter { get; set; } = new Counter();
public void incRoomCounter()
{
CurrentRoomCounter.incCount();
var room_counter = CurrentRoomCounter.getCount();
NamedPipeMonitor.setContentDetail("현재 룸 갯수", Value.ForNumber(room_counter));
setMaxRoomCounterForRunningTime(room_counter);
}
public void decRoomCounter()
{
CurrentRoomCounter.decCount();
NamedPipeMonitor.setContentDetail("현재 룸 갯수", Value.ForNumber(CurrentRoomCounter.getCount()));
}
private long MaxRoomCounterForRunningTime { get; set; }
private void setMaxRoomCounterForRunningTime(long roomCounter)
{
if (MaxRoomCounterForRunningTime < roomCounter )
{
MaxRoomCounterForRunningTime = roomCounter;
}
NamedPipeMonitor.setContentDetail("러닝 타임 중 최대 룸 갯수", Value.ForNumber(MaxRoomCounterForRunningTime));
}
public void setReceivedP2PDataInfo(long packetDataSize = 0, long packetCount = 0, int receivedUserCount = 0)
{
NamedPipeMonitor.setContentDetail("P2P 수신 데이터 양", Value.ForNumber(packetDataSize));
NamedPipeMonitor.setContentDetail("P2P 수신 패킷 수", Value.ForNumber(packetCount));
NamedPipeMonitor.setContentDetail("P2P 수신 유저수", Value.ForNumber(receivedUserCount));
}
}

View File

@@ -0,0 +1,99 @@
using System.Net.NetworkInformation;
using System.Reflection;
using ControlCenter.NamedPipe;
using Google.Protobuf;
using Google.Protobuf.WellKnownTypes;
using ServerCore; using ServerBase;
using ServerControlCenter;
namespace ServerBase;
public static class NamedPipeMonitor
{
public static async Task ChangeServerStatus(ServerStatus status) =>
await NamedPipeClientHelper.Instance.SetServerStatus(status);
public static async Task SetStopReason(StopReason reason) =>
await NamedPipeClientHelper.Instance.SetStopReason(reason);
public static NamedPipeResultCode SetUserConnectionBlockStatus(bool user_connection_block_status) =>
NamedPipeClientHelper.Instance.SetUserConnectionBlockStatus(user_connection_block_status);
public static NamedPipeResultCode SetCurrentClientConnection(int connectionCount, ServerType serverType)
{
var result = NamedPipeClientHelper.Instance.SetCurrentClientConnection(connectionCount);
if (serverType != ServerType.Indun)
{
result = SetCurrentCapacity(connectionCount);
}
return result;
}
public static NamedPipeResultCode setCommonDetail(string key, Value value) =>
NamedPipeClientHelper.Instance.SetDetail(key, value);
public static NamedPipeResultCode setCommonDetails(Dictionary<string, Value> list) =>
NamedPipeClientHelper.Instance.SetDetails(list);
public static NamedPipeResultCode setContentDetail(string key, Value value) =>
NamedPipeClientHelper.Instance.SetContentDetail(key, value);
public static NamedPipeResultCode setContentDetails(Dictionary<string, Value> list) =>
NamedPipeClientHelper.Instance.SetContentDetails(list);
public static NamedPipeResultCode SetCurrentCapacity(int capacity) =>
NamedPipeClientHelper.Instance.SetCurrentCapacity(capacity);
public static async Task<NamedPipeResultCode> SendMessageAsync<T>(T message, string message_id) where T : IMessage =>
await NamedPipeClientHelper.Instance.SendMessageAsync(message, message_id);
public static async Task StopNamedPipeService()
{
Log.getLogger().debug($"NamedPipeClientHelper : StopNamedPipeService Call !!!");
await NamedPipeClientHelper.Instance.StopPipeClient();
}
public static async Task StartNamedPipeService(bool enable_named_pipe, ServerType server_type, int port, ServiceCategory serviceCategory, int max_connection_count, int? channel_no = null, int? world_id = null)
{
var ip = NetworkHelper.getEthernetLocalIPv4();
var type = server_type switch
{
ServerType.Auth => ServerControlCenter.ServerType.Login,
ServerType.Login => ServerControlCenter.ServerType.Login,
ServerType.Channel => ServerControlCenter.ServerType.Channel,
ServerType.Indun => ServerControlCenter.ServerType.Indun,
ServerType.Chat => ServerControlCenter.ServerType.Chat,
ServerType.UgqApi => ServerControlCenter.ServerType.UgqApi,
ServerType.UgqAdmin => ServerControlCenter.ServerType.UgqAdmin,
ServerType.UgqIngame => ServerControlCenter.ServerType.UgqIngame,
ServerType.BrokerApi => ServerControlCenter.ServerType.BrokerApi,
_ => ServerControlCenter.ServerType.None
};
if (type == ServerControlCenter.ServerType.None) return;
var named_pipe_options = new NamedPipeClientHelper.NamedPipeClientOption
{
m_enable_pipe = enable_named_pipe,
m_service_category = serviceCategory.ToString(),
m_type = type, m_ip = ip, m_port = port,
m_channel_no = channel_no,
m_world_id = world_id,
m_max_client_connection = max_connection_count,
m_assemblies = GetAssemblies().ToList(),
m_max_retry_count = 5,
m_retry_delay_time_ms = 5_000
};
Log.getLogger().debug($"Start NamedPipe Client - Name[Agent_{named_pipe_options.m_ip}.{named_pipe_options.m_port}]");
await NamedPipeClientHelper.Instance.StartNamedPipeClientAsync(named_pipe_options);
}
private static IEnumerable<Assembly> GetAssemblies()
=> new List<Assembly>(AppDomain.CurrentDomain.GetAssemblies());
}

View File

@@ -0,0 +1,251 @@
using System.Collections.Concurrent;
using Google.Protobuf;
using NeoSmart.AsyncLock;
using Nettention.Proud;
using ServerCore; using ServerBase;
namespace ServerBase;
public class LargePacketHandler
{
//private ConcurrentDictionary<(Player, string), SortedSet<CollectedPacketInfo>> m_large_packets = new();
private ConcurrentDictionary<IEntityWithSession, SortedSet<CollectedPacketInfo>> m_large_packets = new(); // 하나의 패킷만 처리
private static AsyncLock _lock = new();
private SessionBase? m_network_session { get; set; }
public void onInit(SessionBase networkSession)
{
m_network_session = networkSession;
var func = checkLargePacket;
_ = PeriodicTaskHelper.runTask(func, TimeSpan.FromMilliseconds(Constant.LARGE_PACKET_CHECK_INTERVAL_MSEC));
}
public Result collectPacket(IEntityWithSession ssesion, string packetUid, Int32 totalPacketCount, Int32 packetIdx, ByteString datas)
{
Log.getLogger().debug($"LargePacketCollect packetUid : {packetUid}, totalPacketCount : {totalPacketCount}, packetIdx : {packetIdx}, datasize : {datas.Length}");
var collected_packet = new CollectedPacketInfo(packetUid, totalPacketCount, packetIdx, datas);
//var user_guid = player.getUserGuid();
var result = addCollectedPacket(ssesion, collected_packet);
return result;
}
private Result addCollectedPacket(IEntityWithSession ssesion, CollectedPacketInfo packet)
{
using (_lock.Lock())
{
if (false == m_large_packets.TryGetValue(ssesion, out var collected_packets))
{
collected_packets = new SortedSet<CollectedPacketInfo>();
collected_packets.Add(packet);
m_large_packets.TryAdd(ssesion, collected_packets);
}
else
{
collected_packets.Add(packet);
}
}
Log.getLogger().debug($"addCollectedPacket done");
return new Result();
}
private Result removeCollectedPacket(List<IEntityWithSession> sessionIds)
{
if (sessionIds.Count == 0) return new();
var result = new Result();
using (_lock.Lock())
{
foreach (var session_id in sessionIds)
{
m_large_packets.TryRemove(session_id, out var _);
}
}
return result;
}
public Task<Result> checkLargePacket()
{
var result = new Result();
List<IEntityWithSession> delete_users = new();
foreach (var packets in m_large_packets)
{
var session = packets.Key;
var packet_set = packets.Value;
var isRecvResult = validCheck(session, packet_set);
if (isRecvResult.isSuccess())
{
if (false == processPacket(session, packet_set))
{
Log.getLogger().error("processPacket error!!");
delete_users.Add(session);
break;
}
delete_users.Add(session);
continue;
}
if (isRecvResult.getErrorCode() == ServerErrorCode.LargePacketNotAllReceived) continue;
if (isRecvResult.getErrorCode() != ServerErrorCode.LargePacketRecvTimeOver)
{
send_GS2C_NTF_LARGE_PACKET_TIMEOUT(session, isRecvResult, packet_set);
delete_users.Add(session);
continue;
}
}
if (delete_users.Count > 0)
{
removeCollectedPacket(delete_users);
}
return Task.FromResult(result);
}
private void send_GS2C_NTF_LARGE_PACKET_TIMEOUT(IEntityWithSession session, Result result, SortedSet<CollectedPacketInfo> packetSet)
{
var last_recv_time = packetSet.OrderByDescending(p => p.m_last_recv_time).FirstOrDefault();
NullReferenceCheckHelper.throwIfNull(last_recv_time, () => $"Last Receive time is null !! - player:{session.toBasicString()}");
// var uid = packetSet.Select(p => p.m_packet_uid).FirstOrDefault() ?? string.Empty;
ClientToGame msg = new();
msg.Message = new();
msg.Message.NtfLargePacketTimeout = new();
msg.Message.NtfLargePacketTimeout.Uid = last_recv_time.m_packet_uid;
msg.Message.NtfLargePacketTimeout.LastCheckTime = Google.Protobuf.WellKnownTypes.Timestamp.FromDateTime(last_recv_time.m_last_recv_time);
msg.Message.NtfLargePacketTimeout.LastCheckTime = Google.Protobuf.WellKnownTypes.Timestamp.FromDateTime(DateTime.UtcNow);
}
private bool processPacket(IEntityWithSession session, SortedSet<CollectedPacketInfo> packets)
{
var network_session = getNetworkSession();
using (var stream = new MemoryStream())
{
foreach (var packet in packets)
{
if (packet.m_datas == null)
{
Log.getLogger().warn("Warning: packet.m_datas is null");
continue;
}
packet.m_datas.WriteTo(stream);
}
if (stream.Length == 0)
{
Log.getLogger().warn("Warning: stream is empty after writing packets.");
return false;
}
try
{
stream.Position = 0;
var req = ClientToGame.Parser.ParseFrom(stream);
var func = network_session.onCallProtocolHandler(session, req);
return true;
}
catch (Exception e)
{
Log.getLogger().error($"Exception !!!, Failed to perform !!!, in LargePacketHandler.processPacket() : exception:{e} - {session.toBasicString()}");
return false;
}
}
}
private Result validCheck(IEntityWithSession session, SortedSet<CollectedPacketInfo> packets)
{
var err_msg = string.Empty;
var result = new Result();
var sorted_packet_idxs = packets.OrderBy(p => p.m_packet_idx).ToList();
var total_packet_count = packets.Select(p => p.m_total_packet_count).FirstOrDefault();
//아직 패킷이 다 안들어온거다.
if (sorted_packet_idxs.Count < total_packet_count)
{
err_msg = $"Not all packets were received !!! : recvPacketCount:{sorted_packet_idxs.Count} < totalPacketCount:{total_packet_count} - {session.toBasicString()}";
result.setFail(ServerErrorCode.LargePacketNotAllReceived, err_msg);
return result;
}
var last_recv_time = packets.OrderByDescending(p => p.m_last_recv_time).FirstOrDefault();
NullReferenceCheckHelper.throwIfNull(last_recv_time, () => $"last receive time is null !!! - {session.toBasicString()}");
var diff = DateTimeHelper.Current - last_recv_time.m_last_recv_time;
if (diff.TotalSeconds > Constant.LARGE_PACKET_CHECK_WAIT_SEC)
{
err_msg = $"Packet receive timeout exceeded !!! : waitingSec:{diff.TotalSeconds} > WaitMaxSec:{Constant.LARGE_PACKET_CHECK_WAIT_SEC}, lastRecvTime:{last_recv_time} - {session.toBasicString()}";
result.setFail(ServerErrorCode.LargePacketRecvTimeOver, err_msg);
Log.getLogger().error(result.toBasicString());
return result;
}
return result;
}
public SessionBase getNetworkSession()
{
NullReferenceCheckHelper.throwIfNull(m_network_session, () => $"m_network_session is null !!!");
return m_network_session;
}
}
public class CollectedPacketInfo : IComparable<CollectedPacketInfo>
{
public string m_packet_uid { get; } = string.Empty;
public Int32 m_total_packet_count { get; } = 0;
public Int32 m_packet_idx { get; } = 0;
public ByteString? m_datas { get; } = null;
public DateTime m_last_recv_time { get; } = DateTime.Now;
public CollectedPacketInfo(string packetUid, Int32 totalPacketCount, Int32 packetIdx, ByteString datas)
{
m_packet_uid = packetUid;
m_total_packet_count = totalPacketCount;
m_packet_idx = packetIdx;
m_datas = datas;
}
public Int32 CompareTo(CollectedPacketInfo? other)
{
if (other == null) return 1;
return this.m_packet_idx.CompareTo(other.m_packet_idx);
}
public override bool Equals(object? obj)
{
if (obj == null || GetType() != obj.GetType())
{
return false;
}
CollectedPacketInfo other = (CollectedPacketInfo)obj;
//return m_packet_uid == other.m_packet_uid &&
// m_total_packet_count == other.m_total_packet_count &&
// m_packet_idx == other.m_packet_idx &&
// m_last_recv_time == other.m_last_recv_time;
return m_packet_idx == other.m_packet_idx;
}
public override int GetHashCode()
{
return HashCode.Combine(m_packet_uid, m_total_packet_count, m_packet_idx, m_last_recv_time);
}
}

View File

@@ -0,0 +1,155 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ServerBase;
//=============================================================================================
// ProudNet Packet Header 처리 클래스 이다.
//
// author : kangms
//
//=============================================================================================
public class ProudNetPacketCommand : IPacketCommand
{
private string m_command_key;
private Type m_packet_action_type;
private Type m_command_type;
public ProudNetPacketCommand(Type packetActionType, Type commandType)
{
m_packet_action_type = packetActionType;
m_command_type = commandType;
m_command_key = toKeyString();
}
public override Int32 GetHashCode()
{
return string.Format(m_command_key).GetHashCode();
}
public override bool Equals(object? packetCommand)
{
var packet_command = packetCommand as ProudNetPacketCommand;
if (null == packet_command)
{
return false;
}
if ( m_packet_action_type.Equals(packet_command.getPacketActionType())
&& m_command_type.Equals(packet_command.getCommandType()) )
{
return true;
}
return false;
}
public string getCommandKey()
{
return m_command_key;
}
public Type getPacketActionType()
{
return m_packet_action_type;
}
public Type getCommandType()
{
return m_command_type;
}
public string toKeyString()
{
return $"a:{m_packet_action_type.Name.ToString()},c:{m_command_type.Name.ToString()}";
}
public string toBasicString()
{
return $"AT:{m_packet_action_type.Name.ToString()},CT:{m_command_type.Name.ToString()}";
}
}
//=============================================================================================
// RabbitMQ Packet Header 처리 클래스 이다.
//
// author : kangms
//
//=============================================================================================
public class RabbitMqPacketCommand : IPacketCommand
{
private string m_command_key;
private string m_exchange_name;
private Type m_command_type;
public RabbitMqPacketCommand(string exchangeName, Type commandType)
{
m_exchange_name = exchangeName;
m_command_type = commandType;
m_command_key = toKeyString();
}
public override Int32 GetHashCode()
{
return string.Format(m_command_key).GetHashCode();
}
public override bool Equals(object? packetCommand)
{
var packet_command = packetCommand as RabbitMqPacketCommand;
if (null == packet_command)
{
return false;
}
if (m_exchange_name.Equals(packet_command.getExchangeName())
&& m_command_type.Equals(packet_command.getCommandType()))
{
return true;
}
return false;
}
public string getCommandKey()
{
return m_command_key;
}
public string getExchangeName()
{
return m_exchange_name;
}
public Type getCommandType()
{
return m_command_type;
}
public bool msgHook()
{
return false;
}
public string toKeyString()
{
return $"e:{m_exchange_name},c:{m_command_type.Name.ToString()}";
}
public string toBasicString()
{
return $"EN:{m_exchange_name},CT:{m_command_type.Name.ToString()}";
}
}

View File

@@ -0,0 +1,184 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Nettention.Proud;
using ServerCore;
namespace ServerBase;
//=============================================================================================
// 패킷 응답/통지 수신 처리자 연결을 위한 AttributeUsage 등록
//
// author : kangms
//
//=============================================================================================
[AttributeUsage(AttributeTargets.Class)]
public class PacketHandlerAttribute : Attribute
{
private readonly IPacketCommand m_packet_command;
private readonly Type m_handler_class;
private readonly Type m_handlable_owner;
private readonly bool m_message_hook;
public PacketHandlerAttribute( Type packetAction, Type command
, Type handlerClass, Type handlerOwner
, bool message_hook = false)
{
var packet_cmd = new ProudNetPacketCommand(packetAction, command);
m_packet_command = packet_cmd;
m_handler_class = handlerClass;
m_handlable_owner = handlerOwner;
m_message_hook = message_hook;
}
public PacketHandlerAttribute( string exchangeName, Type command
, Type handlerClass, Type handlerOwner)
{
var packet_cmd = new RabbitMqPacketCommand(exchangeName, command);
m_packet_command = packet_cmd;
m_handler_class = handlerClass;
m_handlable_owner = handlerOwner;
}
public IPacketCommand getPacketCommand()
{
return m_packet_command;
}
public Type getHandlerClass()
{
return m_handler_class;
}
public Type getHandlerOwner()
{
return m_handlable_owner;
}
public bool getMessageHook()
{
return m_message_hook;
}
}
//===========================================================================================
// 패킷 요청 Handler
//
// author : kangms
//
//===========================================================================================
public abstract class PacketSendHandler
{
public PacketSendHandler()
{
}
public virtual Result onFillupPacket(Google.Protobuf.IMessage toFillupMessage)
{
return new Result();
}
public abstract Google.Protobuf.IMessage onGetToSendMessage();
}
//===========================================================================================
// 패킷 수신 Handler
//
// author : kangms
//
//===========================================================================================
public abstract class PacketRecvHandler
{
public PacketRecvHandler()
{
}
public virtual async Task<Result> onCheckValid(ISession session, Google.Protobuf.IMessage recvMessage)
{
await Task.CompletedTask;
return new Result();
}
public abstract Task<Result> onProcessPacket(ISession session, Google.Protobuf.IMessage recvMessage);
public virtual async Task onProcessPacketException( ISession session, Google.Protobuf.IMessage recvMessage
, Result errorResult )
{
await Task.CompletedTask;
Log.getLogger().warn($"Not implemented PacketRecvHandler.onProcessPacketException() !!! : {errorResult?.toBasicString()} - {recvMessage?.toBasicString()}, {session?.toBasicString()}");
}
}
//===========================================================================================
// 패킷 응답 Handler
//
// author : kangms
//
//===========================================================================================
public abstract class PacketAckHandler : PacketRecvHandler
{
public PacketAckHandler()
{
}
}
//===========================================================================================
// 패킷 통지 Handler
//
// author : kangms
//
//===========================================================================================
public abstract class PacketNtfHandler : PacketRecvHandler
{
public PacketNtfHandler()
{
}
}
//===========================================================================================
// p2p 패킷 수신 Handler
//
// author : khlee
//
//===========================================================================================
public abstract class P2PPacketRecvHandler
{
public P2PPacketRecvHandler()
{
}
public virtual async Task<Result> onCheckValid(ISession session, Google.Protobuf.IMessage recvMessage)
{
await Task.CompletedTask;
return new Result();
}
public abstract Task<Result> onProcessP2PPacket(ISession session, ByteArray recvMessage);
// public virtual async Task onProcessP2PPacketException( ISession session, Google.Protobuf.IMessage recvMessage
// , Result errorResult )
// {
// await Task.CompletedTask;
//
// Log.getLogger().warn($"Not implemented PacketRecvHandler.onProcessPacketException() !!! : {errorResult?.toBasicString()} - {recvMessage?.toBasicString()}, {session?.toBasicString()}");
// }
}

View File

@@ -0,0 +1,280 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using NLog.Layouts;
using NLog.Internal.Fakeables;
using Google.Protobuf;
using Google.Protobuf.Reflection;
using Amazon.Runtime.Internal.Transform;
using RabbitMQ.Client.Events;
using ServerCore; using ServerBase;
namespace ServerBase;
//=============================================================================================
// 패킷 수신 관리자
//
// author : kangms
//
//=============================================================================================
public class PacketReceiver
{
private readonly ISession m_owner;
private readonly Dictionary<string, PacketRecvHandler> m_packet_recv_handlers = new();
private readonly Dictionary<string, P2PPacketRecvHandler> m_p2p_packet_recv_handler = new();
public delegate bool FnIsValidPacketNamespace(string toCheckNamespace, IPacketCommand packetCommand);
public PacketReceiver(ISession session)
{
m_owner = session;
}
public async Task<bool> registerRecvHandlerAll()
{
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
FnIsValidPacketNamespace fn_is_valid_packet_namespace;
var packet_namespace_verifier = m_owner as IWithPacketNamespaceVerifier;
if (null != packet_namespace_verifier)
{
fn_is_valid_packet_namespace = packet_namespace_verifier.isValidPacketNamespace;
}
else
{
fn_is_valid_packet_namespace = isValidDefaultNamespace;
}
return await Task.Run(() =>
{
foreach(var assembly in assemblies)
{
foreach (Type type in assembly.GetTypes())
{
var custom_attribs = (PacketHandlerAttribute[])type.GetCustomAttributes(typeof(PacketHandlerAttribute), true);
if (custom_attribs.Length <= 0)
{
continue;
}
foreach (var each in custom_attribs)
{
if (false == m_owner.GetType().Equals(each.getHandlerOwner()))
{
break;
}
if (false == fn_is_valid_packet_namespace(type.Namespace ?? string.Empty, each.getPacketCommand()))
{
break;
}
var packet_command = each.getPacketCommand();
if (false == isValidPacketRecvHander(each.getHandlerClass()))
{
break;
}
if (true == hasRecvHandler(packet_command))
{
Log.getLogger().error($"Already register PacketCommand !!! : packetCommnad:{packet_command.toBasicString()}");
return false;
}
var handler_class = Activator.CreateInstance(each.getHandlerClass()) as PacketRecvHandler;
if (null == handler_class)
{
Log.getLogger().error($"Failed to create PacketRecvHandler !!! : packetCommnad:{packet_command.toBasicString()}");
return false;
}
registerRecvHandler(packet_command, handler_class);
break;
}
}
}
if(0 >= m_packet_recv_handlers.Count)
{
Log.getLogger().error($"No PacketRecvHandler registered !!!");
return false;
}
return true;
});
}
public bool registerRecvHandler(IPacketCommand packetCommand, PacketRecvHandler toRegisterRecvHandler)
{
if (true == m_packet_recv_handlers.ContainsKey(packetCommand.getCommandKey()))
{
Log.getLogger().error($"Already registered Recv Handler !!! : {packetCommand.toBasicString()}");
return false;
}
m_packet_recv_handlers.Add(packetCommand.getCommandKey(), toRegisterRecvHandler);
return true;
}
public bool fillupProudNetPacketCommand(Google.Protobuf.IMessage protoMessage, out ProudNetPacketCommand? packetCommand)
{
packetCommand = null;
if (null == protoMessage)
{
Log.getLogger().fatal($"Invalid param Protobuf Message !!!");
return false;
}
if(false == protoMessage.makeProudNetPacketCommand(out packetCommand))
{
Log.getLogger().fatal($"Failed to make ProudNet PacketCommand !!! : {protoMessage.toBasicString()}");
return false;
}
return true;
}
public bool fillupRabbitMqPacketCommand(BasicDeliverEventArgs ea, Google.Protobuf.IMessage protoMessage, out RabbitMqPacketCommand? packetCommand)
{
packetCommand = null;
if (null == protoMessage)
{
ServerCore.Log.getLogger().fatal($"Invalid param Protobuf Message !!!");
return false;
}
if (false == protoMessage.makeRabbitMqPacketCommand(ea, out packetCommand))
{
ServerCore.Log.getLogger().fatal($"Failed to make RabbitMq PacketCommand !!! : {protoMessage.toBasicString()}");
return false;
}
return true;
}
public bool isValidPacketRecvHander(Type pakcetRecvHandler)
{
if (false == pakcetRecvHandler.IsSubclassOf(typeof(PacketRecvHandler)))
{
return false;
}
return true;
}
public PacketRecvHandler? findRecvHandler(IPacketCommand packetCommand)
{
if (false == m_packet_recv_handlers.ContainsKey(packetCommand.getCommandKey()))
{
return null;
}
return m_packet_recv_handlers[packetCommand.getCommandKey()];
}
public bool hasRecvHandler(IPacketCommand packetCommand)
{
if (true == m_packet_recv_handlers.ContainsKey(packetCommand.getCommandKey()))
{
return true;
}
return false;
}
public bool isValidDefaultNamespace(string toCheckNamespace, IPacketCommand packetCommand)
{
var packet_namespace = ".PacketHandler";
if ( null != toCheckNamespace
&& true == toCheckNamespace.Contains(packet_namespace))
{
return true;
}
else
{
ServerCore.Log.getLogger().error($"Invalid Default PacketNamespace !!!, not included Namespace : {packet_namespace} ⊆ {toCheckNamespace}, packetCommnad:{packetCommand.toBasicString()}");
}
return false;
}
public static bool isEqualPacket<T>(Google.Protobuf.IMessage recvMessage)
where T : class
{
if (false == typeof(T).Equals(recvMessage))
{
return false;
}
return true;
}
public async Task<bool> registerP2PRecvHandlerAll()
{
await Task.CompletedTask;
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
//추후 packet 단위로 쪼개야 할때 이부분 수정해야한다.
foreach(var assembly in assemblies)
{
foreach (Type type in assembly.GetTypes())
{
if (typeof(P2PPacketRecvHandler).IsAssignableFrom(type) && !type.IsAbstract)
{
try
{
var obj = Activator.CreateInstance(type);
if (obj is P2PPacketRecvHandler instance)
{
m_p2p_packet_recv_handler.Add(instance.getTypeName(), instance);
}
else
{
Log.getLogger().error($"Failed to create instance of {type.FullName} or not P2PPacketRecvHandler");
}
}
catch (Exception ex)
{
Log.getLogger().error($"Failed to create instance of {type.FullName}: {ex.Message}");
}
}
}
}
if (m_p2p_packet_recv_handler.Count == 0)
{
Log.getLogger().debug("P2PPacketRecvHandler instance is zero");
}
return true;
}
public P2PPacketRecvHandler? findP2PPacketHandler(string key)
{
if (false == m_p2p_packet_recv_handler.ContainsKey(key))
{
return null;
}
//일단 handler 1개만 등록 해놓고 거기서 처리 하도록 한다. 패킷별로 로직 처리 해야되면 패킷별로 핸들러 등록한다.
return m_p2p_packet_recv_handler.Values.ToArray()[0];
}
}

View File

@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ServerBase;
//=============================================================================================
// 패킷 응답 지연 분석기
//
// author : kangms
//
//=============================================================================================
public class PacketResponseDelayProfiler
{
}

View File

@@ -0,0 +1,167 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Google.Protobuf;
using ServerCore; using ServerBase;
namespace ServerBase;
//=============================================================================================
// 패킷 송신 관리자
//
// author : kangms
//
//=============================================================================================
public class PacketSender
{
private readonly ISession m_owner;
private readonly Dictionary<IPacketCommand, PacketSendHandler> m_packet_send_handlers = new();
public PacketSender(ISession session)
{
m_owner = session;
}
public async Task<bool> registerSendHandlerAll()
{
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
return await Task.Run(() =>
{
foreach (var assembly in assemblies)
{
foreach (Type type in assembly.GetTypes())
{
var custom_attribs = (PacketHandlerAttribute[])type.GetCustomAttributes(typeof(PacketHandlerAttribute), false);
if (custom_attribs.Length <= 0)
{
continue;
}
foreach (var each in custom_attribs)
{
if (false == hasNamespace(type.Namespace, each.getPacketCommand()))
{
break;
}
if (false == m_owner.GetType().Equals(each.getHandlerOwner()))
{
break;
}
var packet_command = each.getPacketCommand();
if (false == isValidPacketSendHandler(each.getHandlerClass()))
{
break;
}
if (true == hasSendHandler(packet_command))
{
Log.getLogger().error($"Already register PacketCommand !!! : packetCommnad:{packet_command.toBasicString()}");
return false;
}
var handler_class = Activator.CreateInstance(each.getHandlerClass()) as PacketSendHandler;
if (null == handler_class)
{
Log.getLogger().error($"Failed to create PacketSendHandler !!! : packetCommnad:{packet_command.toBasicString()}");
return false;
}
registerSendHandler(packet_command, handler_class);
break;
}
}
}
if (0 >= m_packet_send_handlers.Count)
{
Log.getLogger().warn($"No PacketSendHandler registered !!!");
}
return true;
});
}
public bool registerSendHandler(IPacketCommand packetCommand, PacketSendHandler toRegisterSendHandler)
{
if (true == m_packet_send_handlers.ContainsKey(packetCommand))
{
Log.getLogger().error($"Already registered Send Handler !!! : {packetCommand.toBasicString()}");
return false;
}
m_packet_send_handlers.Add(packetCommand, toRegisterSendHandler);
return true;
}
public bool isValidPacketSendHandler(Type pakcetSendHandler)
{
if(false == pakcetSendHandler.IsSubclassOf(typeof(PacketSendHandler)))
{
return false;
}
return true;
}
public PacketSendHandler? findSendHandler(IPacketCommand packetCommand)
{
if (false == m_packet_send_handlers.ContainsKey(packetCommand))
{
return null;
}
return m_packet_send_handlers[packetCommand];
}
public bool hasSendHandler(IPacketCommand packetCommand)
{
if (true == m_packet_send_handlers.ContainsKey(packetCommand))
{
return true;
}
return false;
}
public bool hasNamespace(string? toCheckNamespace, IPacketCommand packetCommand)
{
string packet_handler_namespace = "PacketHandler";
if ( null != toCheckNamespace
&& true == toCheckNamespace.Contains(packet_handler_namespace))
{
return true;
}
else
{
ServerCore.Log.getLogger().error($"Invalid Namespace !!! : {packet_handler_namespace} != {toCheckNamespace}, packetCommnad:{packetCommand.toBasicString()}");
}
return false;
}
public static bool isEqualPacket<T>(Google.Protobuf.IMessage recvMessage)
where T : class
{
if(false == typeof(T).Equals(recvMessage))
{
return false;
}
return true;
}
}

View File

@@ -0,0 +1,86 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ServerCore; using ServerBase;
namespace ServerBase;
public partial class Initializers
{
private string m_contents_name = string.Empty;
private readonly List<IInitializer> m_initializers = new();
public async Task<Result> init(string contentsName)
{
var result = new Result();
m_contents_name = contentsName;
foreach (var each in m_initializers)
{
result = await each.onInit();
if (result.isFail())
{
Log.getLogger().error($"Failed to init of Initializers - {each.getTypeName()}, ContentsName:{contentsName}");
return result;
}
}
Log.getLogger().debug($"Success Initializers.init() - {m_contents_name}");
return result;
}
public bool appendInitializer(IInitializer initializer)
{
if (initializer == null)
{
return false;
}
if (null != m_initializers.Find(x => x.Equals(initializer)))
{
Log.getLogger().fatal($"Failed to append Initializer, cause Duplicated Initializer !!! - {initializer.getTypeName()}");
return false;
}
m_initializers.Add(initializer);
return true;
}
public bool tryConvert<T>(out List<T>? founds)
where T : class
{
founds = null;
foreach (var each in m_initializers)
{
var converted = each as T;
if (converted != null)
{
founds = founds ?? new List<T>();
founds.Add(converted);
}
}
return founds != null && 0 < founds.Count;
}
public List<T>? tryConvert<T>() where T : class
{
if (tryConvert<T>(out var founds))
{
return founds;
}
else
{
return new List<T>();
}
}
}

View File

@@ -0,0 +1,831 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;
using Google.Protobuf;
using Nettention.Proud;
using OtpNet;
using ServerCore; using ServerBase;
using MODULE_ID = System.UInt32;
using HostID = Nettention.Proud.HostID;
using SESSION_ID = System.Int32;
namespace ServerBase;
//=============================================================================================
// 네트워크 세션 기반 클래스 이다.
//
// author : kangms
//
//=============================================================================================
public abstract partial class SessionBase : ISession, IInitializer
{
private readonly IRmiHost m_net_host;
private ConcurrentDictionary<Type, Nettention.Proud.RmiStub> m_stubs = new();
private ConcurrentDictionary<Type, Nettention.Proud.RmiProxy> m_proxies = new();
public delegate bool onRecv<T>(Nettention.Proud.HostID remote, Nettention.Proud.RmiContext rmiContext, T recvProtocol);
public delegate bool FnProudNetIOPacketToHost(Nettention.Proud.HostID remote, Nettention.Proud.RmiContext rmiContext, IMessage msg);
public FnProudNetIOPacketToHost? FnSendPacketToHost;
public delegate bool FnProudNetIOPacketToHosts(Nettention.Proud.HostID[] remotes, Nettention.Proud.RmiContext rmiContext, IMessage msg);
public FnProudNetIOPacketToHosts? FnSendPacketToHosts;
private readonly PacketReceiver m_packet_receiver;
private readonly PacketSender m_packet_sender;
private int m_large_packet_check_size = Constant.LARGE_PACKET_CHECK_DEFAULT_SIZE;
private int m_packet_chunk_size = Constant.PACKET_CHUNK_DEFAULT_SIZE;
private LargePacketHandler m_large_packet_handler = new();
public enum ConnectionState
{
None = 0,
Disconnected = 1,
TryConnecting = 2,
Connected = 3,
}
private ConnectionState m_connection_state = ConnectionState.Disconnected;
public SessionBase(IRmiHost rmiHost)
{
m_net_host = rmiHost;
m_packet_receiver = new PacketReceiver(this);
m_packet_sender = new PacketSender(this);
}
// for IInitializer
public virtual async Task<Result> onInit()
{
var result = new Result();
string err_msg = string.Empty;
var rmi_host = getRmiHost();
if(null == rmi_host)
{
err_msg = $"Rmi Host is Null !!! - {toBasicString()}";
result.setFail(ServerErrorCode.RmiHostIsNull, err_msg);
Log.getLogger().error(result.toBasicString());
return result;
}
if(false == onBindRmiHostHandler(rmi_host))
{
err_msg = $"Failed to bind Rmi Host Handler !!! - {toBasicString()}";
result.setFail(ServerErrorCode.RmiHostHandlerBindFailed, err_msg);
Log.getLogger().error(result.toBasicString());
return result;
}
if (false == await m_packet_receiver.registerRecvHandlerAll())
{
err_msg = $"Failed to register RecvHandler All !!! - {toBasicString()}";
result.setFail(ServerErrorCode.PacketRecvHandlerRegisterFailed, err_msg);
Log.getLogger().error(result.toBasicString());
return result;
}
if (false == await m_packet_sender.registerSendHandlerAll())
{
err_msg = $"Failed to register SendHandler All !!! - {toBasicString()}";
result.setFail(ServerErrorCode.PacketSendHandlerRegisterFailed, err_msg);
Log.getLogger().error(result.toBasicString());
return result;
}
if (false == await m_packet_receiver.registerP2PRecvHandlerAll())
{
//P2PPacketRecvHandler 여러개 추가 할시 Result 필요
var msg = $"Failed to register P2P RecvHandler All !!! - {toBasicString()}";
//result.setFail(ServerErrorCode.PacketRecvHandlerRegisterFailed, err_msg);
Log.getLogger().warn(msg);
}
return result;
}
public bool resisterStub<TStub>(TStub stub)
where TStub : Nettention.Proud.RmiStub
{
var net_server = getRmiHost();
NullReferenceCheckHelper.throwIfNull(net_server, () => $"net_server is null !!!");
if (false == m_stubs.TryAdd(typeof(TStub), stub))
{
return false;
}
net_server.AttachStub(stub);
return true;
}
public bool unresisterStub(Type type)
{
return m_stubs.TryRemove(type, out _);
}
public TStub findStub<TStub>()
where TStub : Nettention.Proud.RmiStub
{
m_stubs.TryGetValue(typeof(TStub), out var found_stub);
var casted_stub = found_stub as TStub;
NullReferenceCheckHelper.throwIfNull(casted_stub, () => $"casted_stub is null !!! : type:{typeof(TStub).getTypeName()}");
return casted_stub;
}
public bool resisterProxy<TProxy>(TProxy proxy)
where TProxy : Nettention.Proud.RmiProxy
{
var net_server = getRmiHost();
NullReferenceCheckHelper.throwIfNull(net_server, () => $"net_server is null !!!");
if(false == m_proxies.TryAdd(typeof(TProxy), proxy))
{
return false;
}
net_server.AttachProxy(proxy);
return true;
}
public bool unresisterProxy(Type type)
{
return m_proxies.TryRemove(type, out _);
}
public TProxy findProxy<TProxy>()
where TProxy : Nettention.Proud.RmiProxy
{
m_proxies.TryGetValue(typeof(TProxy), out var found_proxy);
var casted_proxy = found_proxy as TProxy;
NullReferenceCheckHelper.throwIfNull(casted_proxy, () => $"casted_proxy is null !!! : type:{typeof(TProxy).getTypeName()}");
return casted_proxy;
}
#region
protected abstract bool onBindRmiHostHandler(IRmiHost currRmiHost);
protected abstract bool onBindStubHandler();
#endregion
public abstract IEntityWithSession? onLookupEntityWithSession(SESSION_ID targetSessionId);
protected bool onRecvProtocol<T>(Nettention.Proud.HostID remote, Nettention.Proud.RmiContext rmiContext, T recvProtocol)
where T : Google.Protobuf.IMessage
{
var err_msg = string.Empty;
var session_id = 0;
var recv_protocol_type = string.Empty;
var entity_basic_string = string.Empty;
try
{
ArgumentNullReferenceCheckHelper.throwIfNull(rmiContext, () => $"rmiContext is null !!!");
ArgumentNullReferenceCheckHelper.throwIfNull(recvProtocol, () => $"recvProtocol is null !!!");
Log.getLogger().debug($"recvProtocol:{recvProtocol.ToString()} - HostID:{remote}");
session_id = remote.toSESSION_ID();
recv_protocol_type = recvProtocol.ToString();
var found_entity_with_session = onLookupEntityWithSession(session_id);
if (found_entity_with_session == null)
{
err_msg = $"Not found remoteSession !!! - sessionId:{session_id}, recvProtocolType:{recv_protocol_type}, HostID:{remote}";
Log.getLogger().warn(err_msg);
return false;
}
entity_basic_string = found_entity_with_session.toBasicString();
Log.getLogger().debug($"recvByEntitySession:{recvProtocol.ToString()} - {entity_basic_string}");
var task_serializer = found_entity_with_session as IWithTaskSerializer;
if (null == task_serializer)
{
err_msg = $"Failed to cast IWithTaskSerializer - sessionId:{session_id}, recvProtocolType:{recv_protocol_type}, HostID:{remote}, entityBasicString:{entity_basic_string}";
Log.getLogger().error(err_msg);
return false;
}
recvProtocol.makeProudNetPacketCommand(out var packetCommand);
NullReferenceCheckHelper.throwIfNull(packetCommand, () => $"packetCommand is null !!! - entityBasicString:{entity_basic_string}");
var func = getCallProtocolHandler(found_entity_with_session, recvProtocol, packetCommand);
_ = task_serializer.getTaskSerializer().postLogicFunc(func, packetCommand.toBasicString());
}
catch(Exception e)
{
Log.getLogger().fatal($"Exception !!!, Failed to perform in SessionBase.onRecvProtocol<{recvProtocol.getTypeName()}>() : exception:{e} - sessionId:{session_id}, recvProtocolType:{recv_protocol_type}, HostID:{remote}, entityBasicString:{entity_basic_string}");
return false;
}
return true;
}
public Func<Task> getCallProtocolHandler<T>(IEntityWithSession session, T recvProtocol, ProudNetPacketCommand packetCommand)
where T : Google.Protobuf.IMessage
{
ArgumentNullReferenceCheckHelper.throwIfNull(session, () => $"Session is null !!!");
ArgumentNullReferenceCheckHelper.throwIfNull(recvProtocol, () => $"recvProtocol is null !!!");
ArgumentNullReferenceCheckHelper.throwIfNull(packetCommand, () => $"packetCommand is null !!!");
System.Type command_type = packetCommand.getCommandType();
switch (command_type)
{
case var _ when command_type == typeof(ClientToGameReq.Types.C2S_REQ_LARGE_PACKET):
return async () => await onCallLargeSizeProtocolHandler(session, recvProtocol);
default:
return async () => await onCallProtocolHandler(session, recvProtocol);
}
}
public async Task<Result> onCallProtocolHandler<T>(IEntityWithSession session, T recvProtocol)
where T : Google.Protobuf.IMessage
{
var result = new Result();
var err_msg = string.Empty;
ProudNetPacketCommand? fillup_packet_command = null;
PacketRecvHandler? found_recv_packet_handler = null;
try
{
ArgumentNullReferenceCheckHelper.throwIfNull(session, () => $"session is null !!!");
ArgumentNullReferenceCheckHelper.throwIfNull(recvProtocol, () => $"recvProtocol is null !!! - {session.toBasicString()}");
Stopwatch? stopwatch = null;
var event_tid = string.Empty;
var server_logic = ServerLogicApp.getServerLogicApp();
NullReferenceCheckHelper.throwIfNull(server_logic, () => $"server_logic is null !!! - {session.toBasicString()}");
var config = server_logic.getServerConfig();
NullReferenceCheckHelper.throwIfNull(config, () => $"config is null !!! - {session.toBasicString()}");
if (true == config.PerformanceCheckEnable)
{
event_tid = System.Guid.NewGuid().ToString("N");
stopwatch = Stopwatch.StartNew();
}
var receiver = getPacketReceiver();
NullReferenceCheckHelper.throwIfNull(receiver, fnLog: () => $"receiver is null !!! - {session.toBasicString()}");
if (false == receiver.fillupProudNetPacketCommand(recvProtocol, out fillup_packet_command))
{
err_msg = $"Failed to PacketReceiver.fillupProudNetPacketCommand() !!! : {recvProtocol.ToString()} - {session.toBasicString()}";
result.setFail(ServerErrorCode.PacketRecvInvalid, err_msg);
Log.getLogger().error(result.toBasicString());
return result;
}
NullReferenceCheckHelper.throwIfNull(fillup_packet_command, fnLog: () => $"fillup_packet_command is null !!! - {session.toBasicString()}");
found_recv_packet_handler = receiver.findRecvHandler(fillup_packet_command);
if (null == found_recv_packet_handler)
{
err_msg = $"Not found Recv Packet Handler !!! : {recvProtocol.ToString()} - {session.toBasicString()}";
result.setFail(ServerErrorCode.RacketRecvHandlerNotFound, err_msg);
Log.getLogger().error(result.toBasicString());
return result;
}
result = await found_recv_packet_handler.onCheckValid(session, recvProtocol);
if (result.isFail())
{
Log.getLogger().error($"Failed to onCheckValid() !!! : {result.toBasicString()} - {fillup_packet_command.toBasicString()}");
return result;
}
result = await found_recv_packet_handler.onProcessPacket(session, recvProtocol);
if (result.isFail())
{
Log.getLogger().error($"Failed to onProcessPacket() !!! : {result.toBasicString()} - {fillup_packet_command.toBasicString()}");
}
if (null != stopwatch)
{
var elapsed_msec = stopwatch.ElapsedMilliseconds;
stopwatch.Stop();
Log.getLogger().debug($"{GetType()} CSPacket Stopwatch Stop : ETID:{event_tid}, ElapsedMSec:{elapsed_msec} - {recvProtocol.toBasicString()}");
}
}
catch(Exception e)
{
try
{
err_msg = $"Exception !!!, Failed to perform in SessionBase.onCallProtocolHandler() !!! : exception:{e} - PacketCommand:{fillup_packet_command?.toBasicString()}, {session?.toBasicString()}";
result.setFail(ServerErrorCode.TryCatchException, err_msg);
Log.getLogger().error(result.toBasicString());
if (null != found_recv_packet_handler && null != session)
{
await found_recv_packet_handler.onProcessPacketException(session, recvProtocol, result);
}
}
catch(Exception innerException)
{
err_msg = $"InnerException !!!, Failed to perform in SessionBase.onCallProtocolHandler() !!! : exception:{innerException}";
result.setFail(ServerErrorCode.TryCatchException, err_msg);
Log.getLogger().error(result.toBasicString());
}
}
return result;
}
public async Task<Result> onCallLargeSizeProtocolHandler<T>(IEntityWithSession session, T recvProtocol)
where T : Google.Protobuf.IMessage
{
await Task.CompletedTask;
var result = new Result();
var err_msg = string.Empty;
try
{
var network_session = m_large_packet_handler.getNetworkSession();
NullReferenceCheckHelper.throwIfNull(network_session, () => $"network_session is null !!! - {session.toBasicString()}");
ArgumentNullReferenceCheckHelper.throwIfNull(session, fnLog: () => $"session is null !!!");
ArgumentNullReferenceCheckHelper.throwIfNull(recvProtocol, fnLog: () => $"recvProtocol is null !!! - {session.toBasicString()}");
var recv_msg = recvProtocol as ClientToGame;
NullReferenceCheckHelper.throwIfNull(recv_msg, fnLog: () => $"recv_msg is null !!! - {session.toBasicString()}");
var request = recv_msg.Request.ReqLargePacket;
NullReferenceCheckHelper.throwIfNull(request, fnLog: () => $"request is null !!! - {session.toBasicString()}");
string packet_uid = request.Uid;
Int32 packet_index = request.PacketIndex;
Int32 total_packet_count = request.TotalPacketCount;
m_large_packet_handler.collectPacket(session, packet_uid, total_packet_count, packet_index, request.Data);
}
catch(Exception e)
{
var error_coce = ServerErrorCode.TryCatchException;
err_msg = $"Exception !!!, Failed to perform in onCallLargeSizeProtocolHandler() !!! : errorCode:{error_coce}, exception:{e} - PacketCommand:{recvProtocol?.toBasicString()}, {session?.toBasicString()}";
result.setFail(error_coce, err_msg);
Log.getLogger().error(result.toBasicString());
}
return result;
}
#region
public bool onSendPacket<T>(IEntityWithSession session, T msg)
where T : class, IMessage
{
return sendPacket<T>(session, msg, RmiContext.ReliableSend);
}
public bool onSendPacket<T>(IEntityWithSession[] sessions, T msg)
where T : class, IMessage
{
return sendPacket<T>(sessions, msg, ServerCore.ProudNetHelper.compressRmi());
}
public bool onSendPacketWithSecure<T>(IEntityWithSession session, T msg)
where T : class, IMessage
{
return sendPacket<T>(session, msg, RmiContext.SecureReliableSend);
}
public bool onSendPacketWithSecure<T>(IEntityWithSession[] sessions, T msg)
where T : class, IMessage
{
return sendPacket<T>(sessions, msg, RmiContext.SecureReliableSend);
}
private bool sendPacket<T>(IEntityWithSession session, T msg, RmiContext rmiContext)
where T : class, IMessage
{
ArgumentNullReferenceCheckHelper.throwIfNull(session, () => $"session is null !!!");
var host_id = session.getHostId();
var base_msg = msg as IMessage;
ArgumentNullReferenceCheckHelper.throwIfNull(base_msg, () => $"base_msg is null !!! - {session.toBasicString()}");
var msg_size = base_msg.CalculateSize();
var to_send_msgs = checkSizeAndConvertMessage(msg, msg_size);
foreach (var to_send_msg in to_send_msgs)
{
var send_msg = to_send_msg as IMessage;
NullReferenceCheckHelper.throwIfNull(send_msg, () => $"send_msg is null !!! - {session.toBasicString()}");
var send_msg_size = send_msg.CalculateSize();
string send_message = send_msg.ToString() ?? string.Empty;
var err_msg = $"{toTrySendString(host_id, send_message, send_msg_size)} - {session.toBasicString()}";
Log.getLogger().debug(err_msg);
var is_success = FnSendPacketToHost?.Invoke(host_id, rmiContext, send_msg);
if (false == is_success)
{
err_msg = $"{toFailedSendString(host_id, send_message, msg_size)} - {session.toBasicString()}";
Log.getLogger().error(err_msg);
}
}
return true;
}
private bool sendPacket<T>(IEntityWithSession[] sessions, T msg, RmiContext rmiContext)
where T : class, IMessage
{
ArgumentNullReferenceCheckHelper.throwIfNull(sessions, () => $"sessions is null !!!");
ArgumentNullReferenceCheckHelper.throwIfNull(rmiContext, () => $"rmiContext is null !!!");
var host_ids = new Nettention.Proud.HostID[sessions.Length];
for(var i = 0; i < host_ids.Length; i++)
{
host_ids[i] = sessions[i].getHostId();
}
var base_msg = msg as IMessage;
NullReferenceCheckHelper.throwIfNull(base_msg, () => $"base_msg is null !!! - {toBasicString()}");
var msg_size = base_msg.CalculateSize();
var to_send_msgs = checkSizeAndConvertMessage(msg, msg_size);
foreach (var to_send_msg in to_send_msgs)
{
var send_msg = to_send_msg as IMessage;
NullReferenceCheckHelper.throwIfNull(send_msg, () => $"send_msg is null !!! - {toBasicString()}");
var send_msg_size = send_msg.CalculateSize();
string send_message = send_msg.ToString() ?? string.Empty;
var err_msg = $"{toTrySendStrings(host_ids, send_message, send_msg_size)}";
Log.getLogger().debug(err_msg);
var is_success = FnSendPacketToHosts?.Invoke(host_ids, rmiContext, send_msg);
if (false == is_success)
{
err_msg = $"{toFailedSendStrings(host_ids, send_message, msg_size)}";
Log.getLogger().error(err_msg);
}
}
return true;
}
private List<T> checkSizeAndConvertMessage<T>(T msg, int msg_size)
where T : class, IMessage
{
var msgs = new List<T>();
if (false == Constant.IS_LARGE_PACKET_PROTOCOL_ACTIVE)
{
msgs.Add(msg);
return msgs;
}
if (msg_size > getLargePacketCheckSize())
{
var byte_string = msg.ToByteString();
byte[] byteArray = byte_string.ToByteArray();
int chunkSize = getPacketChunkSize();
int numberOfChunks = (int)Math.Ceiling((double)byteArray.Length / chunkSize);
var uid = KeyGeneration.GenerateRandomKey(20);
var otp_encoding_by_base32 = Base32Encoding.ToString(uid);
for (int i = 0; i < numberOfChunks; i++)
{
int startIndex = i * chunkSize;
int length = Math.Min(chunkSize, byteArray.Length - startIndex);
byte[] chunk = new byte[length];
Array.Copy(byteArray, startIndex, chunk, 0, length);
ByteString chunkByteString = ByteString.CopyFrom(chunk);
var cToG = new ClientToGame();
cToG.Response = new();
cToG.Response.AckLargePacket = new();
cToG.Response.AckLargePacket.PacketIndex = i;
cToG.Response.AckLargePacket.Uid = otp_encoding_by_base32;
cToG.Response.AckLargePacket.Data = chunkByteString;
cToG.Response.AckLargePacket.TotalPacketCount = numberOfChunks;
var chunk_msg = cToG as IMessage;
msgs.Add((T)chunk_msg);
}
}
else
{
msgs.Add(msg);
}
return msgs;
}
protected bool onRecvP2PMotionSync(Nettention.Proud.HostID remote,Nettention.Proud.RmiContext rmiContext, System.String id, Nettention.Proud.ByteArray data)
{
var session_id = remote.toSESSION_ID();
var found_entity_with_session = onLookupEntityWithSession(session_id);
if (found_entity_with_session == null)
{
//err_msg = $"Not found remoteSession !!! - sessionId:{session_id}, recvProtocolType:{recv_protocol_type}, HostID:{remote}";
//Log.getLogger().warn(err_msg);
return false;
}
PacketReceiver receiver = getPacketReceiver();
NullReferenceCheckHelper.throwIfNull(receiver, fnLog: () => $"receiver is null !!! - {found_entity_with_session.toBasicString()}");
var p2p_recv_handler = receiver.findP2PPacketHandler("");
if (null == p2p_recv_handler)
{
var err_msg = $"Not found Recv Packet Handler !!! : {found_entity_with_session.toBasicString()}";
Log.getLogger().error(err_msg);
return false;
}
p2p_recv_handler.onProcessP2PPacket(found_entity_with_session, data);
return true;
}
#endregion
#region
public abstract string toBasicString();
public virtual string toTrySendString(Nettention.Proud.HostID hostId, string msgType, Int32 length) => $"Packet Try Send2Net - TargetHostId:{hostId}, msgType:{msgType}, length:{length}";
public virtual string toFailedSendString(Nettention.Proud.HostID hostId, string msgType, Int32 length) => $"Packet Failed !!! Send2Net - TargetHostId:{hostId}, msgType:{msgType}, length:{length}";
public virtual string toTrySendStrings(Nettention.Proud.HostID[] hostIds, string msgType, Int32 length) => $"Packet Try Send2Net - TargetHostIds:{hostIds}..., msgType:{msgType}, length:{length}";
public virtual string toFailedSendStrings(Nettention.Proud.HostID[] hostIds, string msgType, Int32 length) => $"Packet Failed !!! Send2Net - TargetHostIds:{hostIds}..., msgType:{msgType}, length:{length}";
#endregion
}
//=============================================================================================
// 리슨 역할을 하는 세션 기반 클래스 이다. (서버 역할용)
//
// author : kangms
//
//=============================================================================================
public abstract partial class ListenSessionBase : SessionBase
{
private readonly string m_server_type = string.Empty;
private NetworkAddress m_listen_address = new();
private LargePacketHandler m_large_packet_handler = new();
// SESSION_ID 는 Pround.HostID 와 같다 !!!
private readonly ConcurrentDictionary<SESSION_ID, IEntityWithSession> m_entity_with_sessions = new();
public ListenSessionBase(IRmiHost netForServer, string serverType)
: base(netForServer)
{
m_server_type = serverType;
}
protected override bool onBindRmiHostHandler(IRmiHost currRmiHost)
{
var err_msg = string.Empty;
var rmi_host_for_server = currRmiHost as NetServer;
if(null == rmi_host_for_server)
{
err_msg = $"Invalid Param !!!, NetServer is null - {toBasicString()}";
Log.getLogger().error(err_msg);
return false;
}
rmi_host_for_server.ClientJoinHandler = onConnectedClient;
rmi_host_for_server.ClientLeaveHandler = onDisconnectedClient;
rmi_host_for_server.ErrorHandler = onError;
rmi_host_for_server.WarningHandler = onWarning;
rmi_host_for_server.InformationHandler = onInformation;
rmi_host_for_server.ExceptionHandler = onException;
rmi_host_for_server.NoRmiProcessedHandler = onNoRmiProcessed;
rmi_host_for_server.ClientHackSuspectedHandler = onClientHackSuspected;
return true;
}
private async void onConnectedClient(NetClientInfo clientInfo)
{
var err_msg = string.Empty;
var new_entity_with_session = await onNewEntityWithSession(clientInfo);
if(null == new_entity_with_session)
{
err_msg = $"Failed to create New EnityWithSession - {toBasicString()}";
Log.getLogger().fatal(err_msg);
return;
}
if(false == m_entity_with_sessions.TryAdd(clientInfo.getSessionId(), new_entity_with_session))
{
err_msg = $"Already registered EnityWithSession !!! : {new_entity_with_session.toBasicString()} - {toBasicString()}";
Log.getLogger().fatal(err_msg);
return;
}
new_entity_with_session.setListenSessionBase(this);
NamedPipeMonitor.SetCurrentClientConnection(m_entity_with_sessions.Count, getServerType().toServerType());
var initializer = new_entity_with_session as IInitializer;
if(null != initializer)
{
var result = await initializer.onInit();
if(result.isFail())
{
Log.getLogger().fatal(result.toBasicString());
return;
}
}
var hostId = clientInfo.hostID;
err_msg = $"Client connected, in onConnectedClient() : hostPublicIP:{clientInfo.tcpAddrFromServer.IPToString()}, HostId:{hostId}, {new_entity_with_session.toBasicString()} - {toBasicString()}";
Log.getLogger().debug(err_msg);
}
private async void onDisconnectedClient(NetClientInfo clientInfo, ErrorInfo errorinfo, ByteArray comment)
{
var err_msg = string.Empty;
if (clientInfo == null || errorinfo == null)
{
string clientInfo_log = clientInfo == null ? "Null" : "Not Null";
string errorInfo_log = errorinfo == null ? "Null" : "Not Null";
err_msg = $"onDisconnectedClient clientInfo is null !!! clientInfo : {clientInfo_log}, errorinfo : {errorInfo_log}";
Log.getLogger().fatal(err_msg);
return;
}
var hostId = clientInfo.hostID;
var errInfo = errorinfo.GetString();
var comm = comment == null ? string.Empty : Encoding.Default.GetString(comment.data);
err_msg = $"Client Disconnected, in onDisconnectedClient() : hostPublicIP:{clientInfo.tcpAddrFromServer.IPToString()}, HostId:{hostId}, ErrorInfo:{errInfo}, comment:{comm} - {toBasicString()}";
Log.getLogger().debug(err_msg);
if (false == m_entity_with_sessions.TryGetValue(clientInfo.getSessionId(), out var found_entity_with_session))
{
NamedPipeMonitor.SetCurrentClientConnection(m_entity_with_sessions.Count, getServerType().toServerType());
err_msg = $"Not found EnityWithSession !!! : toDisconnectSessionId:{clientInfo.getSessionId()} - {toBasicString()}";
Log.getLogger().error(err_msg);
return;
}
if(false == m_entity_with_sessions.TryRemove(clientInfo.getSessionId(), out _))
{
NamedPipeMonitor.SetCurrentClientConnection(m_entity_with_sessions.Count, getServerType().toServerType());
err_msg = $"Failed to remove EnityWithSession !!! : {found_entity_with_session.toBasicString()} - {toBasicString()}";
Log.getLogger().error(err_msg);
return;
}
NamedPipeMonitor.SetCurrentClientConnection(m_entity_with_sessions.Count, getServerType().toServerType());
await onDisconnectedEntityWithSession(found_entity_with_session);
}
public bool disconnectClient(SESSION_ID sessionId)
{
var rmi_host_for_server = getRmiHost() as NetServer;
if (rmi_host_for_server == null)
return false;
return rmi_host_for_server.CloseConnection(sessionId.toHostID());
}
private void onError(ErrorInfo errorInfo)
{
var err_msg = errorInfo == null ? string.Empty : errorInfo.GetString() ?? string.Empty;
Log.getLogger().error($"ProudNet Error !!!, called onError() : errMsg:{err_msg} - {toBasicString()}");
}
private void onWarning(ErrorInfo errorInfo)
{
var err_msg = errorInfo == null ? string.Empty : errorInfo.GetString() ?? string.Empty;
Log.getLogger().warn($"ProudNet Warning !!!, called onWarning() : errMsg:{err_msg} - {toBasicString()}");
}
private void onInformation(ErrorInfo errorInfo)
{
var err_msg = errorInfo == null ? string.Empty : errorInfo.GetString() ?? string.Empty;
Log.getLogger().info($"ProudNet Information !!!, called onInformation() : errMsg:{err_msg} - {toBasicString()}");
}
private void onException(Exception e)
{
Log.getLogger().error($"ProudNet Exception !!!, called onException() : errMsg:{e} - {toBasicString()}");
}
private void onNoRmiProcessed(RmiID rmiID)
{
Log.getLogger().warn($"ProudNet NoRmiProcessed !!!, called onNoRmiProcessed() : RmiId:{rmiID} - {toBasicString()}");
}
private void onClientHackSuspected(Nettention.Proud.HostID clientID, HackType hackType)
{
Log.getLogger().error($"ProudNet Client Hack Suspencted !!!, called onClientHackSuspected() : HostId:{clientID}), HackType:{hackType} - {toBasicString()}");
}
public override IEntityWithSession? onLookupEntityWithSession(SESSION_ID sessionId)
{
m_entity_with_sessions.TryGetValue(sessionId, out var found_entity_with_session);
return found_entity_with_session;
}
#region
public override string toBasicString() => $"NetServerInfo: ServerType:{getServerType()}";
#endregion
#region
protected abstract Task<IEntityWithSession?> onNewEntityWithSession(NetClientInfo clientInfo);
protected abstract Task onDisconnectedEntityWithSession(IEntityWithSession disconnectedEntity);
public abstract int getMaxConnectionCount();
#endregion
}
//===========================================================================================
// 서버로 연결할 세션 기반 클래스 이다. (클라이언트 역할용)
//
// author : kangms
//
//===========================================================================================
public abstract partial class ConnectToServerSessionBase : SessionBase
{
private string m_to_connect_server_type = string.Empty;
// 연결할 호스트 정보
private string m_host_ip = string.Empty;
private UInt16 m_host_port;
private string m_host_address = string.Empty;
public ConnectToServerSessionBase(IRmiHost netForClient)
: base(netForClient)
{
}
protected override bool onBindRmiHostHandler(IRmiHost currRmiHost)
{
var err_msg = string.Empty;
var rmi_host_for_client = currRmiHost as NetClient;
if (null == rmi_host_for_client)
{
err_msg = $"Invalid Param !!!, NetClient is null - {toBasicString()}";
Log.getLogger().error(err_msg);
return false;
}
return true;
}
#region
public override string toBasicString() => $"NetClientInfo: TargetServerType:{getToConnectServerType()}, TargetAddress:{getHostAddress()}";
#endregion
}

View File

@@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using ServerCore;
namespace ServerBase;
public partial class RedisConnector : RedisConnectorBase, IModule, IInitializer
{
public class ConfigParam : IConfigParam
{
public string ServiceType { get; set; } = string.Empty;
public string ConnectionString { get; set; } = string.Empty;
public Result tryReadFromJsonOrDefault(JObject jObject)
{
var result = new Result();
var err_msg = string.Empty;
try
{
ServiceType = jObject["ServiceType"]?.Value<string>() ?? string.Empty;
ConnectionString = jObject["Redis"]?.Value<string>() ?? string.Empty;
return result;
}
catch (Exception e)
{
var error_code = ServerErrorCode.TryCatchException;
err_msg = $"Exception !!!, Failed to perform in tryReadFromJsonOrDefault() !!! : errorCode:{error_code}, exception:{e} - {this.getTypeName()}";
result.setFail(error_code, err_msg);
Log.getLogger().error(err_msg);
return result;
}
}
public string toBasicString()
{
return $"ConfigParam: ServiceType:{ServiceType}, ConnectionStringOfRedis:{ConnectionString}";
}
};
}

View File

@@ -0,0 +1,70 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ServerCore;
using MODULE_ID = System.UInt32;
using SORT_ORDER_NO = System.Int32;
namespace ServerBase;
public partial class RedisConnector : RedisConnectorBase, IModule, IInitializer
{
private readonly ModuleContext m_module_context;
public RedisConnector(ModuleContext context)
: base(validateAndGetConnectionString(context))
{
m_module_context = context;
}
private static string validateAndGetConnectionString(ModuleContext context)
{
if (context.getConfigParam() is not ConfigParam param)
throw new InvalidCastException("IConfigParam must be of type RedisConnectorBase.ConfigParam !!!");
return param.ConnectionString;
}
public async Task<Result> onInit()
{
var err_msg = string.Empty;
var result = new Result();
var module_context = getModuleContext();
var config_param = module_context.getConfigParam() as ConfigParam;
NullReferenceCheckHelper.throwIfNull(config_param, () => $"config_param is null !!!");
var is_success = await tryConnectAsync();
if (false == is_success)
{
err_msg = $"Failed to tryConnectAsync() !!! : {config_param.toBasicString()}";
result.setFail(ServerErrorCode.RedisServerConnectFailed, err_msg);
Log.getLogger().error(result.toBasicString());
return result;
}
return result;
}
public async Task<Result> startModule()
{
var result = new Result();
return await Task.FromResult(result);
}
public async Task<Result> stopModule()
{
var result = new Result();
return await Task.FromResult(result);
}
public ModuleContext getModuleContext() => m_module_context;
}

View File

@@ -0,0 +1,75 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.IIS.Core;
using ServerCore; using ServerBase;
namespace ServerBase;
public abstract partial class RedisRequestBase : IInitializer
{
private readonly RedisConnector m_redis_connector;
private string m_key = string.Empty;
private string m_member = string.Empty;
public RedisRequestBase(RedisConnector redisConnector)
{
m_redis_connector = redisConnector;
}
public virtual Task<Result> onInit()
{
var result = new Result();
return Task.FromResult(result);
}
protected abstract string onMakeKey();
protected virtual string onMakeMember() { return m_member; }
public virtual async Task<Result> onPrepareRequest()
{
var result = new Result();
var err_msg = string.Empty;
result = await Task.Run( () =>
{
if(true == m_key.isNullOrWhiteSpace())
{
m_key = onMakeKey();
}
if (0 >= m_key.Length)
{
err_msg = $"Failed to prepare Reuqest of Redis !!! - {toBasicString()}";
result.setFail(ServerErrorCode.RedisRequestKeyIsEmpty, err_msg);
Log.getLogger().error(result.toBasicString());
return result;
}
return result;
});
return result;
}
public virtual async Task<Result> onDoRequest()
{
await Task.CompletedTask;
var result = new Result();
return result;
}
public abstract string toBasicString();
}

View File

@@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ServerCore; using ServerBase;
namespace ServerBase;
public abstract partial class RedisRequestPrivateBase : RedisRequestBase
{
private readonly IActor m_owner;
public RedisRequestPrivateBase(IActor entityBase, RedisConnector redisConnector)
: base(redisConnector)
{
m_owner = entityBase;
}
}

View File

@@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ServerCore; using ServerBase;
namespace ServerBase;
public abstract partial class RedisRequestSharedBase : RedisRequestBase
{
private readonly string m_shared_key;
public RedisRequestSharedBase(string sharedKey, RedisConnector redisConnector)
: base(redisConnector)
{
m_shared_key = sharedKey;
}
}

Some files were not shown because too many files have changed in this diff Show More