311 lines
9 KiB
C++
311 lines
9 KiB
C++
|
|
#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
|