beeta/backend/src/services/RedisHelper.cpp

311 lines
9 KiB
C++
Raw Normal View History

2025-08-03 21:53:15 -04:00
#include "RedisHelper.h"
#include <chrono>
#include <cstdlib>
#include <thread>
#include <algorithm>
#include <cstring>
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<std::mutex> 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<sw::redis::Redis>(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<void()> 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<void(return_type)> callback) { \
executeAsync<return_type>( \
[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<void(bool)> callback) {
executeAsync<bool>(
[this, key, value, ttlSeconds]() {
_redis->setex(key, ttlSeconds, value);
return true;
},
std::move(callback)
);
}
void RedisHelper::getAsync(const std::string &key,
std::function<void(sw::redis::OptionalString)> callback) {
executeAsync<sw::redis::OptionalString>(
[this, key]() {
return _redis->get(key);
},
std::move(callback)
);
}
void RedisHelper::delAsync(const std::string &key,
std::function<void(bool)> callback) {
executeAsync<bool>(
[this, key]() {
return _redis->del(key) > 0;
},
std::move(callback)
);
}
void RedisHelper::saddAsync(const std::string &setName,
const std::string &value,
std::function<void(bool)> callback) {
executeAsync<bool>(
[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<void(bool)> callback) {
executeAsync<bool>(
[this, setName, value]() {
return _redis->srem(setName, value) > 0;
},
std::move(callback)
);
}
void RedisHelper::smembersAsync(const std::string &setName,
std::function<void(std::vector<std::string>)> callback) {
executeAsync<std::vector<std::string>>(
[this, setName]() {
std::vector<std::string> members;
_redis->smembers(setName, std::back_inserter(members));
return members;
},
std::move(callback)
);
}
void RedisHelper::keysAsync(const std::string &pattern,
std::function<void(std::vector<std::string>)> callback) {
executeAsync<std::vector<std::string>>(
[this, pattern]() {
std::vector<std::string> keys;
_redis->keys(pattern, std::back_inserter(keys));
return keys;
},
std::move(callback)
);
}
void RedisHelper::expireAsync(const std::string &key,
long ttlSeconds,
std::function<void(bool)> callback) {
executeAsync<bool>(
[this, key, ttlSeconds]() {
return _redis->expire(key, ttlSeconds);
},
std::move(callback)
);
}
// Sync versions for compatibility
std::unique_ptr<sw::redis::Redis> 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<sw::redis::Redis>(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<void(bool, const std::string&)> 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