233 lines
7.7 KiB
C++
233 lines
7.7 KiB
C++
#include "DatabaseManager.h"
|
|
#include "Logger.h"
|
|
#include <boost/asio/bind_executor.hpp>
|
|
#include <boost/asio/post.hpp>
|
|
#include <boost/asio/use_awaitable.hpp>
|
|
|
|
DatabaseManager::PooledConnection::PooledConnection(
|
|
DatabaseManager &manager,
|
|
std::unique_ptr<boost::mysql::tcp_ssl_connection> 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<boost::asio::io_context::executor_type>>(
|
|
io_context.get_executor());
|
|
|
|
Logger::Log("DatabaseManager Init 성공 (Connection Pool 준비됨): ", host, ":",
|
|
port);
|
|
return true;
|
|
}
|
|
|
|
boost::asio::awaitable<std::unique_ptr<DatabaseManager::PooledConnection>>
|
|
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<boost::mysql::tcp_ssl_connection> 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<boost::mysql::tcp_ssl_connection>(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<boost::asio::steady_timer>(
|
|
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<PooledConnection>(*this, std::move(conn));
|
|
}
|
|
|
|
void DatabaseManager::ReturnConnection(
|
|
std::unique_ptr<boost::mysql::tcp_ssl_connection> 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::UserData>
|
|
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<void> 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::RankingResult>
|
|
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;
|
|
}
|