///////////////////////////////////////////////////////////////////////////////////////////////// #include #include #include #include #include #include //流解析 #include //写去重的设备类型 #include //打开文件 #include #include #include #include #include #include #include #include #include #include #include #include #include ///////////////////////////////////////////////////////////////////////////////////////////////// #include "nlohmann/json.hpp" #include "curl/curl.h" #include "log4cplus/log4.h" //关键上送日志 #include "interface.h" //台账结构 #include "tinyxml2.h" #include "rocketmq.h" ///////////////////////////////////////////////////////////////////////////////////////////////// using namespace std; ///////////////////////////////////////////////////////////////////////////////////////////////// //进程标识 extern std::string subdir; extern int g_front_seg_index; extern int g_front_seg_num; extern unsigned int g_node_id; //前置程序类型(100-600) //初始化完成标识 extern int INITFLAG; //线程阻塞计数 extern uint32_t g_ontime_blocked_times; //台账锁 extern std::mutex ledgermtx; //队列 extern std::mutex queue_data_list_mutex; //queue发送数据锁 extern std::list queue_data_list; //queue发送数据链表 extern int three_secs_enabled; extern std::map xmlinfo_list;//保存所有型号对应的icd映射文件解析数据 extern XmlConfig xmlcfg;//星形接线xml节点解析的数据-默认映射文件解析数据 extern std::list topicList; //队列发送主题链表 extern XmlConfig xmlcfg2;//角型接线xml节点解析的数据-默认映射文件解析数据 extern std::list topicList2; //角型接线发送主题链表 extern std::map xmlinfo_list2;//保存所有型号角形接线对应的icd映射文件解析数据 //////////////////////////////////////////////////////////////////////////////////////////////////// //补招 std::list g_StatisticLackList; //日志补招结构类链表 std::mutex g_StatisticLackList_list_mutex; //补招队列数据锁 std::string DEFAULT_CONFIG_FN = "Default_Config.xml"; //默认映射文件 std::string LEDGER_UPDATE_FN = "LedgerUpdate.log"; //台账更新本地记录日志 //一个终端最大检测点数 const int MAX_CPUNO = 10; //角型接线标志,0不存在角形接线,1存在角形接线 int isdelta_flag = 0; //多前置flag:1为开启,0为关闭 int MULTIPLE_NODE_FLAG = 1; //终端台账数量配置 int IED_COUNT = 300; //默认300 //前置标志 std::string FRONT_INST = ""; std::string FRONT_IP = ""; //终端和监测点的状态筛选 std::string TERMINAL_STATUS = ""; std::string MONITOR_STATUS = ""; std::string ICD_FLAG = ""; //web接口 std::string WEB_DEVICE = ""; std::string WEB_ICD = ""; std::string WEB_EVENT = ""; std::string WEB_FILEUPLOAD = ""; std::string WEB_FILEDOWNLOAD = ""; ////////////////////////////////////////////////////////////////////////////mq配置 //备用 std::string BROKER_LIST = ""; //通用主题 std::string TOPIC_STAT = ""; std::string TOPIC_PST = ""; std::string TOPIC_PLT = ""; std::string TOPIC_EVENT = ""; std::string TOPIC_ALARM = ""; std::string TOPIC_SNG = ""; std::string TOPIC_RTDATA = ""; //通用tagkey std::string G_ROCKETMQ_TAG = "";//tag std::string G_ROCKETMQ_KEY = "";//key //生产者 std::string G_ROCKETMQ_PRODUCER = ""; //rocketmq producer std::string G_MQPRODUCER_IPPORT = ""; //rocketmq ip+port std::string G_MQPRODUCER_ACCESSKEY = ""; //rocketmq 认证 std::string G_MQPRODUCER_SECRETKEY = ""; //rocketmq 秘钥 //日志 std::string G_LOG_TOPIC = "";//topie std::string G_LOG_TAG = "";//tag std::string G_LOG_KEY = "";//key //终端连接 std::string G_CONNECT_TOPIC = "";//consumer topie std::string G_CONNECT_TAG = "";//consumer tag std::string G_CONNECT_KEY = "";//consumer key //心跳 std::string Heart_Beat_Topic = ""; std::string Heart_Beat_Tag = ""; std::string Heart_Beat_Key = ""; //消息响应 std::string Topic_Reply_Topic = ""; std::string Topic_Reply_Tag = ""; std::string Topic_Reply_Key = ""; //消费者 std::string G_ROCKETMQ_CONSUMER = "";//rocketmq consumer std::string G_MQCONSUMER_IPPORT = "";//consumer ip+port std::string G_MQCONSUMER_ACCESSKEY = ""; std::string G_MQCONSUMER_SECRETKEY = ""; std::string G_MQCONSUMER_CHANNEL = ""; //实时数据请求 std::string G_MQCONSUMER_TOPIC_RT = "";//consumer topie std::string G_MQCONSUMER_TAG_RT = "";//consumer tag std::string G_MQCONSUMER_KEY_RT = "";//consumer key //台账更新请求 std::string G_MQCONSUMER_TOPIC_UD = "";//consumer topie std::string G_MQCONSUMER_TAG_UD = "";//consumer tag std::string G_MQCONSUMER_KEY_UD = "";//consumer key //补招数据请求 std::string G_MQCONSUMER_TOPIC_RC = "";//consumer topie std::string G_MQCONSUMER_TAG_RC = "";//consumer tag std::string G_MQCONSUMER_KEY_RC = "";//consumer key //进程控制请求 std::string G_MQCONSUMER_TOPIC_SET = "";//consumer topie std::string G_MQCONSUMER_TAG_SET = "";//consumer tag std::string G_MQCONSUMER_KEY_SET = "";//consumer key //日志数据请求 std::string G_MQCONSUMER_TOPIC_LOG = "";//consumer topie std::string G_MQCONSUMER_TAG_LOG = "";//consumer tag std::string G_MQCONSUMER_KEY_LOG = "";//consumer key //测试用的主题 std::string G_ROCKETMQ_TOPIC_TEST = "";//topie std::string G_ROCKETMQ_TAG_TEST = "";//tag std::string G_ROCKETMQ_KEY_TEST = "";//key //测试相关配置 int G_TEST_FLAG = 0; int G_TEST_NUM = 0; int G_TEST_TYPE = 0; int TEST_PORT = 11000; //用于当前进程登录测试shell的端口 std::string G_TEST_LIST = ""; //测试用的发送实际数据的终端列表 std::vector TESTARRAY; //解析的列表数组 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////其他文件定义的函数引用声明 /////////////////////////////////////////////////////////////////////////////////////////////////////////////////当前文件函数声明 void create_ledger_log(trigger_update_xml_t* ledger_update_xml); void print_trigger_update_xml(const trigger_update_xml_t& trigger_update); ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////辅助函数 //去除字符串前后空格 static void trim(std::string& s) { auto not_space = [](int ch) { return !std::isspace(ch); }; s.erase(s.begin(), std::find_if(s.begin(), s.end(), not_space)); s.erase(std::find_if(s.rbegin(), s.rend(), not_space).base(), s.end()); } //判断空格 bool is_blank(const std::string& str) { for (std::string::const_iterator it = str.begin(); it != str.end(); ++it) { if (!std::isspace(*it)) { return false; } } return true; } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////配置文件读取 //将 G_TEST_LIST 按逗号分割,填充 TESTARRAY static void parseTestList() { TESTARRAY.clear(); std::istringstream iss(G_TEST_LIST); std::string token; while (std::getline(iss, token, ',')) { trim(token); if (!token.empty()) { TESTARRAY.push_back(token); } } } //简易映射方式解析配置文件文件 void loadConfig(const std::string& filename) { // 1. 构造“节.键 → 变量地址”映射表 std::unordered_map strMap; std::unordered_map intMap; // [Flag] strMap["Flag.FrontInst"] = &FRONT_INST; strMap["Flag.FrontIP"] = &FRONT_IP; // [Ledger] intMap["Ledger.IedCount"] = &IED_COUNT; strMap["Ledger.TerminalStatus"]= &TERMINAL_STATUS; strMap["Ledger.MonitorStatus"] = &MONITOR_STATUS; strMap["Ledger.IcdFlag"] = &ICD_FLAG; // [Http] strMap["Http.WebDevice"] = &WEB_DEVICE; strMap["Http.WebIcd"] = &WEB_ICD; strMap["Http.WebEvent"] = &WEB_EVENT; strMap["Http.WebFileupload"] = &WEB_FILEUPLOAD; strMap["Http.WebFiledownload"]= &WEB_FILEDOWNLOAD; // [Queue] strMap["Queue.BrokerList"] = &BROKER_LIST; strMap["Queue.RTDataTopic"] = &TOPIC_RTDATA; strMap["Queue.HisTopic"] = &TOPIC_STAT; strMap["Queue.PSTTopic"] = &TOPIC_PST; strMap["Queue.PLTTopic"] = &TOPIC_PLT; strMap["Queue.EventTopic"] = &TOPIC_EVENT; strMap["Queue.AlmTopic"] = &TOPIC_ALARM; strMap["Queue.SngTopic"] = &TOPIC_SNG; strMap["Queue.QUEUE_TAG"] = &G_ROCKETMQ_TAG; strMap["Queue.QUEUE_KEY"] = &G_ROCKETMQ_KEY; // [RocketMq] —— 生产者 strMap["RocketMq.producer"] = &G_ROCKETMQ_PRODUCER; strMap["RocketMq.Ipport"] = &G_MQPRODUCER_IPPORT; strMap["RocketMq.AccessKey"] = &G_MQPRODUCER_ACCESSKEY; strMap["RocketMq.SecretKey"] = &G_MQPRODUCER_SECRETKEY; strMap["RocketMq.LOGTopic"] = &G_LOG_TOPIC; strMap["RocketMq.LOGTag"] = &G_LOG_TAG; strMap["RocketMq.LOGKey"] = &G_LOG_KEY; strMap["RocketMq.CONNECTTopic"] = &G_CONNECT_TOPIC; strMap["RocketMq.CONNECTTag"] = &G_CONNECT_TAG; strMap["RocketMq.CONNECTKey"] = &G_CONNECT_KEY; strMap["RocketMq.Heart_Beat_Topic"] = &Heart_Beat_Topic; strMap["RocketMq.Heart_Beat_Tag"] = &Heart_Beat_Tag; strMap["RocketMq.Heart_Beat_Key"] = &Heart_Beat_Key; strMap["RocketMq.Topic_Reply_Topic"] = &Topic_Reply_Topic; strMap["RocketMq.Topic_Reply_Tag"] = &Topic_Reply_Tag; strMap["RocketMq.Topic_Reply_Key"] = &Topic_Reply_Key; // [RocketMq] —— 消费者 strMap["RocketMq.consumer"] = &G_ROCKETMQ_CONSUMER; strMap["RocketMq.ConsumerIpport"] = &G_MQCONSUMER_IPPORT; strMap["RocketMq.ConsumerAccessKey"] = &G_MQCONSUMER_ACCESSKEY; strMap["RocketMq.ConsumerSecretKey"] = &G_MQCONSUMER_SECRETKEY; strMap["RocketMq.ConsumerChannel"] = &G_MQCONSUMER_CHANNEL; strMap["RocketMq.ConsumerTopicRT"] = &G_MQCONSUMER_TOPIC_RT; strMap["RocketMq.ConsumerTagRT"] = &G_MQCONSUMER_TAG_RT; strMap["RocketMq.ConsumerKeyRT"] = &G_MQCONSUMER_KEY_RT; strMap["RocketMq.ConsumerTopicUD"] = &G_MQCONSUMER_TOPIC_UD; strMap["RocketMq.ConsumerTagUD"] = &G_MQCONSUMER_TAG_UD; strMap["RocketMq.ConsumerKeyUD"] = &G_MQCONSUMER_KEY_UD; strMap["RocketMq.ConsumerTopicRC"] = &G_MQCONSUMER_TOPIC_RC; strMap["RocketMq.ConsumerTagRC"] = &G_MQCONSUMER_TAG_RC; strMap["RocketMq.ConsumerKeyRC"] = &G_MQCONSUMER_KEY_RC; strMap["RocketMq.ConsumerTopicSET"] = &G_MQCONSUMER_TOPIC_SET; strMap["RocketMq.ConsumerTagSET"] = &G_MQCONSUMER_TAG_SET; strMap["RocketMq.ConsumerKeySET"] = &G_MQCONSUMER_KEY_SET; strMap["RocketMq.ConsumerTopicLOG"] = &G_MQCONSUMER_TOPIC_LOG; strMap["RocketMq.ConsumerTagLOG"] = &G_MQCONSUMER_TAG_LOG; strMap["RocketMq.ConsumerKeyLOG"] = &G_MQCONSUMER_KEY_LOG; strMap["RocketMq.Topic_Test"] = &G_ROCKETMQ_TOPIC_TEST; strMap["RocketMq.Tag_Test"] = &G_ROCKETMQ_TAG_TEST; strMap["RocketMq.Key_Test"] = &G_ROCKETMQ_KEY_TEST; intMap["RocketMq.Testflag"] = &G_TEST_FLAG; intMap["RocketMq.Testnum"] = &G_TEST_NUM; intMap["RocketMq.Testtype"] = &G_TEST_TYPE; intMap["RocketMq.TestPort"] = &TEST_PORT; strMap["RocketMq.TestList"] = &G_TEST_LIST; // 2. 打开并逐行解析 INI 文件 std::ifstream fin(filename); if (!fin.is_open()) { std::cerr << "无法打开配置文件: " << filename << "\n"; return; } std::string line; std::string currentSection; while (std::getline(fin, line)) { trim(line); if (line.empty() || line[0] == ';' || line[0] == '#') { continue; // 跳过空白或注释 } if (line.front() == '[' && line.back() == ']') { currentSection = line.substr(1, line.size() - 2); trim(currentSection); continue; } auto pos = line.find('='); if (pos == std::string::npos) { continue; } std::string key = line.substr(0, pos); std::string value = line.substr(pos + 1); trim(key); trim(value); // 去掉值两端双引号 if (value.size() >= 2 && value.front() == '"' && value.back() == '"') { value = value.substr(1, value.size() - 2); } // 拼出 "节.键" std::string fullKey = currentSection + "." + key; // 如果在字符串映射表里,就写入对应的全局 std::string auto sit = strMap.find(fullKey); if (sit != strMap.end()) { *(sit->second) = value; continue; } // 如果在整型映射表里,就 stoi 后写入对应的全局 int auto iit = intMap.find(fullKey); if (iit != intMap.end()) { try { *(iit->second) = std::stoi(value); } catch (...) { *(iit->second) = 0; } } } fin.close(); // 3. 将 G_TEST_LIST 拆分到 TESTARRAY parseTestList(); } //打印所有全局变量,名称对齐 void printConfig() { const int nameWidth = 30; // 变量名区域宽度 std::cout << "------- Loaded Configuration -------\n"; // 辅助 lambda 方便打印 auto printStr = [&](const std::string& name, const std::string& val) { std::cout << std::left << std::setw(nameWidth) << name << " = " << val << "\n"; }; auto printInt = [&](const std::string& name, int val) { std::cout << std::left << std::setw(nameWidth) << name << " = " << val << "\n"; }; std::cout << "\n// 前置区分 —— 通用\n"; printInt("IED_COUNT", IED_COUNT); printStr("FRONT_INST", FRONT_INST); printStr("FRONT_IP", FRONT_IP); std::cout << "\n// 消息队列 —— 通用\n"; printStr("BROKER_LIST", BROKER_LIST); printStr("TOPIC_STAT", TOPIC_STAT); printStr("TOPIC_PST", TOPIC_PST); printStr("TOPIC_PLT", TOPIC_PLT); printStr("TOPIC_EVENT", TOPIC_EVENT); printStr("TOPIC_ALARM", TOPIC_ALARM); printStr("TOPIC_SNG", TOPIC_SNG); printStr("TOPIC_RTDATA", TOPIC_RTDATA); std::cout << "\n// MQ —— 生产者\n"; printStr("G_ROCKETMQ_PRODUCER", G_ROCKETMQ_PRODUCER); printStr("G_MQPRODUCER_IPPORT", G_MQPRODUCER_IPPORT); printStr("G_MQPRODUCER_ACCESSKEY", G_MQPRODUCER_ACCESSKEY); printStr("G_MQPRODUCER_SECRETKEY", G_MQPRODUCER_SECRETKEY); printStr("G_LOG_TOPIC", G_LOG_TOPIC); printStr("G_LOG_TAG", G_LOG_TAG); printStr("G_LOG_KEY", G_LOG_KEY); printStr("G_CONNECT_TOPIC", G_CONNECT_TOPIC); printStr("G_CONNECT_TAG", G_CONNECT_TAG); printStr("G_CONNECT_KEY", G_CONNECT_KEY); std::cout << "\n// MQ —— 心跳 & 响应\n"; printStr("Heart_Beat_Topic", Heart_Beat_Topic); printStr("Heart_Beat_Tag", Heart_Beat_Tag); printStr("Heart_Beat_Key", Heart_Beat_Key); printStr("Topic_Reply_Topic", Topic_Reply_Topic); printStr("Topic_Reply_Tag", Topic_Reply_Tag); printStr("Topic_Reply_Key", Topic_Reply_Key); std::cout << "\n// MQ —— 消费者\n"; printStr("G_ROCKETMQ_CONSUMER", G_ROCKETMQ_CONSUMER); printStr("G_MQCONSUMER_IPPORT", G_MQCONSUMER_IPPORT); printStr("G_MQCONSUMER_ACCESSKEY", G_MQCONSUMER_ACCESSKEY); printStr("G_MQCONSUMER_SECRETKEY", G_MQCONSUMER_SECRETKEY); printStr("G_MQCONSUMER_CHANNEL", G_MQCONSUMER_CHANNEL); std::cout << "\n// MQ —— 主题细分类\n"; printStr("G_MQCONSUMER_TOPIC_RT", G_MQCONSUMER_TOPIC_RT); printStr("G_MQCONSUMER_TAG_RT", G_MQCONSUMER_TAG_RT); printStr("G_MQCONSUMER_KEY_RT", G_MQCONSUMER_KEY_RT); printStr("G_MQCONSUMER_TOPIC_UD", G_MQCONSUMER_TOPIC_UD); printStr("G_MQCONSUMER_TAG_UD", G_MQCONSUMER_TAG_UD); printStr("G_MQCONSUMER_KEY_UD", G_MQCONSUMER_KEY_UD); printStr("G_MQCONSUMER_TOPIC_RC", G_MQCONSUMER_TOPIC_RC); printStr("G_MQCONSUMER_TAG_RC", G_MQCONSUMER_TAG_RC); printStr("G_MQCONSUMER_KEY_RC", G_MQCONSUMER_KEY_RC); printStr("G_MQCONSUMER_TOPIC_SET", G_MQCONSUMER_TOPIC_SET); printStr("G_MQCONSUMER_TAG_SET", G_MQCONSUMER_TAG_SET); printStr("G_MQCONSUMER_KEY_SET", G_MQCONSUMER_KEY_SET); printStr("G_MQCONSUMER_TOPIC_LOG", G_MQCONSUMER_TOPIC_LOG); printStr("G_MQCONSUMER_TAG_LOG", G_MQCONSUMER_TAG_LOG); printStr("G_MQCONSUMER_KEY_LOG", G_MQCONSUMER_KEY_LOG); std::cout << "\n// MQ —— 测试用主题 & 参数\n"; printStr("G_ROCKETMQ_TOPIC_TEST", G_ROCKETMQ_TOPIC_TEST); printStr("G_ROCKETMQ_TAG_TEST", G_ROCKETMQ_TAG_TEST); printStr("G_ROCKETMQ_KEY_TEST", G_ROCKETMQ_KEY_TEST); printInt("G_TEST_FLAG", G_TEST_FLAG); printInt("G_TEST_NUM", G_TEST_NUM); printInt("G_TEST_TYPE", G_TEST_TYPE); printInt("TEST_PORT", TEST_PORT); printStr("G_TEST_LIST", G_TEST_LIST); // 打印解析后的 TESTARRAY std::cout << std::left << std::setw(nameWidth) << "TESTARRAY" << " = ["; for (size_t i = 0; i < TESTARRAY.size(); ++i) { std::cout << TESTARRAY[i]; if (i + 1 < TESTARRAY.size()) { std::cout << ", "; } } std::cout << "]\n"; std::cout << "\n// 终端 & 监测点状态筛选\n"; printStr("TERMINAL_STATUS", TERMINAL_STATUS); printStr("MONITOR_STATUS", MONITOR_STATUS); printStr("ICD_FLAG", ICD_FLAG); std::cout << "\n// Web 接口\n"; printStr("WEB_DEVICE", WEB_DEVICE); printStr("WEB_ICD", WEB_ICD); printStr("WEB_EVENT", WEB_EVENT); printStr("WEB_FILEUPLOAD", WEB_FILEUPLOAD); printStr("WEB_FILEDOWNLOAD", WEB_FILEDOWNLOAD); std::cout << "-------------------------------------\n"; } //初始化配置 void init_config() { loadConfig(FRONT_PATH + "/config/front.cfg"); printConfig(); //多前置处理 if (g_front_seg_index > 0 && g_front_seg_num > 0) { MULTIPLE_NODE_FLAG = 1; std::cout << "this is multiple process of index:" << g_front_seg_index << std::endl; if(g_front_seg_index > g_front_seg_num){ DIY_ERRORLOG("process","【ERROR】前置当前进程的进程号为:%d,前置的多进程最大进程号为:%d,当前进程的进程号应该为1到最大进程号范围内的整数,退出该进程",g_front_seg_index,g_front_seg_num); exit(-1039); } } else if(g_front_seg_num == 0 && g_front_seg_index == 0){ MULTIPLE_NODE_FLAG = 0; std::cout << "this is single process" << std::endl; } else{ DIY_ERRORLOG("process","【ERROR】前置当前进程的进程号为:%d,前置的多进程最大进程号为:%d,应该为大于0的整数,退出该进程",g_front_seg_index,g_front_seg_num); exit(-1039); } //测试进程端口 /*if (g_node_id == STAT_DATA_BASE_NODE_ID)//统计采集 TEST_PORT = TEST_PORT + STAT_DATA_BASE_NODE_ID + g_front_seg_index; else if (g_node_id == RECALL_HIS_DATA_BASE_NODE_ID) {//补召 TEST_PORT = TEST_PORT + RECALL_HIS_DATA_BASE_NODE_ID + g_front_seg_index; } else if (g_node_id == THREE_SECS_DATA_BASE_NODE_ID) {//3秒采集 TEST_PORT = TEST_PORT + THREE_SECS_DATA_BASE_NODE_ID + g_front_seg_index; } else if (g_node_id == SOE_COMTRADE_BASE_NODE_ID) {//暂态录波 TEST_PORT = TEST_PORT + SOE_COMTRADE_BASE_NODE_ID + g_front_seg_index; }*/ TEST_PORT = TEST_PORT + g_front_seg_index; } ////////////////////////////////////////////////////////////////////////////////////////////获取当前时间 // 用于获取当前时间,单位毫秒 double sGetMsTime() { auto now = std::chrono::system_clock::now(); auto ms = std::chrono::duration_cast( now.time_since_epoch() ).count(); return static_cast(ms); } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////实时触发部分 //获取实时触发文件 std::string get_3s_trig_fn() { const std::string dirPath = FRONT_PATH + "/etc/trigger3s"; DIR* dp = opendir(dirPath.c_str()); if (!dp) return ""; struct dirent* entry; std::vector> xmlFiles; while ((entry = readdir(dp)) != nullptr) { if (strstr(entry->d_name, ".xml")) { std::string fullPath = dirPath + "/" + entry->d_name; struct stat st; if (stat(fullPath.c_str(), &st) == 0 && S_ISREG(st.st_mode)) { xmlFiles.emplace_back(entry->d_name, st.st_mtime); } } } closedir(dp); if (xmlFiles.empty()) return ""; std::sort(xmlFiles.begin(), xmlFiles.end(), [](const std::pair& a, const std::pair& b) { return a.second > b.second; // 最近时间在前 }); return xmlFiles.front().first; } // 打印 trigger_t 结构体的函数 void print_trigger(const trigger_t& trigger) { std::cout << " dev_idx: " << trigger.dev_idx << ", line_id: " << trigger.line_id << ", real_data: " << trigger.real_data << ", soe_data: " << trigger.soe_data << ", limit: " << trigger.limit << ", count: " << trigger.count << std::endl; } // 打印 trigger_3s_xml_t 结构体的函数 void print_trigger_3s_xml(const trigger_3s_xml_t& trigger_3s_xml) { std::cout << "Work Trigger Count: " << trigger_3s_xml.work_trigger_num << std::endl; for (int i = 0; i < trigger_3s_xml.work_trigger_num; ++i) { std::cout << " Work Trigger [" << (i + 1) << "]:" << std::endl; print_trigger(trigger_3s_xml.work_triggers[i]); } std::cout << "New Trigger Count: " << trigger_3s_xml.new_trigger_num << std::endl; for (int i = 0; i < trigger_3s_xml.new_trigger_num; ++i) { std::cout << " New Trigger [" << (i + 1) << "]:" << std::endl; print_trigger(trigger_3s_xml.new_triggers[i]); } std::cout << "Delete Trigger Count: " << trigger_3s_xml.delete_trigger_num << std::endl; for (int i = 0; i < trigger_3s_xml.delete_trigger_num; ++i) { std::cout << " Delete Trigger [" << (i + 1) << "]:" << std::endl; print_trigger(trigger_3s_xml.delete_triggers[i]); } std::cout << "Modify Trigger Count: " << trigger_3s_xml.modify_trigger_num << std::endl; for (int i = 0; i < trigger_3s_xml.modify_trigger_num; ++i) { std::cout << " Modify Trigger [" << (i + 1) << "]:" << std::endl; print_trigger(trigger_3s_xml.modify_triggers[i]); } } //处理触发标志 int getValueFromElemAttrStr(std::string str) { if (str == "true") return 1; else if (str == "false") return 0; else return -1; } //将文件内容读取到结构中 void parse_3s_trigger(trigger_3s_xml_t* trigger_3s_xml, const std::string& parentTag, tinyxml2::XMLElement* trigger_e) { if (!trigger_3s_xml || !trigger_e) return; trigger_t trigger; const char* attr = nullptr; // DevSeries attr = trigger_e->Attribute("DevSeries"); trigger.dev_idx = attr ? std::atoi(attr) : 0; // Line attr = trigger_e->Attribute("Line"); trigger.line_id = attr ? std::atoi(attr) : 0; // RealData attr = trigger_e->Attribute("RealData"); std::string realDataStr = attr ? attr : ""; std::transform(realDataStr.begin(), realDataStr.end(), realDataStr.begin(), ::tolower); trigger.real_data = getValueFromElemAttrStr(realDataStr); // SOEData attr = trigger_e->Attribute("SOEData"); std::string soeDataStr = attr ? attr : ""; std::transform(soeDataStr.begin(), soeDataStr.end(), soeDataStr.begin(), ::tolower); trigger.soe_data = getValueFromElemAttrStr(soeDataStr); // Limit attr = trigger_e->Attribute("Limit"); trigger.limit = attr ? std::atoi(attr) : 0; // Count attr = trigger_e->Attribute("Count"); trigger.count = attr ? std::atoi(attr) : 0; // 分类插入 if (parentTag == "Work") { trigger_3s_xml->work_triggers[trigger_3s_xml->work_trigger_num++] = trigger; } else if (parentTag == "New") { trigger_3s_xml->new_triggers[trigger_3s_xml->new_trigger_num++] = trigger; } else if (parentTag == "Delete") { trigger_3s_xml->delete_triggers[trigger_3s_xml->delete_trigger_num++] = trigger; } else if (parentTag == "Modify") { trigger_3s_xml->modify_triggers[trigger_3s_xml->modify_trigger_num++] = trigger; } // 调试用 print_trigger_3s_xml(*trigger_3s_xml); } //实时触发文件处理 int load_3s_data_from_xml(trigger_3s_xml_t* trigger_3s_xml, const std::string& xml_fn) { if (!trigger_3s_xml) return 1; tinyxml2::XMLDocument doc; tinyxml2::XMLError result = doc.LoadFile(xml_fn.c_str()); if (result == tinyxml2::XML_ERROR_FILE_NOT_FOUND) { return 1; } else if (result != tinyxml2::XML_SUCCESS) { return 1; } tinyxml2::XMLElement* root = doc.RootElement(); if (!root) return 1; for (tinyxml2::XMLElement* groupElem = root->FirstChildElement(); groupElem != nullptr; groupElem = groupElem->NextSiblingElement()) { std::string strTag = groupElem->Name(); if (strTag == "Work" || strTag == "New" || strTag == "Delete" || strTag == "Modify") { for (tinyxml2::XMLElement* triggerElem = groupElem->FirstChildElement("Trigger"); triggerElem != nullptr; triggerElem = triggerElem->NextSiblingElement("Trigger")) { parse_3s_trigger(trigger_3s_xml, strTag, triggerElem); } } } return 0; } //将内容写入协议 void process_3s_config(trigger_3s_xml_t *trigger_3s_xml) { //根据协议补充内容 //根据协议补充内容 } //实时触发处理 int parse_3s_xml(trigger_3s_xml_t* trigger_3s_xml) { std::cout << "begin 3s xml..." << std::endl; std::memset(trigger_3s_xml, 0, sizeof(trigger_3s_xml_t)); std::string THREE_SECS_WEBSERVICE_DIR = FRONT_PATH + "/etc/trigger3s/"; //实时数据读取目录 std::string BAK_WEBSERVICE_3S_TRIG_COMMAND_XML_FN = THREE_SECS_WEBSERVICE_DIR + "bak_3s_trig_command.txt"; //实时触发文件备份文件名 std::string the_webservice_xml_fn = get_3s_trig_fn(); // 获取目录下最新 XML 文件名 if (the_webservice_xml_fn.length() > 4) { std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 等待 0.1 秒 std::string full_path = std::string(THREE_SECS_WEBSERVICE_DIR) + the_webservice_xml_fn; if (load_3s_data_from_xml(trigger_3s_xml, full_path) != 0) { std::cerr << "Failed to load 3s data from XML: " << full_path << std::endl; return 1; } std::remove(BAK_WEBSERVICE_3S_TRIG_COMMAND_XML_FN.c_str()); // 删除旧备份 if (std::rename(full_path.c_str(), BAK_WEBSERVICE_3S_TRIG_COMMAND_XML_FN.c_str()) != 0) { std::perror("Rename failed"); } std::cout << "/etc/trigger3s/*.xml success..." << std::endl; DIY_WARNLOG("process", "【WARN】前置读取实时数据触发文件成功,即将注册实时数据报告"); return 0; } std::cout << "3s xml fail..." << std::endl; return 1; } void check_3s_config() { double now; static double last_check_3s_config_time = 0.0; // 初始化时间 trigger_3s_xml_t trigger_3s_xml; // 3s触发文件 if (!three_secs_enabled) // 只有cfg_3s_data进程才会开启 return; now = sGetMsTime(); // 当前时间 if (std::fabs(now - last_check_3s_config_time) < 3 * 1000) // wait 3secs return; // 当前进程任务执行时查看当前时间和上次执行时间间隔,小于3秒不执行,大于等于3秒往下执行 last_check_3s_config_time = now; // 记录本次运行时间 while (0 == parse_3s_xml(&trigger_3s_xml)) { // 处理3秒文件,一次处理一个 process_3s_config(&trigger_3s_xml); // 根据文件处理数据 } } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////补招部分 //将文件内容读取到结构中 void parse_recall(recall_xml_t* recall_xml, const std::string& parentTag, const std::map& attributes, const std::string& id) { recall_t recall; std::memset(&recall, 0, sizeof(recall_t)); // 设置监测点 ID recall.line_id = id; // 解析时间字符串 auto parse_time = [](const std::string& time_str) -> std::time_t { std::tm tm = {}; std::istringstream ss(time_str); ss >> std::get_time(&tm, "%Y-%m-%d %H:%M:%S"); if (ss.fail()) return 0; return std::mktime(&tm); }; recall.start_time = parse_time(attributes.at("StartTime")); recall.end_time = parse_time(attributes.at("EndTime")); recall.need_steady = std::stoi(attributes.at("STEADY")); recall.need_voltage = std::stoi(attributes.at("VOLTAGE")); std::cout << parentTag << " -> " << recall.line_id << " " << recall.need_steady << " " << recall.need_voltage << " " << recall.start_time << " " << recall.end_time << std::endl; if (parentTag == "Work" && recall_xml->work_recall_num < MAX_RECALL_NUM) { recall_xml->work_recalls[recall_xml->work_recall_num++] = recall; } else if (parentTag == "New" && recall_xml->new_recall_num < MAX_RECALL_NUM) { recall_xml->new_recalls[recall_xml->new_recall_num++] = recall; } } //读取补招文件 int parse_recall_xml(recall_xml_t* recall_xml, const std::string& id) { std::string cfg_dir = FRONT_PATH + "/etc/recall"; std::string pattern = subdir + "_" + std::to_string(g_front_seg_index) + "_" + id + "_*_Recall.xml"; DIR* dir = opendir(cfg_dir.c_str()); if (!dir) { DIY_ERRORLOG("process", "【ERROR】前置的%d号进程 无法解析补招文件,补招文件路径FRONT_PATH + /etc/recall/不存在", g_front_seg_index); return false; } struct dirent* entry; while ((entry = readdir(dir)) != NULL) { std::string filename = entry->d_name; if (fnmatch(pattern.c_str(), filename.c_str(), 0) != 0) continue; std::string filepath = cfg_dir + "/" + filename; tinyxml2::XMLDocument doc; if (doc.LoadFile(filepath.c_str()) != tinyxml2::XML_SUCCESS) { DIY_ERRORLOG("process", "【ERROR】前置的%d号进程 无法解析补招文件%s,补招内容无效", g_front_seg_index, filepath.c_str()); continue; } tinyxml2::XMLElement* root = doc.RootElement(); if (!root) continue; for (tinyxml2::XMLElement* elem = root->FirstChildElement(); elem != nullptr; elem = elem->NextSiblingElement()) { std::string tag = elem->Name(); if (tag == "Work" || tag == "New") { for (tinyxml2::XMLElement* recallElem = elem->FirstChildElement("Recall"); recallElem != nullptr; recallElem = recallElem->NextSiblingElement("Recall")) { std::map attrs; const char* start = recallElem->Attribute("StartTime"); const char* end = recallElem->Attribute("EndTime"); const char* steady = recallElem->Attribute("STEADY"); const char* voltage = recallElem->Attribute("VOLTAGE"); if (start && end && steady && voltage) { attrs["StartTime"] = start; attrs["EndTime"] = end; attrs["STEADY"] = steady; attrs["VOLTAGE"] = voltage; parse_recall(recall_xml, tag, attrs, id); } } } } } closedir(dir); return 0; } //将读取到的补招文件写入到监测点的运行结构中,后续根据实际补充 void process_recall_config(recall_xml_t* recall_xml) { } //根据监测点id来获取补招数据,补招时调用这个 void Check_Recall_Config(const std::string& id) { /*if (g_node_id == HIS_DATA_BASE_NODE_ID || g_node_id == NEW_HIS_DATA_BASE_NODE_ID || g_node_id == RECALL_HIS_DATA_BASE_NODE_ID || g_node_id == RECALL_ALL_DATA_BASE_NODE_ID) {*/ recall_xml_t recall_xml; std::memset(&recall_xml, 0, sizeof(recall_xml_t)); // 解析补招文件 parse_recall_xml(&recall_xml, id); // 将补招数据赋值到全局变量 process_recall_config(&recall_xml); //} } //补招成功后删除补招文件,补招后调用这个 int delete_recall_xml(const std::string& id) { std::string cfg_dir = FRONT_PATH + "/etc/recall"; std::string pattern = std::string(subdir) + "_" + std::to_string(g_front_seg_index) + "_" + id + "_*_Recall.xml"; DIR* dir = opendir(cfg_dir.c_str()); if (!dir) { std::cerr << "Folder does not exist or cannot be opened: " << cfg_dir << std::endl; return false; } struct dirent* entry; while ((entry = readdir(dir)) != NULL) { std::string filename = entry->d_name; if (fnmatch(pattern.c_str(), filename.c_str(), 0) == 0) { std::string fullpath = cfg_dir + "/" + filename; if (remove(fullpath.c_str()) == 0) { std::cout << "Deleted: " << fullpath << std::endl; } else { std::cerr << "Failed to delete: " << fullpath << std::endl; } } } closedir(dir); return 0; } //删除过期的xmllnk:多个进程并发删除导致的失败不会影响进程 void DeletcRecallXml() { std::string cfg_dir = FRONT_PATH + "/etc/recall"; std::string pattern = std::string(subdir) + "_*_Recall.xml"; DIR* dir = opendir(cfg_dir.c_str()); if (!dir) { std::cerr << "folder does not exist!" << std::endl; DIY_ERRORLOG("process", "【ERROR】前置的%d号进程 删除旧的补招文件失败,补招文件路径FRONT_PATH + /etc/recall/不存在", g_front_seg_index); return; } // 获取当前时间,计算 2 天前的时间戳 std::time_t now = std::time(nullptr); std::time_t cutoff = now - 2 * 24 * 60 * 60; // 两天前 struct dirent* entry; while ((entry = readdir(dir)) != NULL) { std::string filename = entry->d_name; if (fnmatch(pattern.c_str(), filename.c_str(), 0) == 0) { std::string fullpath = cfg_dir + "/" + filename; struct stat file_stat; if (stat(fullpath.c_str(), &file_stat) == 0) { if (file_stat.st_mtime < cutoff) { if (remove(fullpath.c_str()) == 0) { DIY_INFOLOG("process", "【NORMAL】前置的%d号进程 删除超过两天的补招文件", g_front_seg_index); } else { std::cerr << "Failed to remove file: " << fullpath << std::endl; } } } } } closedir(dir); } //根据补招列表创建补招文件 void CreateRecallXml() { std::time_t now = std::time(nullptr); std::tm* tm_now = std::localtime(&now); char timestamp[32] = {0}; std::strftime(timestamp, sizeof(timestamp), "%Y%m%d%H%M%S", tm_now); g_StatisticLackList_list_mutex.lock(); if (!g_StatisticLackList.empty()) { DIY_INFOLOG("process", "【NORMAL】前置的%d号进程 开始写入补招文件", g_front_seg_index); std::map> id_map; for (const auto& jr : g_StatisticLackList) { id_map[jr.MonitorID].push_back(jr); } for (const auto& pair : id_map) { const std::string& monitor_id = pair.first; const std::list& recalls = pair.second; std::ostringstream path; path << FRONT_PATH << "/etc/recall/" << subdir << "_" << g_front_seg_index << "_" << monitor_id << "_" << timestamp << "_Recall.xml"; tinyxml2::XMLDocument doc; tinyxml2::XMLDeclaration* decl = doc.NewDeclaration(); doc.InsertFirstChild(decl); tinyxml2::XMLElement* root = doc.NewElement("RecallList"); doc.InsertEndChild(root); // 空 Work 段 tinyxml2::XMLElement* work = doc.NewElement("Work"); root->InsertEndChild(work); // New 段 tinyxml2::XMLElement* new_elem = doc.NewElement("New"); for (const auto& jr : recalls) { tinyxml2::XMLElement* recall = doc.NewElement("Recall"); recall->SetAttribute("MonitorID", jr.MonitorID.c_str()); recall->SetAttribute("StartTime", jr.StartTime.c_str()); recall->SetAttribute("EndTime", jr.EndTime.c_str()); recall->SetAttribute("STEADY", jr.STEADY.c_str()); recall->SetAttribute("VOLTAGE", jr.VOLTAGE.c_str()); new_elem->InsertEndChild(recall); } root->InsertEndChild(new_elem); tinyxml2::XMLError save_result = doc.SaveFile(path.str().c_str()); if (save_result != tinyxml2::XML_SUCCESS) { DIY_ERRORLOG("process", "【ERROR】前置的%d号进程 无法将补招文件写入路径: %s", g_front_seg_index, path.str().c_str()); continue; } } } g_StatisticLackList.clear(); g_StatisticLackList_list_mutex.unlock(); } //生成待补招xml文件 void create_recall_xml() { //if (g_node_id == HIS_DATA_BASE_NODE_ID || g_node_id == NEW_HIS_DATA_BASE_NODE_ID || g_node_id == RECALL_HIS_DATA_BASE_NODE_ID || (g_node_id == RECALL_ALL_DATA_BASE_NODE_ID)) { DeletcRecallXml(); CreateRecallXml(); //} } // 工具函数:将时间字符串转为 time_t(秒级) static long long parse_time_to_epoch(const std::string& time_str) { std::tm tm = {}; std::istringstream ss(time_str); ss >> std::get_time(&tm, "%Y-%m-%d %H:%M:%S"); if (ss.fail()) { return 0; } return static_cast(std::mktime(&tm)); } // 数据完整性补招判断(划分为1小时) void Get_Recall_Time_Char(const std::string& start_time_str, const std::string& end_time_str, std::vector& recallinfo_list_hour) { long long starttime = parse_time_to_epoch(start_time_str); long long endtime = parse_time_to_epoch(end_time_str); if (starttime == 0 || endtime == 0 || starttime >= endtime) { return; } // 初始区间加入 RecallInfo initial; initial.starttime = starttime; initial.endtime = endtime; std::vector recallinfo_list; recallinfo_list.push_back(initial); const long long max_interval = 3600; // 1小时 for (const auto& interval : recallinfo_list) { long long duration = interval.endtime - interval.starttime; for (long long j = 0; j <= duration; j += max_interval) { RecallInfo info; info.starttime = interval.starttime + j; if (j + max_interval > duration) { info.endtime = interval.endtime; } else { info.endtime = interval.starttime + j + max_interval - 1; } recallinfo_list_hour.push_back(info); } } } //mq调用将补招信息写入补招列表 int recall_json_handle(const std::string& jstr) { // 不指定稳态/暂态则全部补招 int stat = 0; int voltage = 0; try { std::vector recallParams; // 1. 解析 JSON 数组 auto json_root = nlohmann::json::parse(jstr); if (!json_root.is_array()) { std::cout << "json root解析错误" << std::endl; return 10000; } // 2. 遍历每个补招项 for (auto& item : json_root) { // 获取必需字段 if (!item.contains("monitorId") || !item.contains("timeInterval") || !item.contains("dataType")) { std::cout << "json内容解析错误" << std::endl; return 10000; } // 2.1 解析 dataType std::string datatype = item["dataType"].get(); if (!datatype.empty()) { stat = (datatype == "0") ? 1 : 0; // 稳态 voltage = (datatype == "1") ? 1 : 0; // 暂态 } else { stat = voltage = 1; // 全补 } // 2.2 处理 monitorId 数组 auto& midArr = item["monitorId"]; auto& tiArr = item["timeInterval"]; if (midArr.is_array() && tiArr.is_array() && !midArr.empty()) { for (auto& idItem : midArr) { std::string monitorId = idItem.get(); // 判断此监测点是否归属当前进程 bool mppair = false; for (const auto& dev : terminal_devlist) { // 只处理本进程对应的终端 if (std::stoi(dev.processNo) != g_front_seg_index && g_front_seg_index != 0) { continue; } for (const auto& mon : dev.line) { if (mon.monitor_id.empty()) continue; if (mon.monitor_id == monitorId) { mppair = true; std::cout << "Matched monitorId " << monitorId << " in terminal " << dev.terminal_id << std::endl; break; } } if (mppair) break; } if (!mppair) continue; // 遍历 timeInterval 数组 for (auto& timeItem : tiArr) { std::string ti = timeItem.get(); auto pos = ti.find('~'); std::string start = ti.substr(0, pos); std::string end = ti.substr(pos + 1); JournalRecall param; param.MonitorID = monitorId; param.StartTime = start; param.EndTime = end; param.STEADY = std::to_string(stat); param.VOLTAGE = std::to_string(voltage); recallParams.push_back(param); } } } // 2.3 monitorId 数组存在但为空 -> 补招所有监测点 else if (midArr.is_array() && midArr.empty()) { std::cout << "monitorIdArray is null,补招所有监测点" << std::endl; for (const auto& dev : terminal_devlist) { if (std::stoi(dev.processNo) != g_front_seg_index && g_front_seg_index != 0) { continue; } for (const auto& mon : dev.line) { if (mon.monitor_id.empty()) continue; for (auto& timeItem : tiArr) { std::string ti = timeItem.get(); auto pos = ti.find('~'); std::string start = ti.substr(0, pos); std::string end = ti.substr(pos + 1); JournalRecall param; param.MonitorID = mon.monitor_id; param.StartTime = start; param.EndTime = end; param.STEADY = std::to_string(stat); param.VOLTAGE = std::to_string(voltage); recallParams.push_back(param); } } } } else { std::cout << "monitorIdArray 不存在或类型不正确" << std::endl; } } // 3. 生成具体补招记录 for (auto& rp : recallParams) { std::string start_time = rp.StartTime; std::string end_time = rp.EndTime; std::cout << "mp_id " << rp.MonitorID << " start_time " << start_time << " end_time " << end_time << " stat " << rp.STEADY << " voltage " << rp.VOLTAGE << std::endl; std::vector recallinfo_list_hour; Get_Recall_Time_Char(start_time, end_time, recallinfo_list_hour); for (auto& info : recallinfo_list_hour) { JournalRecall jr; jr.MonitorID = rp.MonitorID; // 转换 starttime char buf[20]; std::tm tm{}; time_t st = static_cast(info.starttime); localtime_r(&st, &tm); std::strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &tm); jr.StartTime = buf; // 转换 endtime std::tm tm2{}; time_t et = static_cast(info.endtime); localtime_r(&et, &tm2); std::strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &tm2); jr.EndTime = buf; jr.STEADY = rp.STEADY; jr.VOLTAGE = rp.VOLTAGE; std::lock_guard lk(g_StatisticLackList_list_mutex); g_StatisticLackList.push_back(jr); } } } catch (const std::exception& e) { std::cout << "处理客户端发送的消息错误,原因:" << e.what() << std::endl; return 10004; } return 0; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////台账接口打印 // 打印 terminal_dev_map 中所有内容的函数 void printTerminalDevMap(const std::map& terminal_dev_map) { for (const auto& kv : terminal_dev_map) { const std::string& key = kv.first; const terminal_dev& dev = kv.second; // 引用对象 std::cout << "Key: " << key << ", Terminal ID: " << dev.terminal_id << ", Terminal Code: " << dev.terminal_name << ", Organization Name: "<< dev.org_name << ", Maintenance Name: " << dev.maint_name << ", Station Name: " << dev.station_name << ", Factory: " << dev.tmnl_factory << ", Status: " << dev.tmnl_status << ", Device Type: " << dev.dev_type << ", Device Key: " << dev.dev_key << ", Device Series: " << dev.dev_series << ", ProcessNo: " << dev.processNo << ", MaxProcessNum: " << dev.maxProcessNum << ", Address: " << dev.addr_str << ", Port: " << dev.port << ", Timestamp: " << dev.timestamp << ", mac: " << dev.mac << std::endl; // 打印监测点信息 for (size_t i = 0; i < dev.line.size(); ++i) { const auto& m = dev.line[i]; std::cout << " Monitor [" << i << "] " << "ID: " << m.monitor_id << ", Code: " << m.terminal_id << ", Name: " << m.monitor_name << ", Seq: " << m.logical_device_seq << ", Voltage: "<< m.voltage_level << ", Connect: "<< m.terminal_connect << ", Timestamp:"<< m.timestamp << ", Status: " << m.status << ", CT1: " << m.CT1 << ", CT2: " << m.CT2 << ", PT1: " << m.PT1 << ", PT2: " << m.PT2 << std::endl; } } } //////////////////////////////////////////////////////////////////////////////////////////////////////////////更新台账 //在台账更新目录查找自己进程要处理的文件 std::list find_xml_belong_to_this_process() { char prefix[20]; // 假设最多需要20个字符(根据实际需要调整) sprintf(prefix, "%d_%d", g_node_id, g_front_seg_index); // 将g_node_id和g_front_seg_index格式化为字符串 std::string LEDGER_UPDATE_DIR = FRONT_PATH + "/etc/ledgerupdate/"; //台账更新读取目录 DIR *dir = opendir(LEDGER_UPDATE_DIR.c_str()); // 打开目录 struct dirent *entry; std::list found_files; // 用于存储找到的所有匹配文件名 if (dir == NULL) { std::cout << "Failed to open directory: " << LEDGER_UPDATE_DIR << std::endl; return found_files; // 返回空的list } // 遍历目录中的所有文件 while ((entry = readdir(dir)) != NULL) { std::string filename = entry->d_name; // 排除 "." 和 ".." 目录 if (filename == "." || filename == "..") { continue; } std::cout << "find" << filename << "in" << LEDGER_UPDATE_DIR << std::endl; // 判断文件名是否以 prefix 开头且扩展名是 .xml if (filename.find(prefix) == 0 && filename.substr(filename.find_last_of('.') + 1) == "xml") { std::string full_path = LEDGER_UPDATE_DIR + filename; found_files.push_back(full_path); // 将完整路径加入容器 } } closedir(dir); // 关闭目录 return found_files; // 返回所有找到的文件名 } // 根据 str_tag 将 terminal 添加到对应的数组 void add_terminal_to_trigger_update(trigger_update_xml_t& trigger_update_xml, const std::string& str_tag, const terminal_dev& work_terminal) { if (str_tag == "add") { std::cout << "new ledger!!!!" << std::endl; trigger_update_xml.new_updates.push_back(work_terminal); } else if (str_tag == "modify") { std::cout << "modify ledger!!!" << std::endl; trigger_update_xml.modify_updates.push_back(work_terminal); } else { std::cerr << "Unknown tag: " << str_tag << std::endl; } } // 将添加和修改的文件内容写入结构 void parse_terminal_from_data(trigger_update_xml_t& trigger_update_xml, const std::string& str_tag, const std::string& data, const std::string& guid_value) { terminal_dev work_terminal; work_terminal.guid = guid_value; tinyxml2::XMLDocument doc; if (doc.Parse(data.c_str()) != tinyxml2::XML_SUCCESS) { return; } auto root = doc.FirstChildElement("terminal"); if (!root) return; auto get_value = [&](const char* tag) -> std::string { auto elem = root->FirstChildElement(tag); return elem && elem->GetText() ? elem->GetText() : ""; }; work_terminal.terminal_id = get_value("id"); work_terminal.terminal_name = get_value("terminalCode"); work_terminal.org_name = get_value("orgName"); work_terminal.maint_name = get_value("maintName"); work_terminal.station_name = get_value("stationName"); work_terminal.tmnl_factory = get_value("manufacturer"); work_terminal.tmnl_status = get_value("status"); work_terminal.dev_type = get_value("devType"); work_terminal.dev_key = get_value("devKey"); work_terminal.dev_series = get_value("series"); work_terminal.processNo = get_value("processNo"); work_terminal.addr_str = get_value("ip"); work_terminal.port = get_value("port"); work_terminal.timestamp = get_value("updateTime"); work_terminal.mac = get_value("mac"); for (tinyxml2::XMLElement* monitor = root->FirstChildElement("monitorData"); monitor; monitor = monitor->NextSiblingElement("monitorData")) { ledger_monitor mon; mon.monitor_id = monitor->FirstChildElement("id") ? monitor->FirstChildElement("id")->GetText() : "N/A"; mon.monitor_name = monitor->FirstChildElement("name") ? monitor->FirstChildElement("name")->GetText() : "N/A"; mon.voltage_level = monitor->FirstChildElement("voltageLevel") ? monitor->FirstChildElement("voltageLevel")->GetText() : "N/A"; mon.terminal_connect = monitor->FirstChildElement("ptType") ? monitor->FirstChildElement("ptType")->GetText() : "N/A"; mon.logical_device_seq = monitor->FirstChildElement("lineNo") ? monitor->FirstChildElement("lineNo")->GetText() : "N/A"; mon.timestamp = monitor->FirstChildElement("timestamp") ? monitor->FirstChildElement("timestamp")->GetText() : "N/A"; mon.terminal_id = monitor->FirstChildElement("terminal_id") ? monitor->FirstChildElement("terminal_name")->GetText() : "N/A"; mon.status = monitor->FirstChildElement("status") ? monitor->FirstChildElement("status")->GetText() : "N/A"; mon.CT1 = monitor->FirstChildElement("CT1") && monitor->FirstChildElement("CT1")->GetText() ? atof(monitor->FirstChildElement("CT1")->GetText()) : 0.0; mon.CT2 = monitor->FirstChildElement("CT2") && monitor->FirstChildElement("CT2")->GetText() ? atof(monitor->FirstChildElement("CT2")->GetText()) : 0.0; mon.PT1 = monitor->FirstChildElement("PT1") && monitor->FirstChildElement("PT1")->GetText() ? atof(monitor->FirstChildElement("PT1")->GetText()) : 0.0; mon.PT2 = monitor->FirstChildElement("PT2") && monitor->FirstChildElement("PT2")->GetText() ? atof(monitor->FirstChildElement("PT2")->GetText()) : 0.0; work_terminal.line.push_back(mon); } add_terminal_to_trigger_update(trigger_update_xml, str_tag, work_terminal); } // 统一处理文件内容和结构 void parse_ledger_update(trigger_update_xml_t& trigger_update_xml, const std::string& strTag, const std::string& data, const std::string& guid_value) { std::cout << "record one xml.." << std::endl; if (strTag == "add" || strTag == "modify") { parse_terminal_from_data(trigger_update_xml, strTag, data, guid_value); } else if (strTag == "delete") { terminal_dev delete_terminal; tinyxml2::XMLDocument doc; if (doc.Parse(data.c_str()) != tinyxml2::XML_SUCCESS) { std::cerr << "Failed to parse XML for delete tag." << std::endl; return; } tinyxml2::XMLElement* root = doc.FirstChildElement("terminalData"); if (!root) { std::cerr << "Missing terminalData element in delete tag." << std::endl; return; } tinyxml2::XMLElement* idElem = root->FirstChildElement("id"); if (idElem && idElem->GetText()) { delete_terminal.terminal_id = idElem->GetText(); } else { std::cerr << "Missing id element in delete tag." << std::endl; return; } delete_terminal.guid = guid_value; trigger_update_xml.delete_updates.push_back(delete_terminal); } else { std::cerr << "Unsupported strTag: " << strTag << std::endl; } } //读取台账更新文件 int load_ledger_update_from_xml(trigger_update_xml_t& trigger_update_xml, const std::string& xml_fn) { std::cout << "start to load one xml.." << std::endl; std::ifstream file(xml_fn); if (!file.is_open()) { std::cerr << "Failed to open file: " << xml_fn << std::endl; return -1; } std::stringstream buffer; buffer << file.rdbuf(); std::string content = buffer.str(); file.close(); tinyxml2::XMLDocument doc; if (doc.Parse(content.c_str()) != tinyxml2::XML_SUCCESS) { std::cerr << "Failed to parse XML content." << std::endl; return -1; } auto* root = doc.FirstChildElement("ledger_update"); if (!root) { std::cerr << "Missing root tag." << std::endl; return -1; } std::string guid_value; auto* guidElem = root->FirstChildElement("guid"); if (guidElem && guidElem->GetText()) { guid_value = guidElem->GetText(); std::cout << "Found guid: " << guid_value << std::endl; } const char* tag_names[] = {"add", "modify", "delete"}; tinyxml2::XMLElement* tagElem = nullptr; std::string target_tag; for (const auto& tag : tag_names) { tagElem = root->FirstChildElement(tag); if (tagElem) { target_tag = tag; break; } } if (!tagElem) { std::cerr << "No , , or tag found!" << std::endl; return -1; } for (auto* termElem = tagElem->FirstChildElement("terminalData"); termElem; termElem = termElem->NextSiblingElement("terminalData")) { tinyxml2::XMLPrinter printer; termElem->Accept(&printer); std::string data_content = printer.CStr(); std::cout << "ledger data_content is " << data_content << std::endl; parse_ledger_update(trigger_update_xml, target_tag, data_content, guid_value); } std::cout << "load one xml finish" << std::endl; return 0; } //台账更新处理文件 int parse_ledger_update_xml(trigger_update_xml_t& trigger_update_xml) { std::list result = find_xml_belong_to_this_process(); if (result.empty()) return 1; for (const auto& filename : result) { std::cout << "Found XML: " << filename << std::endl; std::this_thread::sleep_for(std::chrono::milliseconds(100)); if (!load_ledger_update_from_xml(trigger_update_xml, filename)) { DIY_WARNLOG("process", "【WARN】成功读取台账更新文件: %s", filename.c_str()); } if (std::remove(filename.c_str()) != 0) { DIY_ERRORLOG("process", "【ERROR】删除台账更新文件失败: %s", filename.c_str()); return 1; } else { DIY_INFOLOG("process", "【NORMAL】成功删除台账更新文件: %s", filename.c_str()); } } if (!trigger_update_xml.new_updates.empty() || !trigger_update_xml.modify_updates.empty() || !trigger_update_xml.delete_updates.empty()) { std::cout << "ledger update xml have data..." << std::endl; return 0; } std::cout << "ledger update xml no data..." << std::endl; return 1; } //更新单个台账 int update_one_terminal_ledger(const terminal_dev& update,terminal_dev& target_dev) { // 更新基本信息 if (!update.terminal_id.empty()) { target_dev.terminal_id = update.terminal_id; std::cout << "terminal_id: " << target_dev.terminal_id << std::endl; } if (!update.terminal_name.empty()) { target_dev.terminal_name = update.terminal_name; std::cout << "terminal_name: " << target_dev.terminal_name << std::endl; } if (!update.tmnl_factory.empty()) { target_dev.tmnl_factory = update.tmnl_factory; std::cout << "tmnl_factory: " << target_dev.tmnl_factory << std::endl; } if (!update.tmnl_status.empty()) { target_dev.tmnl_status = update.tmnl_status; std::cout << "tmnl_status: " << target_dev.tmnl_status << std::endl; } if (!update.dev_type.empty()) { target_dev.dev_type = update.dev_type; std::cout << "dev_type: " << target_dev.dev_type << std::endl; } if (!update.processNo.empty()) { target_dev.processNo = update.processNo; std::cout << "processNo: " << target_dev.processNo << std::endl; } if (!update.dev_series.empty()) { target_dev.dev_series = update.dev_series; std::cout << "dev_series: " << target_dev.dev_series << std::endl; } if (!update.dev_key.empty()) { target_dev.dev_key = update.dev_key; std::cout << "dev_key: " << target_dev.dev_key << std::endl; } if (!update.addr_str.empty()) { target_dev.addr_str = update.addr_str; std::cout << "addr_str: " << target_dev.addr_str << std::endl; } if (!update.port.empty()) { target_dev.port = update.port; std::cout << "port: " << target_dev.port << std::endl; } if (!update.mac.empty()) { target_dev.mac = update.mac; std::cout << "mac: " << target_dev.mac << std::endl; } if (!update.timestamp.empty()) { struct tm timeinfo = {}; if (sscanf(update.timestamp.c_str(), "%4d-%2d-%2d %2d:%2d:%2d", &timeinfo.tm_year, &timeinfo.tm_mon, &timeinfo.tm_mday, &timeinfo.tm_hour, &timeinfo.tm_min, &timeinfo.tm_sec) == 6) { timeinfo.tm_year -= 1900; timeinfo.tm_mon -= 1; timeinfo.tm_isdst = -1; time_t raw_time = mktime(&timeinfo); if (raw_time != -1) { target_dev.timestamp = static_cast(raw_time); std::cout << "timestamp (unix): " << target_dev.timestamp << std::endl; } else { std::cerr << "Error: mktime failed." << std::endl; return -1; } } else { std::cerr << "Error: invalid timestamp format." << std::endl; return -1; } } // 清空旧监测点并重新填充 target_dev.line.clear(); for (const auto& mon : update.line) { if (mon.monitor_id.empty()) break; ledger_monitor m; m.monitor_id = mon.monitor_id; m.monitor_name = mon.monitor_name; m.logical_device_seq = mon.logical_device_seq; m.voltage_level = mon.voltage_level; m.terminal_connect = mon.terminal_connect; m.status = mon.status; m.terminal_id = mon.terminal_id; m.timestamp = mon.timestamp; m.CT1 = mon.CT1; m.CT2 = mon.CT2; m.PT1 = mon.PT1; m.PT2 = mon.PT2; if (m.terminal_connect != "0") { isdelta_flag = 1; std::cout << "monitor_id " << m.monitor_id << " uses delta wiring." << std::endl; } if (!m.timestamp.empty()) { struct tm timeinfo = {}; if (sscanf(m.timestamp.c_str(), "%4d-%2d-%2d %2d:%2d:%2d", &timeinfo.tm_year, &timeinfo.tm_mon, &timeinfo.tm_mday, &timeinfo.tm_hour, &timeinfo.tm_min, &timeinfo.tm_sec) == 6) { timeinfo.tm_year -= 1900; timeinfo.tm_mon -= 1; timeinfo.tm_isdst = -1; time_t raw_time = mktime(&timeinfo); if (raw_time != -1) { m.timestamp = static_cast(raw_time); std::cout << "monitor time (unix): " << m.timestamp << std::endl; } } } target_dev.line.push_back(m); } return 0; } //台账更新到台账列表 void process_ledger_update(trigger_update_xml_t& ledger_update_xml) { // --- 1. 新增处理 --- std::cout << "add ledger num: " << ledger_update_xml.new_updates.size() << std::endl; for (auto it = ledger_update_xml.new_updates.begin(); it != ledger_update_xml.new_updates.end(); ) { terminal_dev& new_dev = *it; auto found = std::find_if(terminal_devlist.begin(), terminal_devlist.end(), [&](const terminal_dev& d) { return d.terminal_id == new_dev.terminal_id; }); if (found != terminal_devlist.end()) { if (ledger_update_xml.modify_updates.size() < MAX_UPDATEA_NUM) { ledger_update_xml.modify_updates.push_back(new_dev); } else { std::cerr << "Exceeded MAX_UPDATEA_NUM limit for modify_updates!" << std::endl; } it = ledger_update_xml.new_updates.erase(it); // 删除已处理项 continue; } if (terminal_devlist.size() >= static_cast(IED_COUNT)) { send_reply_to_queue(new_dev.guid, "2", "终端 id: " + new_dev.terminal_id + " 台账更新失败,配置台账数量已满"); ++it; continue; } terminal_dev target_dev; if (update_one_terminal_ledger(new_dev, target_dev) != 0) { send_reply_to_queue(new_dev.guid, "2", "终端 id: " + new_dev.terminal_id + " 台账更新失败,无法写入台账"); ++it; continue; } if (parse_model_cfg_web_one(target_dev.dev_type).empty()) { send_reply_to_queue(new_dev.guid, "2", "终端 id: " + new_dev.terminal_id + " 台账更新失败,未找到装置型号"); ++it; continue; } Set_xml_nodeinfo_one(target_dev.dev_type); init_loggers_bydevid(target_dev.terminal_id); terminal_devlist.push_back(target_dev); send_reply_to_queue(new_dev.guid, "2", "终端 id: " + new_dev.terminal_id + " 台账添加成功"); it = ledger_update_xml.new_updates.erase(it); } // --- 2. 修改处理 --- std::cout << "modify ledger num: " << ledger_update_xml.modify_updates.size() << std::endl; for (auto& mod_dev : ledger_update_xml.modify_updates) { auto it = std::find_if(terminal_devlist.begin(), terminal_devlist.end(), [&](const terminal_dev& d) { return d.terminal_id == mod_dev.terminal_id; }); if (it != terminal_devlist.end()) { remove_loggers_by_terminal_id(mod_dev.terminal_id); if (update_one_terminal_ledger(mod_dev, *it) != 0) { send_reply_to_queue(mod_dev.guid, "2", "终端 id: " + mod_dev.terminal_id + " 台账更新失败,写入失败"); continue; } if (parse_model_cfg_web_one(it->dev_type).empty()) { send_reply_to_queue(mod_dev.guid, "2", "终端 id: " + mod_dev.terminal_id + " 台账更新失败,未找到装置型号"); continue; } Set_xml_nodeinfo_one(it->dev_type); init_loggers_bydevid(mod_dev.terminal_id); send_reply_to_queue(mod_dev.guid, "2", "终端 id: " + mod_dev.terminal_id + " 台账修改成功"); } else { send_reply_to_queue(mod_dev.guid, "2", "终端 id: " + mod_dev.terminal_id + " 台账修改失败,未找到终端"); } } // --- 3. 删除处理 --- std::cout << "delete ledger num: " << ledger_update_xml.delete_updates.size() << std::endl; for (auto& del_dev : ledger_update_xml.delete_updates) { auto it = std::find_if(terminal_devlist.begin(), terminal_devlist.end(), [&](const terminal_dev& d) { return d.terminal_id == del_dev.terminal_id; }); if (it != terminal_devlist.end()) { remove_loggers_by_terminal_id(del_dev.terminal_id); terminal_devlist.erase(it); send_reply_to_queue(del_dev.guid, "2", "终端 id: " + del_dev.terminal_id + " 台账删除成功"); } else { send_reply_to_queue(del_dev.guid, "2", "终端 id: " + del_dev.terminal_id + " 台账删除失败,未找到终端"); } } // --- 4. 日志记录 --- if (!ledger_update_xml.modify_updates.empty() || !ledger_update_xml.new_updates.empty() || !ledger_update_xml.delete_updates.empty()) { create_ledger_log(&ledger_update_xml); } } //台账更新处理函数 void check_ledger_update() { static double last_check_time = 0.0; double now = sGetMsTime(); if (now - last_check_time < 3000.0) return; last_check_time = now; std::unique_ptr trigger_ledger_update_xml(new trigger_update_xml_t()); if (0 == parse_ledger_update_xml(*trigger_ledger_update_xml)) { print_trigger_update_xml(*trigger_ledger_update_xml); std::lock_guard lock(ledgermtx); process_ledger_update(*trigger_ledger_update_xml); } } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////台账更新记录日志 // 获取当前时间并格式化为 "YYYY-MM-DD HH:MM:SS" std::string get_current_time() { std::time_t t = std::time(NULL); struct std::tm tm = *std::localtime(&t); char buffer[80]; strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &tm); return std::string(buffer); } // 写入日志条目 void write_log_entry(std::ofstream &log_file, const std::string &action, const std::string &terminal_id, const std::string ¤t_time) { log_file << terminal_id << "\t" << action << "time:" << current_time << "\n"; } // 创建台账更新日志 void create_ledger_log(trigger_update_xml_t* ledger_update_xml) { std::cout << "create_ledger_log." << std::endl; std::string log_filename = FRONT_PATH + "/etc/" + LEDGER_UPDATE_FN; std::ofstream log_file(log_filename.c_str(), std::ios::app); // 以追加模式打开文件 if (!log_file.is_open()) { std::cerr << "Failed to open log file: " << log_filename << std::endl; return; } std::vector> new_entries; std::vector> modify_entries; std::vector> delete_entries; std::string current_time = get_current_time(); // 获取当前时间 // new_updates for (const auto& dev : ledger_update_xml->new_updates) { new_entries.emplace_back(dev.terminal_id, current_time); } // modify_updates for (const auto& dev : ledger_update_xml->modify_updates) { modify_entries.emplace_back(dev.terminal_id, current_time); } // delete_updates for (const auto& dev : ledger_update_xml->delete_updates) { delete_entries.emplace_back(dev.terminal_id, current_time); } // 写入日志 if (!new_entries.empty()) { log_file << "\n"; for (const auto& entry : new_entries) { write_log_entry(log_file, "add", entry.first, entry.second); } log_file << "\n"; } if (!modify_entries.empty()) { log_file << "\n"; for (const auto& entry : modify_entries) { write_log_entry(log_file, "modify", entry.first, entry.second); } log_file << "\n"; } if (!delete_entries.empty()) { log_file << "\n"; for (const auto& entry : delete_entries) { write_log_entry(log_file, "delete", entry.first, entry.second); } log_file << "\n"; } log_file.close(); std::cout << "Ledger log has been updated." << std::endl; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////shell打印日志 // ------------------ 全局日志列表和锁 ------------------ std::list errorList; std::list warnList; std::list normalList; pthread_mutex_t errorListMutex = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_t warnListMutex = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_t normalListMutex = PTHREAD_MUTEX_INITIALIZER; // ------------------ 输出开关 ------------------ bool errorOutputEnabled = false; // 是否将 error 级别写入 errorList bool warnOutputEnabled = false; // 是否将 warn 级别写入 warnList bool normalOutputEnabled = false; // 是否将 normal 级别写入 normalList // ------------------ 用于恢复原始缓冲区 ------------------ static std::streambuf* g_originalCoutBuf = NULL; static std::streambuf* g_originalClogBuf = NULL; static std::streambuf* g_originalCerrBuf = NULL; // ------------------ 日志级别枚举(C++98) ------------------ enum LogLevel { LOGERROR, LOGWARN, LOGNORMAL }; // ------------------------------------------------------------------ // TeeStreamBuf: 先写到原始buf(保持终端输出),再拷贝到list里 // ------------------------------------------------------------------ class TeeStreamBuf : public std::streambuf { public: // 默认构造:先把指针设为NULL TeeStreamBuf() : m_originalBuf(NULL), m_level(LOGNORMAL) { pthread_mutex_init(&m_mutex, NULL); } // 带参构造:直接初始化 TeeStreamBuf(std::streambuf* originalBuf, LogLevel level) : m_originalBuf(originalBuf), m_level(level) { pthread_mutex_init(&m_mutex, NULL); } // 析构函数:销毁互斥锁 virtual ~TeeStreamBuf() { pthread_mutex_destroy(&m_mutex); } // 自定义 init(...) 函数:在同一个对象上重新设置 void init(std::streambuf* originalBuf, LogLevel level) { m_originalBuf = originalBuf; m_level = level; pthread_mutex_lock(&m_mutex); m_buffer.clear(); pthread_mutex_unlock(&m_mutex); } protected: // 当 flush 或 std::endl 时会调用 sync() virtual int sync() { // 先让原始缓冲区执行同步 if (m_originalBuf) { m_originalBuf->pubsync(); } // 再将自身的缓存 flush flushBuffer(); return 0; // 成功 } // 当写入一个新字符时,overflow() 被调用 virtual int_type overflow(int_type ch) { if (ch == traits_type::eof()) { return ch; } // 1) 写到原始缓冲区 → 保留终端输出 if (m_originalBuf) { if (m_originalBuf->sputc(static_cast(ch)) == traits_type::eof()) { return traits_type::eof(); } } // 2) 存到我们的临时缓存,注意加锁保护 pthread_mutex_lock(&m_mutex); //防止多线程推入崩溃lnk20250305 m_buffer.push_back(static_cast(ch)); // 3) 遇到换行就 flushBuffer() if (ch == '\n') { flushBuffer_locked(); } pthread_mutex_unlock(&m_mutex); return ch; } private: // 内部版本:假定互斥锁已经被加锁 void flushBuffer_locked() { if (m_buffer.empty()) { return; } // 根据等级和对应开关,将 m_buffer 写入相应的 list switch (m_level) { case LOGERROR: if (normalOutputEnabled) { pthread_mutex_lock(&normalListMutex); normalList.push_back(m_buffer); pthread_mutex_unlock(&normalListMutex); } else if (warnOutputEnabled) { pthread_mutex_lock(&warnListMutex); warnList.push_back(m_buffer); pthread_mutex_unlock(&warnListMutex); } else if (errorOutputEnabled) { pthread_mutex_lock(&errorListMutex); errorList.push_back(m_buffer); pthread_mutex_unlock(&errorListMutex); } break; case LOGWARN: if (normalOutputEnabled) { pthread_mutex_lock(&normalListMutex); normalList.push_back(m_buffer); pthread_mutex_unlock(&normalListMutex); } else if (warnOutputEnabled) { pthread_mutex_lock(&warnListMutex); warnList.push_back(m_buffer); pthread_mutex_unlock(&warnListMutex); } break; case LOGNORMAL: if (normalOutputEnabled) { pthread_mutex_lock(&normalListMutex); normalList.push_back(m_buffer); pthread_mutex_unlock(&normalListMutex); } break; } m_buffer.clear(); } // 对外接口,内部对 m_buffer 加锁 void flushBuffer() { pthread_mutex_lock(&m_mutex); flushBuffer_locked(); pthread_mutex_unlock(&m_mutex); } private: // 禁止自动生成的赋值函数 TeeStreamBuf& operator=(const TeeStreamBuf&); private: std::streambuf* m_originalBuf; LogLevel m_level; std::string m_buffer; pthread_mutex_t m_mutex; }; // ------------------ 全局Tee对象(避免重复赋值) ------------------ static TeeStreamBuf g_errorTeeBuf; static TeeStreamBuf g_warnTeeBuf; static TeeStreamBuf g_normalTeeBuf; // ------------------ 重定向函数 ------------------ // 只在第一次启用时,用 init(...) 初始化 TeeStreamBuf; // 之后再启用时,不再 new 或 赋值,而是直接用之前构造好的对象。 void redirectErrorOutput(bool enabled) { errorOutputEnabled = enabled; if (enabled) { if (g_originalCerrBuf == NULL) { g_originalCerrBuf = std::cerr.rdbuf(); g_errorTeeBuf.init(g_originalCerrBuf, LOGERROR); } std::cerr.rdbuf(&g_errorTeeBuf); } else { if (g_originalCerrBuf) { std::cerr.rdbuf(g_originalCerrBuf); } } } void redirectWarnOutput(bool enabled) { warnOutputEnabled = enabled; if (enabled) { if (g_originalClogBuf == NULL) { g_originalClogBuf = std::clog.rdbuf(); g_warnTeeBuf.init(g_originalClogBuf, LOGWARN); } std::clog.rdbuf(&g_warnTeeBuf); } else { if (g_originalClogBuf) { std::clog.rdbuf(g_originalClogBuf); } } } void redirectNormalOutput(bool enabled) { normalOutputEnabled = enabled; if (enabled) { if (g_originalCoutBuf == NULL) { g_originalCoutBuf = std::cout.rdbuf(); g_normalTeeBuf.init(g_originalCoutBuf, LOGNORMAL); } std::cout.rdbuf(&g_normalTeeBuf); } else { if (g_originalCoutBuf) { std::cout.rdbuf(g_originalCoutBuf); } } } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////脚本控制 void execute_bash(string fun,int process_num,string type) { // 为 char 数组分配足够的空间 char p_num_str[20]; // 使用 sprintf 转换 std::sprintf(p_num_str, "%d", process_num); const char* script = std::string(FRONT_PATH + "/bin/set_process.sh").c_str();//使用setsid防止端口占用 const char* param1 = fun.c_str(); const char* param2 = p_num_str; const char* param3 = type.c_str(); // 构造完整的命令 char command[256]; snprintf(command, sizeof(command), "%s %s %s %s &", script, param1, param2, param3); std::cout << "command:" << command < *ctopiclist, const std::string& path) { // 注释同原代码 const std::string strPhasic[4] = { "A", "B", "C", "T" }; const std::string strLine[4] = { "AB", "BC", "CA", "T" }; int nStart = 1, nEnd = 1; std::string strValueTemp; tinyxml2::XMLDocument doc; std::string xmlFile; if (path == "not define") { xmlFile = FRONT_PATH + "/etc/" + DEFAULT_CONFIG_FN; //默认映射文件 std::cout << "[调试] 加载XML路径: " << xmlFile << std::endl; } else { xmlFile = FRONT_PATH + "/dat/" + path + ".xml"; std::cout << "[调试] 加载XML路径: " << xmlFile << std::endl; } auto result = doc.LoadFile(xmlFile.c_str()); if (result != tinyxml2::XML_SUCCESS) { std::cout << "[错误] 无法打开XML文件: " << xmlFile << std::endl; return false; } tinyxml2::XMLElement* root = doc.RootElement(); if (!root) { std::cout << "[错误] XML无根节点!" << std::endl; return false; } for (tinyxml2::XMLElement* topicElem = root->FirstChildElement(); topicElem; topicElem = topicElem->NextSiblingElement()) { std::string strTag = topicElem->Name(); if (strTag == "Topic") { CTopic* topic = new CTopic(); topic->strTopic = topicElem->Attribute("name") ? topicElem->Attribute("name") : ""; ctopiclist->push_back(topic); std::cout << "[调试] 解析Topic节点: " << topic->strTopic << std::endl; // HISDATA、RTDATA if (topic->strTopic == "HISDATA" || topic->strTopic == "RTDATA") { for (tinyxml2::XMLElement* dataTypeElem = topicElem->FirstChildElement(); dataTypeElem; dataTypeElem = dataTypeElem->NextSiblingElement()) { if (std::string(dataTypeElem->Name()) == "DataType") { CDataType* dt = new CDataType(); dt->iDataType = dataTypeElem->IntAttribute("value", 0); dt->type = dataTypeElem->Attribute("type") ? dataTypeElem->Attribute("type") : ""; dt->BaseFlag0 = 0; dt->BaseFlag1 = 0; topic->DataTypeList.push_back(dt); // 监测点 for (tinyxml2::XMLElement* monitorElem = dataTypeElem->FirstChildElement(); monitorElem; monitorElem = monitorElem->NextSiblingElement()) { if (std::string(monitorElem->Name()) == "Monitor") { CMonitor* mt = new CMonitor(); mt->strMonitor = monitorElem->Attribute("name") ? monitorElem->Attribute("name") : ""; mt->type = monitorElem->Attribute("type") ? monitorElem->Attribute("type") : ""; dt->MonitorList.push_back(mt); // 数据项 for (tinyxml2::XMLElement* itemElem = monitorElem->FirstChildElement(); itemElem; itemElem = itemElem->NextSiblingElement()) { if (std::string(itemElem->Name()) == "Item") { CItem* it = new CItem(); it->strItemName = itemElem->Attribute("name") ? itemElem->Attribute("name") : ""; it->type = itemElem->Attribute("type") ? itemElem->Attribute("type") : ""; if (it->strItemName == "FLAG") it->strItemValue = itemElem->Attribute("value") ? itemElem->Attribute("value") : ""; mt->ItemList.push_back(it); // Sequence for (tinyxml2::XMLElement* seqElem = itemElem->FirstChildElement(); seqElem; seqElem = seqElem->NextSiblingElement()) { if (std::string(seqElem->Name()) == "Sequence") { std::string strPhase = seqElem->Attribute("value") ? seqElem->Attribute("value") : ""; // 读取ABC三相 if ((!xml_flag || it->strItemName != "V") && strPhase == "7") { for (int n = 0; n < 3; ++n) { CSequence* sq = new CSequence(); sq->strSValue = strPhase; sq->type = seqElem->Attribute("type") ? seqElem->Attribute("type") : ""; sq->strSeq = strPhasic[n]; it->SequenceList.push_back(sq); for (tinyxml2::XMLElement* valueElem = seqElem->FirstChildElement(); valueElem; valueElem = valueElem->NextSiblingElement()) { if (std::string(valueElem->Name()) == "Value") { std::string strDVName = valueElem->Attribute("name") ? valueElem->Attribute("name") : ""; std::string strDAName = valueElem->Attribute("DA") ? valueElem->Attribute("DA") : ""; // l_phs* → phsAB if (strDAName.find("l_phs*") != std::string::npos) { strDAName.replace(strDAName.find("l_phs"), 5, "phs"); size_t starPos = strDAName.find("*"); if (starPos != std::string::npos) strDAName.replace(starPos, 1, strLine[n]); } else if (strDAName.find("phs*") != std::string::npos) { size_t starPos = strDAName.find("*"); if (starPos != std::string::npos) strDAName.replace(starPos, 1, sq->strSeq); } // 谐波数据 if (strDVName.find("%") != std::string::npos && strDAName.find("%-") != std::string::npos) { size_t firstP = strDVName.find('%'), lastP = strDVName.rfind('%'); if (firstP != std::string::npos && lastP != std::string::npos && firstP != lastP) { std::string harmRange = strDVName.substr(firstP + 1, lastP - firstP - 1); // 0,49 size_t comma = harmRange.find(','); if (comma != std::string::npos) { nStart = std::stoi(harmRange.substr(0, comma)); nEnd = std::stoi(harmRange.substr(comma + 1)); strValueTemp = "%" + harmRange + "%"; } } // 取DA的范围 size_t lb = strDAName.find('['), rb = strDAName.find(']'); int strDAoffset = 0; if (lb != std::string::npos && rb != std::string::npos && lb < rb) { std::string sub = strDAName.substr(lb + 1, rb - lb - 1); size_t dash = sub.find('-'); if (dash != std::string::npos) strDAoffset = std::stoi(sub.substr(dash + 1)); } int offset = valueElem->IntAttribute("Offset", 0); for (int i = nStart; i <= nEnd; ++i) { std::string strDVNameTemp = strDVName; std::string strDANameTemp = strDAName; CDataValue* dv1 = new CDataValue(); dv1->type = valueElem->Attribute("type") ? valueElem->Attribute("type") : ""; if (valueElem->Attribute("Coefficient")) { dv1->strCoefficient = valueElem->Attribute("Coefficient"); dv1->fCoefficient = valueElem->FloatAttribute("Coefficient", 1.0f); } dv1->strOffset = valueElem->Attribute("Offset") ? valueElem->Attribute("Offset") : ""; dv1->iOffset = offset; dv1->DO = valueElem->Attribute("DO") ? valueElem->Attribute("DO") : ""; size_t tmpPos = strDVNameTemp.find(strValueTemp); if (tmpPos != std::string::npos) strDVNameTemp.replace(tmpPos, strValueTemp.size(), std::to_string(i + offset)); size_t subPos = strDANameTemp.find('['); size_t subPos2 = strDANameTemp.find(']'); if (subPos != std::string::npos && subPos2 != std::string::npos) strDANameTemp.replace(subPos + 1, subPos2 - subPos - 1, std::to_string(i - strDAoffset)); dv1->strName = strDVNameTemp; dv1->DA = strDANameTemp; // BaseFlag, LimitUp, LimitDown if (valueElem->Attribute("BaseFlag")) { dv1->BaseFlag = valueElem->Attribute("BaseFlag"); if (dv1->BaseFlag == "1") dt->BaseFlag1++; else dt->BaseFlag0++; } else { dt->BaseFlag0++; dv1->BaseFlag = "0"; } dv1->LimitUp = valueElem->Attribute("LimitUp") ? valueElem->Attribute("LimitUp") : "not define"; dv1->LimitDown = valueElem->Attribute("LimitDown") ? valueElem->Attribute("LimitDown") : "not define"; if (!dv1->DO.empty() && !dv1->DA.empty()) dv1->strFullName = dv1->DO + "$" + dv1->DA; else dv1->strFullName = "not define"; sq->DataValueList.push_back(dv1); // 调试打印谐波展开 std::cout << "[调试] 谐波展开DataValue: " << dv1->strName << " DA=" << dv1->DA << std::endl; } } else { // 非谐波数据 CDataValue* dv2 = new CDataValue(); dv2->strName = strDVName; dv2->type = valueElem->Attribute("type") ? valueElem->Attribute("type") : ""; if (valueElem->Attribute("Coefficient")) { dv2->strCoefficient = valueElem->Attribute("Coefficient"); dv2->fCoefficient = valueElem->FloatAttribute("Coefficient", 1.0f); } dv2->DO = valueElem->Attribute("DO") ? valueElem->Attribute("DO") : ""; dv2->DA = strDAName; if (valueElem->Attribute("BaseFlag")) { dv2->BaseFlag = valueElem->Attribute("BaseFlag"); if (dv2->BaseFlag == "1") dt->BaseFlag1++; else dt->BaseFlag0++; } else { dt->BaseFlag0++; dv2->BaseFlag = "0"; } dv2->LimitUp = valueElem->Attribute("LimitUp") ? valueElem->Attribute("LimitUp") : "not define"; dv2->LimitDown = valueElem->Attribute("LimitDown") ? valueElem->Attribute("LimitDown") : "not define"; if (valueElem->Attribute("PltFlag") && std::string(valueElem->Attribute("PltFlag")) == "True") dv2->bPlt = true; else dv2->bPlt = false; if (!dv2->DO.empty() && !dv2->DA.empty()) dv2->strFullName = dv2->DO + "$" + dv2->DA; else dv2->strFullName = "not define"; sq->DataValueList.push_back(dv2); std::cout << "[调试] 普通DataValue: " << dv2->strName << " DA=" << dv2->DA << std::endl; } } } } } // T相 if (strPhase == "8") { CSequence* sq = new CSequence(); sq->strSValue = strPhase; sq->type = seqElem->Attribute("type") ? seqElem->Attribute("type") : ""; sq->strSeq = strPhasic[3]; it->SequenceList.push_back(sq); for (tinyxml2::XMLElement* valueElem = seqElem->FirstChildElement(); valueElem; valueElem = valueElem->NextSiblingElement()) { if (std::string(valueElem->Name()) == "Value") { CDataValue* dv2 = new CDataValue(); dv2->strName = valueElem->Attribute("name") ? valueElem->Attribute("name") : ""; dv2->type = valueElem->Attribute("type") ? valueElem->Attribute("type") : ""; if (valueElem->Attribute("Coefficient")) { dv2->strCoefficient = valueElem->Attribute("Coefficient"); dv2->fCoefficient = valueElem->FloatAttribute("Coefficient", 1.0f); } dv2->DO = valueElem->Attribute("DO") ? valueElem->Attribute("DO") : ""; dv2->DA = valueElem->Attribute("DA") ? valueElem->Attribute("DA") : ""; if (valueElem->Attribute("BaseFlag")) { dv2->BaseFlag = valueElem->Attribute("BaseFlag"); if (dv2->BaseFlag == "1") dt->BaseFlag1++; else dt->BaseFlag0++; } else { dt->BaseFlag0++; dv2->BaseFlag = "0"; } dv2->LimitUp = valueElem->Attribute("LimitUp") ? valueElem->Attribute("LimitUp") : "not define"; dv2->LimitDown = valueElem->Attribute("LimitDown") ? valueElem->Attribute("LimitDown") : "not define"; if (!dv2->DO.empty() && !dv2->DA.empty()) dv2->strFullName = dv2->DO + "$" + dv2->DA; else dv2->strFullName = "not define"; sq->DataValueList.push_back(dv2); std::cout << "[调试] T相DataValue: " << dv2->strName << std::endl; } } } } } } } } } } } } // RTDATASOE else if (topic->strTopic == "RTDATASOE") { for (tinyxml2::XMLElement* dataTypeElem = topicElem->FirstChildElement(); dataTypeElem; dataTypeElem = dataTypeElem->NextSiblingElement()) { if (std::string(dataTypeElem->Name()) == "DataType") { CDataType* dt = new CDataType(); dt->iDataType = dataTypeElem->IntAttribute("value", 0); dt->type = dataTypeElem->Attribute("type") ? dataTypeElem->Attribute("type") : ""; topic->DataTypeList.push_back(dt); for (tinyxml2::XMLElement* soeElem = dataTypeElem->FirstChildElement(); soeElem; soeElem = soeElem->NextSiblingElement()) { if (std::string(soeElem->Name()) == "SOE") { CEventData* ed = new CEventData(); ed->triggerFlag = soeElem->Attribute("TriggerFlag") ? soeElem->Attribute("TriggerFlag") : ""; ed->DO = soeElem->Attribute("DO") ? soeElem->Attribute("DO") : ""; ed->DA = soeElem->Attribute("DA") ? soeElem->Attribute("DA") : ""; ed->type = soeElem->Attribute("type") ? soeElem->Attribute("type") : ""; if (!ed->DO.empty() && !ed->DA.empty()) ed->strFullName = ed->DO + "$" + ed->DA; else ed->strFullName = "not define"; dt->SOEList.push_back(ed); std::cout << "[调试] SOE事件: " << ed->strFullName << std::endl; } } } } } // SOEDATA else if (topic->strTopic == "SOEDATA") { for (tinyxml2::XMLElement* dataTypeElem = topicElem->FirstChildElement(); dataTypeElem; dataTypeElem = dataTypeElem->NextSiblingElement()) { if (std::string(dataTypeElem->Name()) == "DataType") { CEventData* ed = new CEventData(); ed->triggerFlag = dataTypeElem->Attribute("name") ? dataTypeElem->Attribute("name") : ""; ed->DO = dataTypeElem->Attribute("DO") ? dataTypeElem->Attribute("DO") : ""; ed->DA = dataTypeElem->Attribute("DA") ? dataTypeElem->Attribute("DA") : ""; ed->type = dataTypeElem->Attribute("type") ? dataTypeElem->Attribute("type") : ""; if (!ed->DO.empty() && !ed->DA.empty()) ed->strFullName = ed->DO + "$" + ed->DA; else ed->strFullName = "not define"; cfg->SOEList.push_back(ed); std::cout << "[调试] SOEDATA事件: " << ed->strFullName << std::endl; } } } } // 其他节点 else if (strTag == "WavePhasic") { const char* flagAttr = topicElem->Attribute("Flag"); if (flagAttr) cfg->WavePhasicFlag = flagAttr; if (!cfg->WavePhasicFlag.empty() && cfg->WavePhasicFlag == "1") { const char* aAttr = topicElem->Attribute("A"); if (aAttr) cfg->WavePhasicA = aAttr; const char* bAttr = topicElem->Attribute("B"); if (bAttr) cfg->WavePhasicB = bAttr; const char* cAttr = topicElem->Attribute("C"); if (cAttr) cfg->WavePhasicC = cAttr; } std::cout << "[调试] WavePhasic解析" << std::endl; } else if (strTag == "UnitOfTime") { const char* unitAttr = topicElem->Attribute("Unit"); if (unitAttr) cfg->UnitOfTimeUnit = unitAttr; std::cout << "[调试] UnitOfTime解析" << std::endl; } else if (strTag == "ValueOfTime") { const char* unitAttr = topicElem->Attribute("Unit"); if (unitAttr) cfg->ValueOfTimeUnit = unitAttr; std::cout << "[调试] ValueOfTime解析" << std::endl; } else if (strTag == "ComtradeFile") { const char* waveTimeFlag = topicElem->Attribute("WaveTimeFlag"); if (waveTimeFlag) cfg->WaveTimeFlag = waveTimeFlag; std::cout << "[调试] ComtradeFile解析" << std::endl; } else if (strTag == "IED") { const char* nameAttr = topicElem->Attribute("name"); if (nameAttr) cfg->IEDname = nameAttr; std::cout << "[调试] IED解析" << std::endl; } else if (strTag == "LDevice") { const char* prefixAttr = topicElem->Attribute("Prefix"); if (prefixAttr) cfg->LDevicePrefix = prefixAttr; std::cout << "[调试] LDevice解析" << std::endl; } } return true; } //清理旧的映射配置 void clearXmlConfigAndTopicList(Xmldata* data) { // 清空 XmlConfig data->xmlcfg = XmlConfig(); // 通过重新赋值重置 xmlcfg // 清空 topicList std::list::iterator it; for (it = data->topicList.begin(); it != data->topicList.end(); ++it) { delete *it; // 释放内存 } data->topicList.clear(); // 清空链表 } //配置映射文件单个 void Set_xml_nodeinfo_one(const std::string& dev_type) { bool ret = false; // 假定 xmlinfo_list 是 std::map if (xmlinfo_list.count(dev_type) && xmlinfo_list[dev_type] != nullptr) { // 已存在这个类型的节点 if (xmlinfo_list[dev_type]->updataflag == true) { // 需要更新 // 删除这个点的 xmlcfg 和 topicList clearXmlConfigAndTopicList(xmlinfo_list[dev_type]); ret = ParseXMLConfig2( 0, &(xmlinfo_list[dev_type]->xmlcfg), &(xmlinfo_list[dev_type]->topicList), xmlinfo_list[dev_type]->xmlbase.MODEL_ID ); if (!ret) { std::cout << "!!!! this ledger xml config fail!!!!" << std::endl; } } } else { std::cout << "xmlinfo_list not contain this devtype" << std::endl; } // 处理角形 if (isdelta_flag) { if (xmlinfo_list2.count(dev_type) && xmlinfo_list2[dev_type] != nullptr) { // 已存在 if (xmlinfo_list2[dev_type]->updataflag == true) { // 需要更新 // 删除这个点的 xmlcfg 和 topicList clearXmlConfigAndTopicList(xmlinfo_list2[dev_type]); ret = ParseXMLConfig2( 1, &(xmlinfo_list2[dev_type]->xmlcfg), &(xmlinfo_list2[dev_type]->topicList), xmlinfo_list2[dev_type]->xmlbase.MODEL_ID ); if (!ret) { std::cout << "!!!! this ledger xml config fail!!!!" << std::endl; } } } else { std::cout << "xmlinfo_list2 not contain this devtype" << std::endl; } } } //配置映射文件全部 void Set_xml_nodeinfo() { // 配置无对应 xml 文件时的默认解析配置 if (!DEFAULT_CONFIG_FN.empty()) { //可改为在配置中读取默认映射文件名 std::string path = "not define"; ParseXMLConfig2(0, &xmlcfg, &topicList, path); // 调用 ParseXMLConfig() 解析 JiangSu_Config.xml 配置文件 if (isdelta_flag) { ParseXMLConfig2(1, &xmlcfg2, &topicList2, path); // 角型接线 } } if (!xmlinfo_list.empty()) { std::cout << "!!!!!!!!!! xmlinfo_list.size() != 0 !!!!!!!!!!!" << std::endl; for (auto& pair : xmlinfo_list) { const std::string& key = pair.first; Xmldata* value = pair.second; if (value && value->updataflag) { ParseXMLConfig2(0, &(value->xmlcfg), &(value->topicList), value->xmlbase.MODEL_ID); } } if (isdelta_flag) { for (auto& pair : xmlinfo_list2) { const std::string& key = pair.first; Xmldata* value = pair.second; if (value && value->updataflag) { ParseXMLConfig2(1, &(value->xmlcfg), &(value->topicList), value->xmlbase.MODEL_ID); } } } } else { std::cout << "!!!!!!!!!! xmlinfo_list.size() == 0 !!!!!!!!!!!" << std::endl; } } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////录播波形匹配 // 工具函数:解析 "dd/MM/yyyy,hh:mm:ss.zzz" 格式的字符串为时间戳(毫秒) static long long parse_datetime_to_epoch_ms(const std::string& dt_str) { std::tm tm = {}; char dummy; int ms = 0; std::istringstream ss(dt_str); ss >> std::get_time(&tm, "%d/%m/%Y,%H:%M:%S") >> dummy >> ms; if (ss.fail()) return 0; std::time_t time_sec = std::mktime(&tm); if (time_sec < 0) return 0; return static_cast(time_sec) * 1000 + ms; } // 主函数:从 .cfg 文件中提取起始和触发时间戳(毫秒) bool extract_timestamp_from_cfg_file(const std::string& cfg_path, long long& start_tm, long long& trig_tm) { struct stat st; if (stat(cfg_path.c_str(), &st) != 0) { std::cerr << "File not found: " << cfg_path << "\n"; return false; } std::ifstream infile(cfg_path); if (!infile) { std::cerr << "Cannot open file: " << cfg_path << "\n"; return false; } std::string line, prev_line, current_line; while (std::getline(infile, line)) { // 去除前后空白 line.erase(line.find_last_not_of(" \t\r\n") + 1); line.erase(0, line.find_first_not_of(" \t\r\n")); std::string upper = line; std::transform(upper.begin(), upper.end(), upper.begin(), ::toupper); if (upper == "ASCII" || upper == "BINARY") break; prev_line = current_line; current_line = line; } if (prev_line.length() > 3) prev_line = prev_line.substr(0, prev_line.length() - 3); if (current_line.length() > 3) current_line = current_line.substr(0, current_line.length() - 3); start_tm = parse_datetime_to_epoch_ms(prev_line); trig_tm = parse_datetime_to_epoch_ms(current_line); return start_tm > 0 && trig_tm > 0; } bool compare_qvvr_and_file(const std::string& cfg_path, const std::vector& data_list,qvvr_data& matched_data) { long long start_tm = 0; long long trig_tm = 0; // 提取 .cfg 文件中的时间戳 if (!extract_timestamp_from_cfg_file(cfg_path, start_tm, trig_tm)) { std::cerr << "Failed to extract timestamp from cfg file: " << cfg_path << "\n"; return false; } // 遍历所有暂态事件,查找与 trig_tm 匹配的 for (const auto& data : data_list) { long long diff = static_cast(data.QVVR_time) - trig_tm; if (std::abs(diff) <= 1) { matched_data = data; // 返回匹配到的事件 return true; } } return false; } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////数据转换函数 // DataArrayItem to_json void to_json(nlohmann::json& j, const DataArrayItem& d) { j = nlohmann::json{ {"DataAttr", d.DataAttr}, {"DataTimeSec", d.DataTimeSec}, {"DataTimeUSec", d.DataTimeUSec}, {"DataTag", d.DataTag}, {"Data", d.Data} }; } // MsgObj to_json void to_json(nlohmann::json& j, const MsgObj& m) { j = nlohmann::json{ {"Cldid", m.Cldid}, {"DataType", m.DataType}, {"DataAttr", m.DataAttr}, {"DsNameIdx", m.DsNameIdx}, {"DataArray", m.DataArray} }; } // FullObj to_json void to_json(nlohmann::json& j, const FullObj& f) { j = nlohmann::json{ {"Id", f.mac}, {"Mid", f.Mid}, {"Did", f.Did}, {"Pri", f.Pri}, {"Type", f.Type}, {"Msg", f.Msg} }; } std::string generate_json( const std::string mac, int Mid, //需应答的报文订阅者收到后需以此ID应答,无需应答填入“-1” int Did, //设备唯一标识Ldid,填入0代表Ndid。 int Pri, //报文处理的优先级 int Type, //消息类型 int Cldid, //逻辑子设备ID,0-逻辑设备本身,无填-1 int DataType, //数据类型,0-表示以数据集方式上送 int DataAttr, //数据属性:无“0”、实时“1”、统计“2”等。 int DsNameIdx, //数据集序号(以数据集方式上送),无填-1 const std::vector& dataArray //数据数组。 ) { FullObj fobj; fobj.mac = mac; fobj.Mid = Mid; fobj.Did = Did; fobj.Pri = Pri; fobj.Type = Type; fobj.Msg.Cldid = Cldid; fobj.Msg.DataType = DataType; fobj.Msg.DataAttr = DataAttr; fobj.Msg.DsNameIdx = DsNameIdx; fobj.Msg.DataArray = dataArray; nlohmann::json j = fobj; return j.dump(); // 输出标准 json 字符串 } void upload_data_test(){ std::vector arr; arr.push_back({1, 1725477660, 0, 1, "xxxx"}); //数据属性 -1-无, 0-“Rt”,1-“Max”,2-“Min”,3-“Avg”,4-“Cp95” //数据时标,相对1970年的秒,无效填入“-1” //数据时标,微秒钟,无效填入“-1” //数据标识,1-标识数据异常 //数据序列(数据集上送时将二进制数据流转换成Base64字符串,其他数据为object) arr.push_back({2, 1691741340, 0, 1, "yyyy"}); std::string js = generate_json( "123",-1, 1, 1, 4866, 1, 0, 2, 1, arr ); std::cout << js << std::endl; queue_data_t data; data.monitor_no = 1; data.strTopic = TOPIC_ALARM; data.strText = js; data.mp_id = "test"; std::lock_guard lock(queue_data_list_mutex); queue_data_list.push_back(data); } //////////////////////////////////////////////////////////////////////////////////////////////////台账赋值给通信 std::vector GenerateDeviceInfoFromLedger(const std::vector& terminal_devlist) { std::vector devices; for (const auto& terminal : terminal_devlist) { DeviceInfo device; device.device_id = terminal.terminal_id; device.name = terminal.terminal_name; device.model = terminal.dev_type; device.mac = terminal.addr_str; device.status = 1; for (const auto& monitor : terminal.line) { PointInfo point; point.point_id = monitor.monitor_id; point.name = monitor.monitor_name; point.device_id = terminal.terminal_id; point.PT1 = monitor.PT1; point.PT2 = monitor.PT2; point.CT1 = monitor.CT1; point.CT2 = monitor.CT2; point.strScale = monitor.voltage_level; point.nCpuNo = std::stoi(monitor.logical_device_seq); point.nPTType = std::stoi(monitor.terminal_connect); device.points.push_back(point); } devices.push_back(device); } return devices; } ////////////////////////////////////////////////////////////////////////////////////////////////////录波文件通知 bool assign_qvvr_file_list(const std::string& id, ushort nCpuNo, const std::vector& file_list_raw) { std::vector file_names; // 1. 提取文件名部分 for (const auto& full_path : file_list_raw) { size_t pos = full_path.find_last_of("/\\"); if (pos != std::string::npos && pos + 1 < full_path.size()) { file_names.push_back(full_path.substr(pos + 1)); } else { file_names.push_back(full_path); } } // 2. 遍历终端 for (auto& dev : terminal_devlist) { if (dev.terminal_id == id) { //根据终端id匹配终端 for (auto& monitor : dev.line) { try { ushort monitor_seq = static_cast(std::stoi(monitor.logical_device_seq)); if (monitor_seq == nCpuNo) { //根据监测点编号匹配监测点 // 构造 qvvr_file qvvr_file qfile; qfile.file_name.assign(file_names.begin(), file_names.end()); //终端文件列表 qfile.is_download = false; qfile.is_pair = false; qfile.file_time_count = 0; qfile.used_status =true; // 添加到唯一的 qvvrevent monitor.qvvrevent.qvvrfile.push_back(std::move(qfile)); //记录暂态文件组 return true; } } catch (...) { continue; } } } } return false; } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////下载成功通知 //提取下载路径的文件名 std::string extract_filename1(const std::string& path) { size_t pos = path.find_last_of("/\\"); return (pos != std::string::npos) ? path.substr(pos + 1) : path; } //发送匹配的所有录波文件 bool SendAllQvvrFiles(qvvr_file& qfile, std::string& out_wavepath) { std::vector wavepaths; std::string first_wavepath; bool send_success = true; for (const auto& file_localpath : qfile.file_download) { std::string file_cloudpath = "comtrade/" + file_localpath; std::string wavepath_result; // 发送本地文件到远端,返回 wavepath SOEFileWeb(const_cast(file_localpath), file_cloudpath, wavepath_result); // 如果失败,重发一次 if (wavepath_result.empty()) { std::cerr << "[SOEFileWeb] Warning: first send failed for file: " << file_localpath << ", retrying...\n"; SOEFileWeb(const_cast(file_localpath), file_cloudpath, wavepath_result); } if (wavepath_result.empty()) { send_success = false; std::cerr << "[SOEFileWeb] Failed: wavepath empty for file: " << file_localpath << "\n"; break; } if (wavepaths.empty()) { first_wavepath = wavepath_result; } else if (wavepath_result != first_wavepath) { send_success = false; std::cerr << "[SOEFileWeb] Mismatch wavepath: " << wavepath_result << " vs " << first_wavepath << "\n"; break; } wavepaths.push_back(wavepath_result); } // 检查数量是否一致 if (!send_success || wavepaths.size() != qfile.file_download.size()) { std::cerr << "[SOEFileWeb] Failed to send all qvvr files. " << "Sent: " << wavepaths.size() << ", Expected: " << qfile.file_download.size() << "\n"; return false; } out_wavepath = first_wavepath; // 返回统一的 wavepath std::cout << "[SOEFileWeb] Success: all files sent for qfile. Wavepath = " << first_wavepath << "\n"; return true; } //文件下载结束接口 bool update_qvvr_file_download(const std::string& filename_with_mac, const std::string& terminal_id) { //台账加锁 std::lock_guard lock(ledgermtx); // 去除 mac 路径前缀,仅保留文件名 std::string filename = extract_filename1(filename_with_mac); // 提取逻辑序号(如 PQM1 → 1) size_t under_pos = filename.find('_'); if (under_pos == std::string::npos) return false; std::string type_part = filename.substr(0, under_pos); // PQMonitor_PQM1 size_t num_start = type_part.find_last_not_of("0123456789"); if (num_start == std::string::npos || num_start + 1 >= type_part.size()) return false; std::string seq_str = type_part.substr(num_start + 1); ushort logical_seq = static_cast(std::stoi(seq_str)); // 逻辑序号 //找终端 for (auto& dev : terminal_devlist) { if (dev.terminal_id != terminal_id) continue; //找监测点 for (auto& monitor : dev.line) { try { // 将监测点台账中的 logical_device_seq 转换为数字进行匹配 ushort monitor_seq = static_cast(std::stoi(monitor.logical_device_seq)); if (monitor_seq != logical_seq) continue; } catch (...) { continue; // logical_device_seq 非法,跳过 } // 匹配监测点下 qvvrfile 中的 file_name for (size_t i = 0; i < monitor.qvvrevent.qvvrfile.size(); ++i) { auto& qfile = monitor.qvvrevent.qvvrfile[i]; // file_name 中是文件名,需与提取的 filename 比较 auto it = std::find(qfile.file_name.begin(), qfile.file_name.end(), filename); //找到匹配文件名 if (it != qfile.file_name.end()) { // 添加到 file_download(记录完整路径,避免重复) if (std::find(qfile.file_download.begin(), qfile.file_download.end(), filename_with_mac) == qfile.file_download.end()) { qfile.file_download.push_back(filename_with_mac); } qfile.file_time_count = 0; // 任一录波文件下载后,计时归零 // file_download 中是完整路径,需提取文件名后与 file_name 做集合比较 std::set s_name(qfile.file_name.begin(), qfile.file_name.end()); std::set s_down; for (const auto& path : qfile.file_download) { s_down.insert(extract_filename1(path)); // 提取每个路径中的文件名 } // 检查 file_download 是否与 file_name 完全一致(集合相同) if (s_name == s_down) { qfile.is_download = true; // 全部下载完成 // 找到其中的 .cfg 文件进行匹配 for (const auto& fpath : qfile.file_download) { std::string fname = extract_filename1(fpath); if (fname.size() >= 4 && fname.substr(fname.size() - 4) == ".cfg") { // 提取文件时标和监测点事件的时标匹配 qvvr_data matched; if (compare_qvvr_and_file(fpath, monitor.qvvrevent.qvvrdata,matched)) { qfile.is_pair = true; // 文件与事件匹配成功 // 发送所有文件(已下载完成) std::string wavepath; if (SendAllQvvrFiles(qfile, wavepath)) { //文件发送成功后更新事件 transfer_json_qvvr_data(terminal_id, logical_seq, matched.QVVR_Amg, matched.QVVR_PerTime, matched.QVVR_time, matched.QVVR_type, matched.phase, wavepath); // 删除上传成功的文件 for (const auto& uploaded_file : qfile.file_download) { if (std::remove(uploaded_file.c_str()) != 0) { std::cerr << "[Cleanup] Failed to delete file: " << uploaded_file << "\n"; } else { std::cout << "[Cleanup] Deleted uploaded file: " << uploaded_file << "\n"; } } // 清除已发送的暂态文件 monitor.qvvrevent.qvvrfile.erase(monitor.qvvrevent.qvvrfile.begin() + i); //清除暂态事件 auto it = std::find_if( monitor.qvvrevent.qvvrdata.begin(), monitor.qvvrevent.qvvrdata.end(), [&](const qvvr_data& d) { return d.QVVR_time == matched.QVVR_time; }); if (it != monitor.qvvrevent.qvvrdata.end()) { monitor.qvvrevent.qvvrdata.erase(it); } } } break; // 只处理第一个 cfg 文件 } } } else{ std::cout << "qvvr file still imcomplete!!!" << std::endl; } return true; // 当前文件处理成功 } } std::cout << "file name doesnt match any file in this monitor!!!" << std::endl; } } return false; // 未匹配到终端ID或逻辑序号对应的监测点 } ////////////////////////////////////////////////////////////////////////////////////////提取mac std::string normalize_mac(const std::string& mac) { std::string result = mac; result.erase(std::remove(result.begin(), result.end(), '-'), result.end()); return result; }