146 lines
4.7 KiB
C++
146 lines
4.7 KiB
C++
#include "DatabaseManager.h"
|
|
#include "Logger.h"
|
|
#include <boost/asio/bind_executor.hpp>
|
|
#include <boost/asio/use_awaitable.hpp>
|
|
#include <boost/mysql.hpp>
|
|
|
|
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) {
|
|
try {
|
|
strand_ = std::make_unique<
|
|
boost::asio::strand<boost::asio::io_context::executor_type>>(
|
|
io_context.get_executor());
|
|
|
|
boost::asio::ip::tcp::resolver resolver(io_context.get_executor());
|
|
auto endpoints = resolver.resolve(host, std::to_string(port));
|
|
|
|
boost::mysql::handshake_params params(user, password, db);
|
|
static boost::asio::ssl::context ssl_ctx(
|
|
boost::asio::ssl::context::tlsv12_client);
|
|
|
|
conn_ = std::make_unique<boost::mysql::tcp_ssl_connection>(
|
|
io_context.get_executor(), ssl_ctx);
|
|
conn_->connect(*endpoints.begin(), params);
|
|
|
|
Logger::Log("Database 연결 성공 (AsyncLock 적용): ", host, ":", port);
|
|
return true;
|
|
} catch (const std::exception &e) {
|
|
Logger::Log("Database 연결 실패: ", e.what());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// 비동기 락 획득 (이미 락이 걸려있으면 대기열에 들어감)
|
|
boost::asio::awaitable<void> DatabaseManager::Lock() {
|
|
auto executor = co_await boost::asio::this_coro::executor;
|
|
|
|
co_await boost::asio::post(
|
|
boost::asio::bind_executor(*strand_, boost::asio::use_awaitable));
|
|
|
|
if (!is_locked_) {
|
|
is_locked_ = true;
|
|
co_return;
|
|
}
|
|
|
|
// 이미 잠겨있으면 타이머를 만들어서 대기열에 추가 (비동기 대기)
|
|
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 (...) {
|
|
// 타이머 취소(Unlock에서 호출함)되면 성공적으로 락을 획득한 것으로 간주
|
|
}
|
|
co_return;
|
|
}
|
|
|
|
// 비동기 락 해제 (대기열에 다음 사람이 있으면 깨워줌)
|
|
void DatabaseManager::Unlock() {
|
|
boost::asio::dispatch(*strand_, [this]() {
|
|
if (waiting_queue_.empty()) {
|
|
is_locked_ = false;
|
|
} else {
|
|
auto next = waiting_queue_.front();
|
|
waiting_queue_.pop();
|
|
next->cancel();
|
|
}
|
|
});
|
|
}
|
|
|
|
boost::asio::awaitable<DatabaseManager::UserData>
|
|
DatabaseManager::LoadUser(std::string nickname) {
|
|
co_await Lock();
|
|
|
|
try {
|
|
if (!conn_) {
|
|
Unlock();
|
|
co_return UserData{nickname, 10000, 0};
|
|
}
|
|
|
|
boost::mysql::results result;
|
|
boost::mysql::statement stmt = co_await conn_->async_prepare_statement(
|
|
"SELECT username, gold, 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);
|
|
Unlock();
|
|
co_return UserData{nickname, 10000, 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();
|
|
Unlock();
|
|
co_return data;
|
|
}
|
|
} catch (const std::exception &e) {
|
|
Logger::Log("LoadUser 에러: ", e.what());
|
|
Unlock();
|
|
co_return UserData{nickname, 10000, 0};
|
|
}
|
|
}
|
|
|
|
boost::asio::awaitable<void> DatabaseManager::SaveUser(std::string nickname,
|
|
uint64_t gold,
|
|
uint32_t swordLevel) {
|
|
co_await Lock();
|
|
|
|
try {
|
|
if (!conn_) {
|
|
Unlock();
|
|
co_return;
|
|
}
|
|
|
|
boost::mysql::results result;
|
|
boost::mysql::statement stmt = co_await conn_->async_prepare_statement(
|
|
"UPDATE users SET gold = ?, sword_level = ? WHERE username = ?",
|
|
boost::asio::use_awaitable);
|
|
co_await conn_->async_execute(
|
|
stmt.bind(gold, (int64_t)swordLevel, nickname), result,
|
|
boost::asio::use_awaitable);
|
|
} catch (const std::exception &e) {
|
|
Logger::Log("SaveUser 에러: ", e.what());
|
|
}
|
|
|
|
Unlock();
|
|
co_return;
|
|
}
|