前言
最近手头上接到一个临时任务,使用C++或者Nodejs编写程序,从redis服务器中获取100个站点的keys(env:hbxd:421000401:*),获取站点编码,然后根据站点编码如421000401,遍历获取前缀为env:hbxd:421000401的所有该站点的keys(比如env:hbxd:421000401:20200519:101025),然后按照某种格式生成这100个站点的数据文件(txt格式),然后再使用bcp命令将数据文件导入到对应的SQLServer数据库中。
使用hiredis封装的Redis客户端类
// RedisClient.h 头文件
#pragma once
#include <type.h>
#include <my_log.h>
#include <Locker.h>
class RedisClient : public my_log
{
public:
RedisClient(sig_log& lg);
~RedisClient();
void set_connect_info(const String& host, const uint16 port, const String& password = "", const uint16 index = 0);
int connect();
bool SET(const String& key, const String& value);
bool SETEX(const String& key, const String& value, const uint32 seconds);
bool HSET(const String& key, const String& field, const String& value);
bool EXPIRE(const String& key, int32 seconds);
bool KEYS(StringArray& _return, const String& pattern);
bool GET(String& _return, const String& key);
bool HGETALL(StringMap& _return, const String& key);
bool DEL(const String& key);
bool DEL(const StringArray& keys);
bool HDEL(const String& key, const String& field);
bool HDEL(const String& key, const StringArray& fields);
protected:
bool AUTH(struct redisContext* ctx, const String& password);
bool SELECT(struct redisContext* ctx, const int32 index);
bool PING(struct redisContext* ctx);
bool refresh();
private:
struct redisContext* redis_ctx_ = nullptr;
RWMutex mutex_;
String host_ = "localhost";
uint16 port_ = 6379;
String password_;
uint16 index_ = 0;
};
// RedisClient.cpp 实现文件
#include "RedisClient.h"
#include <hiredis/hiredis.h>
#ifdef WIN32
#pragma comment(lib, "hiredis.lib")
#endif
#include <func.h>
RedisClient::RedisClient(sig_log& lg)
: my_log(lg)
{
}
RedisClient::~RedisClient()
{
}
void RedisClient::set_connect_info(const String& host, const uint16 port, const String& password /*= ""*/, const uint16 index /*= 0*/)
{
host_ = host;
port_ = port;
password_ = password;
index_ = index;
}
int RedisClient::connect()
{
redis_ctx_ = redisConnect(host_.c_str(), port_);
if (!redis_ctx_) {
return -2;
}
if (redis_ctx_->err != REDIS_OK) {
EmitLog(error, "Redis Connect: %s\n", redis_ctx_->errstr);
goto failed;
}
if (redisEnableKeepAlive(redis_ctx_) != REDIS_OK) {
EmitLog(error, "redisEnableKeepAlive: %s\n", redis_ctx_->errstr);
goto failed;
}
if (password_.size() && !AUTH(redis_ctx_, password_)) {
EmitLog(error, "AUTH: %s\n", redis_ctx_->errstr);
goto failed;
}
if (!PING(redis_ctx_)) {
EmitLog(error, "PING: %s\n", redis_ctx_->errstr);
goto failed;
}
if (!SELECT(redis_ctx_, index_)) {
EmitLog(error, "SELECT: %s\n", redis_ctx_->errstr);
goto failed;
}
return 0;
failed:
redisFree(redis_ctx_);
redis_ctx_ = nullptr;
return -1;
}
bool RedisClient::refresh()
{
if (!redis_ctx_) {
return connect() == 0;
}
if (password_.size() && !AUTH(redis_ctx_, password_)) {
EmitLog(error, "AUTH: %s\n", redis_ctx_->errstr);
goto failed;
}
if (!PING(redis_ctx_)) {
EmitLog(error, "PING: %s\n", redis_ctx_->errstr);
goto failed;
}
if (!SELECT(redis_ctx_, index_)) {
EmitLog(error, "SELECT: %s\n", redis_ctx_->errstr);
goto failed;
}
return true;
failed:
redisFree(redis_ctx_);
redis_ctx_ = nullptr;
return false;
}
bool RedisClient::PING(struct redisContext* ctx)
{
bool rc = false;
redisReply* reply = (redisReply *)redisCommand(ctx, "PING");
if (reply) {
if (reply->type == REDIS_REPLY_ERROR) {
rc = false;
EmitLog(error, "PING: %s\n", reply->str);
}
else if (reply->type == REDIS_REPLY_STATUS) {
rc = (strcmp(reply->str, "PONG") == 0);
}
}
freeReplyObject(reply);
return rc;
}
bool RedisClient::AUTH(struct redisContext* ctx, const String & password)
{
bool rc = false;
redisReply* reply = (redisReply *)redisCommand(ctx, "AUTH %s", password.c_str());
if (reply) {
if (reply->type == REDIS_REPLY_ERROR) {
rc = false;
EmitLog(error, "AUTH: %s\n", reply->str);
}
else if (reply->type == REDIS_REPLY_STATUS) {
rc = (strcmp(reply->str, "OK") == 0);
}
}
freeReplyObject(reply);
return rc;
}
bool RedisClient::SELECT(struct redisContext* ctx, const int32 index)
{
bool rc = false;
redisReply* reply = (redisReply *)redisCommand(ctx, "SELECT %d", index);
if (reply) {
if (reply->type == REDIS_REPLY_ERROR) {
rc = false;
EmitLog(error, "SELECT: %s\n", reply->str);
}
else if (reply->type == REDIS_REPLY_STATUS) {
rc = (strcmp(reply->str, "OK") == 0);
}
}
freeReplyObject(reply);
return rc;
}
bool RedisClient::SET(const String& key, const String& value)
{
WriteLocker locker(mutex_);
bool rc = false;
redisReply* reply = nullptr;
int retry_count = 3;
while (!reply && retry_count) {
if (redis_ctx_) {
reply = (redisReply *)redisCommand(redis_ctx_, "SET %s %s", key.c_str(), value.c_str());
}
if (reply) {
if (reply->type == REDIS_REPLY_ERROR) {
rc = false;
EmitLog(error, "SET: %s\n", reply->str);
}
else if (reply->type == REDIS_REPLY_INTEGER) {
rc = (reply->integer == 1);
}
}
else if (!redis_ctx_ || redis_ctx_->err != REDIS_OK) {
refresh();
retry_count--;
}
freeReplyObject(reply);
}
return rc;
}
bool RedisClient::SETEX(const String& key, const String& value, const uint32 seconds)
{
WriteLocker locker(mutex_);
bool rc = false;
redisReply* reply = nullptr;
int retry_count = 3;
while (!reply && retry_count) {
if (redis_ctx_) {
reply = (redisReply *)redisCommand(redis_ctx_, "SETEX %s %d %s", key.c_str(), seconds, value.c_str());
}
if (reply) {
if (reply->type == REDIS_REPLY_ERROR) {
rc = false;
EmitLog(error, "SETEX: %s\n", reply->str);
}
else if (reply->type == REDIS_REPLY_INTEGER) {
rc = (reply->integer == 1);
}
}
else if (!redis_ctx_ || redis_ctx_->err != REDIS_OK) {
refresh();
retry_count--;
}
freeReplyObject(reply);
}
return rc;
}
bool RedisClient::HSET(const String & key, const String & field, const String & value)
{
WriteLocker locker(mutex_);
bool rc = false;
redisReply* reply = nullptr;
int retry_count = 3;
while (!reply && retry_count) {
if (redis_ctx_) {
reply = (redisReply *)redisCommand(redis_ctx_, "HSET %s %s %s", key.c_str(), field.c_str(), value.c_str());
}
if (reply) {
if (reply->type == REDIS_REPLY_ERROR) {
rc = false;
EmitLog(error, "HSET: %s\n", reply->str);
}
else if (reply->type == REDIS_REPLY_INTEGER) {
rc = (reply->integer == 1);
}
}
else if (!redis_ctx_ || redis_ctx_->err != REDIS_OK) {
refresh();
retry_count--;
}
freeReplyObject(reply);
}
return rc;
}
bool RedisClient::EXPIRE(const String & key, int32 seconds)
{
WriteLocker locker(mutex_);
bool rc = false;
redisReply* reply = nullptr;
int retry_count = 3;
while (!reply && retry_count) {
if (redis_ctx_) {
reply = (redisReply *)redisCommand(redis_ctx_, "EXPIRE %s %d", key.c_str(), seconds);
}
if (reply) {
if (reply->type == REDIS_REPLY_ERROR) {
rc = false;
EmitLog(error, "EXPIRE: %s\n", reply->str);
}
else if (reply->type == REDIS_REPLY_INTEGER) {
rc = (reply->integer == 1);
}
}
else if (!redis_ctx_ || redis_ctx_->err != REDIS_OK) {
refresh();
retry_count--;
}
freeReplyObject(reply);
}
return rc;
}
bool RedisClient::KEYS(StringArray & _return, const String & pattern)
{
WriteLocker locker(mutex_);
bool rc = false;
redisReply* reply = nullptr;
int retry_count = 3;
while (!reply && retry_count) {
if (redis_ctx_) {
reply = (redisReply *)redisCommand(redis_ctx_, "KEYS %s", pattern.c_str());
}
if (reply) {
if (reply->type == REDIS_REPLY_ERROR) {
rc = false;
EmitLog(error, "KEYS: %s\n", reply->str);
}
else if (reply->type == REDIS_REPLY_ARRAY) {
for (size_t i = 0; i < reply->elements; i++) {
_return.push_back(reply->element[i]->str);
}
rc = true;
}
}
else if (!redis_ctx_ || redis_ctx_->err != REDIS_OK) {
refresh();
retry_count--;
}
freeReplyObject(reply);
}
return rc;
}
bool RedisClient::GET(String& _return, const String& key)
{
WriteLocker locker(mutex_);
bool rc = false;
redisReply* reply = nullptr;
int retry_count = 3;
while (!reply && retry_count) {
if (redis_ctx_) {
reply = (redisReply *)redisCommand(redis_ctx_, "GET %s", key.c_str());
}
if (reply) {
if (reply->type == REDIS_REPLY_ERROR) {
rc = false;
EmitLog(error, "GET: %s\n", reply->str);
}
else if (reply->type == REDIS_REPLY_STRING) {
_return = reply->str;
rc = true;
}
else if (reply->type == REDIS_REPLY_INTEGER) {
_return = std::to_string(reply->integer);
rc = true;
}
}
else if (!redis_ctx_ || redis_ctx_->err != REDIS_OK) {
refresh();
retry_count--;
}
freeReplyObject(reply);
}
return rc;
}
bool RedisClient::HGETALL(StringMap & _return, const String & key)
{
WriteLocker locker(mutex_);
bool rc = false;
redisReply* reply = nullptr;
int retry_count = 3;
while (!reply && retry_count) {
if (redis_ctx_) {
reply = (redisReply *)redisCommand(redis_ctx_, "HGETALL %s", key.c_str());
}
if (reply) {
if (reply->type == REDIS_REPLY_ERROR) {
rc = false;
EmitLog(error, "HGETALL: %s\n", reply->str);
}
else if (reply->type == REDIS_REPLY_ARRAY) {
for (size_t i = 0; i < reply->elements / 2; i++) {
_return.insert(std::make_pair(reply->element[i * 2]->str, reply->element[i * 2 + 1]->str));
}
rc = true;
}
}
else if (!redis_ctx_ || redis_ctx_->err != REDIS_OK) {
refresh();
retry_count--;
}
freeReplyObject(reply);
}
return rc;
}
bool RedisClient::DEL(const String & key)
{
WriteLocker locker(mutex_);
bool rc = false;
redisReply* reply = nullptr;
int retry_count = 3;
while (!reply && retry_count) {
if (redis_ctx_) {
reply = (redisReply *)redisCommand(redis_ctx_, "DEL %s", key.c_str());
}
if (reply) {
if (reply->type == REDIS_REPLY_ERROR) {
rc = false;
EmitLog(error, "DEL: %s\n", reply->str);
}
else if (reply->type == REDIS_REPLY_INTEGER) {
rc = (reply->integer == 1);
}
}
else if (!redis_ctx_ || redis_ctx_->err != REDIS_OK) {
refresh();
retry_count--;
}
freeReplyObject(reply);
}
return rc;
}
bool RedisClient::DEL(const StringArray & keys)
{
const String arrstr = Math::Tools::join(keys, " ");
return DEL(arrstr);
}
bool RedisClient::HDEL(const String & key, const String & field)
{
WriteLocker locker(mutex_);
bool rc = false;
redisReply* reply = nullptr;
int retry_count = 3;
while (!reply && retry_count) {
if (redis_ctx_) {
reply = (redisReply *)redisCommand(redis_ctx_, "HDEL %s %s", key.c_str(), field.c_str());
}
if (reply) {
if (reply->type == REDIS_REPLY_ERROR) {
rc = false;
EmitLog(error, "HDEL: %s\n", reply->str);
}
else if (reply->type == REDIS_REPLY_INTEGER) {
rc = (reply->integer == 1);
}
}
else if (!redis_ctx_ || redis_ctx_->err != REDIS_OK) {
refresh();
retry_count--;
}
freeReplyObject(reply);
}
return rc;
}
bool RedisClient::HDEL(const String & key, const StringArray & fields)
{
for (auto& field : fields)
{
HDEL(key, field);
}
return true;
}
上述使用C++封装了hiredis库,实现常用的redis命令如
AUTH、SELECT、GET、HKEYS、HGETALL、DEL、HDEL等。
关于在Windows下使用hiredis可以参考hiredis在windows下的编译以及使用,去microsoft/hiredis下载windows版本的hiredis,然后使用对应的Visual Studio比如VS2017编译对应的hiredis.lib库,再导入到项目中使用。
main.cpp
// RedisToTxtApp.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include <type.h>
#include <mutex>
#include <func.h>
#include <unordered_map>
#include "RedisClient.h"
#include "ado/CustomADO.h"
// 查询站点映射表
StringMap g_stationMap;
// 从SQLServer数据库中查询UniqueCode->SStation的映射转换关系表
StringMap getStationMap(sig_log& lg)
{
StringMap mapResult;
// 连接SQLServer数据库
CCustomADO ado(lg);
ado.SetConnectInfo("192.19.0.9", "AirData", "sa", "123456");
if (!ado.OpenConnect())
{
return mapResult;
}
char sql[10240] = { 0 };
// 构造查询SQL脚本
sprintf(sql, "select SStation, UniqueCode from t_AllSStation;");
if (!ado.ExecuteProcess(sql, true))
{
return mapResult;
}
// 获取站点的所有站点的总个数
long recordSum = ado.GetRecordsetCount();
for (long i = 0; i < recordSum; ++i)
{
// 将查询到的站点信息保存到stbInfoItem中
String strStation;
String strUniqueCode;
// String strZDStationCode;
ado.GetFieldValue("SStation", strStation);
ado.GetFieldValue("UniqueCode", strUniqueCode);
// ado.GetFieldValue("ZDStationCode", strZDStationCode);
mapResult.insert({ strUniqueCode, strStation });
ado.MoveToNext();
}
return mapResult;
}
int main(int argc, char* argv[])
{
std::mutex mutex_;
sig_log log_;
log_.connect([&mutex_](const char* file, const char* func, long line, log_level level, bool need_send, const char* msg) {
std::lock_guard<std::mutex> locker(mutex_);
#ifdef _DEBUG
printf("filelog===>[%d]%s:%ld %s: %s\n", level, file, line, func, msg);
#else
char str[4096];
sprintf(str, "[%s][%d] %s\n", Math::Date::getnow().c_str(), level, msg);
printf("%s", str);
if (Math::File::mk_dirs("./log"))
{
if (level != log_level::info)
{
String logfile = "./log/error_" + Math::Date::getnow("%04d_%02d") + ".log";
FILE* f = fopen(logfile.c_str(), "a+");
if (f)
{
fwrite(str, 1, strlen(str), f);
fclose(f);
}
}
}
#endif
});
// 1.从SQLServer数据库中获取站点映射关系表
g_stationMap = getStationMap(log_);
// 2. 从redis中获取某个站点的keys
// keys env:hbxd:* 所有站点的站房数据
// keys env:hbxd:421000401:* uniquecode编码为421000401的站点的站房数据
RedisClient redis_(log_);
redis_.set_connect_info("192.19.0.9", 7001, "123456", 1);
redis_.connect();
// 获取包含所有站点站房数据的所有key
StringArray stationKeys;
// redis_.KEYS(stationKeys, "env:hbxd:421000401:*");
redis_.KEYS(stationKeys, "env:hbxd:*");
// 获取env:hbxd:* 中所有的站点编码
std::unordered_map<String, StringArray> strStcodeMap;
for (auto &key1 : stationKeys)
{
StringArray strKeysArr = Math::Tools::split(key1, ":");
String strStcode = strKeysArr[2];
// 在strStcodeVec中查询是否已经存在该站点
auto fi = strStcodeMap.find(strStcode);
if (fi == strStcodeMap.end()) {
strStcodeMap.insert(std::make_pair(strStcode, StringArray()));
}
strStcodeMap.at(strStcode).emplace_back(key1);
}
// 遍历所有站点,获取该站点env:hbxd:421000401:*下的所有keys,然后一个个站点生成txt监测数据
for (auto &i : strStcodeMap)
{
auto& stcode = i.first;
// 将站点编码转换成SStation即SS打头的站点编码格式
auto iter = g_stationMap.find(stcode);
if (iter == g_stationMap.end()){
continue;
}
String strConvertStcode = iter->second;
// 3.根据站点的key,获取对应的站点监测因子map数据
String strFileName = strConvertStcode.empty() ? "tmp.txt" : strConvertStcode + ".txt";
String strFolderPath = "./data";
Math::File::mk_dirs(strFolderPath.c_str());
String strFullPath = strFolderPath + "/" + strFileName;
FILE *fp = fopen(strFullPath.c_str(), "w+");
if (fp == NULL) {
std::cout << "打开文件失败!";
continue;
}
for (auto &key : i.second)
{
// std::cout << key << std::endl;
// 4.从对应的key中找到对应的站点监测数据,然后再转换成txt文件,每个站点生成一个(20200512-20200518)
StringMap monitorMap;
redis_.HGETALL(monitorMap, key);
// 从key中获取对应的站点编码,日期,时间
StringArray strKeysArr = Math::Tools::split(key, ":");
String strDate = strKeysArr[3];
String strTime = strKeysArr[4];
String strTimeTmp = strDate + strTime;
String strDateTime = Math::Date::convertfmt(strTimeTmp.c_str(),
"%04d%02d%02d%02d%02d%02d", "%04d-%02d-%02d %02d:%02d:%02d");
// 获取输入字符串时间时间戳,默认格式 2018-10-01 01:10:20, %04d-%02d-%02d
uint64 tt = Math::Date::getunix(strDateTime.c_str());
// 遍历monitorMap,按照该站点421000401,将每个监测因子数据写入对应的421000401.txt文件中
// 每行数据格式为:
// SS4201051\tEP121\t1589526600\t2020-05-15 15:10:00\t62.0\tN\tM03\r\n
String items;
for (auto &jt : monitorMap)
{
// std::cout << "filed:" << jt.first << ",value:" << jt.second << "\n";
// 解析得到的value(IA|10.01||EP117|),从中获取对应的监测因子的EPID如EP117和值10.01,还有标记位和单位
StringArray strArr = Math::Tools::split(jt.second, "|");
// 监测数值
double dVal;
Math::Tools::to_float(dVal, strArr[1]);
// 监测标记位
String strMark = strArr[2].empty() ? "N" : strArr[2];
// 监测因子 EPId
String strParamId = strArr[3];
// 监测单位
String strUnit = strArr[4].empty() ? " " : strArr[3];
char strLineBuf[1024] = { 0 };
sprintf(strLineBuf, "%s\t%s\t%lld\t%s\t%lf\t%s\t%s\r\n",
strConvertStcode.c_str(), strParamId.c_str(), tt,
strDateTime.c_str(), dVal, strMark.c_str(), strUnit.c_str());
items += strLineBuf;
}
if (fp && items.size() && fwrite(items.data(), sizeof(char), items.size(), fp) > 0) {
if (!redis_.DEL(key)) {
printf("%s删除失败", key.c_str());
}
}
}
if (fp) {
fclose(fp);
}
std::cout << "站点文件" << strFileName << "生成完毕!" << std::endl;
}
return 0;
}