초기커밋
This commit is contained in:
660
ServerBase/0. Base/BaseGetSet.cs
Normal file
660
ServerBase/0. Base/BaseGetSet.cs
Normal 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;
|
||||
}
|
||||
674
ServerBase/0. Base/BaseInterface.cs
Normal file
674
ServerBase/0. Base/BaseInterface.cs
Normal 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);
|
||||
}
|
||||
26
ServerBase/1. Define/Constant.cs
Normal file
26
ServerBase/1. Define/Constant.cs
Normal 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;
|
||||
}
|
||||
20
ServerBase/1. Define/DatabaseDefine.cs
Normal file
20
ServerBase/1. Define/DatabaseDefine.cs
Normal 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";
|
||||
}
|
||||
}
|
||||
33
ServerBase/1. Define/TypeDefine.cs
Normal file
33
ServerBase/1. Define/TypeDefine.cs
Normal 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,
|
||||
}
|
||||
17
ServerBase/BusinessLog/Appender/IAppender.cs
Normal file
17
ServerBase/BusinessLog/Appender/IAppender.cs
Normal 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
|
||||
60
ServerBase/BusinessLog/Appender/NLogAppender.cs
Normal file
60
ServerBase/BusinessLog/Appender/NLogAppender.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
93
ServerBase/BusinessLog/BusinessLogger.cs
Normal file
93
ServerBase/BusinessLog/BusinessLogger.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
17
ServerBase/BusinessLog/Formatter/IFormatter.cs
Normal file
17
ServerBase/BusinessLog/Formatter/IFormatter.cs
Normal 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
|
||||
82
ServerBase/BusinessLog/Formatter/JsonText.cs
Normal file
82
ServerBase/BusinessLog/Formatter/JsonText.cs
Normal 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
|
||||
21
ServerBase/BusinessLog/Log/Actor/ILogActor.cs
Normal file
21
ServerBase/BusinessLog/Log/Actor/ILogActor.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
243
ServerBase/BusinessLog/Log/ILog.cs
Normal file
243
ServerBase/BusinessLog/Log/ILog.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
48
ServerBase/Cache/CacheBase.cs
Normal file
48
ServerBase/Cache/CacheBase.cs
Normal 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)}";
|
||||
}
|
||||
}
|
||||
153
ServerBase/Cache/ServerMetrics/ServerMetricsCacheRequest.cs
Normal file
153
ServerBase/Cache/ServerMetrics/ServerMetricsCacheRequest.cs
Normal 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;
|
||||
}
|
||||
555
ServerBase/Config/ServerConfig.cs
Normal file
555
ServerBase/Config/ServerConfig.cs
Normal 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}";
|
||||
}
|
||||
}
|
||||
51
ServerBase/Config/ServerConfigMetaverseBroker.cs
Normal file
51
ServerBase/Config/ServerConfigMetaverseBroker.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
82
ServerBase/DB/DynamoDb/AttribBase.cs
Normal file
82
ServerBase/DB/DynamoDb/AttribBase.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
275
ServerBase/DB/DynamoDb/DynamoDbClient.cs
Normal file
275
ServerBase/DB/DynamoDb/DynamoDbClient.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
217
ServerBase/DB/DynamoDb/DynamoDbCommon.cs
Normal file
217
ServerBase/DB/DynamoDb/DynamoDbCommon.cs
Normal 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;
|
||||
}
|
||||
59
ServerBase/DB/DynamoDb/DynamoDbConfig.cs
Normal file
59
ServerBase/DB/DynamoDb/DynamoDbConfig.cs
Normal 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}";
|
||||
}
|
||||
};
|
||||
}
|
||||
766
ServerBase/DB/DynamoDb/DynamoDbDocBase.cs
Normal file
766
ServerBase/DB/DynamoDb/DynamoDbDocBase.cs
Normal 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")}";
|
||||
}
|
||||
}
|
||||
66
ServerBase/DB/DynamoDb/DynamoDbItemRequestBase.cs
Normal file
66
ServerBase/DB/DynamoDb/DynamoDbItemRequestBase.cs
Normal 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()}";
|
||||
}
|
||||
}
|
||||
77
ServerBase/DB/DynamoDb/DynamoDbQueryExceptionNotifier.cs
Normal file
77
ServerBase/DB/DynamoDb/DynamoDbQueryExceptionNotifier.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
61
ServerBase/DB/MongoDb/MongoDbConfig.cs
Normal file
61
ServerBase/DB/MongoDb/MongoDbConfig.cs
Normal 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}";
|
||||
}
|
||||
};
|
||||
}
|
||||
62
ServerBase/DB/MongoDb/MongoDbConnector.cs
Normal file
62
ServerBase/DB/MongoDb/MongoDbConnector.cs
Normal 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()}";
|
||||
}
|
||||
}
|
||||
213
ServerBase/DB/MySqlDb/MySqlDbConnector.cs
Normal file
213
ServerBase/DB/MySqlDb/MySqlDbConnector.cs
Normal 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
610
ServerBase/DB/QueryBatch.cs
Normal 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>
|
||||
254
ServerBase/DB/QueryDbRequester.cs
Normal file
254
ServerBase/DB/QueryDbRequester.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
133
ServerBase/DB/QueryDbRequesterBase.cs
Normal file
133
ServerBase/DB/QueryDbRequesterBase.cs
Normal 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;
|
||||
}
|
||||
189
ServerBase/DB/QueryExecutorBase.cs
Normal file
189
ServerBase/DB/QueryExecutorBase.cs
Normal 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
|
||||
158
ServerBase/DB/QueryHelper.cs
Normal file
158
ServerBase/DB/QueryHelper.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
69
ServerBase/DB/QueryInterface.cs
Normal file
69
ServerBase/DB/QueryInterface.cs
Normal 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();
|
||||
}
|
||||
644
ServerBase/DB/QueryRunnerWithDocument.cs
Normal file
644
ServerBase/DB/QueryRunnerWithDocument.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
611
ServerBase/DB/QueryRunnerWithItemRequest.cs
Normal file
611
ServerBase/DB/QueryRunnerWithItemRequest.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
194
ServerBase/DB/Query_GetSet.cs
Normal file
194
ServerBase/DB/Query_GetSet.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
8
ServerBase/Directory.Build.props
Normal file
8
ServerBase/Directory.Build.props
Normal file
@@ -0,0 +1,8 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
<BaseIntermediateOutputPath>..\..\obj\AnyCPU\$(MSBuildProjectName)\</BaseIntermediateOutputPath>
|
||||
<BaseOutputPath>..\..\bin\</BaseOutputPath>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
46
ServerBase/Entity/Action/EntityActionBase.cs
Normal file
46
ServerBase/Entity/Action/EntityActionBase.cs
Normal 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()}";
|
||||
}
|
||||
}
|
||||
72
ServerBase/Entity/Aggregation/EntityAggregator.cs
Normal file
72
ServerBase/Entity/Aggregation/EntityAggregator.cs
Normal 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;
|
||||
}
|
||||
397
ServerBase/Entity/Attribute/EntityAttributeBase.cs
Normal file
397
ServerBase/Entity/Attribute/EntityAttributeBase.cs
Normal 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 로그와 관련된 함수들
|
||||
}
|
||||
113
ServerBase/Entity/Attribute/EntityAttributeTransactorBase.cs
Normal file
113
ServerBase/Entity/Attribute/EntityAttributeTransactorBase.cs
Normal 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()}";
|
||||
}
|
||||
}
|
||||
82
ServerBase/Entity/Delta/EntityDeltaRecorder.cs
Normal file
82
ServerBase/Entity/Delta/EntityDeltaRecorder.cs
Normal 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;
|
||||
}
|
||||
1026
ServerBase/Entity/EntityBase.cs
Normal file
1026
ServerBase/Entity/EntityBase.cs
Normal file
File diff suppressed because it is too large
Load Diff
75
ServerBase/Entity/State/EntityHFSMBase.cs
Normal file
75
ServerBase/Entity/State/EntityHFSMBase.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
61
ServerBase/Entity/State/EntityStateBase.cs
Normal file
61
ServerBase/Entity/State/EntityStateBase.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
107
ServerBase/Entity/Task/EntityTicker.cs
Normal file
107
ServerBase/Entity/Task/EntityTicker.cs
Normal 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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
847
ServerBase/Entity/Transaction/TransactionRunner.cs
Normal file
847
ServerBase/Entity/Transaction/TransactionRunner.cs
Normal 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}";
|
||||
}
|
||||
}
|
||||
46
ServerBase/Etc/ModuleContext.cs
Normal file
46
ServerBase/Etc/ModuleContext.cs
Normal 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()}";
|
||||
}
|
||||
}
|
||||
38
ServerBase/Etc/ResultValue.cs
Normal file
38
ServerBase/Etc/ResultValue.cs
Normal 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;
|
||||
}
|
||||
33
ServerBase/Event/SimpleEventTriggerBase.cs
Normal file
33
ServerBase/Event/SimpleEventTriggerBase.cs
Normal 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()}";
|
||||
}
|
||||
}
|
||||
283
ServerBase/Helper/DataCopyHelper.cs
Normal file
283
ServerBase/Helper/DataCopyHelper.cs
Normal 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
|
||||
|
||||
}
|
||||
2505
ServerBase/Helper/DynamoDbClientHelper.cs
Normal file
2505
ServerBase/Helper/DynamoDbClientHelper.cs
Normal file
File diff suppressed because it is too large
Load Diff
1369
ServerBase/Helper/DynamoDbDocBaseHelper.cs
Normal file
1369
ServerBase/Helper/DynamoDbDocBaseHelper.cs
Normal file
File diff suppressed because it is too large
Load Diff
617
ServerBase/Helper/DynamoDbItemRequestHelper.cs
Normal file
617
ServerBase/Helper/DynamoDbItemRequestHelper.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
256
ServerBase/Helper/DynamoDbStopwatchHelper.cs
Normal file
256
ServerBase/Helper/DynamoDbStopwatchHelper.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
25
ServerBase/Helper/EntityHelper.cs
Normal file
25
ServerBase/Helper/EntityHelper.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
46
ServerBase/Helper/ErrorHelper.cs
Normal file
46
ServerBase/Helper/ErrorHelper.cs
Normal 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()}";
|
||||
}
|
||||
}
|
||||
86
ServerBase/Helper/HttpClientHelper.cs
Normal file
86
ServerBase/Helper/HttpClientHelper.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
97
ServerBase/Helper/MySqlConnectorHelper.cs
Normal file
97
ServerBase/Helper/MySqlConnectorHelper.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
16
ServerBase/Helper/MySqlHelper.cs
Normal file
16
ServerBase/Helper/MySqlHelper.cs
Normal 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}";
|
||||
}
|
||||
}
|
||||
138
ServerBase/Helper/ProgramVersionHelper.cs
Normal file
138
ServerBase/Helper/ProgramVersionHelper.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
187
ServerBase/Helper/ProtoHelper.cs
Normal file
187
ServerBase/Helper/ProtoHelper.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
41
ServerBase/Helper/ProudNetHelper.cs
Normal file
41
ServerBase/Helper/ProudNetHelper.cs
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
88
ServerBase/Helper/ResultHelper.cs
Normal file
88
ServerBase/Helper/ResultHelper.cs
Normal 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()}";
|
||||
}
|
||||
}
|
||||
212
ServerBase/Helper/ServerConfigHelper.cs
Normal file
212
ServerBase/Helper/ServerConfigHelper.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
151
ServerBase/Helper/ServerHelper.cs
Normal file
151
ServerBase/Helper/ServerHelper.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
93
ServerBase/Helper/ServerLogicHelper.cs
Normal file
93
ServerBase/Helper/ServerLogicHelper.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
172
ServerBase/Helper/ServerMetricsHelper.cs
Normal file
172
ServerBase/Helper/ServerMetricsHelper.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
33
ServerBase/Helper/TickerHelper.cs
Normal file
33
ServerBase/Helper/TickerHelper.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
58
ServerBase/Helper/TransactionRunnerHelper.cs
Normal file
58
ServerBase/Helper/TransactionRunnerHelper.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
63
ServerBase/Helper/VersionHelper.cs
Normal file
63
ServerBase/Helper/VersionHelper.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
37
ServerBase/Logic/ServerLogicApp.cs
Normal file
37
ServerBase/Logic/ServerLogicApp.cs
Normal 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;
|
||||
}
|
||||
1002
ServerBase/Logic/ServerLogicBase.cs
Normal file
1002
ServerBase/Logic/ServerLogicBase.cs
Normal file
File diff suppressed because it is too large
Load Diff
71
ServerBase/Manager/DailyTimeEventManager.cs
Normal file
71
ServerBase/Manager/DailyTimeEventManager.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
46
ServerBase/Manager/ServerMetricsManager.cs
Normal file
46
ServerBase/Manager/ServerMetricsManager.cs
Normal 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;
|
||||
}
|
||||
64
ServerBase/MessageQueue/RabbitMqConfig.cs
Normal file
64
ServerBase/MessageQueue/RabbitMqConfig.cs
Normal 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}";
|
||||
}
|
||||
}
|
||||
};
|
||||
336
ServerBase/MessageQueue/RabbitMqConnector.cs
Normal file
336
ServerBase/MessageQueue/RabbitMqConnector.cs
Normal 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()}";
|
||||
}
|
||||
}
|
||||
69
ServerBase/Meta/IMetaHelper.cs
Normal file
69
ServerBase/Meta/IMetaHelper.cs
Normal 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>
|
||||
}
|
||||
*/
|
||||
51
ServerBase/Meta/MetaAssets/ContentLoader.cs
Normal file
51
ServerBase/Meta/MetaAssets/ContentLoader.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
11
ServerBase/Meta/MetaAssets/ContentTableBase.cs
Normal file
11
ServerBase/Meta/MetaAssets/ContentTableBase.cs
Normal 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) { }
|
||||
}
|
||||
8
ServerBase/Meta/MetaAssets/IMetaData.cs
Normal file
8
ServerBase/Meta/MetaAssets/IMetaData.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
|
||||
|
||||
namespace ServerBase;
|
||||
|
||||
public interface IMetaData
|
||||
{
|
||||
string toBasicString();
|
||||
}
|
||||
37
ServerBase/Meta/MetaValidation/MetaValidator.cs
Normal file
37
ServerBase/Meta/MetaValidation/MetaValidator.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
11
ServerBase/Meta/MetaValidation/MetaValidatorAttribute.cs
Normal file
11
ServerBase/Meta/MetaValidation/MetaValidatorAttribute.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
|
||||
|
||||
|
||||
|
||||
namespace ServerBase;
|
||||
|
||||
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
public class MetaValidatorAttribute : Attribute
|
||||
{
|
||||
}
|
||||
41
ServerBase/Meta/MetaValidation/MethodInfoExtentions.cs
Normal file
41
ServerBase/Meta/MetaValidation/MethodInfoExtentions.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
|
||||
69
ServerBase/Meta/MetaValidation/ValidattionErrorCollection.cs
Normal file
69
ServerBase/Meta/MetaValidation/ValidattionErrorCollection.cs
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
88
ServerBase/Monitor/Monitor.cs
Normal file
88
ServerBase/Monitor/Monitor.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
99
ServerBase/Monitor/NamedPipe/NamedPipeMonitor.cs
Normal file
99
ServerBase/Monitor/NamedPipe/NamedPipeMonitor.cs
Normal 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());
|
||||
}
|
||||
251
ServerBase/Network/Handler/LargePacketHandler.cs
Normal file
251
ServerBase/Network/Handler/LargePacketHandler.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
155
ServerBase/Network/Packet/PacketCommand.cs
Normal file
155
ServerBase/Network/Packet/PacketCommand.cs
Normal 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()}";
|
||||
}
|
||||
}
|
||||
184
ServerBase/Network/Packet/PacketHandler.cs
Normal file
184
ServerBase/Network/Packet/PacketHandler.cs
Normal 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()}");
|
||||
// }
|
||||
}
|
||||
280
ServerBase/Network/Packet/PacketReceiver.cs
Normal file
280
ServerBase/Network/Packet/PacketReceiver.cs
Normal 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];
|
||||
}
|
||||
}
|
||||
21
ServerBase/Network/Packet/PacketResponseDelayProfiler.cs
Normal file
21
ServerBase/Network/Packet/PacketResponseDelayProfiler.cs
Normal 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
|
||||
{
|
||||
|
||||
}
|
||||
167
ServerBase/Network/Packet/PacketSender.cs
Normal file
167
ServerBase/Network/Packet/PacketSender.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
86
ServerBase/Pattern/Initializer.cs
Normal file
86
ServerBase/Pattern/Initializer.cs
Normal 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>();
|
||||
}
|
||||
}
|
||||
}
|
||||
831
ServerBase/ProudNet/Session/SessionBase.cs
Normal file
831
ServerBase/ProudNet/Session/SessionBase.cs
Normal 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 로그용 문자열 함수들
|
||||
}
|
||||
50
ServerBase/Redis/RedisConfig.cs
Normal file
50
ServerBase/Redis/RedisConfig.cs
Normal 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}";
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
70
ServerBase/Redis/RedisConnector.cs
Normal file
70
ServerBase/Redis/RedisConnector.cs
Normal 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;
|
||||
}
|
||||
75
ServerBase/Redis/RedisRequestBase.cs
Normal file
75
ServerBase/Redis/RedisRequestBase.cs
Normal 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();
|
||||
}
|
||||
23
ServerBase/Redis/RedisRequestPrivateBase.cs
Normal file
23
ServerBase/Redis/RedisRequestPrivateBase.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
22
ServerBase/Redis/RedisRequestSharedBase.cs
Normal file
22
ServerBase/Redis/RedisRequestSharedBase.cs
Normal 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
Reference in New Issue
Block a user