#include "RedisHelper.h" #include #include #include #include #include namespace services { RedisHelper &RedisHelper::instance() { static RedisHelper inst; return inst; } RedisHelper::RedisHelper() : _initialized(false) { LOG_INFO << "RedisHelper created (connection will be established on first use)"; } RedisHelper::~RedisHelper() = default; void RedisHelper::ensureConnected() { if (_initialized) return; std::lock_guard lock(_initMutex); if (_initialized) return; // Double-check try { sw::redis::ConnectionOptions opts; opts.host = getRedisHost(); opts.port = getRedisPort(); const char* envPass = std::getenv("REDIS_PASS"); if (envPass && strlen(envPass) > 0) { opts.password = envPass; } opts.socket_timeout = std::chrono::milliseconds(1000); opts.connect_timeout = std::chrono::milliseconds(1000); LOG_INFO << "Connecting to Redis at " << opts.host << ":" << opts.port; _redis = std::make_unique(opts); _redis->ping(); _initialized = true; LOG_INFO << "Redis connection established successfully"; } catch (const sw::redis::Error& e) { LOG_ERROR << "Failed to connect to Redis: " << e.what(); throw; } } std::string RedisHelper::getRedisHost() const { const char* envHost = std::getenv("REDIS_HOST"); if (envHost) return std::string(envHost); try { const auto& config = drogon::app().getCustomConfig(); if (config.isMember("redis") && config["redis"].isMember("host")) { return config["redis"]["host"].asString(); } } catch (...) {} return "redis"; } int RedisHelper::getRedisPort() const { const char* envPort = std::getenv("REDIS_PORT"); if (envPort) { try { return std::stoi(envPort); } catch (...) {} } try { const auto& config = drogon::app().getCustomConfig(); if (config.isMember("redis") && config["redis"].isMember("port")) { return config["redis"]["port"].asInt(); } } catch (...) {} return 6379; } void RedisHelper::executeInThreadPool(std::function task) { auto loop = drogon::app().getLoop(); if (!loop) { LOG_ERROR << "Event loop not available, executing task synchronously"; try { task(); } catch (const std::exception& e) { LOG_ERROR << "Error executing task: " << e.what(); } return; } loop->queueInLoop([task = std::move(task)]() { std::thread([task]() { try { task(); } catch (const std::exception& e) { LOG_ERROR << "Error in thread pool task: " << e.what(); } }).detach(); }); } // Define a macro to generate async methods #define REDIS_ASYNC_IMPL(method, return_type, operation) \ void RedisHelper::method##Async(const std::string &key, \ std::function callback) { \ executeAsync( \ [this, key]() { \ return _redis->operation; \ }, \ std::move(callback) \ ); \ } // Specialized async methods using the template void RedisHelper::setexAsync(const std::string &key, const std::string &value, long ttlSeconds, std::function callback) { executeAsync( [this, key, value, ttlSeconds]() { _redis->setex(key, ttlSeconds, value); return true; }, std::move(callback) ); } void RedisHelper::getAsync(const std::string &key, std::function callback) { executeAsync( [this, key]() { return _redis->get(key); }, std::move(callback) ); } void RedisHelper::delAsync(const std::string &key, std::function callback) { executeAsync( [this, key]() { return _redis->del(key) > 0; }, std::move(callback) ); } void RedisHelper::saddAsync(const std::string &setName, const std::string &value, std::function callback) { executeAsync( [this, setName, value]() { return _redis->sadd(setName, value) > 0; }, std::move(callback) ); } void RedisHelper::sremAsync(const std::string &setName, const std::string &value, std::function callback) { executeAsync( [this, setName, value]() { return _redis->srem(setName, value) > 0; }, std::move(callback) ); } void RedisHelper::smembersAsync(const std::string &setName, std::function)> callback) { executeAsync>( [this, setName]() { std::vector members; _redis->smembers(setName, std::back_inserter(members)); return members; }, std::move(callback) ); } void RedisHelper::keysAsync(const std::string &pattern, std::function)> callback) { executeAsync>( [this, pattern]() { std::vector keys; _redis->keys(pattern, std::back_inserter(keys)); return keys; }, std::move(callback) ); } void RedisHelper::expireAsync(const std::string &key, long ttlSeconds, std::function callback) { executeAsync( [this, key, ttlSeconds]() { return _redis->expire(key, ttlSeconds); }, std::move(callback) ); } // Sync versions for compatibility std::unique_ptr RedisHelper::getConnection() { ensureConnected(); sw::redis::ConnectionOptions opts; opts.host = getRedisHost(); opts.port = getRedisPort(); const char* envPass = std::getenv("REDIS_PASS"); if (envPass && strlen(envPass) > 0) { opts.password = envPass; } opts.socket_timeout = std::chrono::milliseconds(200); opts.connect_timeout = std::chrono::milliseconds(200); return std::make_unique(opts); } bool RedisHelper::storeKey(const std::string &key, const std::string &value, int ttl) { try { ensureConnected(); if (ttl > 0) { _redis->setex(key, ttl, value); } else { _redis->set(key, value); } return true; } catch (const sw::redis::Error &e) { LOG_ERROR << "Redis SET error: " << e.what(); return false; } } std::string RedisHelper::getKey(const std::string &key) { try { ensureConnected(); auto val = _redis->get(key); return val.has_value() ? val.value() : ""; } catch (const sw::redis::Error &e) { LOG_ERROR << "Redis GET error: " << e.what(); return ""; } } bool RedisHelper::deleteKey(const std::string &key) { try { ensureConnected(); return _redis->del(key) > 0; } catch (const sw::redis::Error &e) { LOG_ERROR << "Redis DEL error: " << e.what(); return false; } } bool RedisHelper::addToSet(const std::string &setName, const std::string &value) { try { ensureConnected(); return _redis->sadd(setName, value) > 0; } catch (const sw::redis::Error &e) { LOG_ERROR << "Redis SADD error: " << e.what(); return false; } } bool RedisHelper::removeFromSet(const std::string &setName, const std::string &value) { try { ensureConnected(); return _redis->srem(setName, value) > 0; } catch (const sw::redis::Error &e) { LOG_ERROR << "Redis SREM error: " << e.what(); return false; } } // Deprecated command executor - simplified void RedisHelper::executeAsync(const std::string &command, std::function callback) { // For the single use case in the code (EXPIRE), handle it directly std::istringstream iss(command); std::string op, key; long ttl; iss >> op >> key >> ttl; if (op == "EXPIRE" || op == "expire") { expireAsync(key, ttl, [callback](bool success) { callback(success, success ? "1" : "0"); }); } else { if (auto loop = drogon::app().getLoop()) { loop->queueInLoop([callback]() { callback(false, "Unsupported command in executeAsync. Use specific async methods."); }); } else { callback(false, "Unsupported command in executeAsync. Use specific async methods."); } } } } // namespace services