#include "DatabaseManager.h" #include "Logger.h" #include #include #include DatabaseManager::PooledConnection::PooledConnection( DatabaseManager &manager, std::unique_ptr conn) : manager_(manager), conn_(std::move(conn)) {} DatabaseManager::PooledConnection::~PooledConnection() { if (conn_) manager_.ReturnConnection(std::move(conn_)); } boost::mysql::tcp_ssl_connection &DatabaseManager::PooledConnection::Get() { return *conn_; } boost::mysql::tcp_ssl_connection * DatabaseManager::PooledConnection::operator->() { return conn_.get(); } DatabaseManager &DatabaseManager::GetInstance() { static DatabaseManager instance; return instance; } bool DatabaseManager::Init(boost::asio::io_context &io_context, const std::string &host, uint16_t port, const std::string &user, const std::string &password, const std::string &db) { host_ = host; port_ = port; user_ = user; password_ = password; db_ = db; strand_ = std::make_unique< boost::asio::strand>( io_context.get_executor()); Logger::Log("DatabaseManager Init 성공 (Connection Pool 준비됨): ", host, ":", port); return true; } boost::asio::awaitable> DatabaseManager::GetConnection() { auto executor = co_await boost::asio::this_coro::executor; co_await boost::asio::post( boost::asio::bind_executor(*strand_, boost::asio::use_awaitable)); std::unique_ptr conn; if (!available_conns_.empty()) { // 가져오기 conn = std::move(available_conns_.back()); available_conns_.pop_back(); } else if (current_total_conns_ < max_pool_size_) { // 생성 current_total_conns_++; try { static boost::asio::ssl::context ssl_ctx( boost::asio::ssl::context::tlsv12_client); conn = std::make_unique(executor, ssl_ctx); boost::asio::ip::tcp::resolver resolver(executor); auto endpoints = resolver.resolve(host_, std::to_string(port_)); boost::mysql::handshake_params params(user_, password_, db_); co_await conn->async_connect(*endpoints.begin(), params, boost::asio::use_awaitable); Logger::Log("새로운 DB 커넥션 생성됨. 현재 총 개수: ", current_total_conns_); } catch (const std::exception &e) { current_total_conns_--; Logger::Log("커넥션 생성 실패: ", e.what()); throw; } } else { // 대기 auto timer = std::make_shared( executor, std::chrono::steady_clock::time_point::max()); waiting_queue_.push(timer); try { co_await timer->async_wait(boost::asio::use_awaitable); } catch (...) { } co_return co_await GetConnection(); } co_return std::make_unique(*this, std::move(conn)); } void DatabaseManager::ReturnConnection( std::unique_ptr conn) { boost::asio::dispatch(*strand_, [this, conn = std::move(conn)]() mutable { if (!waiting_queue_.empty()) { // 대기 중이면 넣어주기 available_conns_.push_back(std::move(conn)); auto next = waiting_queue_.front(); waiting_queue_.pop(); next->cancel(); } else { // 없으면 보관 available_conns_.push_back(std::move(conn)); } }); } boost::asio::awaitable DatabaseManager::LoadUser(std::string nickname) { try { auto conn_ptr = co_await GetConnection(); auto &conn = conn_ptr->Get(); boost::mysql::results result; boost::mysql::statement stmt = co_await conn.async_prepare_statement( "SELECT username, gold, sword_level, max_sword_level FROM users WHERE " "username = ?", boost::asio::use_awaitable); co_await conn.async_execute(stmt.bind(nickname), result, boost::asio::use_awaitable); if (result.rows().empty()) { boost::mysql::statement ins_stmt = co_await conn.async_prepare_statement( "INSERT INTO users (username, gold, sword_level) VALUES (?, 10000, " "0)", boost::asio::use_awaitable); co_await conn.async_execute(ins_stmt.bind(nickname), result, boost::asio::use_awaitable); Logger::Log("새로운 유저 탄생 (DB): ", nickname); co_return UserData{nickname, 10000, 0, 0}; } else { auto row = result.rows().at(0); UserData data; data.nickname = row.at(0).as_string(); data.gold = row.at(1).as_uint64(); data.swordLevel = (uint32_t)row.at(2).as_int64(); data.maxSwordLevel = (uint32_t)row.at(3).as_int64(); co_return data; } } catch (const std::exception &e) { Logger::Log("LoadUser 에러: ", e.what()); co_return UserData{nickname, 10000, 0, 0}; } } boost::asio::awaitable DatabaseManager::SaveUser(std::string nickname, uint64_t gold, uint32_t swordLevel) { try { auto conn_ptr = co_await GetConnection(); auto &conn = conn_ptr->Get(); boost::mysql::results result; boost::mysql::statement stmt = co_await conn.async_prepare_statement( "UPDATE users SET gold = ?, sword_level = ?, max_sword_level = " "GREATEST(max_sword_level, ?) WHERE username = ?", boost::asio::use_awaitable); co_await conn.async_execute( stmt.bind(gold, (int64_t)swordLevel, (int64_t)swordLevel, nickname), result, boost::asio::use_awaitable); } catch (const std::exception &e) { Logger::Log("SaveUser 에러: ", e.what()); } co_return; } boost::asio::awaitable DatabaseManager::GetRanking(int type, std::string nickname) { RankingResult res; res.myRank = -1; res.myValue = 0; try { auto conn_ptr = co_await GetConnection(); auto &conn = conn_ptr->Get(); std::string columnName = (type == 0) ? "max_sword_level" : "gold"; boost::mysql::results topResult; std::string topQuery = "SELECT username, " + columnName + " FROM users ORDER BY " + columnName + " DESC LIMIT 10"; co_await conn.async_execute(topQuery, topResult, boost::asio::use_awaitable); uint32_t rank = 1; for (auto row : topResult.rows()) { RankingResult::Entry entry; entry.rank = rank++; entry.nickname = row.at(0).as_string(); if (type == 0) { entry.value = (uint64_t)row.at(1).as_int64(); } else { entry.value = row.at(1).as_uint64(); } res.topEntries.push_back(entry); } boost::mysql::results myResult; std::string myQuery = "SELECT " + columnName + ", (SELECT COUNT(*) + 1 FROM users WHERE " + columnName + " > u." + columnName + ") FROM users u WHERE username = ?"; boost::mysql::statement stmt = co_await conn.async_prepare_statement( myQuery, boost::asio::use_awaitable); co_await conn.async_execute(stmt.bind(nickname), myResult, boost::asio::use_awaitable); if (!myResult.rows().empty()) { auto row = myResult.rows().at(0); if (type == 0) { res.myValue = (uint64_t)row.at(0).as_int64(); } else { res.myValue = row.at(0).as_uint64(); } res.myRank = (int32_t)row.at(1).as_int64(); } } catch (const std::exception &e) { Logger::Log("GetRanking 에러: ", e.what()); } co_return res; }