Files
front_linux/LFtid1056/cloudfront/code/cfg_parser.cpp
2025-08-04 13:28:51 +08:00

2933 lines
123 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/////////////////////////////////////////////////////////////////////////////////////////////////
#include <ctime>
#include <unistd.h>
#include <string>
#include <iostream>
#include <list>
#include <sstream> //流解析
#include <set> //写去重的设备类型
#include <fstream> //打开文件
#include <algorithm>
#include <cctype>
#include <dirent.h>
#include <vector>
#include <array>
#include <map>
#include <mutex>
#include <thread>
#include <atomic>
#include <cstdio>
#include <sys/stat.h>
#include <fnmatch.h>
#include <memory>
/////////////////////////////////////////////////////////////////////////////////////////////////
#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_t> queue_data_list; //queue发送数据链表
extern int three_secs_enabled;
extern std::map<std::string, Xmldata*> xmlinfo_list;//保存所有型号对应的icd映射文件解析数据
extern XmlConfig xmlcfg;//星形接线xml节点解析的数据-默认映射文件解析数据
extern std::list<CTopic *> topicList; //队列发送主题链表
extern XmlConfig xmlcfg2;//角型接线xml节点解析的数据-默认映射文件解析数据
extern std::list<CTopic*> topicList2; //角型接线发送主题链表
extern std::map<std::string, Xmldata*> xmlinfo_list2;//保存所有型号角形接线对应的icd映射文件解析数据
////////////////////////////////////////////////////////////////////////////////////////////////////
//补招
std::list<JournalRecall> 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<std::string> 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<std::string, std::string*> strMap;
std::unordered_map<std::string, int*> 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<std::chrono::milliseconds>(
now.time_since_epoch()
).count();
return static_cast<double>(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<std::pair<std::string, time_t>> 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<std::string, time_t>& a, const std::pair<std::string, time_t>& 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<std::string, std::string>& 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<std::string, std::string> 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<std::string, std::list<JournalRecall>> 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<JournalRecall>& 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<long long>(std::mktime(&tm));
}
// 数据完整性补招判断划分为1小时
void Get_Recall_Time_Char(const std::string& start_time_str,
const std::string& end_time_str,
std::vector<RecallInfo>& 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> 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<JournalRecall> 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<std::string>();
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<std::string>();
// 判断此监测点是否归属当前进程
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<std::string>();
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<std::string>();
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> 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<time_t>(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<time_t>(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<std::mutex> 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<std::string, terminal_dev>& 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<std::string> 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<std::string> 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 <ledger_update> 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 <add>, <modify>, or <delete> 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<std::string> 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<long long>(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<long long>(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<size_t>(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_update_xml_t> 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<std::mutex> 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 &current_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<std::pair<std::string, std::string>> new_entries;
std::vector<std::pair<std::string, std::string>> modify_entries;
std::vector<std::pair<std::string, std::string>> 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 << "<new>\n";
for (const auto& entry : new_entries) {
write_log_entry(log_file, "add", entry.first, entry.second);
}
log_file << "</new>\n";
}
if (!modify_entries.empty()) {
log_file << "<modify>\n";
for (const auto& entry : modify_entries) {
write_log_entry(log_file, "modify", entry.first, entry.second);
}
log_file << "</modify>\n";
}
if (!delete_entries.empty()) {
log_file << "<delete>\n";
for (const auto& entry : delete_entries) {
write_log_entry(log_file, "delete", entry.first, entry.second);
}
log_file << "</delete>\n";
}
log_file.close();
std::cout << "Ledger log has been updated." << std::endl;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////shell打印日志
// ------------------ 全局日志列表和锁 ------------------
std::list<std::string> errorList;
std::list<std::string> warnList;
std::list<std::string> 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<char>(ch)) == traits_type::eof()) {
return traits_type::eof();
}
}
// 2) 存到我们的临时缓存,注意加锁保护
pthread_mutex_lock(&m_mutex); //防止多线程推入崩溃lnk20250305
m_buffer.push_back(static_cast<char>(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 <<std::endl;
// 执行命令
system(command);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////打印台账更新
void print_monitor(const ledger_monitor& mon) {
auto safe = [](const std::string& s) { return s.empty() ? "N/A" : s; };
std::cout << "Monitor ID: " << safe(mon.monitor_id) << "\n";
std::cout << "Terminal ID: " << safe(mon.terminal_id) << "\n";
std::cout << "Monitor Name: " << safe(mon.monitor_name) << "\n";
std::cout << "Logical Device Sequence: " << safe(mon.logical_device_seq) << "\n";
std::cout << "Voltage Level: " << safe(mon.voltage_level) << "\n";
std::cout << "Terminal Connect: " << safe(mon.terminal_connect) << "\n";
std::cout << "Timestamp: " << safe(mon.timestamp) << "\n";
std::cout << "Status: " << safe(mon.status) << "\n";
std::cout << "CT1: " << mon.CT1 << "\n";
std::cout << "CT2: " << mon.CT2 << "\n";
std::cout << "PT1: " << mon.PT1 << "\n";
std::cout << "PT2: " << mon.PT2 << "\n";
}
void print_terminal(const terminal_dev& tmnl) {
auto safe = [](const std::string& s) { return s.empty() ? "N/A" : s; };
std::cout << "GUID: " << safe(tmnl.guid) << "\n";
std::cout << "Terminal ID: " << safe(tmnl.terminal_id) << "\n";
std::cout << "Terminal Code: " << safe(tmnl.terminal_name)<< "\n";
std::cout << "Organization Name: "<< safe(tmnl.org_name) << "\n";
std::cout << "Maintenance Name: " << safe(tmnl.maint_name) << "\n";
std::cout << "Station Name: " << safe(tmnl.station_name) << "\n";
std::cout << "Factory Name: " << safe(tmnl.tmnl_factory) << "\n";
std::cout << "Terminal Status: " << safe(tmnl.tmnl_status) << "\n";
std::cout << "Device Type: " << safe(tmnl.dev_type) << "\n";
std::cout << "Device Key: " << safe(tmnl.dev_key) << "\n";
std::cout << "Device Series: " << safe(tmnl.dev_series) << "\n";
std::cout << "Address: " << safe(tmnl.addr_str) << "\n";
std::cout << "Port: " << safe(tmnl.port) << "\n";
std::cout << "Timestamp: " << safe(tmnl.timestamp) << "\n";
std::cout << "mac: " << safe(tmnl.mac) << "\n";
for (size_t i = 0; i < 10 && !tmnl.line[i].monitor_id.empty(); ++i) {
std::cout << " Monitor " << (i + 1) << ":\n";
print_monitor(tmnl.line[i]);
}
}
void print_trigger_update_xml(const trigger_update_xml_t& trigger_update) {
std::cout << "Work Updates Count: " << trigger_update.work_updates.size() << "\n";
std::cout << "New Updates Count: " << trigger_update.new_updates.size() << "\n";
std::cout << "Delete Updates Count: " << trigger_update.delete_updates.size() << "\n";
std::cout << "Modify Updates Count: " << trigger_update.modify_updates.size() << "\n\n";
std::cout << "Work Updates:\n";
for (size_t i = 0; i < trigger_update.work_updates.size(); ++i) {
std::cout << "Work Update " << (i + 1) << ":\n";
print_terminal(trigger_update.work_updates[i]);
}
std::cout << "\nNew Updates:\n";
for (size_t i = 0; i < trigger_update.new_updates.size(); ++i) {
std::cout << "New Update " << (i + 1) << ":\n";
print_terminal(trigger_update.new_updates[i]);
}
std::cout << "\nDelete Updates:\n";
for (size_t i = 0; i < trigger_update.delete_updates.size(); ++i) {
std::cout << "Delete Update " << (i + 1) << ":\n";
print_terminal(trigger_update.delete_updates[i]);
}
std::cout << "\nModify Updates:\n";
for (size_t i = 0; i < trigger_update.modify_updates.size(); ++i) {
std::cout << "Modify Update " << (i + 1) << ":\n";
print_terminal(trigger_update.modify_updates[i]);
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////解析模板文件
//解析映射文件
bool ParseXMLConfig2(int xml_flag, XmlConfig *cfg, std::list<CTopic*> *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<CTopic*>::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<std::string, Xmldata*>
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<long long>(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;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////数据转换函数
// 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{
{"Mid", f.Mid},
{"Did", f.Did},
{"Pri", f.Pri},
{"Type", f.Type},
{"Msg", f.Msg}
};
}
std::string generate_json(
int Mid, //需应答的报文订阅者收到后需以此ID应答无需应答填入“-1”
int Did, //设备唯一标识Ldid填入0代表Ndid。
int Pri, //报文处理的优先级
int Type, //消息类型
int Cldid, //逻辑子设备ID0-逻辑设备本身,无填-1
int DataType, //数据类型0-表示以数据集方式上送
int DataAttr, //数据属性无“0”、实时“1”、统计“2”等。
int DsNameIdx, //数据集序号(以数据集方式上送),无填-1
const std::vector<DataArrayItem>& dataArray //数据数组。
) {
FullObj fobj;
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<DataArrayItem> 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(
-1, 2, 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<std::mutex> lock(queue_data_list_mutex);
queue_data_list.push_back(data);
}
//////////////////////////////////////////////////////////////////////////////////////////////////台账赋值给通信
std::vector<DeviceInfo> GenerateDeviceInfoFromLedger(const std::vector<terminal_dev>& terminal_devlist) {
std::vector<DeviceInfo> devices;
for (const auto& terminal : terminal_devlist) {
DeviceInfo device;
device.device_id = terminal.terminal_id;
device.name = terminal.terminal_name;
device.model = terminal.dev_series;
device.mac = terminal.mac;
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;
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<std::string>& file_list_raw) {
std::vector<std::string> 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<ushort>(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.file_start =false;
// 添加到唯一的 qvvrevent
monitor.qvvrevent.qvvrfile.push_back(std::move(qfile)); //记录暂态文件组
return true;
}
} catch (...) {
continue;
}
}
}
}
return false;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////下载成功通知
bool update_qvvr_file_download(const std::string& filename_with_mac, const std::string& terminal_id) {
// 去除 mac 路径前缀
size_t pos = filename_with_mac.find_last_of("/\\");
std::string filename = (pos != std::string::npos) ? filename_with_mac.substr(pos + 1) : 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<ushort>(std::stoi(seq_str));
for (auto& dev : terminal_devlist) {
if (dev.terminal_id != terminal_id) continue;
for (auto& monitor : dev.line) {
try {
ushort monitor_seq = static_cast<ushort>(std::stoi(monitor.logical_device_seq));
if (monitor_seq != logical_seq) continue;
} catch (...) {
continue;
}
// 匹配监测点下 qvvrfile 中的 file_name
for (auto& qfile : monitor.qvvrevent.qvvrfile) {
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) == qfile.file_download.end()) {
qfile.file_download.push_back(filename);
}
qfile.file_time_count = 0;
qfile.file_start = true; //开始下载文件
// 检查 file_download 是否与 file_name 完全一致(集合相同)//每次下载都会对比
std::set<std::string> s_name(qfile.file_name.begin(), qfile.file_name.end());
std::set<std::string> s_down(qfile.file_download.begin(), qfile.file_download.end());
if (s_name == s_down) {
qfile.is_download = true; //全部下载完成
// 找到其中的 .cfg 文件进行匹配
for (const auto& f : qfile.file_download) {
if (f.size() >= 4 && f.substr(f.size() - 4) == ".cfg") {
if (compare_qvvr_and_file(f)) {//提取文件时标和监测点事件的时标匹配
qfile.is_pair = true;
//发送所有文件
//发送暂态事件
}
break; // 只处理第一个 cfg 文件
}
}
}
return true;
}
}
}
}
return false;
}