feat : RDB 기능 추가 (라이브러리, base 코드 구현)
This commit is contained in:
@@ -11,12 +11,21 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="LiteNetLib" Version="2.0.2" />
|
<PackageReference Include="LiteNetLib" Version="2.0.2" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Configuration" Version="10.0.3" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="10.0.3" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.3" />
|
||||||
<PackageReference Include="protobuf-net" Version="3.2.56" />
|
<PackageReference Include="protobuf-net" Version="3.2.56" />
|
||||||
<Content Include="..\.dockerignore">
|
<Content Include="..\.dockerignore">
|
||||||
<Link>.dockerignore</Link>
|
<Link>.dockerignore</Link>
|
||||||
</Content>
|
</Content>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Update="config.json">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\ServerLib\ServerLib.csproj" />
|
<ProjectReference Include="..\ServerLib\ServerLib.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
using MMOserver.Game;
|
using MMOserver.Game;
|
||||||
|
using MMOserver.RDB;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace MMOserver;
|
namespace MMOserver;
|
||||||
@@ -9,6 +11,15 @@ class Program
|
|||||||
{
|
{
|
||||||
// .MinimumLevel.Warning() // Warning 이상만 출력 배포시
|
// .MinimumLevel.Warning() // Warning 이상만 출력 배포시
|
||||||
|
|
||||||
|
IConfigurationRoot config = new ConfigurationBuilder()
|
||||||
|
.SetBasePath(AppContext.BaseDirectory)
|
||||||
|
.AddJsonFile("config.json", optional: true) // 로컬 개발용
|
||||||
|
.AddEnvironmentVariables() // 도커 배포용
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
// DB 연결
|
||||||
|
// DbConnectionFactory dbFactory = new DbConnectionFactory(config);
|
||||||
|
|
||||||
string timestamp = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss");
|
string timestamp = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss");
|
||||||
|
|
||||||
Log.Logger = new LoggerConfiguration()
|
Log.Logger = new LoggerConfiguration()
|
||||||
|
|||||||
31
MMOTestServer/MMOserver/RDB/DbConnectionFactory.cs
Normal file
31
MMOTestServer/MMOserver/RDB/DbConnectionFactory.cs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
using System.Data;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using MySql.Data.MySqlClient;
|
||||||
|
using ServerLib.RDB.Database;
|
||||||
|
|
||||||
|
namespace MMOserver.RDB;
|
||||||
|
|
||||||
|
public class DbConnectionFactory : IDbConnectionFactory
|
||||||
|
{
|
||||||
|
private readonly string connectionString;
|
||||||
|
|
||||||
|
public DbConnectionFactory(IConfiguration config)
|
||||||
|
{
|
||||||
|
IConfigurationSection db = config.GetSection("Database");
|
||||||
|
connectionString = $"Server={db["Host"]};" +
|
||||||
|
$"Port={db["Port"]};" +
|
||||||
|
$"Database={db["Name"]};" +
|
||||||
|
$"User={db["User"]};" +
|
||||||
|
$"Password={db["Password"]};" +
|
||||||
|
$"Pooling=true;" +
|
||||||
|
$"MinimumPoolSize={db["Pooling:MinimumPoolSize"]};" +
|
||||||
|
$"MaximumPoolSize={db["Pooling:MaximumPoolSize"]};" +
|
||||||
|
$"ConnectionTimeout={db["Pooling:ConnectionTimeout"]};" +
|
||||||
|
$"ConnectionIdleTimeout={db["Pooling:ConnectionIdleTimeout"]};";
|
||||||
|
}
|
||||||
|
|
||||||
|
public IDbConnection CreateConnection()
|
||||||
|
{
|
||||||
|
return new MySqlConnection(connectionString);
|
||||||
|
}
|
||||||
|
}
|
||||||
48
MMOTestServer/MMOserver/RDB/Handlers/TestHandlers.cs
Normal file
48
MMOTestServer/MMOserver/RDB/Handlers/TestHandlers.cs
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
using MMOserver.RDB.Models;
|
||||||
|
using MMOserver.RDB.Services;
|
||||||
|
using ServerLib.RDB.Handlers;
|
||||||
|
|
||||||
|
namespace MMOserver.RDB.Handlers;
|
||||||
|
|
||||||
|
public class TestHandler
|
||||||
|
{
|
||||||
|
private readonly TestService _testService;
|
||||||
|
|
||||||
|
public TestHandler(TestService testService) => _testService = testService;
|
||||||
|
|
||||||
|
public async Task<string> GetTestAsync(int id)
|
||||||
|
{
|
||||||
|
Test? result = await _testService.GetTestAsync(id);
|
||||||
|
return result is null
|
||||||
|
? HandlerHelper.Error("Test not found")
|
||||||
|
: HandlerHelper.Success(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> GetTestByUuidAsync(string uuid)
|
||||||
|
{
|
||||||
|
Test? result = await _testService.GetTestByUuidAsync(uuid);
|
||||||
|
return result is null
|
||||||
|
? HandlerHelper.Error("Test not found")
|
||||||
|
: HandlerHelper.Success(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> GetAllTestsAsync()
|
||||||
|
{
|
||||||
|
return HandlerHelper.Success(await _testService.GetAllTestsAsync());
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> CreateTestAsync(int testA, string testB, double testC)
|
||||||
|
{
|
||||||
|
return HandlerHelper.Success(await _testService.CreateTestAsync(testA, testB, testC));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> UpdateTestAsync(int id, int? testA, string? testB, double? testC)
|
||||||
|
{
|
||||||
|
return HandlerHelper.Success(await _testService.UpdateTestAsync(id, testA, testB, testC));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> DeleteTestAsync(int id)
|
||||||
|
{
|
||||||
|
return HandlerHelper.Success(await _testService.DeleteTestAsync(id));
|
||||||
|
}
|
||||||
|
}
|
||||||
16
MMOTestServer/MMOserver/RDB/Models/TestM.cs
Normal file
16
MMOTestServer/MMOserver/RDB/Models/TestM.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
using Dapper.Contrib.Extensions;
|
||||||
|
|
||||||
|
namespace MMOserver.RDB.Models;
|
||||||
|
|
||||||
|
// 테이블 entity
|
||||||
|
[Table("test")]
|
||||||
|
public class Test
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
public int Id { get; set; }
|
||||||
|
public int TestA { get; set; }
|
||||||
|
public string? TestB { get; set; }
|
||||||
|
public double TestC { get; set; }
|
||||||
|
public string? Uuid { get; set; } // UNIQUE
|
||||||
|
}
|
||||||
|
|
||||||
18
MMOTestServer/MMOserver/RDB/Repositories/TestRepository.cs
Normal file
18
MMOTestServer/MMOserver/RDB/Repositories/TestRepository.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
using MMOserver.RDB.Models;
|
||||||
|
using ServerLib.RDB.Database;
|
||||||
|
using ServerLib.RDB.Repositories;
|
||||||
|
|
||||||
|
namespace MMOserver.RDB.Repositories;
|
||||||
|
|
||||||
|
// 실제 호출할 쿼리들
|
||||||
|
public class TestRepository : ARepository<Test>
|
||||||
|
{
|
||||||
|
public TestRepository(DbConnectionFactory factory) : base(factory)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Test?> GetByUuidAsync(string uuid)
|
||||||
|
{
|
||||||
|
return await QueryFirstOrDefaultAsync("SELECT * FROM test WHERE uuid = @Uuid", new { Uuid = uuid });
|
||||||
|
}
|
||||||
|
}
|
||||||
57
MMOTestServer/MMOserver/RDB/Services/TestService.cs
Normal file
57
MMOTestServer/MMOserver/RDB/Services/TestService.cs
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
using MMOserver.RDB.Models;
|
||||||
|
using MMOserver.RDB.Repositories;
|
||||||
|
|
||||||
|
namespace MMOserver.RDB.Services;
|
||||||
|
|
||||||
|
public class TestService
|
||||||
|
{
|
||||||
|
private readonly TestRepository repo;
|
||||||
|
|
||||||
|
public TestService(TestRepository repo)
|
||||||
|
{
|
||||||
|
this.repo = repo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<Test?> GetTestAsync(int id)
|
||||||
|
{
|
||||||
|
return repo.GetByIdAsync(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<Test?> GetTestByUuidAsync(string uuid)
|
||||||
|
{
|
||||||
|
return repo.GetByUuidAsync(uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<IEnumerable<Test>> GetAllTestsAsync()
|
||||||
|
{
|
||||||
|
return repo.GetAllAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<long> CreateTestAsync(int testA, string testB, double testC)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(testB))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("TestB is required");
|
||||||
|
}
|
||||||
|
|
||||||
|
return await repo.InsertAsync(new Test { TestA = testA, TestB = testB, TestC = testC, Uuid = Guid.NewGuid().ToString() });
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> UpdateTestAsync(int id, int? testA, string? testB, double? testC)
|
||||||
|
{
|
||||||
|
Test entity = await repo.GetByIdAsync(id) ?? throw new KeyNotFoundException("Test not found");
|
||||||
|
|
||||||
|
entity.TestA = testA ?? entity.TestA;
|
||||||
|
entity.TestB = testB ?? entity.TestB;
|
||||||
|
entity.TestC = testC ?? entity.TestC;
|
||||||
|
|
||||||
|
return await repo.UpdateAsync(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> DeleteTestAsync(int id)
|
||||||
|
{
|
||||||
|
Test entity = await repo.GetByIdAsync(id) ?? throw new KeyNotFoundException("Test not found");
|
||||||
|
|
||||||
|
return await repo.DeleteAsync(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
15
MMOTestServer/MMOserver/config.json
Normal file
15
MMOTestServer/MMOserver/config.json
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"Database": {
|
||||||
|
"Host": "localhost",
|
||||||
|
"Port": "0000",
|
||||||
|
"Name": "XXXX",
|
||||||
|
"User": "root",
|
||||||
|
"Password": "11212121",
|
||||||
|
"Pooling": {
|
||||||
|
"MinimumPoolSize": "5",
|
||||||
|
"MaximumPoolSize": "100",
|
||||||
|
"ConnectionTimeout": "30",
|
||||||
|
"ConnectionIdleTimeout": "180"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
using System.Data;
|
||||||
|
|
||||||
|
namespace ServerLib.RDB.Database;
|
||||||
|
|
||||||
|
public interface IDbConnectionFactory
|
||||||
|
{
|
||||||
|
public IDbConnection CreateConnection();
|
||||||
|
}
|
||||||
22
MMOTestServer/ServerLib/RDB/Handlers/HelperHandler.cs
Normal file
22
MMOTestServer/ServerLib/RDB/Handlers/HelperHandler.cs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace ServerLib.RDB.Handlers;
|
||||||
|
|
||||||
|
public static class HandlerHelper
|
||||||
|
{
|
||||||
|
public static string Success<T>(T data)
|
||||||
|
{
|
||||||
|
return JsonSerializer.Serialize(new Response<T> { Success = true, Data = data });
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string Error(string message)
|
||||||
|
{
|
||||||
|
return JsonSerializer.Serialize(new Response<object> { Success = false, Error = message });
|
||||||
|
}
|
||||||
|
|
||||||
|
public static T? Deserialize<T>(string payload)
|
||||||
|
{
|
||||||
|
try { return JsonSerializer.Deserialize<T>(payload); }
|
||||||
|
catch { return default; }
|
||||||
|
}
|
||||||
|
}
|
||||||
8
MMOTestServer/ServerLib/RDB/Handlers/Response.cs
Normal file
8
MMOTestServer/ServerLib/RDB/Handlers/Response.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace ServerLib.RDB.Handlers;
|
||||||
|
|
||||||
|
public class Response<T>
|
||||||
|
{
|
||||||
|
public bool Success { get; init; }
|
||||||
|
public T? Data { get; init; }
|
||||||
|
public string? Error { get; init; }
|
||||||
|
}
|
||||||
90
MMOTestServer/ServerLib/RDB/Repositories/ARepository.cs
Normal file
90
MMOTestServer/ServerLib/RDB/Repositories/ARepository.cs
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
using System.Data;
|
||||||
|
using Dapper;
|
||||||
|
using Dapper.Contrib.Extensions;
|
||||||
|
using ServerLib.RDB.Database;
|
||||||
|
|
||||||
|
namespace ServerLib.RDB.Repositories;
|
||||||
|
|
||||||
|
public abstract class ARepository<T> where T : class
|
||||||
|
{
|
||||||
|
private readonly IDbConnectionFactory factory;
|
||||||
|
|
||||||
|
protected ARepository(IDbConnectionFactory factory)
|
||||||
|
{
|
||||||
|
this.factory = factory;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected IDbConnection CreateConnection()
|
||||||
|
{
|
||||||
|
return factory.CreateConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dapper.Contrib 기본 CRUD
|
||||||
|
public async Task<T?> GetByIdAsync(int id)
|
||||||
|
{
|
||||||
|
IDbConnection conn = CreateConnection();
|
||||||
|
return await conn.GetAsync<T>(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<T>> GetAllAsync()
|
||||||
|
{
|
||||||
|
IDbConnection conn = CreateConnection();
|
||||||
|
return await conn.GetAllAsync<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<long> InsertAsync(T entity)
|
||||||
|
{
|
||||||
|
IDbConnection conn = CreateConnection();
|
||||||
|
return await conn.InsertAsync(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> UpdateAsync(T entity)
|
||||||
|
{
|
||||||
|
IDbConnection conn = CreateConnection();
|
||||||
|
return await conn.UpdateAsync(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> DeleteAsync(T entity)
|
||||||
|
{
|
||||||
|
IDbConnection conn = CreateConnection();
|
||||||
|
return await conn.DeleteAsync(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 커스텀 쿼리 헬퍼 (복잡한 쿼리용)
|
||||||
|
protected async Task<IEnumerable<T>> QueryAsync(string sql, object? param = null)
|
||||||
|
{
|
||||||
|
IDbConnection conn = CreateConnection();
|
||||||
|
return await conn.QueryAsync<T>(sql, param);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async Task<T?> QueryFirstOrDefaultAsync(string sql, object? param = null)
|
||||||
|
{
|
||||||
|
IDbConnection conn = CreateConnection();
|
||||||
|
return await conn.QueryFirstOrDefaultAsync<T>(sql, param);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async Task<int> ExecuteAsync(string sql, object? param = null)
|
||||||
|
{
|
||||||
|
IDbConnection conn = CreateConnection();
|
||||||
|
return await conn.ExecuteAsync(sql, param);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 트랜잭션 헬퍼
|
||||||
|
protected async Task<TResult> WithTransactionAsync<TResult>(Func<IDbConnection, IDbTransaction, Task<TResult>> action)
|
||||||
|
{
|
||||||
|
IDbConnection conn = CreateConnection();
|
||||||
|
conn.Open();
|
||||||
|
IDbTransaction tx = conn.BeginTransaction();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
TResult result = await action(conn, tx);
|
||||||
|
tx.Commit();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
tx.Rollback();
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,7 +8,11 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Dapper" Version="2.1.66" />
|
||||||
|
<PackageReference Include="Dapper.Contrib" Version="2.0.78" />
|
||||||
<PackageReference Include="LiteNetLib" Version="2.0.2" />
|
<PackageReference Include="LiteNetLib" Version="2.0.2" />
|
||||||
|
<PackageReference Include="MySql.Data" Version="9.6.0" />
|
||||||
|
<PackageReference Include="MySqlConnector" Version="2.5.0" />
|
||||||
<PackageReference Include="protobuf-net" Version="3.2.56" />
|
<PackageReference Include="protobuf-net" Version="3.2.56" />
|
||||||
<PackageReference Include="Serilog" Version="4.3.1" />
|
<PackageReference Include="Serilog" Version="4.3.1" />
|
||||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.1.1" />
|
<PackageReference Include="Serilog.Sinks.Console" Version="6.1.1" />
|
||||||
|
|||||||
73
ReadMe.md
Normal file
73
ReadMe.md
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
# DB 사용법
|
||||||
|
|
||||||
|
## 구조
|
||||||
|
```
|
||||||
|
MMOServer (실행 프로젝트 Test파일들은 임시용)
|
||||||
|
├── RDB/
|
||||||
|
├──── Handlers/
|
||||||
|
│ └──── TestHandler.cs
|
||||||
|
├──── Models/
|
||||||
|
│ └──── TestM.cs
|
||||||
|
├──── Repositories/
|
||||||
|
│ └──── TestRepository.cs
|
||||||
|
└──── Services/
|
||||||
|
└──── TestService.cs
|
||||||
|
|
||||||
|
ServerLib (DLL)
|
||||||
|
├── RDB/
|
||||||
|
├──── Database/
|
||||||
|
│ └── DbConnectionFactory.cs
|
||||||
|
├──── Handlers/
|
||||||
|
│ ├── HandlerHelper.cs
|
||||||
|
│ └── Response.cs
|
||||||
|
└──── Repositories/
|
||||||
|
└── ARepository.cs
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 데이터 흐름
|
||||||
|
|
||||||
|
```
|
||||||
|
호출부 (Packet 등)
|
||||||
|
↓ 파라미터 전달
|
||||||
|
Handler → Service 호출 + 응답 포맷팅 (string)
|
||||||
|
↓
|
||||||
|
Service → 비즈니스 로직, 유효성 검사, 예외 throw
|
||||||
|
↓
|
||||||
|
Repository → DB 접근 (TestM? / bool / long 반환)
|
||||||
|
↓
|
||||||
|
MySQL
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 반환 타입 정리
|
||||||
|
|
||||||
|
| 레이어 | 반환 타입 |
|
||||||
|
|--------|---------------------------------------------------|
|
||||||
|
| Repository | `class?` / `IEnumerable<class>` / `long` / `bool` |
|
||||||
|
| Service | 동일 (그대로 올림) |
|
||||||
|
| Handler | `string` (JSON 포장) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 새 테이블 추가 시
|
||||||
|
|
||||||
|
1. `MMOServer/RDB/Models/` 에 Model 추가
|
||||||
|
2. `MMOServer/RDB/Repositories/` 에 Repository 추가 (ARepository 상속)
|
||||||
|
3. `MMOServer/RDB/Services/` 에 Service 추가
|
||||||
|
4. `MMOServer/RDB/Handlers/` 에 Handler 추가
|
||||||
|
|
||||||
|
**ServerLib (DLL) 은 건드릴 필요 없음.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Connection Pool 설정(아직 구성 안됨)
|
||||||
|
|
||||||
|
```
|
||||||
|
Server=localhost;Database=mydb;User=root;Password=1234;
|
||||||
|
Pooling=true;MinimumPoolSize=5;MaximumPoolSize=100;
|
||||||
|
ConnectionTimeout=30;ConnectionIdleTimeout=180;
|
||||||
|
```
|
||||||
|
|
||||||
Reference in New Issue
Block a user