/////////////////////////////////////////////////////////////////////////////////////////////////

#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 "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映射文件解析数据

////////////////////////////////////////////////////////////////////////////////////////////////////
static std::mutex g_filemenu_cache_mtx;
std::map<std::string, std::vector<tag_dir_info>> g_filemenu_cache;

//补招
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

//实时数据tagkey
std::string G_RT_TAG = "";//tag
std::string G_RT_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_MQCONSUMER_TOPIC_CLOUD = "";//consumer topie
std::string G_MQCONSUMER_TAG_CLOUD = "";//consumer tag
std::string G_MQCONSUMER_KEY_CLOUD = "";//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;

    //添加rt的tagkey
    strMap["Queue.RT_TAG"]        = &G_RT_TAG;
    strMap["Queue.RT_KEY"]        = &G_RT_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.ConsumerTopicCLOUD"]     = &G_MQCONSUMER_TOPIC_CLOUD;
    strMap["RocketMq.ConsumerTagCLOUD"]       = &G_MQCONSUMER_TAG_CLOUD;
    strMap["RocketMq.ConsumerKeyCLOUD"]       = &G_MQCONSUMER_KEY_CLOUD;

    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);
}

//秒时间转为标准时间字符串
// ★新增：将 epoch 秒级时间转成 "YYYY-MM-DD HH:MM:SS"
static std::string epoch_to_datetime_str(long long epoch_sec) {
    char buf[20];
    std::tm tm{};
    time_t t = static_cast<time_t>(epoch_sec);
    localtime_r(&t, &tm);
    std::strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &tm);
    return std::string(buf);
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////实时触发部分

//获取实时触发文件
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_from_mq(const std::string& body)
{
    try {
        // ====== 解析外层 JSON ======
        nlohmann::json root;
        try {
            root = nlohmann::json::parse(body);
        } catch (const std::exception& e) {
            std::cerr << "Error parsing JSON: " << e.what() << std::endl;
            // ★与原逻辑等价：无法解析，不再进入 recall_json_handle
            DIY_ERRORLOG("process","【ERROR】前置的%d号进程处理topic:%s_%s的补招触发消息失败,消息的json结构不正确",
                         g_front_seg_index, FRONT_INST.c_str(), G_MQCONSUMER_TOPIC_RC.c_str());
            return 10004;
        }

        // 提取 "messageBody"（字符串）
        if (!root.contains("messageBody") || !root["messageBody"].is_string()) {
            std::cerr << "'messageBody' is missing or is not a string" << std::endl;
            DIY_ERRORLOG("process","【ERROR】前置的%d号进程处理topic:%s_%s的补招触发消息失败,没有messageBody字段",
                         g_front_seg_index, FRONT_INST.c_str(), G_MQCONSUMER_TOPIC_RC.c_str());
            return 10004;
        }
        std::string messageBodyStr = root["messageBody"].get<std::string>();
        if (messageBodyStr.empty()) {
            std::cerr << "'messageBody' is empty" << std::endl;
            DIY_ERRORLOG("process","【ERROR】前置的%d号进程处理topic:%s_%s的补招触发消息失败,messageBody为空",
                         g_front_seg_index, FRONT_INST.c_str(), G_MQCONSUMER_TOPIC_RC.c_str());
            return 10004;
        }

        // 解析 messageBody 内层 JSON
        nlohmann::json messageBody;
        try {
            messageBody = nlohmann::json::parse(messageBodyStr);
        } catch (const std::exception& e) {
            std::cerr << "Failed to parse 'messageBody' JSON: " << e.what() << std::endl;
            DIY_ERRORLOG("process","【ERROR】前置的%d号进程处理topic:%s_%s的补招触发消息失败,messageBody的json结构不正确",
                         g_front_seg_index, FRONT_INST.c_str(), G_MQCONSUMER_TOPIC_RC.c_str());
            return 10004;
        }

        // 提取 guid 并立即回执
        if (!messageBody.contains("guid") || !messageBody["guid"].is_string()) {
            std::cerr << "'guid' is missing or is not a string" << std::endl;
            DIY_ERRORLOG("process","【ERROR】前置的%d号进程处理topic:%s_%s的补招触发消息失败,没有guid字段",
                         g_front_seg_index, FRONT_INST.c_str(), G_MQCONSUMER_TOPIC_RC.c_str());
            return 10004;
        }
        std::string guid = messageBody["guid"].get<std::string>();
        send_reply_to_queue(guid, "1", "收到补招指令");

        // 提取 data 数组
        if (!messageBody.contains("data") || !messageBody["data"].is_array()) {
            std::cerr << "'data' is missing or is not an array" << std::endl;
            DIY_ERRORLOG("process","【ERROR】前置的%d号进程处理topic:%s_%s的补招触发消息失败,没有data字段",
                         g_front_seg_index, FRONT_INST.c_str(), G_MQCONSUMER_TOPIC_RC.c_str());
            return 10004;
        }
        // 仅用于保留你原先的调试输出
        std::string data_dump;
        try { data_dump = messageBody["data"].dump(); } catch (...) { data_dump.clear(); }
        std::cout << "parseJsonMessageRC: " << data_dump << std::endl;

        // 不指定稳态/暂态则全部补招
        int stat = 0;
        int voltage = 0;

        // 2. 遍历每个补招项（这里直接用已解析的 messageBody["data"]）
        for (auto& item : messageBody["data"]) {
            // 获取必需字段
            // ★修改：强制要求 terminalId；
            if (!item.contains("terminalId") ||
                !item.contains("monitor") ||
                !item.contains("timeInterval") ||
                !item.contains("dataType"))
            {
                std::cout << "json内容解析错误" << std::endl;
                return 10000;
            }

            // ★新增：读取 terminalId（必填）
            std::string terminalId = item["terminalId"].get<std::string>();
            if (terminalId.empty()) {
                std::cout << "terminalId为空" << std::endl;
                continue;
            }

            // 2.1 解析 dataType（仅保留稳态/暂态）
            std::string datatype = item["dataType"].get<std::string>();
            if (!datatype.empty()) {
                if (datatype == "0" || datatype == "稳态" || datatype == "steady" || datatype == "STEADY") {
                    stat = 1;  voltage = 0;   // 稳态
                } else if (datatype == "1" || datatype == "暂态" || datatype == "voltage" || datatype == "VOLTAGE") {
                    stat = 0;  voltage = 1;   // 暂态
                } else {
                    stat = voltage = 1;       // 其他情况按全补
                }
            } else {
                stat = voltage = 1;           // 全补
            }

            // ★新增：定位并校验该 terminal 是否归属当前进程
            std::lock_guard<std::mutex> lock(ledgermtx);

            const terminal_dev* targetDev = NULL;
            for (std::vector<terminal_dev>::const_iterator it = terminal_devlist.begin();
                 it != terminal_devlist.end(); ++it)
            {
                if (it->terminal_id == terminalId) {
                    targetDev = &(*it);
                    break;
                }
            }
            if (!targetDev) {
                std::cout << "terminalId未匹配当前进程内装置: " << terminalId << std::endl;
                continue;
            }

            // 添加判断装置在线，不注册guid，异步补招
            if (ClientManager::instance().get_dev_status(targetDev->terminal_id) != 1) {
                std::cout << "terminalId对应装置不在线: " << targetDev->terminal_id << std::endl;

                // 响应 web
                std::string msg = std::string("装置：") + targetDev->terminal_name + " 不在线，无法补招";
                send_reply_to_kafka_recall("12345", "2", static_cast<int>(ResponseCode::INTERNAL_ERROR), msg, targetDev->terminal_id, "", "", "");
                continue;//处理下一个装置的补招记录
            }

            // ★新增：按新结构解析 monitor 层级
            nlohmann::json& monitors = item["monitor"];
            if (!monitors.is_array() || monitors.empty()) {
                std::cout << "monitor数组为空或非数组类型" << std::endl;
                continue;
            }

            for (auto& mobj : monitors) {
                if (!mobj.contains("monitorId") || !mobj.contains("timeInterval")) {
                    std::cout << "monitor项缺少 monitorId 或 timeInterval" << std::endl;
                    continue;
                }

                std::string monitorId = mobj["monitorId"].get<std::string>();
                if (monitorId.empty()) continue;

                // 不只是判断存在，还要拿到指针 lm 以便后续 push_back
                ledger_monitor* lm = NULL;
                // 注意：这里需要非常量指针，取 const_cast 后遍历
                terminal_dev* dev_nc = const_cast<terminal_dev*>(targetDev);
                for (std::vector<ledger_monitor>::iterator itLm = dev_nc->line.begin();
                     itLm != dev_nc->line.end(); ++itLm)
                {
                    if (!itLm->monitor_id.empty() && itLm->monitor_id == monitorId) {
                        lm = &(*itLm);
                        break;
                    }
                }
                if (!lm) {
                    std::cout << "monitorId未在terminal内找到: " << monitorId
                              << " @ " << terminalId << std::endl;
                    continue;
                }

                nlohmann::json& tiArr = mobj["timeInterval"];
                if (!tiArr.is_array() || tiArr.empty()) {
                    std::cout << "timeInterval为空或非数组类型: monitorId=" << monitorId << std::endl;
                    continue;
                }

                // 这里拆分时间段并 push 到 lm->recall_list / lm->recall_list_static
                for (auto& timeItem : tiArr) {
                    std::string ti = timeItem.get<std::string>();
                    std::string::size_type pos = ti.find('~');
                    if (pos == std::string::npos) {
                        std::cout << "timeInterval格式错误: " << ti << std::endl;
                        continue;
                    }
                    std::string start = ti.substr(0, pos);
                    std::string end   = ti.substr(pos + 1);

                    // 仅对 recall_list（事件）进行 1 小时拆分；recall_list_static（稳态）不拆分
                    {
                        // 公共字段（整体区间，不拆分）用于 recall_list_static
                        RecallFile rm_all;                 // ★类型正确：稳态列表的元素
                        rm_all.recall_status = 0;          // 初始状态：未补招
                        rm_all.StartTime     = start;      // 直接使用字符串
                        rm_all.EndTime       = end;
                        rm_all.STEADY        = std::to_string(stat);
                        rm_all.VOLTAGE       = std::to_string(voltage);

                        // 仅当需要事件补招（voltage==1）时，才进行 1 小时拆分并压入 recall_list
                        if (voltage == 1) {
                            // 拆分时间段为 1 小时一段，并存入 recall_list
                            std::vector<RecallInfo> recallinfo_list_hour;
                            Get_Recall_Time_Char(start, end, recallinfo_list_hour);

                            for (std::size_t i = 0; i < recallinfo_list_hour.size(); ++i) {
                                const RecallInfo& info = recallinfo_list_hour[i];

                                RecallMonitor rm;             // ★类型正确：事件列表的元素
                                rm.recall_status = 0;         // 初始状态：未补招
                                rm.StartTime     = epoch_to_datetime_str(info.starttime);
                                rm.EndTime       = epoch_to_datetime_str(info.endtime);
                                rm.STEADY        = std::to_string(stat);
                                rm.VOLTAGE       = std::to_string(voltage);

                                lm->recall_list.push_back(rm);

                                // 事件补招列表（recall_list）调试打印
                                std::cout << "[recall_json_handle] terminal=" << terminalId
                                          << " monitor=" << monitorId
                                          << " [recall_list] start=" << lm->recall_list.back().StartTime
                                          << " end="   << lm->recall_list.back().EndTime
                                          << " steady="<< lm->recall_list.back().STEADY
                                          << " voltage="<< lm->recall_list.back().VOLTAGE
                                          << std::endl;
                            }
                        }

                        // 仅当需要稳态补招（stat==1）时，不拆分，直接压入 recall_list_static
                        if (stat == 1) {
                            lm->recall_list_static.push_back(rm_all); // 不拆分，整体区间

                            // 稳态补招列表（recall_list_static）调试打印
                            std::cout << "[recall_json_handle] terminal=" << terminalId
                                      << " monitor=" << monitorId
                                      << " [recall_list_static] start=" << lm->recall_list_static.back().StartTime
                                      << " end="   << lm->recall_list_static.back().EndTime
                                      << " steady="<< lm->recall_list_static.back().STEADY
                                      << " voltage="<< lm->recall_list_static.back().VOLTAGE
                                      << std::endl;
                        }

                        // 非法输入保护（保留你原来的保护与返回码）
                        if (stat == 0 && voltage == 0) {
                            std::cout << "[recall_json_handle] skip: stat=0 && voltage=0, monitor=" << monitorId
                                      << " terminal=" << terminalId
                                      << " start=" << rm_all.StartTime
                                      << " end="   << rm_all.EndTime
                                      << std::endl;
                            return 10003;
                        }
                    }
                }
            }
        }
    }
    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;
}

bool compare_qvvr_and_file(const std::string& cfg_path, const std::vector<qvvr_data>& 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;
    }

    //打印提取到的时间戳
    std::cout << "[调试] 提取到的起始时间戳: " << start_tm << ", 触发时间戳: " << trig_tm << "\n";

    // 遍历所有暂态事件，查找与 trig_tm 匹配的
    for (const auto& data : data_list) {
        long long diff = static_cast<long long>(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<DataArrayItem>& 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<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(
        "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";
    data.tag = G_ROCKETMQ_TAG_TEST;
    data.key = G_ROCKETMQ_KEY_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::lock_guard<std::mutex> lock(ledgermtx);

    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_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<std::string>& file_list_raw) {
    // ★新增：台账加锁（若上层已有锁，可移除本行）
    std::lock_guard<std::mutex> lk(ledgermtx);

    std::vector<std::string> file_names;

    // 1. 提取文件名部分
    for (const auto& full_path_raw : file_list_raw) {
        std::string full_path = sanitize(full_path_raw);                    // ★修改：清洗入参路径
        size_t pos = full_path.find_last_of("/\\");
        std::string name = (pos != std::string::npos && pos + 1 < full_path.size())
                           ? full_path.substr(pos + 1)
                           : full_path;
        name = sanitize(name);                                              // ★修改：清洗提取的文件名
        file_names.push_back(name);
    }

    // ★可选：去重（如果 file_list_raw 里可能有重复）
     std::sort(file_names.begin(), file_names.end());
     file_names.erase(std::unique(file_names.begin(), file_names.end()), file_names.end());

    // 2. 遍历终端
    for (auto& dev : terminal_devlist) {
        if (dev.terminal_id == id) {    // 根据终端id匹配终端
            for (auto& monitor : dev.line) {
                try {
                    // ★修改：清洗 logical_device_seq 再进行转换
                    std::string seq_str = sanitize(monitor.logical_device_seq);
                    ushort monitor_seq = static_cast<ushort>(std::stoi(seq_str));
                    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;
}

// ★新增：dirname，返回“目录/”（保留末尾斜杠；若没有目录则返回空串）
static inline std::string dirname_with_slash(const std::string& path) {
    size_t pos = path.find_last_of("/\\");
    if (pos == std::string::npos) return std::string{};
    return path.substr(0, pos + 1);
}

//发送匹配的所有录波文件
bool SendAllQvvrFiles(qvvr_file& qfile, std::string& out_wavepath) {
    std::vector<std::string> wavepaths;
    std::string first_wavepath;
    bool send_success = true;

    for (const auto& file_localpath : qfile.file_download) {
        std::string file_cloudpath = "comtrade/" + dirname_with_slash(file_localpath);
        std::string wavepath_result;

        // 发送本地文件到远端，返回 wavepath
        SOEFileWeb(const_cast<std::string&>(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<std::string&>(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_in, const std::string& terminal_id) {

    // ★ 先把原始入参清洗
    std::string filename_with_mac = sanitize(filename_with_mac_in);

    std::cout << "[update_qvvr_file_download] raw=" << filename_with_mac_in
              << " | sanitized=" << filename_with_mac
              << " | terminal_id=" << terminal_id << std::endl;

    //台账加锁
    std::unique_lock<std::mutex> lock(ledgermtx);

    // 去除 mac 路径前缀，仅保留文件名
    std::string filename = sanitize(extract_filename1(filename_with_mac));

    // 提取逻辑序号（如 PQ_PQLD1 → 1）
    size_t under_pos1 = filename.find('_');
    if (under_pos1 == std::string::npos) {
        std::cout << "[DEBUG] 未找到 '_'，filename=" << filename
                  << "，under_pos=npos，返回 false\n";
        return false;
    }
    size_t under_pos2 = filename.find('_', under_pos1 + 1);
    std::string type_part = (under_pos2 == std::string::npos)
        ? filename.substr(0, under_pos1)         // 兜底：只有一个下划线
        : filename.substr(0, under_pos2);         // 取到第二个下划线（得到 PQ_PQLD1）
    std::cout << "[DEBUG] type_part=" << type_part
              << " (under_pos1=" << under_pos1
              << ", under_pos2=" << under_pos2 << ")\n";

    size_t num_start = type_part.find_last_not_of("0123456789");
    if (num_start == std::string::npos || num_start + 1 >= type_part.size()) {
        std::cout << "[DEBUG] 数字起始位置异常：num_start=" << num_start
                  << "，type_part.size()=" << type_part.size()
                  << "，type_part=\"" << type_part << "\"，返回 false\n";
        return false;
    }
    std::string seq_str = type_part.substr(num_start + 1);
    unsigned short logical_seq = static_cast<unsigned short>(std::stoul(seq_str));
    std::cout << "[DEBUG] 解析到 logical_seq=" << logical_seq << "\n";

    //找终端
    for (auto& dev : terminal_devlist) {
        if (dev.terminal_id != terminal_id) {
            std::cout << "[cmp-terminal-id][NOT-MATCH]"
                      << " dev_id=" << dev.terminal_id
                      << " target_id=" << terminal_id
                      << std::endl;
            continue;
        }
        //找监测点
        for (auto& monitor : dev.line) {
            try {
                // 将监测点台账中的 logical_device_seq 转换为数字进行匹配
                ushort monitor_seq = static_cast<ushort>(std::stoi(monitor.logical_device_seq));
                if (monitor_seq != logical_seq) {
                    // ★新增：不匹配时对比打印
                    std::cout << "[cmp-monitor-seq][NOT-MATCH]"
                          << " monitor_id=" << monitor.monitor_id
                          // ★ 这里之前打印的是 seq_str，容易误导。改为 ledger 的原始串：
                            << " seq_in_ledger_raw=\"" << monitor.logical_device_seq << "\""
                          << " parsed=" << monitor_seq
                          << " target_seq=" << logical_seq
                          << std::endl;
                    continue;
                }
                else{
                    std::cout << "[cmp-monitor-seq][MATCH!!!]"
                          << " monitor_id=" << monitor.monitor_id
                          << " seq_in_ledger_raw=\"" << monitor.logical_device_seq << "\""
                          << " parsed=" << monitor_seq
                          << " target_seq=" << logical_seq
                          << std::endl;
                }
            } catch (const std::exception& e) {
                // ★新增：解析失败详细原因
                std::cout << "[cmp-monitor-seq][PARSE-FAIL]"
                      << " monitor_id=" << monitor.monitor_id
                      << " seq_in_ledger=\"" << monitor.logical_device_seq << "\""
                      << " err=" << e.what()
                      << std::endl;
                continue; // logical_device_seq 非法，跳过
            }catch (...) {
                // ★新增：未知异常
                std::cout << "[cmp-monitor-seq][PARSE-FAIL]"
                      << " monitor_id=" << monitor.monitor_id
                      << " seq_in_ledger=\"" << monitor.logical_device_seq << "\""
                      << " err=<unknown>"
                      << std::endl;
                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()) {
                        std::cout << "[update_qvvr_file_download] Adding downloaded file: " << filename_with_mac << std::endl;
                        qfile.file_download.push_back(filename_with_mac);
                    }

                    qfile.file_time_count = 0;      // 任一录波文件下载后，计时归零

                    // file_download 中是完整路径，需提取文件名后与 file_name 做集合比较
                    std::set<std::string> s_name(qfile.file_name.begin(), qfile.file_name.end());
                    std::set<std::string> s_down;
                    for (const auto& path : qfile.file_download) {
                        s_down.insert(sanitize(extract_filename1(path)));  // 提取每个路径中的文件名
                    }

                    //打印s_name和s_down内容
                    std::cout << "[update_qvvr_file_download] Expected files (file_name): ";
                    for (const auto& fn : s_name) std::cout << fn << " ";
                    std::cout << std::endl;
                    std::cout << "[update_qvvr_file_download] Downloaded files (file_download): ";
                    for (const auto& fn : s_down) std::cout << fn << " ";
                    std::cout << std::endl;

                    // 检查 file_download 是否与 file_name 完全一致（集合相同）
                    if (s_name == s_down) {
                        std::cout << "[update_qvvr_file_download] All files downloaded for qfile in logical_seq=" << logical_seq << std::endl;
                        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::vector<std::string> files_to_send(qfile.file_download.begin(),
                                                                           qfile.file_download.end());

                                    // ★新增：构造一个临时 qvvr_file，仅用于上传（不改动原结构）
                                    qvvr_file tmp_send;
                                    tmp_send.file_download.assign(files_to_send.begin(), files_to_send.end());


                                    // 发送所有文件（已下载完成）
                                    std::string wavepath;

                                    // ★在解锁前，备份“签名”，用于回锁后定位同一个 qfile
                                    std::set<std::string> sig_names(qfile.file_name.begin(), qfile.file_name.end());
                                    std::set<std::string> sig_downs(qfile.file_download.begin(), qfile.file_download.end());

                                    // ★修改：把上传与上送 JSON 放到“解锁区间”
                                    lock.unlock();  // ★新增：提前解锁

                                    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);

                                        // ★新增：上传成功后再加锁，准备修改台账
                                        lock.lock();

                                        // 删除上传成功的文件
                                        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";
                                            }
                                        }

                                        // ★替换原来的 i<size 判断为：按签名查找当前容器里的那一条
                                        auto it_qf = std::find_if(monitor.qvvrevent.qvvrfile.begin(),
                                                                  monitor.qvvrevent.qvvrfile.end(),
                                                                  [&](const qvvr_file& x){
                                                                      std::set<std::string> n(x.file_name.begin(), x.file_name.end());
                                                                      std::set<std::string> d(x.file_download.begin(), x.file_download.end());
                                                                      return n==sig_names && d==sig_downs;
                                                                  });
                                                              
                                        if (it_qf != monitor.qvvrevent.qvvrfile.end()) {
                                            monitor.qvvrevent.qvvrfile.erase(it_qf);                // ✔ 删到同一条
                                        } else {
                                            std::cerr << "[Cleanup] qvvrfile changed; target group not found, skip erase\n";
                                        }

                                        //清除暂态事件
                                        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);
                                        }
                                    } 
                                    else {
                                        lock.lock(); // ★新增：失败时补回锁
                                        std::cerr << "[update_qvvr_file_download] Failed to send qvvr files for logical_seq=" << logical_seq << std::endl;
                                    }                                  
                                }
                                else {
                                    std::cout << "[update_qvvr_file_download] No matching qvvr_data found for cfg file: " << fpath << std::endl;
                                }
                                break; // 只处理第一个 cfg 文件
                            }
                        }
                    }
                    else{
                       std::cout << "qvvr file still imcomplete!!!" << std::endl; 
                    }
                    lock.unlock();
                    return true; // 当前文件处理成功
                }                      
            }

            std::cout << "file name doesnt match any file in this monitor!!!" << std::endl; 

        }
    }
    lock.unlock();
    return false; // 未匹配到终端ID或逻辑序号对应的监测点
}

////////////////////////////////////////////////////////////////////////////////////////提取mac
std::string normalize_mac(const std::string &mac) {
    std::string res;
    res.reserve(mac.size());
    for (char c : mac) {
        if (c != '-' && c != ':' && c != ' ')
            res.push_back(c);
    }
    return res;
}


////////////////////////////////////////////////////////////////////////////////////////目录信息发送接口函数

bool send_file_list(terminal_dev* dev, const std::vector<tag_dir_info>& FileList) {

    if (!dev) {
        std::cerr << "[send_file_list_locked] dev=nullptr\n";
        return false;
    }

    // 判断 isbusy==1 且 busytype==READING_FILEMENU
    if (dev->isbusy != 1 || dev->busytype != static_cast<int>(DeviceState::READING_FILEMENU)) {
        std::cerr << "[send_file_list] device not in READING_FILEMENU state." << std::endl;
        return false;
    }

    // 构造 JSON 报文
    nlohmann::json j;
    j["guid"]    = dev->guid;
    j["FrontId"] = FRONT_INST;                // 这里填你的前置机id
    j["Node"]    = g_front_seg_index;       // 节点号
    j["Dev_mac"] = normalize_mac(dev->addr_str); // addr_str 存的是 MAC

    // 构造 DirInfo 数组
    nlohmann::json dirArray = nlohmann::json::array();
    for (const auto &f : FileList) {
        nlohmann::json item;
        item["Name"] = f.name;
        item["Type"] = (f.flag == 0) ? "dir" : "file";
        item["Size"] = f.size;
        dirArray.push_back(item);
    }

    // 构造 Detail 部分
    nlohmann::json detail;
    detail["Type"] = 0x2131; // 读取目录
    detail["Msg"]  = { {"DirInfo", dirArray} };
    detail["Code"] = 200; // 请求成功

    // 放到顶层
    j["Detail"] = detail;

    // 打印调试
    std::cout << j.dump(4) << std::endl;

    // ---- 入队发送 ----
    queue_data_t connect_info;
    connect_info.strTopic = Topic_Reply_Topic;
    connect_info.strText  = j.dump();  // 序列化为字符串
    connect_info.tag = Topic_Reply_Tag;
    connect_info.key = Topic_Reply_Key;
    {
        std::lock_guard<std::mutex> lock(queue_data_list_mutex);
        queue_data_list.push_back(std::move(connect_info));
    }
    // 调试打印
    std::cout << "[send_reply_to_cloud] queued: " << j.dump() << std::endl;

    //发送后清除guid和标志
    if (dev->isbusy > 0) {
        dev->isbusy--;
    }
    if(dev->isbusy == 0){
        dev->guid.clear();
        dev->busytype = 0;
    }

    return true;
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////检查云前置终端的mq业务超时
int get_type_by_state(int state) {
    switch (static_cast<DeviceState>(state)) {
        case DeviceState::READING_STATS:
        case DeviceState::READING_STATS_TIME:
        case DeviceState::READING_REALSTAT:
        case DeviceState::READING_FIXEDVALUE:
        case DeviceState::READING_FIXEDVALUEDES:
        case DeviceState::SET_FIXEDVALUE:
        case DeviceState::READING_INTERFIXEDVALUE:
        case DeviceState::READING_INTERFIXEDVALUEDES:
        case DeviceState::READING_CONTROLWORD:
        case DeviceState::SET_INTERFIXEDVALUE:
            return 0x2106;  //读数据

        case DeviceState::READING_FILEMENU:
            return 0x2131;  //读目录

        case DeviceState::READING_EVENTFILE:
        case DeviceState::READING_FILEDATA:
            return 0x2132;  //读文件

        default:
            return 0; // 没有对应的type
    }
}

// 定时检查业务超时
void check_device_busy_timeout()
{
    std::lock_guard<std::mutex> lock(ledgermtx);

    for (auto &dev : terminal_devlist)
    {
        if (dev.isbusy != 0) // 有业务在进行
        {
            dev.busytimecount++;

            if (dev.busytype == static_cast<int>(DeviceState::READING_FILEDATA))    //下载文件业务
            {
                if (dev.busytimecount > 30)
                {
                    std::cout << "[Timeout] Device " << dev.terminal_id
                              << " busytype=READING_FILEDATA 超时(" 
                              << dev.busytimecount << "s)" << std::endl;

                    //发送超时响应
                    send_reply_to_cloud(static_cast<int>(ResponseCode::TIMEOUT),dev.terminal_id,get_type_by_state(dev.busytype),dev.guid,dev.mac);

                    // 超时清空状态
                    dev.guid.clear();      // 清空进行中的 guid
                    dev.busytype = 0;      // 复位业务类型
                    dev.isbusy = 0;        // 标记空闲
                    dev.busytimecount = 0; // 计时清零
                }
            }
            else    //其他业务
            {
                if (dev.busytimecount > 10)
                {
                    std::cout << "[Timeout] Device " << dev.terminal_id
                              << " busytype=" << dev.busytype 
                              << " 超时(" << dev.busytimecount << "s)" << std::endl;

                    //发送超时响应
                    send_reply_to_cloud(static_cast<int>(ResponseCode::TIMEOUT),dev.terminal_id,get_type_by_state(dev.busytype),dev.guid,dev.mac);

                    // 超时清空状态
                    dev.guid.clear();
                    dev.busytype = 0;
                    dev.isbusy = 0;
                    dev.busytimecount = 0;
                }
            }
        }
    }
}

/////////////////////////////////////////////////////////////////////////////////// 一分钟调用一次：检查 qvvr_file 超时并备份
static bool ensure_dir_exists(const std::string &path)
{
    struct stat st;
    if (stat(path.c_str(), &st) == 0) {
        if (S_ISDIR(st.st_mode)) {
            return true; // 已存在
        }
        return false; // 存在但不是目录
    }
    // 不存在则创建
    if (mkdir(path.c_str(), 0755) == 0) {
        return true;
    }
    return false;
}

void check_and_backup_qvvr_files() {
    std::lock_guard<std::mutex> lock(ledgermtx);

    const std::string data_dir   = FRONT_PATH + "/data";
    const std::string backup_dir = data_dir + "/comtrade_bak";

    if (!ensure_dir_exists(data_dir) && !ensure_dir_exists(data_dir)) {
        std::cerr << "[check_and_backup_qvvr_files] 创建 data 目录失败\n";
        return;
    }
    if (!ensure_dir_exists(backup_dir) && !ensure_dir_exists(backup_dir)) {
        std::cerr << "[check_and_backup_qvvr_files] 创建 comtrade_bak 目录失败\n";
        return;
    }

    for (auto &dev : terminal_devlist) {
        for (auto &line : dev.line) {

            // 用迭代器遍历，允许在循环中 erase 当前元素
            for (std::vector<qvvr_file>::iterator qit = line.qvvrevent.qvvrfile.begin();
                 qit != line.qvvrevent.qvvrfile.end(); /* no ++ here */) {

                qvvr_file &qfile = *qit;

                if (!qfile.used_status) {
                    ++qit;
                    continue;
                }

                ++qfile.file_time_count; // 每分钟+1

                // 超时阈值：>10 分钟
                if (qfile.file_time_count > 10) {
                    std::cout << "[Qvvr Timeout] dev=" << dev.terminal_id
                              << " -> move files to: " << backup_dir << std::endl;

                    // 移动该记录内的所有文件
                    for (std::list<std::string>::const_iterator it = qfile.file_download.begin();
                         it != qfile.file_download.end(); ++it) {
                        const std::string &src = *it;
                        const size_t pos = src.find_last_of("/\\");
                        const std::string base = (pos == std::string::npos) ? src : src.substr(pos + 1);
                        const std::string dst = backup_dir + "/" + base;

                        if (rename(src.c_str(), dst.c_str()) != 0) {
                            std::cerr << "  [ERROR] 移动失败: " << src
                                      << " -> " << dst << " , " << strerror(errno) << std::endl;
                        } else {
                            std::cout << "  moved: " << src << " -> " << dst << std::endl;
                        }
                    }

                    // ✅ 直接从 qvvrfile 向量中删除这条记录
                    qit = line.qvvrevent.qvvrfile.erase(qit);
                    // 注意：不再对 qfile 读写，因为它已被删除
                    continue; // 不自增，由 erase 返回的新迭代器继续
                }

                ++qit; // 正常前进
            }
        }
    }
}

/////////////////////////////////////////////////////////////////////////////////////////定值信息发送接口函数
bool save_set_value(const std::string &dev_id, unsigned char mp_index, const std::vector<float> &fabsf) {
    std::lock_guard<std::mutex> lock(ledgermtx);

    // 1. 找到对应 terminal_dev
    auto it = std::find_if(terminal_devlist.begin(), terminal_devlist.end(),
                           [&](const terminal_dev &dev) { return dev.terminal_id == dev_id; });
    if (it == terminal_devlist.end()) {
        std::cerr << "[send_set_reply] device not found: " << dev_id << std::endl;
        return false;
    }

    terminal_dev &dev = *it;

    // 2. 检查状态
    if (dev.isbusy != 2 || dev.busytype != static_cast<int>(DeviceState::READING_FIXEDVALUE)) {
        std::cerr << "[send_set_reply] device not in READING_FIXEDVALUE state." << std::endl;
        return false;
    }

    // 3. 遍历监测点，找到 logical_device_seq == mp_index 的监测点
    bool found = false;
    for (auto &mon : dev.line) {
        if (std::atoi(mon.logical_device_seq.c_str()) == static_cast<int>(mp_index)) {
            // ★ 清理原有的 set_values
            mon.set_values.clear();
            // ★ 将 fabsf 依次存入该监测点的 set_values
            for (const auto &val : fabsf) {
                mon.set_values.push_back(val);
            }
            found = true;
            break;
        }
    }

    if (!found) {
        std::cerr << "[send_set_reply] monitor with seq=" << (int)mp_index
                  << " not found in terminal " << dev_id << std::endl;
        return false;
    }

    // 4. 状态递减
    dev.isbusy--;

    return true;
}

bool save_internal_value(const std::string &dev_id, const std::vector<ushort> &fabsf) {
    // 找到对应 terminal_dev
    std::lock_guard<std::mutex> lock(ledgermtx);

    auto it = std::find_if(terminal_devlist.begin(), terminal_devlist.end(),
                           [&](const terminal_dev &dev) { return dev.terminal_id == dev_id; });
    if (it == terminal_devlist.end()) {
        std::cerr << "[send_set_reply] device not found: " << dev_id << std::endl;
        return false;
    }

    terminal_dev &dev = *it;

    // 判断 isbusy==3 且 busytype==READING_INTERFIXEDVALUE
    if (dev.isbusy != 3 || dev.busytype != static_cast<int>(DeviceState::READING_INTERFIXEDVALUE)) {
        std::cerr << "[send_set_reply] device not in READING_INTERFIXEDVALUE state." << std::endl;
        return false;
    }

    // ★ 新增：清理原有的内部定值列表
    dev.internal_values.clear();
    //将值严格按顺序存入list中
    for (const auto &val : fabsf) {
        dev.internal_values.push_back(val);
    }

    dev.isbusy--;

    return true;
}

bool save_internal_info(const std::string &dev_id, const std::vector<NameFixValue> &fixValueList) {
    // 找到对应 terminal_dev
    std::lock_guard<std::mutex> lock(ledgermtx);

    auto it = std::find_if(terminal_devlist.begin(), terminal_devlist.end(),
                           [&](const terminal_dev &dev) { return dev.terminal_id == dev_id; });
    if (it == terminal_devlist.end()) {
        std::cerr << "[send_set_reply] device not found: " << dev_id << std::endl;
        return false;
    }

    terminal_dev &dev = *it;

    // 判断 isbusy==2 且 busytype==READING_INTERFIXEDVALUE
    if (dev.isbusy != 2 || dev.busytype != static_cast<int>(DeviceState::READING_INTERFIXEDVALUE)) {
        std::cerr << "[send_set_reply] device not in READING_INTERFIXEDVALUE state." << std::endl;
        return false;
    }

    // ★ 新增：清理原有的内部定值列表
    dev.dz_internal_info_list.clear();
    //将值严格按顺序存入list中
    for (const auto &info : fixValueList) {
        dev.dz_internal_info_list.push_back(info);
    }

    dev.isbusy--;

    return true;
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////回复定值读取响应
bool send_set_value_reply(const std::string &dev_id, unsigned char mp_index, const std::vector<DZ_TAB_STRUCT> &dz_info) {
    std::lock_guard<std::mutex> lock(ledgermtx);

    // 1) 找终端
    auto it = std::find_if(terminal_devlist.begin(), terminal_devlist.end(),
                           [&](const terminal_dev &d) { return d.terminal_id == dev_id; });
    if (it == terminal_devlist.end()) {
        std::cerr << "[send_set_value_reply] device not found: " << dev_id << std::endl;
        return false;
    }
    terminal_dev &dev = *it;

    // 2) 校验状态：发送“定值读取结果”回复，应处于 READING_FIXEDVALUE；isbusy == 1
    if (dev.isbusy != 1 || dev.busytype != static_cast<int>(DeviceState::READING_FIXEDVALUE)) { //定值读取
        std::cerr << "[send_set_value_reply] device not in READING_FIXEDVALUE state." << std::endl;
        return false;
    }

    // 3) 定位监测点（mp_index ∈ [1,6]），与 logical_device_seq 比对
    ledger_monitor *pMon = nullptr;
    for (auto &mon : dev.line) {
        if (std::atoi(mon.logical_device_seq.c_str()) == static_cast<int>(mp_index)) {
            pMon = &mon;
            break;
        }
    }
    if (!pMon) {
        std::cerr << "[send_set_value_reply] monitor with seq=" << (int)mp_index
                  << " not found in terminal " << dev_id << std::endl;
        return false;
    }

    //将dz_info存入监测点
    pMon->dz_info_list.clear();
    for (const auto &dz : dz_info) {
        pMon->dz_info_list.push_back(dz);
    }

    // 4) 取该监测点的 set_values，严格按顺序用于 DZ_Value
    std::vector<float> ordered_vals;
    ordered_vals.reserve(pMon->set_values.size());
    for (float v : pMon->set_values) ordered_vals.push_back(v);

    if (ordered_vals.empty()) {
        std::cerr << "[send_set_value_reply] monitor seq=" << (int)mp_index
                  << " has empty set_values." << std::endl;
        return false;
    }

    // 5) 生成 JSON（结构严格贴合你给的样例）
    nlohmann::json j;

    // 顶层
    j["guid"]    = dev.guid;
    j["FrontId"] = FRONT_INST;           // 你的前置机 IP（项目已有常量/变量）
    j["Node"]    = g_front_seg_index;  // 节点号（项目已有变量）
    j["Dev_mac"] = normalize_mac(dev.addr_str);

    // Detail
    nlohmann::json detail;
    detail["Type"] = 0x2106;  // 设备数据

    // Msg
    nlohmann::json msg;
    msg["Cldid"]    = mp_index;      //测点序号
    msg["DataType"] = 0x0C;   //定值

    // DataArray（对象数组）：逐个填充，DZ_Value 严格按 set_values 顺序
    nlohmann::json dataArray = nlohmann::json::array();

    const size_t n_meta = dz_info.size();
    const size_t n_vals = ordered_vals.size();
    const size_t n = std::min(n_meta, n_vals); // 以两者较短长度为准

    if (n_meta != n_vals) {
        std::cerr << "[send_set_value_reply] warn: dz_info size(" << n_meta
                  << ") != set_values size(" << n_vals << "), will emit " << n << " items.\n";
        return false; // 或者继续发送，视需求而定
    }

    for (size_t i = 0; i < n; ++i) {
        const DZ_TAB_STRUCT &dz = dz_info[i];
        nlohmann::json item;
        item["LN_Num"]    = dz.LN_Num;
        item["DZ_Num"]    = dz.DZ_Num;
        item["DZ_Name"]   = dz.DZ_Name;
        item["DZ_Value"]  = ordered_vals[i];   // ★ 严格按顺序
        item["DZ_Type"]   = dz.DZ_Type;
        item["DZ_Min"]    = dz.DZ_Min;
        item["DZ_Max"]    = dz.DZ_Max;
        item["DZ_Default"]= dz.DZ_Default;
        item["DZ_UNIT"]   = dz.DZ_UNIT;
        dataArray.push_back(std::move(item));
    }

    msg["DataArray"] = std::move(dataArray);
    detail["Msg"]    = std::move(msg);
    detail["Code"]   = 200;

    j["Detail"] = std::move(detail);

    // 6) 入队发送
    queue_data_t connect_info;
    connect_info.strTopic = Topic_Reply_Topic;
    connect_info.strText  = j.dump(); // 序列化为字符串
    connect_info.tag = Topic_Reply_Tag;
    connect_info.key = Topic_Reply_Key;

    {
        std::lock_guard<std::mutex> lock(queue_data_list_mutex);
        queue_data_list.push_back(std::move(connect_info));
    }

    // 调试打印
    std::cout << "[send_set_value_reply] queued JSON:\n" << j.dump(4) << std::endl;

    // 7) 发送后更新终端状态（按你现有规则）
    if (dev.isbusy > 0) {
        dev.isbusy--;
    }
    if (dev.isbusy == 0) {
        dev.guid.clear();
        dev.busytype = 0;
        if (pMon) {
            pMon->set_values.clear();//清理本次定值记录
        }
    }

    return true;
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////回复内部定值响应读取


bool send_internal_value_reply(const std::string &dev_id, const std::vector<DZ_kzz_bit> &control_words)
{
    std::lock_guard<std::mutex> lock(ledgermtx);

    // 1) 找终端
    auto it = std::find_if(terminal_devlist.begin(), terminal_devlist.end(),
                           [&](const terminal_dev &d) { return d.terminal_id == dev_id; });
    if (it == terminal_devlist.end()) {
        std::cerr << "[send_internal_value_reply] device not found: " << dev_id << std::endl;
        return false;
    }
    terminal_dev &dev = *it;

    // 2) 校验状态：发送“内部定值读取结果”回复，应处于 READING_INTERFIXEDVALUE；isbusy == 1
    if (dev.isbusy != 1 || dev.busytype != static_cast<int>(DeviceState::READING_INTERFIXEDVALUE)) {
        std::cerr << "[send_internal_value_reply] device not in READING_INTERFIXEDVALUE state." << std::endl;
        return false;
    }

    //将control_words存入dev
    dev.control_words.clear();
    for (const auto &cw : control_words) {
        dev.control_words.push_back(cw);
    }

    // -------------------- [新增] 建立 internal_values 与 dz_internal_info_list 的一一对应 --------------------
    // 说明：按索引次序一一对应（第 i 个 NameFixValue 对应 internal_values 的第 i 个）
    //       若数量不同，按 min 对齐，忽略多出来的一边并告警。
    std::vector<float> internal_vals;                       // [新增]
    internal_vals.reserve(dev.internal_values.size());      // [新增]
    for (float v : dev.internal_values) internal_vals.push_back(v); // [新增]

    const size_t n_dz  = dev.dz_internal_info_list.size();  // [新增]
    const size_t n_val = internal_vals.size();              // [新增]
    const size_t n_use = std::min(n_dz, n_val);             // [新增]
    if (n_dz != n_val) {                                    // [新增]
        std::cerr << "[send_internal_value_reply] WARN: dz_internal_info_list size("
                  << n_dz << ") != internal_values size(" << n_val
                  << "), will use min(" << n_use << ")." << std::endl;
    }
    // ------------------------------------------------------------------------------------------------------

    // 3) 组包顶层
    nlohmann::json j;
    j["guid"]    = dev.guid;
    j["FrontId"] = FRONT_INST;
    j["Node"]    = g_front_seg_index;
    j["Dev_mac"] = normalize_mac(dev.addr_str);

    nlohmann::json detail;
    detail["Type"] = 0x2106;  // 设备数据
    nlohmann::json msg;
    msg["DataType"] = 0x0D;   // 内部定值

    // 4) === 将 C# 的拼接逻辑移植为 DataArray ===
    // C# 变量对应关系：
    //   DevInfo.nDevIndex       -> 这里用 1
    //   DevInfo.strGuId         -> 这里用 装置id
    //   DevInfo.controlwordlist -> 这里用参数 control_words（DZ_kzz_bit 含 kzz_bit/bit_enable）
    //
    //   NameFixValue 列表：使用 dev.dz_internal_info_list
    //
    // 关键逻辑：
    // - 遍历每个 NameFixValue，k 从 1 递增，nStep 每个定值递增 1
    // - 若 DataType == 1：将 Max/Min/Default 都 /100，并 property 输出一个空对象 [{}]（保持与 C# 一致）
    // - 否则：为该定值构建 property 位数组，范围 [nStep*16, (nStep+1)*16)，
    //         名称为空则提前结束本定值的 property；flag = (DefaultValue >> j) & 0x01
    //

    nlohmann::json dataArray = nlohmann::json::array();  // [新增]

    int nStep = 0;                       // [新增] 每个 NameFixValue 递增
    int kSort = 1;                       // [新增] 排序号，从 1 开始

    // 保护：dz_internal_info_list 是引用成员，确保不会因并发被改动
    //for (const auto& nf : dev.dz_internal_info_list) {                // [新增]
    for (size_t idxNF = 0; idxNF < n_use; ++idxNF) {                    // [修改] 使用 idxNF 控制索引
        const auto& nf = dev.dz_internal_info_list[idxNF];
        // 取字段
        const uint16_t dataType     = nf.DataType;
        const uint16_t minRaw       = nf.MinValue;
        const uint16_t maxRaw       = nf.MaxValue;
        const uint16_t defaultRaw   = nf.DefaultValue;
        const std::string unit      = trim_cstr(nf.sDimension, sizeof(nf.sDimension));
        const std::string name      = trim_cstr(nf.sFixValueName, sizeof(nf.sFixValueName));

        // 取对应内部值
        const float internal_v_raw  = internal_vals[idxNF];                 // [新增]
        const double internal_v_out = static_cast<double>(internal_v_raw);  // [新增] 直接转 double 输出，不缩放

        // 构造一条记录
        nlohmann::json one;
        one["cpu_no"]      = 1;                         // [新增] C#: DevInfo.nDevIndex 填设备号，固定为1
        one["dev_type"]    = dev_id;                    // [新增] C#: DevInfo.strGuId 填装置id
        one["type"]        = 90;                        // [新增] 固定 "90"
        one["unit"]        = unit;                      // [新增]
        one["describe"]    = name;                      // [新增]
        one["sort"]        = kSort;                     // [新增]
        one["Internal_Value"] = internal_v_out;        // [新增] 精确对应 internal_values 的值（含必要缩放）

        // 数值：DataType == 1 时缩放 /100
        if (dataType == 1) {                            // [新增] 缩放分支
            int ChangeMaxValue     = static_cast<int>(maxRaw) / 100;
            int ChangeMinValue     = static_cast<int>(minRaw) / 100;
            int ChangeDefaultValue = static_cast<int>(defaultRaw) / 100;

            one["maxvalue"]     = ChangeMaxValue;
            one["minvalue"]     = ChangeMinValue;
            one["defaultvalue"] = ChangeDefaultValue;
            one["value"]        = ChangeDefaultValue;

            // C# 在该分支 property 写成 [{ }]（一个空对象的数组）
            nlohmann::json prop = nlohmann::json::array();
            prop.push_back(nlohmann::json::object());   // [{}]
            one["property"] = std::move(prop);
        } else {                                        // [新增] 未缩放分支 + property 位描述
            one["maxvalue"]     = static_cast<int>(maxRaw);
            one["minvalue"]     = static_cast<int>(minRaw);
            one["defaultvalue"] = static_cast<int>(defaultRaw);
            one["value"]        = static_cast<int>(defaultRaw);

            // 构建 property：16 位窗口，从 nStep*16 到 (nStep+1)*16 - 1
            nlohmann::json prop = nlohmann::json::array();
            bool hasAny = false;

            const int begin = nStep * 16;
            const int end   = (nStep + 1) * 16; // 不含 end

            for (int idx = begin, jbit = 0; idx < end; ++idx, ++jbit) {
                if (idx < 0 || static_cast<size_t>(idx) >= control_words.size()) break;

                // 名称空则提前退出（仿 C#：temp=="" break）
                const std::string cw_name = trim_cstr(control_words[idx].kzz_bit, sizeof(control_words[idx].kzz_bit));
                if (cw_name.empty()) {
                    // 注意：C# 如果 j==0 则设置了 flag2=1，仅用于逗号处理，这里不需要
                    break;
                }

                int flag = (defaultRaw >> jbit) & 0x01; // 取该位默认值
                nlohmann::json bitItem;
                bitItem["type_num"] = jbit;
                bitItem["bit0"]     = "";              // 保持与 C# 一致
                bitItem["bit1"]     = "";
                bitItem["describe"] = cw_name;
                bitItem["flag"]     = (flag ? "1" : "0"); // C# 用字符串
                prop.push_back(std::move(bitItem));
                hasAny = true;
            }

            if (!hasAny) {
                // 与 C# 对齐：如果一个都没有，就给 [{}] 以避免 "property":[] 的结构差异
                prop.push_back(nlohmann::json::object());
            }
            one["property"] = std::move(prop);
        }

        dataArray.push_back(std::move(one));            // [新增]
        ++nStep;                                        // [新增] 进入下一个 16 位窗口
        ++kSort;                                        // [新增]
    }

    msg["DataArray"] = std::move(dataArray);            // [新增]
    detail["Msg"]    = std::move(msg);
    j["Detail"]      = std::move(detail);

    // 5) 入队发送（保持你的队列逻辑）
    queue_data_t connect_info;
    connect_info.strTopic = Topic_Reply_Topic;
    connect_info.strText  = j.dump(); // 序列化为字符串
    connect_info.tag = Topic_Reply_Tag;
    connect_info.key = Topic_Reply_Key;

    {
        std::lock_guard<std::mutex> lock(queue_data_list_mutex);
        queue_data_list.push_back(std::move(connect_info));
    }

    // 调试打印
    std::cout << "[send_internal_value_reply] queued JSON:\n" << j.dump(4) << std::endl;

    // 6) 发送后更新终端状态（保持你现有规则）
    if (dev.isbusy > 0) {
        dev.isbusy--;
    }
    if (dev.isbusy == 0) {
        dev.guid.clear();
        dev.busytype = 0;
        dev.internal_values.clear();        // 清理本次定值记录
        dev.dz_internal_info_list.clear();  // 清理本次定值描述记录（注意：如果这是引用成员，确保其实际容器存在）
    }

    return true;
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////处理补招逻辑
//发送补招响应给web
void send_reply_to_kafka_recall(const std::string& guid, const std::string& step,int code, const std::string& result,const std::string& terminalId,const std::string& lineIndex,const std::string& recallStartDate,const std::string& recallEndDate){
	// 构造 JSON 字符串
    std::ostringstream oss;
    oss << "{"
        << "\"guid\":\"" << guid << "\","
        << "\"step\":\"" << step << "\","
        << "\"code\":" << code << ","
        << "\"result\":\"" << result << "\","
        << "\"terminalId\":\"" << terminalId << "\","
		<< "\"lineIndex\":\"" << lineIndex << "\","
        << "\"recallStartDate\":\"" << recallStartDate << "\","
        << "\"recallEndDate\":\"" << recallEndDate << "\","
		<< "\"processNo\":\"" << g_front_seg_index << "\","
		<< "\"nodeId\":\"" << FRONT_INST << "\""
        << "}";

    std::string jsonString = oss.str();

    // 封装 Kafka 消息
    queue_data_t connect_info;
    connect_info.strTopic = Topic_Reply_Topic;
    connect_info.strText = jsonString;
    connect_info.tag = Topic_Reply_Tag;
    connect_info.key = Topic_Reply_Key;

    // 加入发送队列（带互斥锁保护）
    queue_data_list_mutex.lock();
    queue_data_list.push_back(connect_info);
    queue_data_list_mutex.unlock();
}

// ===== 一次遍历可下发“多个终端的一条” =====
void check_recall_event() {

    std::vector<RecallTask> tasks;     // 本轮要发送的“每终端一条”

    {   //锁作用域
        std::lock_guard<std::mutex> lock(ledgermtx);

        // 遍历所有 terminal —— 每个 terminal 只挑一条，先不判断运行状态，因为正在处理其他事务的装置也可以记录待补招信息
        for (auto& dev : terminal_devlist) {
            //如果该终端不是正在补招或者idle则直接跳过，节省运行时间
            if(dev.busytype != static_cast<int>(DeviceState::READING_EVENTLOG) && dev.busytype != static_cast<int>(DeviceState::IDLE)){
                continue;
            }
            // 对正在补招或idle终端的所有监测点的待补招列表进行处理
            // 1) 先弹掉首条为 DONE/FAILED 的记录（所有 monitor 都要处理首条）
            bool any_non_empty = false;
            for (auto& lm : dev.line) {
                while (!lm.recall_list.empty()) {
                    const RecallMonitor& front = lm.recall_list.front();
                    if (front.recall_status == static_cast<int>(RecallStatus::DONE)) {
                        std::cout << "[check_recall_event] DONE dev=" << dev.terminal_id
                                  << " monitor=" << lm.monitor_id
                                  << " " << front.StartTime << " ~ " << front.EndTime << std::endl;

                        //调用reply接口通知web端该时间段补招完成
                        std::string msg = std::string("监测点：") + lm.monitor_name
                            + " 补招时间范围：" + front.StartTime
                            + " ~ " + front.EndTime
                            + " 补招执行完成";
                        send_reply_to_kafka_recall("12345","2",static_cast<int>(ResponseCode::OK),msg,dev.terminal_id,lm.logical_device_seq,front.StartTime,front.EndTime);

                        lm.recall_list.pop_front(); // 弹掉首条
                    } else if (front.recall_status == static_cast<int>(RecallStatus::FAILED)) {
                        std::cout << "[check_recall_event] FAILED dev=" << dev.terminal_id
                                  << " monitor=" << lm.monitor_id
                                  << " " << front.StartTime << " ~ " << front.EndTime << std::endl;

                        //调用reply接口通知web端该时间段补招失败
                        std::string msg = std::string("监测点：") + lm.monitor_name
                            + " 补招时间范围：" + front.StartTime
                            + " ~ " + front.EndTime
                            + " 补招执行失败";
                        send_reply_to_kafka_recall("12345","2",static_cast<int>(ResponseCode::BAD_REQUEST),msg,dev.terminal_id,lm.logical_device_seq,front.StartTime,front.EndTime);

                        lm.recall_list.pop_front(); // 弹掉首条
                    } else {
                        break; // 首条不是 2/3，停，如果是正在处理其他业务或者idle的装置写入了待补招列表，应该都是0；如果是正在补招的装置，新增的部分不会影响原有顺序
                    }
                }
                if (!lm.recall_list.empty()) any_non_empty = true;  // 处理了成功和失败的以后只要有一条非空就标记，可能是待处理或者正在处理的补招
            }

            if (!any_non_empty && dev.busytype == static_cast<int>(DeviceState::READING_EVENTLOG)) {
                // 该终端本轮已无任何补招条目，且处于补招暂态事件的状态清空运行态
                //通知补招全部完成

                dev.guid.clear();       // 清空 guid
                dev.busytype = 0;       // 业务类型归零
                dev.isbusy = 0;         // 清空业务标志
                dev.busytimecount = 0;  // 计时归零
                continue;
            }
            //如果是idle又没有待补招任务了，应该跳过
            else if(!any_non_empty && dev.busytype == static_cast<int>(DeviceState::IDLE)){
                continue;
            }
            else{//有待补招任务且处于补招状态或者idle状态
                // 继续补招处理
            }

            // 2) 若任一 monitor 的首条为 RUNNING，则该终端正在补招中 -> 跳过该终端,不会下发新的补招请求
            bool has_running = false;
            for (auto& lm : dev.line) {
                if (!lm.recall_list.empty() &&
                    lm.recall_list.front().recall_status == static_cast<int>(RecallStatus::RUNNING)) {
                    has_running = true;
                    break;
                }
            }
            if (has_running) continue;

            // 3) 选择该终端的“第一条 NOT_STARTED(0)”作为本终端本轮任务
            bool picked = false;
            for (auto& lm : dev.line) {
                if (lm.recall_list.empty()) continue;   //跳过空的监测点

                RecallMonitor& front = lm.recall_list.front();  //取非空测点的列表的第一条
                if (front.recall_status == static_cast<int>(RecallStatus::NOT_STARTED)) {
                    // 标记为 RUNNING，并设置终端忙状态
                    front.recall_status = static_cast<int>(RecallStatus::RUNNING);//该补招记录刷新为补招中

                    dev.isbusy        = 1;      //标记为忙
                    dev.busytype      = static_cast<int>(DeviceState::READING_EVENTLOG);//装置状态正在补招和idle的都刷新为正在补招
                    dev.busytimecount = 0;      //刷新业务超时计数
                    
                    // 记录任务（每终端只取这一条）
                    tasks.push_back(RecallTask{
                        dev.terminal_id,
                        front.StartTime,
                        front.EndTime,
                        lm.monitor_id
                    });
                    picked = true;  //该装置已取
                    break;
                }
            }
            // 若该终端没有 NOT_STARTED 的首条（可能剩下的都是 RUNNING 或内部已经被清空），
            // 就留待下一轮；不要清空运行态，直到所有条目被处理为止。
        }
    } // 解锁

    // 对于本轮挑选到的任务，逐终端下发一条，这些终端的状态都已设为补招
    for (const auto& t : tasks) {
        //处理入参
        std::tm tm1{}, tm2{};
        if (!parse_datetime_tm(t.start_time, tm1) || !parse_datetime_tm(t.end_time, tm2)) {
            std::cout << "[check_recall_event] parse time fail: "
                      << t.start_time << " ~ " << t.end_time << std::endl;
            
            continue;
        }

        uint8_t mp = 1; // 默认
        try {
            mp = static_cast<uint8_t>(std::stoi(t.monitor_index));
        } catch (...) { 
            std::cout << "[check_recall_event] parse mpid fail: "
                      << t.monitor_index << std::endl;
            continue;
        }

        // 下发补招请求，action=2
        ClientManager::instance().read_eventlog_action_to_device(
            t.dev_id, tm1, tm2, 2, mp);//2是暂态事件

        std::cout << "[check_recall_event] SEND dev=" << t.dev_id
                  << " monitor=" << mp
                  << " start=" << t.start_time
                  << " end="   << t.end_time
                  << " action(2)" << std::endl;
    }

    // 重要：本函数不把 RUNNING 改成 DONE/FAILED；
    //       应由设备回调/结果处理逻辑在完成后调用通知函数，把对应 monitor.front().recall_status 从1置为 2/3，
    //       下一轮本函数会弹掉 2/3，并在该终端所有 recall_list 全部清空后重置装置状态。
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////处理补招逻辑统计数据
// ====== 从文件名中提取“第二段下划线分隔字段”并转换为 epoch 秒 ======
static bool extract_epoch_from_filename(const std::string& name,
                                        long long& out_epoch,
                                        int logical_device_seq)
{
    // 拆分
    std::vector<std::string> parts;
    parts.reserve(8);
    size_t start = 0, pos;
    while ((pos = name.find('_', start)) != std::string::npos) {
        parts.emplace_back(name.substr(start, pos - start));
        start = pos + 1;
    }
    parts.emplace_back(name.substr(start)); // 最后一段（含扩展名）

    if (parts.size() < 4) return false;

    // 第二段序号是倒数第4段
    const std::string& seq_str = parts[parts.size() - 4];
    // 允许前导 0：把字符串转 int 后比较
    for (char c : seq_str) if (!std::isdigit(static_cast<unsigned char>(c))) return false;
    int seq_val = 0;
    try {
        seq_val = std::stoi(seq_str);
    } catch (...) {
        return false;
    }
    if (seq_val != logical_device_seq) return false;

    // 其余与上面相同
    const std::string& date_str = parts[parts.size() - 3];
    const std::string& time_str = parts[parts.size() - 2];
    std::string ms_str          = parts.back();
    size_t dot = ms_str.find('.');
    if (dot != std::string::npos) {
        ms_str.erase(dot);
    }
    if (date_str.size() != 8 || time_str.size() != 6) return false;
    for (char c : date_str) if (!std::isdigit(static_cast<unsigned char>(c))) return false;
    for (char c : time_str) if (!std::isdigit(static_cast<unsigned char>(c))) return false;
    for (char c : ms_str)   if (!std::isdigit(static_cast<unsigned char>(c))) return false;

    int year  = std::stoi(date_str.substr(0, 4));
    int month = std::stoi(date_str.substr(4, 2));
    int day   = std::stoi(date_str.substr(6, 2));
    int hour  = std::stoi(time_str.substr(0, 2));
    int min   = std::stoi(time_str.substr(2, 2));
    int sec   = std::stoi(time_str.substr(4, 2));
    // int msec  = std::stoi(ms_str);

    std::tm tm{}; tm.tm_isdst = -1;
    tm.tm_year = year - 1900;
    tm.tm_mon  = month - 1;
    tm.tm_mday = day;
    tm.tm_hour = hour;
    tm.tm_min  = min;
    tm.tm_sec  = sec;

    time_t t = timegm(&tm);
    if (t < 0) return false;

    out_epoch = static_cast<long long>(t); // 秒级
    return true;
}

// ====== ★修改：check_recall_stat —— 加入“两步法”状态机 ======
void check_recall_file() {

    std::vector<RecallTask> tasks;  // 本轮要发送的“每终端一条”（目录请求 或 文件下载 请求）

    {   //锁作用域
        std::lock_guard<std::mutex> lock(ledgermtx);

        for (auto& dev : terminal_devlist) {
            // 仅处理“正在补招/空闲”终端，与你原逻辑一致
            if (dev.busytype != static_cast<int>(DeviceState::READING_STATSFILE) &&
                dev.busytype != static_cast<int>(DeviceState::IDLE)) {
                continue;
            }

            // 1) 清理首条 DONE/FAILED
            bool any_non_empty = false;
            for (auto& lm : dev.line) {
                while (!lm.recall_list_static.empty()) {
                    const RecallFile& front = lm.recall_list_static.front();
                    if (front.recall_status == static_cast<int>(RecallStatus::DONE)) {
                        std::cout << "[check_recall_stat] DONE dev=" << dev.terminal_id
                                  << " monitor=" << lm.monitor_id
                                  << " " << front.StartTime << " ~ " << front.EndTime << std::endl;
                        lm.recall_list_static.pop_front();
                    } else if (front.recall_status == static_cast<int>(RecallStatus::FAILED)) {
                        std::cout << "[check_recall_stat] FAILED dev=" << dev.terminal_id
                                  << " monitor=" << lm.monitor_id
                                  << " " << front.StartTime << " ~ " << front.EndTime << std::endl;
                        lm.recall_list_static.pop_front();
                    } else {
                        break;
                    }
                }
                if (!lm.recall_list_static.empty()) any_non_empty = true;
            }

            // 无条目时的装置态收尾
            if (!any_non_empty && dev.busytype == static_cast<int>(DeviceState::READING_EVENTLOG)) {
                // （保持你原注释）处于“暂态补招”的状态且无条目 -> 清空运行态
                dev.guid.clear();
                dev.busytype = 0;
                dev.isbusy = 0;
                dev.busytimecount = 0;
                continue;
            } else if (!any_non_empty && dev.busytype == static_cast<int>(DeviceState::IDLE)) {
                continue;
            }

            // 2) 若任一 monitor 的首条为 RUNNING，则该终端正在补招中 -> 不下发新的任务（但需要推进状态机！）
            bool has_running = false;
            for (auto& lm : dev.line) {
                if (!lm.recall_list_static.empty() &&
                    lm.recall_list_static.front().recall_status == static_cast<int>(RecallStatus::RUNNING)) {
                    has_running = true;
                    break;
                }
            }

            // ★新增：当存在 RUNNING 时，推进“该终端的首条补招记录”的两步状态机
            if (has_running) {
                for (auto& lm : dev.line) {
                    if (lm.recall_list_static.empty()) continue;
                    RecallFile& front = lm.recall_list_static.front();
                    if (front.recall_status != static_cast<int>(RecallStatus::RUNNING)) continue;

                    // 初始化阶段：从 NOT_STARTED->RUNNING 已完成，此处保证 phase 就绪
                    if (front.phase == RecallPhase::IDLE) {
                        front.phase = RecallPhase::LISTING;
                        front.cur_dir_index = 0;
                        front.cur_dir.clear();
                        front.list_result = ActionResult::PENDING;
                        front.download_result = ActionResult::PENDING;
                        front.download_queue.clear();
                        // 立即发起第一个目录请求
                        if (front.cur_dir_index < static_cast<int>(front.dir_candidates.size())) {
                            front.cur_dir = front.dir_candidates[front.cur_dir_index];
                            // ★★ 只发一个目录请求，并等待外部线程回写结果与文件名列表
                            ClientManager::instance().add_file_menu_action_to_device(dev.terminal_id, front.cur_dir);
                            std::cout << "[check_recall_stat] LIST req dev=" << dev.terminal_id
                                      << " monitor=" << lm.monitor_id
                                      << " dir=" << front.cur_dir << std::endl;
                        } else {
                            // 无目录可查
                            front.recall_status = static_cast<int>(RecallStatus::FAILED);
                            std::cout << "[check_recall_stat] no dir candidates, FAIL dev=" << dev.terminal_id
                                      << " monitor=" << lm.monitor_id << std::endl;
                        }
                        // 一个终端只推进首条，跳出
                        break;
                    }

                    // LISTING 阶段：等待其他线程回写 list_result + dir_files[cur_dir]
                    if (front.phase == RecallPhase::LISTING) {
                        if (front.list_result == ActionResult::PENDING) {
                            // 还在等目录回执，本轮不再发任何请求
                            // （外部线程拿到目录文件列表后，应写入：front.dir_files[front.cur_dir] = {a,b,c...} 并置 list_result=OK/FAIL）
                            continue;
                        }

                        if (front.list_result == ActionResult::FAIL) {
                            // 尝试下一个目录
                            front.cur_dir_index++;
                            front.list_result = ActionResult::PENDING;
                            front.download_queue.clear();
                            if (front.cur_dir_index < static_cast<int>(front.dir_candidates.size())) {
                                front.cur_dir = front.dir_candidates[front.cur_dir_index];
                                ClientManager::instance().add_file_menu_action_to_device(dev.terminal_id, front.cur_dir);
                                std::cout << "[check_recall_stat] LIST retry dev=" << dev.terminal_id
                                          << " monitor=" << lm.monitor_id
                                          << " dir=" << front.cur_dir << std::endl;
                            } else {
                                // 所有目录都失败
                                front.recall_status = static_cast<int>(RecallStatus::FAILED);
                                std::cout << "[check_recall_stat] all dir failed, FAIL dev=" << dev.terminal_id
                                          << " monitor=" << lm.monitor_id << std::endl;
                            }
                            continue;
                        }

                        // OK：根据起止时间筛选文件
                        {
                            long long beg = parse_time_to_epoch(front.StartTime);
                            long long end = parse_time_to_epoch(front.EndTime);
                            if (beg < 0 || end < 0 || beg > end) {
                                front.recall_status = static_cast<int>(RecallStatus::FAILED);
                                std::cout << "[check_recall_stat] time parse ERR, FAIL dev=" << dev.terminal_id
                                          << " monitor=" << lm.monitor_id
                                          << " start=" << front.StartTime
                                          << " end="   << front.EndTime << std::endl;
                                continue;
                            }

                            auto it = front.dir_files.find(front.cur_dir);
                            if (it != front.dir_files.end()) {
                                if (front.direct_mode) {
                                    // ★新增：直下文件（精确匹配文件名）
                                    for (const auto& ent : it->second) {
                                        if (ent.flag != 1) continue; // 只要文件
                                        // 安全从 char[64] 转成 std::string
                                        size_t n = ::strnlen(ent.name, sizeof(ent.name));
                                        std::string fname(ent.name, n);
                                        if (fname == front.target_filename) {
                                            front.download_queue.push_back(front.cur_dir + "/" + fname);
                                            break; // 找到一个就足够
                                        }
                                    }
                                } else {
                                    // ☆原有：按时间窗筛选
                                    long long beg = parse_time_to_epoch(front.StartTime);
                                    long long end = parse_time_to_epoch(front.EndTime);
                                
                                    for (const auto& ent : it->second) {
                                        if (ent.flag != 1) continue; // 只要文件
                                        // 文件名
                                        size_t n = ::strnlen(ent.name, sizeof(ent.name));
                                        std::string fname(ent.name, n);
                                    
                                        long long fe = -1;
                                        int seq = 0;
                                        try { seq = std::stoi(lm.logical_device_seq); } catch (...) { seq = 0; }
                                        if (!extract_epoch_from_filename(fname, fe, seq)) continue;
                                        if (fe >= beg && fe <= end) {
                                            front.download_queue.push_back(front.cur_dir + "/" + fname);
                                        }
                                    }
                                }
                            }
                        }

                        if (front.download_queue.empty()) {
                            // 当前目录无匹配文件 -> 试下一个目录
                            front.cur_dir_index++;
                            front.list_result = ActionResult::PENDING;
                            if (front.cur_dir_index < static_cast<int>(front.dir_candidates.size())) {
                                front.cur_dir = front.dir_candidates[front.cur_dir_index];
                                ClientManager::instance().add_file_menu_action_to_device(dev.terminal_id, front.cur_dir);
                                std::cout << "[check_recall_stat] LIST next dev=" << dev.terminal_id
                                          << " monitor=" << lm.monitor_id
                                          << " dir=" << front.cur_dir << std::endl;
                            } else {
                                // 所有目录都“无匹配文件”
                                front.recall_status = static_cast<int>(RecallStatus::FAILED);
                                std::cout << "[check_recall_stat] no matched files in ALL dirs, FAIL dev=" << dev.terminal_id
                                          << " monitor=" << lm.monitor_id << std::endl;
                            }
                            continue;
                        } else {
                            // 进入下载阶段
                            front.phase = RecallPhase::DOWNLOADING;
                            front.download_result = ActionResult::PENDING;
                            front.downloading_file.clear();
                            std::cout << "[check_recall_stat] enter DOWNLOADING dev=" << dev.terminal_id
                                      << " monitor=" << lm.monitor_id
                                      << " count=" << front.download_queue.size() << std::endl;
                        }
                    }

                    // DOWNLOADING 阶段：一次只下一个文件，等待外部线程填充 download_result
                    if (front.phase == RecallPhase::DOWNLOADING) {
                        if (front.downloading_file.empty()) {
                            if (front.download_queue.empty()) {
                                // 所有文件下载完毕
                                front.recall_status = static_cast<int>(RecallStatus::DONE);
                                std::cout << "[check_recall_stat] DONE dev=" << dev.terminal_id
                                          << " monitor=" << lm.monitor_id << std::endl;
                                continue;
                            }
                            // 发起下一文件下载
                            front.downloading_file = front.download_queue.front();
                            front.download_queue.pop_front();
                            front.download_result = ActionResult::PENDING;

                            // ★★ 只发一个下载请求，等待外部线程回写结果
                            ClientManager::instance().add_file_download_action_to_device(dev.terminal_id, front.downloading_file);
                            std::cout << "[check_recall_stat] DL req dev=" << dev.terminal_id
                                      << " monitor=" << lm.monitor_id
                                      << " file=" << front.downloading_file << std::endl;
                            continue;
                        } else {
                            // 等待 download_result
                            if (front.download_result == ActionResult::PENDING) {
                                continue; // 仍在等待
                            }
                            if (front.download_result == ActionResult::OK) {
                                // 记录成功文件
                                front.file_paths.push_back(front.downloading_file);
                                std::cout << "[check_recall_stat] DL ok dev=" << dev.terminal_id
                                          << " monitor=" << lm.monitor_id
                                          << " file=" << front.downloading_file << std::endl;
                                // 清空当前文件标志，进入下一轮取队首
                                front.downloading_file.clear();
                                continue;
                            } else {
                                // 失败：直接尝试下一个文件（不中断整条补招）
                                std::cout << "[check_recall_stat] DL fail dev=" << dev.terminal_id
                                          << " monitor=" << lm.monitor_id
                                          << " file=" << front.downloading_file << std::endl;
                                front.downloading_file.clear();
                                continue;
                            }
                        }
                    }

                    // 一个终端只推进“一个监测点的首条”状态机，避免并行
                    break;
                }

                // 本终端已有 RUNNING 项，且已推进；不再挑选新的 NOT_STARTED
                continue;
            }

            // 3) 没有 RUNNING：挑选第一条 NOT_STARTED，并发起“首个目录”的请求
            for (auto& lm : dev.line) {
                if (lm.recall_list_static.empty()) continue;

                RecallFile& front = lm.recall_list_static.front();
                if (front.recall_status == static_cast<int>(RecallStatus::NOT_STARTED)) {
                    // 标记为 RUNNING，并设置终端忙状态
                    front.recall_status = static_cast<int>(RecallStatus::RUNNING);
                    dev.isbusy        = 1;
                    dev.busytype      = static_cast<int>(DeviceState::READING_STATSFILE);
                    dev.busytimecount = 0;

                    // 初始化状态机并发出第一个目录请求
                    front.reset_runtime(true);//保留直下文件信息
                    front.phase = RecallPhase::LISTING;
                    if (!front.dir_candidates.empty()) {
                        front.cur_dir_index = 0;
                        front.cur_dir = front.dir_candidates[0];
                        front.list_result = ActionResult::PENDING;
                        ClientManager::instance().add_file_menu_action_to_device(dev.terminal_id, front.cur_dir);
                        std::cout << "[check_recall_stat] LIST start dev=" << dev.terminal_id
                                  << " monitor=" << lm.monitor_id
                                  << " dir=" << front.cur_dir
                                  << " start=" << front.StartTime
                                  << " end="   << front.EndTime << std::endl;
                    } else {
                        front.recall_status = static_cast<int>(RecallStatus::FAILED);
                        std::cout << "[check_recall_stat] empty dir_candidates, FAIL dev=" << dev.terminal_id
                                  << " monitor=" << lm.monitor_id << std::endl;
                    }

                    // 每终端本轮仅取一条
                    break;
                }
            }
        } // end for dev
    } // 解锁

    // ★说明：本函数不主动把 RUNNING 改 DONE/FAILED；
    //        由“其他线程”在目录/下载结果返回后，找到对应 dev/monitor 的 lm.recall_list_static.front()，
    //        写入：
    //          - 目录请求：front.dir_files[front.cur_dir] = {...}; front.list_result = OK/FAIL;
    //          - 文件下载：front.download_result = OK/FAIL;
    //        下一轮本函数会推进状态机（继续本目录的文件或切到下一个目录）。
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////根据下发的指令直接补招文件
// ★新增：直下文件的任务入队（终端、测点、文件名）
// 约定：外部线程的回调，照旧把目录返回值写到 lm.recall_list_static.front().dir_files[dir]，
// 并置 front.list_result=OK/FAIL；下载回执置 front.download_result=OK/FAIL。
// 处理指令部分将文件名拼接出来调用这个函数
bool enqueue_direct_download(const std::string& dev_id,
                             const std::string& monitor_id,
                             const std::string& filename,
                             const std::vector<std::string>& dir_candidates)
{
    std::lock_guard<std::mutex> lk(ledgermtx);

    // 找终端
    auto dev_it = std::find_if(terminal_devlist.begin(), terminal_devlist.end(),
                               [&](const terminal_dev& d){ return d.terminal_id == dev_id; });
    if (dev_it == terminal_devlist.end()) return false;

    // 找监测点
    auto lm_it = std::find_if(dev_it->line.begin(), dev_it->line.end(),
                              [&](const ledger_monitor& lm){ return lm.monitor_id == monitor_id; });
    if (lm_it == dev_it->line.end()) return false;

    // 组装一条 RecallFile
    RecallFile rf;
    rf.recall_status   = static_cast<int>(RecallStatus::NOT_STARTED);
    rf.StartTime       = "1970-01-01 00:00:00";   // 仅占位，直下文件不会用到时间窗
    rf.EndTime         = "1970-01-01 00:00:01";
    rf.dir_candidates  = dir_candidates;          // 传入要检索的目录列表
    rf.direct_mode     = true;                    // ★关键：直下文件
    rf.target_filename = filename;                // ★关键：匹配的目标文件名

    lm_it->recall_list_static.push_back(std::move(rf));

    // 若设备空闲，可直接置忙（可选，视你的流程而定）
    if (dev_it->busytype == static_cast<int>(DeviceState::IDLE)) {
        dev_it->isbusy        = 1;
        dev_it->busytype      = static_cast<int>(DeviceState::READING_STATSFILE); // 或 READING_EVENTFILE
        dev_it->busytimecount = 0;
    }
    return true;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////通讯响应处理

void filemenu_cache_put(const std::string& dev_id,
                        std::vector<tag_dir_info> FileList)
{
    std::lock_guard<std::mutex> lk(g_filemenu_cache_mtx);
    g_filemenu_cache[dev_id] = std::move(FileList);  // 直接存 vector<tag_dir_info>
}

bool filemenu_cache_take(const std::string& dev_id, std::vector<tag_dir_info>& out)
{
    std::lock_guard<std::mutex> lk(g_filemenu_cache_mtx);
    auto it = g_filemenu_cache.find(dev_id);
    if (it == g_filemenu_cache.end()) return false;

    out = std::move(it->second);   // 转移 vector<tag_dir_info>
    g_filemenu_cache.erase(it);    // 删除缓存
    return true;
}

// 提取文件名列表（仅 flag==1）
static inline void build_file_name_list(const std::vector<tag_dir_info>& in,
                                        std::list<std::string>& out)
{
    out.clear();
    for (const auto& e : in) {
        if (e.flag == 1) {
            // 安全地从固定长度 char[64] 转成 std::string
            size_t n = ::strnlen(e.name, sizeof(e.name));
            out.emplace_back(std::string(e.name, n));
        }
    }
}

void on_device_response_minimal(int response_code,
                                const std::string& id,
                                unsigned char cid,
                                int device_state_int)
{
    const bool ok = is_ok(response_code);
    DeviceState state = static_cast<DeviceState>(device_state_int);

    switch (state) {
    // ================= 特殊：补招事件日志 =================
    case DeviceState::READING_EVENTLOG: {
        std::lock_guard<std::mutex> lk(ledgermtx);

        terminal_dev* dev = nullptr;
        for (auto& d : terminal_devlist) {
            if (d.terminal_id == id) { dev = &d; break; }
        }
        if (!dev) {
            std::cout << "[RESP][EVENTLOG] dev not found, id=" << id
                      << " rc=" << response_code << std::endl;
            return;
        }

        // 找到“首条 RUNNING”的补招条目，按 rc==200 成功/失败标记
        bool updated = false;

        // 1) 找到 logical_device_seq 与 cid 匹配的 monitor
        ledger_monitor* matched_monitor = nullptr;

        // 将 cid 转成十进制字符串用于直接比较
        const std::string cid_str = std::to_string(static_cast<int>(cid));

        for (auto& lm : dev->line) {
            // 直接字符串相等
            if (lm.logical_device_seq == cid_str) {
                matched_monitor = &lm;
                break;
            }
        }

        if (!matched_monitor) { //没有匹配的监测点
            std::cout << "[RESP][EVENTLOG][WARN] dev=" << id
                      << " cannot find monitor by cid=" << static_cast<int>(cid)
                      << " (no logical_device_seq match)" << std::endl;
        } 
        else {

            // 2) 仅更新该监测点 recall_list 的首条，且要求处于 RUNNING
            if (!matched_monitor->recall_list.empty()) {
                RecallMonitor& front = matched_monitor->recall_list.front();    //取出首条
                if (front.recall_status == static_cast<int>(RecallStatus::RUNNING)) {   //正在补招
                    if (ok) {   //补招成功
                        front.recall_status = static_cast<int>(RecallStatus::DONE);  //标记为完成，补招任务会执行下一条
                        std::cout << "[RESP][EVENTLOG][OK] dev=" << id
                                  << " mon=" << matched_monitor->monitor_id
                                  << " " << front.StartTime << "~" << front.EndTime
                                  << std::endl;
                    } else {    //补招失败
                        front.recall_status = static_cast<int>(RecallStatus::FAILED);
                        std::cout << "[RESP][EVENTLOG][FAIL] dev=" << id
                                  << " mon=" << matched_monitor->monitor_id
                                  << " " << front.StartTime << "~" << front.EndTime
                                  << " rc=" << response_code << std::endl;  //错误响应码

                        //记录日志
                        DIY_ERRORLOG_CODE(matched_monitor->monitor_id.c_str(),2,static_cast<int>(LogCode::LOG_CODE_RECALL),"【ERROR】监测点:%s 补招数据失败 - 失败时间点:%lld 至 %lld",matched_monitor->monitor_id.c_str(),front.StartTime,front.EndTime);
                    }
                    updated = true;
                } else {    //首条不是 RUNNING 状态，不应该收到这条响应
                    std::cout << "[RESP][EVENTLOG][WARN] dev=" << id
                              << " mon=" << matched_monitor->monitor_id
                              << " first item not RUNNING (status="
                              << front.recall_status << ")" << std::endl;   //打印调试
                }
            } else {    //补招列表为空，不应该收到这条响应
                std::cout << "[RESP][EVENTLOG][WARN] dev=" << id
                          << " mon=" << matched_monitor->monitor_id
                          << " recall_list empty" << std::endl;
            }
        }

        if (!updated) {//不更新补招列表
            std::cout << "[RESP][EVENTLOG][WARN] no RUNNING item, dev=" << id
                      << " rc=" << response_code << std::endl;
        }

        break;
    }

    // ================= 特殊：读取文件目录 =================
    case DeviceState::READING_FILEMENU: {
        std::lock_guard<std::mutex> lk(ledgermtx);

        // 1) 找到对应终端
        terminal_dev* dev = nullptr;
        for (auto& d : terminal_devlist) {
            if (d.terminal_id == id) { dev = &d; break; }
        }
        if (!dev) {
            std::cout << "[RESP][FILEMENU] dev not found, id=" << id
                      << " rc=" << response_code << std::endl;
            break;
        }

        // 2) 按该终端当前 busytype 分支处理
        const int bt = dev->busytype;
        if (bt == static_cast<int>(DeviceState::READING_FILEMENU)) {
            // ====== 分支 A：当前业务就是“读取文件目录” ======
            if (ok) {
                std::vector<tag_dir_info> names;
                if (filemenu_cache_take(id, names)) {

                    //发送目录
                    send_file_list(dev,names);

                } else {
                    // 失败：响应web并复位为空闲
                    send_reply_to_cloud(static_cast<int>(ResponseCode::BAD_REQUEST), id, static_cast<int>(DeviceState::READING_FILEMENU),dev->guid,dev->mac);
                    std::cout << "[RESP][FILEMENU->FILEMENU][WARN] dev=" << id
                              << " names missing in cache" << std::endl;
                }

                // 成功：复位
                dev->guid.clear();
                dev->isbusy        = 0;
                dev->busytype      = 0;
                dev->busytimecount = 0;
                std::cout << "[RESP][FILEMENU->FILEMENU][OK] dev=" << id << std::endl;
            } else {
                // 失败：响应web并复位为空闲
                send_reply_to_cloud(response_code, id, static_cast<int>(DeviceState::READING_FILEMENU),dev->guid,dev->mac);

                dev->guid.clear();
                dev->isbusy        = 0;
                dev->busytype      = 0;
                dev->busytimecount = 0;
                std::cout << "[RESP][FILEMENU->FILEMENU][FAIL] dev=" << id
                          << " rc=" << response_code << std::endl;
            }
        }
        else if (
            bt == static_cast<int>(DeviceState::READING_EVENTFILE)
         || bt == static_cast<int>(DeviceState::READING_STATSFILE)
        ) {
            // ====== 分支 B：当前业务为“下载事件文件/统计文件”（两者处理相同） ======
            // 一个装置同一时刻只会有一个监测点在下载
            ledger_monitor* running_monitor = nullptr;
            RecallFile*     running_front   = nullptr;
        
            // 在该终端下，找到“首条 recall_list_static.front() 正在运行且处于 DOWNLOADING 的监测点”
            for (auto& lm : dev->line) {
                if (lm.recall_list_static.empty()) continue;
                RecallFile& f = lm.recall_list_static.front();
                if (f.recall_status == static_cast<int>(RecallStatus::RUNNING)
                    && f.phase == RecallPhase::DOWNLOADING) {
                    running_monitor = &lm;
                    running_front   = &f;
                    break;
                }
            }
        
            if (!running_monitor || !running_front) {
                std::cout << "[RESP][FILEDATA->(EVENT/STATS)FILE][WARN] dev=" << id
                          << " no RUNNING/DOWNLOADING recall on this device, ignore resp"
                          << " rc=" << response_code << std::endl;
                break;
            }
        
            // 根据回执结果，回写下载结果；状态机会在下一轮推进到下一个文件/结束
            if (ok) {
                running_front->download_result = ActionResult::OK;
                std::cout << "[RESP][FILEDATA->(EVENT/STATS)FILE][OK] dev=" << id
                          << " monitor=" << running_monitor->monitor_id
                          << " file=" << running_front->downloading_file << std::endl;
            } else {
                running_front->download_result = ActionResult::FAIL;
                std::cout << "[RESP][FILEDATA->(EVENT/STATS)FILE][FAIL] dev=" << id
                          << " monitor=" << running_monitor->monitor_id
                          << " file=" << running_front->downloading_file
                          << " rc=" << response_code << std::endl;
            }
            break;
        }
        else {
            // ====== 分支 C：其他业务场景下收到 FILEMENU 响应，统一处理 打印提示======
            if (ok) {
                // 成功：

                std::cout << "[unknown][RESP][FILEMENU->OTHER][OK] dev=" << id
                          << " keep busytype=" << bt << std::endl;
            } else {
                // 失败：

                std::cout << "[unknown][RESP][FILEMENU->OTHER][FAIL] dev=" << id
                          << " rc=" << response_code << " prev busytype=" << bt << std::endl;
            }
        }
        break;
    }

    // ================= 特殊：下载文件 =================
    case DeviceState::READING_FILEDATA: {
        std::lock_guard<std::mutex> lk(ledgermtx);

        // 1) 找到对应终端
        terminal_dev* dev = nullptr;
        for (auto& d : terminal_devlist) {
            if (d.terminal_id == id) { dev = &d; break; }
        }
        if (!dev) {
            std::cout << "[RESP][FILEDATA] dev not found, id=" << id
                      << " rc=" << response_code << std::endl;
            break;
        }

        // 2) 按该终端当前 busytype 分支处理
        const int bt = dev->busytype;
        if (bt == static_cast<int>(DeviceState::READING_FILEDATA)) {
            // ====== 分支 A：当前业务就是“读取文件数据” ======
            if (ok) {
                // 成功：复位
                send_reply_to_cloud(static_cast<int>(ResponseCode::OK), id, static_cast<int>(DeviceState::READING_FILEDATA),dev->guid,dev->mac);

                dev->guid.clear();
                dev->isbusy        = 0;
                dev->busytype      = 0;
                dev->busytimecount = 0;
                std::cout << "[RESP][FILEDATA->FILEDATA][OK] dev=" << id << std::endl;
            } else {
                // 失败：响应web并复位
                send_reply_to_cloud(response_code, id, static_cast<int>(DeviceState::READING_FILEDATA),dev->guid,dev->mac);

                dev->guid.clear();
                dev->isbusy        = 0;
                dev->busytype      = 0;
                dev->busytimecount = 0;
                std::cout << "[RESP][FILEDATA->FILEDATA][FAIL] dev=" << id
                          << " rc=" << response_code << std::endl;
            }
        }
        else if (
            bt == static_cast<int>(DeviceState::READING_EVENTFILE)
            || bt == static_cast<int>(DeviceState::READING_STATSFILE)
        ) {
            // ====== 分支 B：当前业务为“下载事件文件/统计文件”（两者处理相同） ======
            // ★新增：通过 cid 精确找到监测点（logical_device_seq == cid）
            ledger_monitor* matched_monitor = nullptr;
            {
                const std::string cid_str = std::to_string(static_cast<int>(cid));
                for (auto& lm : dev->line) {
                    if (lm.logical_device_seq == cid_str) { matched_monitor = &lm; break; }
                }
            }
        
            if (!matched_monitor || matched_monitor->recall_list_static.empty()) {
                std::cout << "[RESP][FILEDATA->(EVENT/STATS)FILE][WARN] dev=" << id
                          << " no matched monitor or empty recall list, cid="
                          << static_cast<int>(cid) << std::endl;
                break;
            }
        
            RecallFile& front = matched_monitor->recall_list_static.front();
            if (front.recall_status != static_cast<int>(RecallStatus::RUNNING)
                || front.phase != RecallPhase::DOWNLOADING) {
                std::cout << "[RESP][FILEDATA->(EVENT/STATS)FILE][WARN] dev=" << id
                          << " monitor=" << matched_monitor->monitor_id
                          << " ignore resp: status=" << front.recall_status
                          << " phase=" << static_cast<int>(front.phase) << std::endl;
                break;
            }
        
            if (ok) {
                // ★新增：下载成功 -> 通知状态机推进到下一个文件（真正入账 file_paths 在 check_recall_stat 里做）
                front.download_result = ActionResult::OK;
                std::cout << "[RESP][FILEDATA->(EVENT/STATS)FILE][OK] dev=" << id
                          << " file=" << front.downloading_file << std::endl;
            } else {
                // ★新增：下载失败 -> 让状态机尝试下一个文件
                front.download_result = ActionResult::FAIL;
                std::cout << "[RESP][FILEDATA->(EVENT/STATS)FILE][FAIL] dev=" << id
                          << " file=" << front.downloading_file
                          << " rc=" << response_code << std::endl;
            }
            break;
        }
        else {
            // ====== 分支 C：其他业务场景下收到 FILEDATA 响应，统一处理 打印提示======
            if (ok) {
                // 成功：

                std::cout << "[unknown][RESP][FILEDATA->OTHER][OK] dev=" << id
                          << " keep busytype=" << bt << std::endl;
            } else {
                // 失败：

                std::cout << "[unknown][RESP][FILEDATA->OTHER][FAIL] dev=" << id
                          << " rc=" << response_code << " prev busytype=" << bt << std::endl;
            }
        }
        break;
    }

    // ================= 其它状态统一处理 =================
    default: {

        std::lock_guard<std::mutex> lk(ledgermtx);
        terminal_dev* dev = nullptr;
        for (auto& d : terminal_devlist) {
            if (d.terminal_id == id) { dev = &d; break; }
        }
        if (dev) {
            //直接根据输入响应mq
            send_reply_to_cloud(response_code, id, device_state_int, dev->guid, dev->mac);
            //其他的错误和成功都会结束业务
            dev->guid.clear();       // 清空 guid
            dev->busytype = 0;       // 业务类型归零
            dev->isbusy = 0;         // 清空业务标志
            dev->busytimecount = 0;  // 计时归零
            std::cout << "[clear_terminal_runtime_state] Cleared runtime state for terminal_id="
                  << id << std::endl;
        }
    
        break;
    }
    } // end switch
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////记录暂态事件到本地
// 将一条暂态数据更新/写入到指定终端ID与逻辑序号的监测点
// 返回 true 表示已写入（更新或追加），false 表示未找到对应终端/监测点。

bool append_qvvr_event(const std::string& terminal_id,
                              int              logical_seq,      // 监测点序号（如 1）
                              int              nType,            // 事件类型
                              double           fPersisstime_sec, // 持续时间（秒）
                              double           fMagnitude_pu,    // 幅值（pu）
                              uint64_t         triggerTimeMs,    // 触发时间（毫秒）
                              int              phase)            // 相别
{
    std::cout << "[append_qvvr_event] enter"
              << " tid=" << std::this_thread::get_id()
              << " terminal_id=" << terminal_id
              << " logical_seq=" << logical_seq
              << " type=" << nType
              << " per_s=" << fPersisstime_sec
              << " mag_pu=" << fMagnitude_pu
              << " time_ms=" << static_cast<unsigned long long>(triggerTimeMs)
              << " phase=" << phase
              << std::endl;

    std::lock_guard<std::mutex> lk(ledgermtx);
    std::cout << "[append_qvvr_event] lock acquired. terminal_devlist.size="
              << terminal_devlist.size() << std::endl;

    // 1) 找终端
    auto dev_it = std::find_if(terminal_devlist.begin(), terminal_devlist.end(),
        [&](const terminal_dev& d){ return d.terminal_id == terminal_id; });

    if (dev_it == terminal_devlist.end()) {
        std::cout << "[append_qvvr_event][MISS] terminal not found: "
                  << terminal_id << std::endl;
        return false;
    }
    std::cout << "[append_qvvr_event][HIT] terminal_id=" << terminal_id
              << " monitors(line).size=" << dev_it->line.size()
              << std::endl;

    // 2) 找监测点（按逻辑序号匹配：字符串等于 或 数值等于）
    ledger_monitor* pMon = nullptr;
    for (size_t i = 0; i < dev_it->line.size(); ++i) {
        auto& m = dev_it->line[i];
        bool eq_str = (m.logical_device_seq == std::to_string(logical_seq));
        bool eq_num = false;
        try {
            if (!m.logical_device_seq.empty())
                eq_num = (std::stoi(m.logical_device_seq) == logical_seq);
        } catch (...) {
            // 仅调试提示，不改变原逻辑
            std::cout << "[append_qvvr_event][monitor #" << i
                      << "] stoi fail, logical_device_seq=\""
                      << m.logical_device_seq << "\"" << std::endl;
        }

        std::cout << "[append_qvvr_event][probe monitor #" << i
                  << "] monitor_id=" << m.monitor_id
                  << " logical_device_seq=\"" << m.logical_device_seq << "\""
                  << " eq_str=" << eq_str << " eq_num=" << eq_num << std::endl;

        if (eq_str || eq_num) { pMon = &m; break; }
    }

    if (!pMon) {
        std::cout << "[append_qvvr_event][MISS] monitor not found by seq="
                  << logical_seq << " in terminal_id=" << terminal_id << std::endl;
        return false;
    }
    std::cout << "[append_qvvr_event][HIT] monitor_id=" << pMon->monitor_id
              << " logical_device_seq=" << pMon->logical_device_seq
              << " qvvrdata.size=" << pMon->qvvrevent.qvvrdata.size()
              << std::endl;

    qvvr_event& qe = pMon->qvvrevent;

    // 3) 先尝试“就地更新”（同类型 + 同时间戳 视为同一事件）
    for (size_t i = 0; i < qe.qvvrdata.size(); ++i) {
        auto& q = qe.qvvrdata[i];
        if (q.QVVR_type == nType && q.QVVR_time == triggerTimeMs) {
            std::cout << "[append_qvvr_event][UPDATE match idx=" << i << "]"
                      << " old{used=" << q.used_status
                      << ", per=" << q.QVVR_PerTime
                      << ", mag=" << q.QVVR_Amg
                      << ", phase=" << q.phase
                      << "} -> new{used=true"
                      << ", per=" << fPersisstime_sec
                      << ", mag=" << fMagnitude_pu
                      << ", phase=" << phase
                      << "}" << std::endl;

            q.used_status   = true;
            q.QVVR_PerTime  = fPersisstime_sec;
            q.QVVR_Amg      = fMagnitude_pu;
            q.phase         = phase;

            std::cout << "[append_qvvr_event] done(update)."
                      << std::endl;
            return true;
        }
    }

    // 4) 复用空槽（used_status=false）
    for (size_t i = 0; i < qe.qvvrdata.size(); ++i) {
        auto& q = qe.qvvrdata[i];
        if (!q.used_status) {
            std::cout << "[append_qvvr_event][REUSE idx=" << i << "]"
                      << " set{type=" << nType
                      << ", time_ms=" << static_cast<unsigned long long>(triggerTimeMs)
                      << ", per=" << fPersisstime_sec
                      << ", mag=" << fMagnitude_pu
                      << ", phase=" << phase
                      << "}" << std::endl;

            q.used_status   = true;
            q.QVVR_type     = nType;
            q.QVVR_time     = triggerTimeMs;
            q.QVVR_PerTime  = fPersisstime_sec;
            q.QVVR_Amg      = fMagnitude_pu;
            q.phase         = phase;

            std::cout << "[append_qvvr_event] done(reuse)."
                      << std::endl;
            return true;
        }
    }

    // 5) 直接追加
    qvvr_data q{};
    q.used_status   = true;
    q.QVVR_type     = nType;
    q.QVVR_time     = triggerTimeMs;     // ms
    q.QVVR_PerTime  = fPersisstime_sec;  // s
    q.QVVR_Amg      = fMagnitude_pu;
    q.phase         = phase;
    qe.qvvrdata.push_back(q);

    std::cout << "[append_qvvr_event][PUSH_BACK]"
              << " new_size=" << qe.qvvrdata.size()
              << " last_idx=" << (qe.qvvrdata.empty() ? -1 : (int)qe.qvvrdata.size()-1)
              << " {type=" << nType
              << ", time_ms=" << static_cast<unsigned long long>(triggerTimeMs)
              << ", per=" << fPersisstime_sec
              << ", mag=" << fMagnitude_pu
              << ", phase=" << phase
              << "}" << std::endl;

    std::cout << "[append_qvvr_event] done(push_back)."
              << std::endl;
    return true;
}






