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

#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 <unordered_set>

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

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

//////////////////////////////////////////////////////////////////////////////////////////////////
extern time_t ConvertToTimestamp(const tagTime& time);

/////////////////////////////////////////////////////////////////////////////////////////////////
// 运行信息
struct RunningInformation {
    std::string Time;
    std::string CpuLoad;
    std::string FreeMemory;
    std::string TotalMemory;
    std::string FreeStorage;
    std::string TotalStorage;
    std::string HardTimeSync;
    std::string SntpTimeSync;
    std::string CloudTimeSync;
    std::string SignalStrength;
};

// 版本信息
struct DeviceVersionInfo {
    std::string BaseModel;          // 1
    std::string CloudProtocolVer;   // 2
    std::string AppVersion;         // 3
    std::string AppDate;            // 4
    std::string AppChecksum;        // 5
    std::string VoltageWiring;      // 6
    std::string CurrentBSynthetic;  // 7
    int DataStatInterval = 0;       // 8
    float RatedVoltage = 0.0f;      // 9
    float PTRatio = 0.0f;           // 10
    float CTRatio = 0.0f;           // 11
    std::string SntpIP;             // 12
    int SntpPort = 0;               // 13
    int SntpInterval = 0;           // 14
    int WebPort = 0;                // 15
    int FtpPort = 0;                // 16
    int PqdifInterval = 0;          // 17
    int WaveFileTypeCount = 0;      // 18
    std::string SpecialVersion;     // 19
    std::string DeviceModel;        // 20
    int HarmonicEnergyFlag = 0;     // 21
    std::string PhysicalName;       // 22
    std::string WaveLDName;         // 23
    int HighFreqHarmonicFlag = 0;   // 24
    unsigned int CommProtocols = 0; // 51
    unsigned int TimeSyncMethods = 0;// 52
    unsigned int DeviceFunctions = 0;// 53
};

////////////////////////////////////////////////////////////////////////////////////////////////////
//补招记录文件
std::mutex g_recall_file_mtx;
std::map<std::pair<std::string,std::string>, std::string> g_recall_file_index;

//实时数据时间记录
std::mutex g_last_ts_mtx;
std::unordered_map<std::string, time_t> g_last_ts_by_devid;

//目录信息缓存
static std::mutex g_filemenu_cache_mtx;
std::map<std::string, std::vector<tag_dir_info>> g_filemenu_cache;

// 运行信息缓存
static std::mutex g_runninginfo_cache_mtx;
static std::map<std::string, RunningInformation> g_runninginfo_cache;

// 版本信息缓存
static std::mutex g_versioninfo_cache_mtx;
static std::map<std::string, DeviceVersionInfo> g_versioninfo_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         LEDGER_MAX_ITEMS = 5; //台账打印最大项数限制    
int 		TEST_PORT = 11000;			//用于当前进程登录测试shell的端口
std::string G_TEST_LIST 		= "";	//测试用的发送实际数据的终端列表
std::vector<std::string> TESTARRAY;		//解析的列表数组

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////其他文件定义的函数引用声明

bool enqueue_direct_download(const std::string& dev_id,
                             const std::string& monitor_id,
                             const std::string& filename,
                             const std::string& guid);

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////当前文件函数声明

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

// 获取本地当天 00:00:00 的毫秒时间戳
static inline uint64_t get_local_midnight_ms_today() {
    std::time_t now = std::time(nullptr);
    std::tm local_tm{};
#if defined(_WIN32) || defined(_WIN64)
    localtime_s(&local_tm, &now);
#else
    local_tm = *std::localtime(&now);
#endif
    local_tm.tm_hour = 0;
    local_tm.tm_min  = 0;
    local_tm.tm_sec  = 0;
    std::time_t midnight_sec = std::mktime(&local_tm); // 本地时区
    return static_cast<uint64_t>(midnight_sec) * 1000ULL;
}

// 获取“本地前一天 00:00:00”的毫秒时间戳
static inline uint64_t get_local_midnight_ms_prev_day() {
    uint64_t today_ms = get_local_midnight_ms_today();
    const uint64_t one_day_ms = 24ULL * 60ULL * 60ULL * 1000ULL;
    return today_ms - one_day_ms;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////配置文件读取

//将 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_CODE("process",0,LOG_CODE_CONFIG,"进程号参数异常,当前进程退出");
			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_CODE("process",0,LOG_CODE_CONFIG,"进程号参数异常,当前进程退出");
		exit(-1039);
	}
    //测试端口处理
	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;

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

        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) {
                
                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（秒级）
// ▲新增：从 monitorId 提取结尾数字（不含前导非数字部分），失败返回空串
static std::string get_monitor_digits_from_terminal_list(const std::string& dev_id,
                                                         const std::string& monitor_id)
{
    std::lock_guard<std::mutex> lk(ledgermtx);

    // 找终端
    const terminal_dev* dev = NULL;
    for (std::vector<terminal_dev>::const_iterator it = terminal_devlist.begin();
         it != terminal_devlist.end(); ++it)
    {
        if (it->terminal_id == dev_id) { dev = &(*it); break; }
    }
    if (!dev) {
        std::cout << "[digits] dev not found: " << dev_id << std::endl;
        return std::string();
    }

    // 找监测点
    for (std::vector<ledger_monitor>::const_iterator itLm = dev->line.begin();
         itLm != dev->line.end(); ++itLm)
    {
        if (!itLm->monitor_id.empty() && itLm->monitor_id == monitor_id) {
            // 常见就是 logical_device_seq，比如 "1"、"02" 等
            std::string seq = itLm->logical_device_seq;

            // 可选：去掉前导 0（与您生成“数字_时间.xxx”的命名规则保持一致）
            // 若不需要去零，注释以下 5 行即可。
            size_t p = 0;
            while (p < seq.size() && seq[p] == '0') ++p;
            seq = (p >= seq.size()) ? "0" : seq.substr(p);

            return seq;
        }
    }

    std::cout << "[digits] monitor not found in dev: mon=" << monitor_id
              << " dev=" << dev_id << std::endl;
    return std::string();
}

// ▲新增：把 "YYYY-MM-DD HH:MM:SS[.ffffff]" -> "YYYYMMDD_HHMMSS_mmm"
static std::string compact_ts_for_filename(const std::string& ts) {
    // 允许输入 "2025-09-09 07:46:57.246000"
    // 输出 "20250909_074657_246"
    int year, mon, day, hour, min, sec, ms = 0;
    char dotpart[16] = {0};
    if (sscanf(ts.c_str(), "%d-%d-%d %d:%d:%d.%15s",
               &year, &mon, &day, &hour, &min, &sec, dotpart) >= 6)
    {
        // 提取前三位毫秒
        if (dotpart[0]) {
            std::string frac(dotpart);
            while (frac.size() < 3) frac.push_back('0');  // 不足3补0
            ms = std::atoi(frac.substr(0, 3).c_str());
        }

        char buf[32];
        /*snprintf(buf, sizeof(buf), "%04d%02d%02d_%02d%02d%02d_%03d",
                 year, mon, day, hour, min, sec, ms);*/
        snprintf(buf, sizeof(buf), "%04d%02d%02d_%02d%02d%02d",
                 year, mon, day, hour, min, sec);
        return std::string(buf);
    }
    return "";
}

// ▲新增：按“数字_时间后缀.后缀”拼直下文件名（返回{*.cfg, *.dat}两种）
static std::vector<std::string> build_direct_filenames(const std::string& monitorDigits,
                                                       const std::string& ts_compact)
{
    std::vector<std::string> out;
    if (monitorDigits.empty() || ts_compact.empty()) return out;
    out.push_back(monitorDigits + "_" + ts_compact);
    return out;
}

static long long parse_time_to_epoch(const std::string& time_str) {
    std::string s = time_str;

    // ★去掉可能存在的微秒部分（例如 .000000）
    size_t dot_pos = s.find('.');
    if (dot_pos != std::string::npos) {
        s = s.substr(0, dot_pos);
    }

    std::tm tm = {};
    std::istringstream ss(s);
    ss >> std::get_time(&tm, "%Y-%m-%d %H:%M:%S");
    if (ss.fail()) {
        std::cerr << "[parse_time_to_epoch] failed to parse time string: " << s << std::endl;
        return -1;  // ★返回 -1 表示失败（便于判断）
    }

    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_CODE("process",0,LOG_CODE_JSON,"无法解析补招信息，补招触发失败");
            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_CODE("process",0,LOG_CODE_JSON,"无法解析补招信息，补招触发失败");
            return 10004;
        }
        std::string messageBodyStr = root["messageBody"].get<std::string>();
        if (messageBodyStr.empty()) {
            std::cerr << "'messageBody' is empty" << std::endl;
            DIY_ERRORLOG_CODE("process",0,LOG_CODE_JSON,"无法解析补招信息，补招触发失败");
            return 10004;
        }

        // 解析 messageBody 内层 JSON
        nlohmann::json mb;
        try {
            mb = nlohmann::json::parse(messageBodyStr);
        } catch (const std::exception& e) {
            std::cerr << "Failed to parse 'messageBody' JSON: " << e.what() << std::endl;
            DIY_ERRORLOG_CODE("process",0,LOG_CODE_JSON,"无法解析补招信息，补招触发失败");
            return 10004;
        }

        if (mb.is_array()) {
            // ====== 新格式（数组）：支持 dataType=0/1 的区间补招 & dataType=2 的直下文件 ======
            for (const auto& rec : mb) {
                if (!rec.is_object()) continue;

                // 必要字段
                std::string guid       = rec.value("guid", "");
                std::string terminalId = rec.value("terminalId", "");
                if (terminalId.empty()) continue;

                // ▲dataType：string "0/1/2" 或 number 0/1/2，先判断 contains
                int dt = -1;
                if (rec.contains("dataType")) {
                    if (rec["dataType"].is_number_integer()) {
                        dt = rec["dataType"].get<int>();
                    } else if (rec["dataType"].is_string()) {
                        std::string s = rec["dataType"].get<std::string>();
                        if (s == "0") dt = 0;
                        else if (s == "1") dt = 1;
                        else if (s == "2") dt = 2;
                    }
                }
                if (dt == -1) {
                    std::cout << "[recall] skip: invalid dataType, guid=" << guid << "\n";
                    continue;
                }

                // === 统一收集监测点：支持 monitorIdList 或 monitorId ===
                std::vector<std::string> monitors;
                if (rec.contains("monitorIdList") && rec["monitorIdList"].is_array()) {
                    for (const auto& m : rec["monitorIdList"]) {
                        if (m.is_string()) monitors.push_back(m.get<std::string>());
                    }
                }
                if (monitors.empty() && rec.contains("monitorId")) {
                    if (rec["monitorId"].is_array()) {
                        for (const auto& m : rec["monitorId"]) {
                            if (m.is_string()) monitors.push_back(m.get<std::string>());
                        }
                    } else if (rec["monitorId"].is_string()) {
                        monitors.push_back(rec["monitorId"].get<std::string>());
                    }
                }

                if (monitors.empty()) {
                    std::cout << "[recall] skip: monitors empty (no monitorIdList/monitorId), guid=" << guid << "\n";
                    continue;
                }

                // ▲沿用：校验终端归属 + 在线性
                {
                    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; }
                    if (ClientManager::instance().get_dev_status(targetDev->terminal_id) != 1) {
                        std::cout << "terminalId对应装置不在线: " << targetDev->terminal_id << std::endl;
                        std::string msg = std::string("装置：") + targetDev->terminal_name + " 不在线，无法补招";
                        send_reply_to_kafka_recall(guid, dt, static_cast<int>(ResponseCode::NOT_FOUND), msg, targetDev->terminal_id, "", "", "");
                        continue;
                    }
                }

                if (dt == 2) {  //一个测点一个guid对应多个文件
                    // ▲直下文件：timeInterval -> fun1/fun2 -> enqueue_direct_download
                    if (!rec.contains("timeInterval") || !rec["timeInterval"].is_array()) continue;

                    for (const auto& monId : monitors) {
                        // fun1：提取 monitor 数字
                        std::string digits = get_monitor_digits_from_terminal_list(terminalId, monId);//有锁
                        if (digits.empty()) { std::cout << "monitorId数字提取失败: " << monId << std::endl; continue; }

                        // 根据 monitorId 和提取的数字初始化补招记录
                        init_recall_record_file(guid, terminalId, monId, "", "");

                        //根据时间戳单独补招事件
                        if(0)// ★新增（dt==2 同步生成“按小时”的事件补招到 recall_list，与 dt==1 逻辑一致）——开始
                        {
                            std::lock_guard<std::mutex> lock2(ledgermtx); // 复用与 dt==1 相同的加锁粒度
                            // 找终端
                            terminal_dev* dev_nc = NULL;
                            for (auto& d : terminal_devlist) if (d.terminal_id == terminalId) { dev_nc = &d; break; }
                            if (!dev_nc) { /* 理论上前面已校验，这里兜底 */ continue; }
                        
                            // 找监测点
                            ledger_monitor* lm = NULL;
                            for (auto itLm = dev_nc->line.begin(); itLm != dev_nc->line.end(); ++itLm) {
                                if (!itLm->monitor_id.empty() && itLm->monitor_id == monId) { lm = &(*itLm); break; }
                            }
                            if (!lm) { std::cout << "monitorId未在terminal内找到(直下事件回灌): " << monId << " @ " << terminalId << std::endl; continue; }
                        
                            // 将 timeList 的每个时间点映射为 [该整点, 下一整点) 的一小时窗口
                            for (const auto& t : rec["timeInterval"]) {
                                if (!t.is_string()) continue;
                                std::string tstr = t.get<std::string>();
                            
                                long long ep = parse_time_to_epoch(tstr);  // 需与系统时间解析格式一致（已有同名函数）
                                if (ep < 0) {
                                    std::cout << "[direct->event] parse_time_to_epoch fail: " << tstr << std::endl;
                                    continue;
                                }
                                long long hour_start = ep - (ep % 3600);
                                long long hour_end   = hour_start + 3600 - 1; // 与 Get_Recall_Time_Char 产物风格保持一致（闭区间）
                            
                                RecallMonitor rm;
                                rm.recall_guid   = guid;
                                rm.recall_status = 0;
                                rm.StartTime     = epoch_to_datetime_str(hour_start);
                                rm.EndTime       = epoch_to_datetime_str(hour_end);
                                rm.STEADY        = "0"; // 与 dt==1 一致：事件（波形）
                                rm.VOLTAGE       = "1"; // 与 dt==1 一致
                            
                                lm->recall_list.push_back(rm);
                            }
                        }
                        // ★新增（dt==2 同步生成“按小时”的事件补招到 recall_list，与 dt==1 逻辑一致）——结束
                        //根据时间戳单独补招事件

                        for (const auto& t : rec["timeInterval"]) {
                            if (!t.is_string()) continue;

                            std::string ts_compact = compact_ts_for_filename(t.get<std::string>());
                            if (ts_compact.empty()) { std::cout << "时间解析失败: " << t << std::endl; continue; }

                            // fun2：生成 *.cfg/*.dat 两个文件名
                            std::vector<std::string> fns = build_direct_filenames(digits, ts_compact);

                            for (const auto& fn : fns) {
                                bool ok = enqueue_direct_download(terminalId, monId, fn, guid);//有锁
                                std::cout << "[direct] enqueue " << (ok ? "ok " : "fail ")
                                          << "dev=" << terminalId << " mon=" << monId
                                          << " file=" << fn << std::endl;
                            }
                        }

                    }
                } else if (dt == 0 || dt == 1) {    //一个装置对应一个guid对应多个监测点的多个时间段
                    // ▲保持老逻辑（与“对象+data”一致）：timeInterval 数组
                    if (!rec.contains("timeInterval") || !rec["timeInterval"].is_array()) continue;

                    // 解析 dataType-> stat/voltage
                    int stat = (dt == 0) ? 1 : 0;
                    int voltage = (dt == 1) ? 1 : 0;

                    // 把每个 monitor 的区间写入 recall_list / recall_list_static
                    std::lock_guard<std::mutex> lock(ledgermtx);
                    // 找终端
                    terminal_dev* dev_nc = NULL;
                    for (auto& d : terminal_devlist) if (d.terminal_id == terminalId) { dev_nc = &d; break; }
                    if (!dev_nc) continue;

                    for (const auto& monId : monitors) {
                        // 找监测点
                        ledger_monitor* lm = NULL;
                        for (auto itLm = dev_nc->line.begin(); itLm != dev_nc->line.end(); ++itLm) {
                            if (!itLm->monitor_id.empty() && itLm->monitor_id == monId) { lm = &(*itLm); break; }
                        }
                        if (!lm) { std::cout << "monitorId未在terminal内找到: " << monId << " @ " << terminalId << std::endl; continue; }

                        for (const auto& ti : rec["timeInterval"]) {
                            if (!ti.is_string()) continue;
                            std::string s = ti.get<std::string>();
                            std::string::size_type pos = s.find('~');
                            if (pos == std::string::npos) { std::cout << "timeInterval格式错误: " << s << std::endl; continue; }
                            std::string start = s.substr(0, pos);
                            std::string end   = s.substr(pos + 1);

                            RecallFile rm_all;
                            rm_all.recall_guid = guid;
                            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);

                            //lnk 20251027xuyang request:生成文件记录单个测点单个时间段的补招记录文件，补招结束后使用这个文件信息来响应
                            init_recall_record_file(guid, terminalId, monId, start, end);

                            if (voltage == 1) {
                                std::vector<RecallInfo> recallinfo_list_hour;
                                Get_Recall_Time_Char(start, end, recallinfo_list_hour);
                                for (size_t i = 0; i < recallinfo_list_hour.size(); ++i) {
                                    const RecallInfo& info = recallinfo_list_hour[i];
                                    RecallMonitor rm;
                                    rm.recall_guid   = guid;
                                    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);
                                }                                
                            }
                            if (stat == 1) {
                                lm->recall_list_static.push_back(rm_all);
                            }
                            if (stat == 0 && voltage == 0) return 10003;
                        }
                    }
                } else {
                    // 未知 dataType，忽略
                    std::cout << "unknown dataType: " << dt << std::endl;
                    continue;
                }
            }
        }
        else {
            // 不支持的 messageBody 形态
            std::cout << "unknown messageBody form" << std::endl;
            return 10004;
        }
    }
    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 update_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) {
    update_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.Righttime      = get_value("Righttime");

    work_terminal.mac      = get_value("mac");

    for (tinyxml2::XMLElement* monitor = root->FirstChildElement("monitorData");
         monitor;
         monitor = monitor->NextSiblingElement("monitorData")) {
        update_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") {
        update_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)) {
            
        }

        if (std::remove(filename.c_str()) != 0) {
            
            return 1;
        } else {
            
        }
    }

    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 update_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(); ) {
        update_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, static_cast<int>(ResponseCode::BAD_REQUEST),
                "终端 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, static_cast<int>(ResponseCode::BAD_REQUEST),
                "终端 id: " + new_dev.terminal_id + " 台账更新失败，无法写入台账");
            ++it;
            continue;
        }

        init_loggers_bydevid(target_dev.terminal_id);
        terminal_devlist.push_back(target_dev);

        DeviceInfo device = make_device_from_terminal(target_dev);
        ClientManager::instance().add_device(device);

        send_reply_to_queue(new_dev.guid, static_cast<int>(ResponseCode::OK),
            "终端 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()) {
            erase_one_terminals_by_id(mod_dev.terminal_id);
            if (update_one_terminal_ledger(mod_dev, *it) != 0) {
                send_reply_to_queue(mod_dev.guid, static_cast<int>(ResponseCode::BAD_REQUEST),
                    "终端 id: " + mod_dev.terminal_id + " 台账更新失败，写入失败");
                continue;
            }

            init_loggers_bydevid(mod_dev.terminal_id);

            DeviceInfo device = make_device_from_terminal(*it);
            ClientManager::instance().add_device(device);

            send_reply_to_queue(mod_dev.guid, static_cast<int>(ResponseCode::OK),
                "终端 id: " + mod_dev.terminal_id + " 台账修改成功");
        } else {
            send_reply_to_queue(mod_dev.guid, static_cast<int>(ResponseCode::NOT_FOUND),
                "终端 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()) {
            erase_one_terminals_by_id(del_dev.terminal_id);
            ClientManager::instance().remove_device(del_dev.terminal_id);
            send_reply_to_queue(del_dev.guid, static_cast<int>(ResponseCode::OK),
                "终端 id: " + del_dev.terminal_id + " 台账删除成功");
        } else {
            send_reply_to_queue(del_dev.guid, static_cast<int>(ResponseCode::NOT_FOUND),
                "终端 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);
	std::string script = FRONT_PATH + "/bin/set_process.sh";//使用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.c_str(), param1, param2, param3);

	std::cout << "command:" << command <<std::endl;
    
    // 执行命令
    system(command);
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////打印台账更新

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, 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 (auto& data : data_list) {
        long long diff = static_cast<long long>(data.QVVR_time) - trig_tm;

        std::cout << "[调试] QVVR_time=" << data.QVVR_time
              << ", trig_tm=" << trig_tm
              << ", diff=" << diff << "\n";

        if (std::abs(diff) <= 1) {

            data.is_pair = true;  // 标记为已匹配

            matched_data = data;  // 返回匹配到的事件

            std::cout << "[调试] 匹配成功，diff=" << diff << "\n";

            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) {
                                std::string ext = fname.substr(fname.size() - 4);
                                std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
                                if (ext == ".cfg") {
                            //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 文件
                                }//end if ext==.cfg
                            }
                            else {
                                std::cout << "[update_qvvr_file_download] Filename too short to check extension: " << fname << std::endl;
                            }
                        }//end for file_download
                    }
                    else{
                       std::cout << "qvvr file still imcomplete!!!" << std::endl; 
                    }
                    lock.unlock();
                    return true; // 当前文件处理成功
                }                      
            }//end for qvvrfile

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

        }//end for monitor
    }//end for dev
    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;
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////检查云前置终端的mq业务超时
std::string get_type_by_state(int state) {
    switch (state) {
        case static_cast<int>(DeviceState::IDLE):
            return "空闲状态";

        case static_cast<int>(DeviceState::READING_STATS):
            return "读取统计数据";

        case static_cast<int>(DeviceState::READING_STATS_TIME):
            return "读取统计时间";

        case static_cast<int>(DeviceState::READING_REALSTAT):
            return "读取实时数据";

        case static_cast<int>(DeviceState::READING_EVENTFILE):
            return "暂态波形文件下载";

        case static_cast<int>(DeviceState::READING_FILEMENU):
            return "读取文件目录";

        case static_cast<int>(DeviceState::READING_FILEDATA):
            return "读取文件数据";

        case static_cast<int>(DeviceState::READING_FIXEDVALUE):
            return "读取测点定值";

        case static_cast<int>(DeviceState::READING_FIXEDVALUEDES):
            return "读取测点定值描述";

        case static_cast<int>(DeviceState::SET_FIXEDVALUE):
            return "设置测点定值";

        case static_cast<int>(DeviceState::READING_INTERFIXEDVALUE):
            return "读取内部定值";

        case static_cast<int>(DeviceState::READING_INTERFIXEDVALUEDES):
            return "读取内部定值描述";

        case static_cast<int>(DeviceState::READING_CONTROLWORD):
            return "读取控制字描述";

        case static_cast<int>(DeviceState::SET_INTERFIXEDVALUE):
            return "设置内部定值";

        case static_cast<int>(DeviceState::READING_RUNNINGINFORMATION_1):
            return "读取装置运行信息(主动触发)";

        case static_cast<int>(DeviceState::READING_RUNNINGINFORMATION_2):
            return "读取装置运行信息(定时执行)";

        case static_cast<int>(DeviceState::READING_DEVVERSION):
            return "读取装置版本配置信息";

        case static_cast<int>(DeviceState::SET_RIGHTTIME):
            return "设置装置对时";

        case static_cast<int>(DeviceState::READING_EVENTLOG):
            return "补招事件日志";

        case static_cast<int>(DeviceState::READING_STATSFILE):
            return "补招文件";

        case static_cast<int>(DeviceState::CUSTOM_ACTION):
            return "自定义动作";

        default:
            return "未知业务"; // 未匹配的类型
    }
}


// 定时检查业务超时
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) || dev.busytype == static_cast<int>(DeviceState::READING_STATSFILE))    //下载文件业务
            {
                if (dev.busytimecount > 600)
                {
                    std::cout << "[Timeout] Device " << dev.terminal_id
                              << " busytype=READING_FILEDATA 超时(" 
                              << dev.busytimecount << "s)" << std::endl;

                    //标记当前 RUNNING 的首条为 FAILED（或 TIMEOUT），而不是只清设备状态
                    bool marked = false;
                    if (dev.busytype == static_cast<int>(DeviceState::READING_STATSFILE)) {
                        for (auto& lm : dev.line) {
                            if (!lm.recall_list_static.empty() &&
                                lm.recall_list_static.front().recall_status == static_cast<int>(RecallStatus::RUNNING)) {
                                lm.recall_list_static.front().recall_status = static_cast<int>(RecallStatus::FAILED);
                                marked = true;
                                break;
                            }
                        }
                    }

                    //发送超时响应
                    //send_reply_to_cloud(static_cast<int>(ResponseCode::TIMEOUT),dev.terminal_id,get_type_by_state(dev.busytype),dev.guid,dev.mac);
                    send_reply_to_queue(dev.guid, static_cast<int>(ResponseCode::TIMEOUT),
                        "终端 id: " + dev.terminal_id + "进行业务:" + get_type_by_state(dev.busytype) +"超时600秒，停止该业务处理");

                    // 超时清空状态
                    //若还有未处理条目，则仅复位计时，不清设备状态，交给 check_recall_event() 弹掉 FAILED 并继续下一条
                    bool any_non_empty = false;
                    for (auto& lm : dev.line) {
                        if (!lm.recall_list_static.empty()) { any_non_empty = true; break; }
                    }
                
                    //补招业务的话
                    if (any_non_empty && dev.busytype == static_cast<int>(DeviceState::READING_STATSFILE)) {
                        dev.busytimecount = 0;                 // 复位计时
                        dev.isbusy = 1;                        // 仍视为在补招流程中
                        dev.busytype = static_cast<int>(DeviceState::READING_STATSFILE);
                        //不要清 dev.guid，让回调/通知能“对上号” //因为超时后发送结果需要guid，所以不能清，在补招任务中pop就行
                    } else {
                        //队列都空了，或者是正在进行其他业务才清设备状态
                        dev.guid.clear();
                        dev.busytype = static_cast<int>(DeviceState::IDLE);
                        dev.isbusy = 0;
                        dev.busytimecount = 0;
                    }
                }
            }
            else    //其他业务，包括补招日志都是20s一条，一问一答的时间
            {
                if (dev.busytimecount > 30)
                {
                    std::cout << "[Timeout] Device " << dev.terminal_id
                              << " busytype=" << dev.busytype 
                              << " 超时(" << dev.busytimecount << "s)" << std::endl;

                    //标记当前 RUNNING 的首条为 FAILED（或 TIMEOUT），而不是只清设备状态
                    bool marked = false;
                    if (dev.busytype == static_cast<int>(DeviceState::READING_EVENTLOG)) {
                        for (auto& lm : dev.line) {
                            if (!lm.recall_list.empty() &&
                                lm.recall_list.front().recall_status == static_cast<int>(RecallStatus::RUNNING)) {
                                lm.recall_list.front().recall_status = static_cast<int>(RecallStatus::FAILED);
                                marked = true;
                                break;
                            }
                        }
                    }

                    //发送超时响应
                    //send_reply_to_cloud(static_cast<int>(ResponseCode::TIMEOUT),dev.terminal_id,get_type_by_state(dev.busytype),dev.guid,dev.mac);
                    send_reply_to_queue(dev.guid, static_cast<int>(ResponseCode::TIMEOUT),
                        "终端 id: " + dev.terminal_id + "进行业务:" + get_type_by_state(dev.busytype) +"超时30秒，停止该业务处理");

                    // 超时清空状态
                    //若还有未处理条目，则仅复位计时，不清设备状态，交给 check_recall_event() 弹掉 FAILED 并继续下一条
                    bool any_non_empty = false;
                    for (auto& lm : dev.line) {
                        if (!lm.recall_list.empty()) { any_non_empty = true; break; }
                    }
                
                    //补招业务的话
                    if (any_non_empty && dev.busytype == static_cast<int>(DeviceState::READING_EVENTLOG)) {
                        dev.busytimecount = 0;                 // 复位计时
                        dev.isbusy = 1;                        // 仍视为在补招流程中
                        dev.busytype = static_cast<int>(DeviceState::READING_EVENTLOG);
                        //不要清 dev.guid，让回调/通知能“对上号” //因为超时后发送结果需要guid，所以不能清，在补招任务中pop就行
                    } else {
                        //队列都空了，或者是正在进行其他业务才清设备状态
                        dev.guid.clear();
                        dev.busytype = static_cast<int>(DeviceState::IDLE);
                        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; // 正常前进
            }
        }
    }
}

/////////////////////////////////////////////////////////////////////////////////////////定时清理内存的暂态事件
// 每天执行一次；删除“is_pair=false 且 QVVR_time < 昨天 00:00:00(毫秒)”的 qvvr_data
void cleanup_old_unpaired_qvvr_events() {
    const uint64_t cutoff_ms = get_local_midnight_ms_prev_day();

    std::lock_guard<std::mutex> lk(ledgermtx); // ★新增：加锁保护台账

    size_t total_removed = 0;
    for (auto& dev : terminal_devlist) {
        for (auto& lm : dev.line) {
            auto& vec = lm.qvvrevent.qvvrdata;
            const size_t before = vec.size();

            // 删除条件：未配对 且 发生时间 < 昨天 00:00:00（本地时区毫秒）
            vec.erase(std::remove_if(vec.begin(), vec.end(),
                        [&](const qvvr_data& d){
                            return (d.is_pair == false) && (d.QVVR_time < cutoff_ms);
                        }),
                      vec.end());

            const size_t removed = before - vec.size();
            if (removed > 0) {
                total_removed += removed;
                std::cout << "[cleanup_qvvr] dev=" << dev.terminal_id
                          << " mon=" << lm.monitor_id
                          << " removed=" << removed
                          << " cutoff_ms=" << cutoff_ms
                          << " (prev 00:00 local)" << std::endl;
            }
        }
    }

    std::cout << "[cleanup_qvvr] total_removed=" << total_removed << std::endl;
}

/////////////////////////////////////////////////////////////////////////////////////////定值信息发送接口函数
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, int dataType,int code, const std::string& result,const std::string& terminalId,const std::string& monitorId,const std::string& recallStartDate,const std::string& recallEndDate){
	// 构造 JSON 字符串
    std::ostringstream oss;
    oss << "{"
        << "\"guid\":\"" << guid << "\","
        << "\"dataType\":\"" << dataType << "\","
        << "\"code\":" << code << ","
        << "\"result\":\"" << result << "\","
        << "\"terminalId\":\"" << terminalId << "\","
		<< "\"monitorId\":\"" << monitorId << "\","
        << "\"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 = "RECALL";
    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则直接跳过，节省运行时间
            bool allow_check = 
                dev.busytype == static_cast<int>(DeviceState::IDLE) ||
                dev.busytype == static_cast<int>(DeviceState::READING_EVENTLOG);

            // 如果你允许“事件抢占文件”，并且当前没有文件 RUNNING，则也允许评估事件：
            if (!allow_check && dev.busytype == static_cast<int>(DeviceState::READING_STATSFILE)) {
                bool has_file_running = false;
                for (const auto& lm2 : dev.line) {
                    if (!lm2.recall_list_static.empty() &&
                        lm2.recall_list_static.front().recall_status != static_cast<int>(RecallStatus::NOT_STARTED)) {
                        has_file_running = true; break;
                    }
                }
                if (!has_file_running) allow_check = true; // 没有文件正在跑，允许事件优先
            }

            if (!allow_check) {
                std::cout << "[check_recall_event] skip dev=" << dev.terminal_id
                          << " busytype=" << dev.busytype << std::endl;
                continue;
            }
            // 对正在补招或idle终端的所有监测点的待补招列表进行处理
            // 1) 先弹掉首条为 DONE/FAILED/EMPTY 的记录（所有 monitor 都要处理首条）
            bool any_non_empty = false;
            for (auto& lm : dev.line) {

                // 标记这个测点在本轮开始时是否非空
                bool had_items_before = !lm.recall_list.empty();
                bool popped_any = false;                 // 本轮是否真的弹过
                //std::string popped_guid_for_file;       // 记录被弹出的条目的 guid（用于文件）

                while (!lm.recall_list.empty()) {
                    const RecallMonitor& front = lm.recall_list.front();

                    // ===== 弹掉首条前：打印首条内容与列表大小 =====
                    std::cout << "[check_recall_event] before pop: list size=" << lm.recall_list.size()
                        << " first=(" << front.StartTime << " ~ " << front.EndTime
                        << ", status=" << front.recall_status << ")" << std::endl;

                    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(dev.guid,1,static_cast<int>(ResponseCode::OK),msg,dev.terminal_id,lm.monitor_id,front.StartTime,front.EndTime);

                        append_recall_record_line(dev.guid, lm.monitor_id, msg);

                        lm.recall_list.pop_front(); // 弹掉首条

                        popped_any = true;

                        // ===== 弹掉首条后：打印新首条与列表大小 =====
                        if (!lm.recall_list.empty()) {
                            const RecallMonitor& newfront = lm.recall_list.front();
                            std::cout << "[check_recall_event] after pop: list size=" << lm.recall_list.size()
                                      << " new first=(" << newfront.StartTime << " ~ " << newfront.EndTime
                                      << ", status=" << newfront.recall_status << ")" << std::endl;
                        } else {
                            std::cout << "[check_recall_event success] after pop: list empty" << std::endl;
                        }

                        break;
                    }
                    else if (front.recall_status == static_cast<int>(RecallStatus::EMPTY)) {
                        std::cout << "[check_recall_event] EMPTY 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(dev.guid,1,static_cast<int>(ResponseCode::NOT_FOUND),msg,dev.terminal_id,lm.monitor_id,front.StartTime,front.EndTime);

                        append_recall_record_line(dev.guid, lm.monitor_id, msg);

                        lm.recall_list.pop_front(); // 弹掉首条

                        popped_any = true;

                        // ===== 弹掉首条后：打印新首条与列表大小 =====
                        if (!lm.recall_list.empty()) {
                            const RecallMonitor& newfront = lm.recall_list.front();
                            std::cout << "[check_recall_event] after pop: list size=" << lm.recall_list.size()
                                      << " new first=(" << newfront.StartTime << " ~ " << newfront.EndTime
                                      << ", status=" << newfront.recall_status << ")" << std::endl;
                        } else {
                            std::cout << "[check_recall_event empty] after pop: list empty" << std::endl;
                        }

                        break;
                    }  
                    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(dev.guid,1,static_cast<int>(ResponseCode::BAD_REQUEST),msg,dev.terminal_id,lm.monitor_id,front.StartTime,front.EndTime);

                        append_recall_record_line(dev.guid, lm.monitor_id, msg);

                        lm.recall_list.pop_front(); // 弹掉首条

                        popped_any = true;

                        // ===== 弹掉首条后：打印新首条与列表大小 =====
                        if (!lm.recall_list.empty()) {
                            const RecallMonitor& newfront = lm.recall_list.front();
                            std::cout << "[check_recall_event] after pop: list size=" << lm.recall_list.size()
                                      << " new first=(" << newfront.StartTime << " ~ " << newfront.EndTime
                                      << ", status=" << newfront.recall_status << ")" << std::endl;
                        } else {
                            std::cout << "[check_recall_event fail] after pop: list empty" << std::endl;
                        }

                        break;
                    } else {//如果每个测点都有补招记录，但是首条不是 DONE/FAILED/EMPTY 则跳过该测点，检查下一个测点
                        std::cout << "[check_recall_event] skip line=" << lm.monitor_name<< std::endl;
                        break; // 首条不是 2/3/4，停，如果是正在处理其他业务或者idle的装置写入了待补招列表，应该都是0；如果是正在补招的装置，新增的部分不会影响原有顺序
                    }
                }

                if (had_items_before && popped_any && lm.recall_list.empty()) {//处理后，当前测点补招记录为空
                    //通知补招全部完成
                    std::cout << "[check_recall_event] finish recall monitor=" << lm.monitor_id << std::endl;
                    //读取记录文件获取响应参数
                    std::string file_guid, terminalId, file_monitorId, startTime, endTime, msg;

                    if (get_recall_record_fields_by_guid_monitor(dev.guid, lm.monitor_id,
                                                                 file_guid, terminalId, file_monitorId,
                                                                 startTime, endTime, msg)) {
                        // 校验通过，拿到参数了
                        std::cout << "guid=" << file_guid << std::endl;
                        std::cout << "terminalId=" << terminalId << std::endl;
                        std::cout << "monitor_id=" << file_monitorId << std::endl;
                        std::cout << "start=" << startTime << std::endl;
                        std::cout << "end=" << endTime << std::endl;
                        std::cout << "msg:\n" << msg << std::endl;
                                                                
                        send_reply_to_kafka_recall(file_guid, 1, static_cast<int>(ResponseCode::OK),
                                                    msg, terminalId, file_monitorId, startTime, endTime);
                    }

                    delete_recall_record_file(dev.guid, lm.monitor_id);
                }

                //处理后，当前测点补招记录非空
                if (!lm.recall_list.empty()) 
                {
                    any_non_empty = true;  // 处理了成功/失败/无消息以后该装置只要有任一测点有一条记录就标记，可能是待处理或者正在处理的补招
                }
                
            }

            // pop后判断是否仍有 RUNNING（pop后应该都为unstarted，没pop的才会是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 && dev.busytype == static_cast<int>(DeviceState::READING_EVENTLOG)) {
                dev.busytimecount = 0;   // 避免“上一条刚结束但下一条尚未起”期间被误判超时
            }

            // 有条目但目前存在 RUNNING：继续等待该 RUNNING 完成，当前装置本轮不新发
            if (any_non_empty && has_running) {
                std::cout << "[check_recall_event] skip dev=" << dev.terminal_id
                          << " already running recall" << std::endl;
                continue;
            }

            //pop后无任何补招条目，且处于补招状态：清空运行态
            if (!any_non_empty && dev.busytype == static_cast<int>(DeviceState::READING_EVENTLOG)) {
                // 该终端本轮已无任何补招条目，且处于补招暂态事件的状态清空运行态
                std::cout << "[check_recall_event] finish recall dev=" << dev.terminal_id << std::endl;
                

                dev.guid.clear();       // 清空 guid
                dev.busytype = static_cast<int>(DeviceState::IDLE);       // 业务类型归零
                dev.isbusy = 0;         // 清空业务标志
                dev.busytimecount = 0;  // 计时归零
                continue;   //处理完处理下一个装置
            }
            

            //有待补招任务，且idle或者在补招，继续补招处理
            //std::cout << "[check_recall_event] idle or continue recall dev=" << dev.terminal_id << std::endl;

            // 若无 RUNNING，则说明该终端空闲，可以挑选新的补招任务
            if (any_non_empty && !has_running) {

                // 3) 选择该终端的“第一条 NOT_STARTED(0)”作为本终端本轮任务
                bool picked = false;
                for (auto& lm : dev.line) {
                    if (lm.recall_list.empty()) {
                        std::cout << "[check_recall_event] skip empty line=" << lm.monitor_name<< std::endl;
                        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.guid          = front.recall_guid; // 记录本次补招的 guid
                        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.logical_device_seq//记录测点号
                        });
                        picked = true;  //该装置已取
                        break;
                    }
                }
                // 若该终端没有 NOT_STARTED 的首条（可能剩下的都是 RUNNING 或内部已经被清空），
                 if (!picked) {
                    // 没挑到 NOT_STARTED（例如都是“首条非空但非 NOT_STARTED/非 RUNNING”的情况）：
                    // 这种情况下本终端留给下一轮处理即可。
                    std::cout << "[check_recall_event] skip dev=" << dev.terminal_id
                              << " no NOT_STARTED at head" << std::endl;
                }
                // 就留待下一轮；不要清空运行态，直到所有条目被处理为止。
                continue;
            }
        }
    } // 解锁

    // 对于本轮挑选到的任务，逐终端下发一条，这些终端的状态都已设为补招
    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=" << static_cast<int>(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;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 从文件名中解析出 "监测点号_YYYYMMDD_HHMMSS_mmm"；成功返回 true
static bool make_target_key_from_filename(const std::string& fname, std::string& out_key) {
    // 例：PQMonitor_PQM1_000005_20250909_074657_246.dat
    // 例：PQ_PQLD1_000005_20250909_074656_435.dat
    // 以 '_' 切分，应至少 6 段
    std::vector<std::string> parts;
    parts.reserve(8);
    size_t start = 0, pos;
    while ((pos = fname.find('_', start)) != std::string::npos) {
        parts.emplace_back(fname.substr(start, pos - start));
        start = pos + 1;
    }
    parts.emplace_back(fname.substr(start));

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

    // 索引意义：
    // [0]=前缀(如 PQMonitor / PQ)
    // [1]=监测点号(如 PQM1 / PQLD1)
    // [2]=序号(如 000005)
    // [3]=YYYYMMDD
    // [4]=HHMMSS
    // [5]=mmm(.dat)
    const std::string& monitor = parts[1];
    const std::string& ymd     = parts[3];
    const std::string& hms     = parts[4];

    // 去掉末段的扩展名（如 "246.dat" -> "246"）
    std::string mmm = parts[5];
    size_t dot = mmm.rfind('.');
    if (dot != std::string::npos) mmm.erase(dot);

    // 基本合法性校验（长度与全数字）
    auto all_digits = [](const std::string& s) {
        return !s.empty() && std::find_if(s.begin(), s.end(),
                   [](unsigned char c){ return !std::isdigit(c); }) == s.end();
    };
    if (ymd.size() != 8 || !all_digits(ymd)) return false;
    if (hms.size() != 6 || !all_digits(hms)) return false;
    if (mmm.size() != 3 || !all_digits(mmm)) return false;
    if (monitor.empty()) return false;

    // 目标 key：监测点号_YYYYMMDD_HHMMSS_mmm
    //out_key.reserve(monitor.size() + 1 + 8 + 1 + 6 + 1 + 3);
    out_key.reserve(monitor.size() + 1 + 8 + 1 + 6);
    out_key.clear();
    //out_key.append(monitor).append("_").append(ymd).append("_").append(hms).append("_").append(mmm);
    out_key.append(monitor).append("_").append(ymd).append("_").append(hms);
    return true;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////补招文件新增优化20251105
// ★新增：设备级目录缓存（dev_id -> { dir -> files }）
static std::mutex g_device_dir_cache_mtx;
static std::map<std::string, std::map<std::string, std::vector<tag_dir_info>>> g_device_dir_cache;

// ★新增：尝试从缓存获取目录文件列表；命中返回 true（即便是空列表）
static bool dircache_try_get(const std::string& dev_id,
                             const std::string& dir,
                             std::vector<tag_dir_info>& out)
{
    std::lock_guard<std::mutex> lk(g_device_dir_cache_mtx);
    auto it_dev = g_device_dir_cache.find(dev_id);
    if (it_dev == g_device_dir_cache.end()) return false;
    auto it_dir = it_dev->second.find(dir);
    if (it_dir == it_dev->second.end()) return false;
    out = it_dir->second; // 可能为空，但表示“已问过/已缓存”
    return true;
}

// ★新增：把目录文件列表写入缓存（空列表也写入，代表“已询问且为空”）
static void dircache_store(const std::string& dev_id,
                           const std::string& dir,
                           const std::vector<tag_dir_info>& files)
{
    std::lock_guard<std::mutex> lk(g_device_dir_cache_mtx);
    g_device_dir_cache[dev_id][dir] = files; // 覆盖式记录
}

// ★新增：清空某个装置的目录缓存（当该装置所有补招文件记录都处理完毕时调用）
static void dircache_clear_device(const std::string& dev_id)
{
    std::lock_guard<std::mutex> lk(g_device_dir_cache_mtx);
    g_device_dir_cache.erase(dev_id);
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////补招文件逻辑

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

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

    // ★修改开始：新增“待上传动作”容器与两个小工具（局部作用域，函数私有）
    struct PendingUpload {
        std::string terminal_id;
        unsigned short logical_seq = 0;
        std::vector<std::string> files_to_send;  // 完整路径（含MAC目录）
        qvvr_data matched{};                     // 与 .cfg 匹配到的事件（如有）
        qvvr_data mismatched{};                  // 未匹配到的事件（如有）
        bool has_matched = false;
        // 用于回锁后在台账中定位并删掉对应 qvvr_file 组
        std::set<std::string> sig_names;   // 仅文件名集合
        std::set<std::string> sig_downs;   // 完整路径集合（与 files_to_send 相同内容的 set）
    };

    auto ExtractFileNames = [](const std::vector<std::string>& full_paths) {
        std::vector<std::string> names;
        names.reserve(full_paths.size());
        for (const auto& p : full_paths) {
            auto s = sanitize(p);
            size_t pos = s.find_last_of("/\\");
            std::string name = (pos == std::string::npos) ? s : s.substr(pos + 1);
            names.push_back(sanitize(name));
        }
        std::sort(names.begin(), names.end());
        names.erase(std::unique(names.begin(), names.end()), names.end());
        return names;
    };

    auto MakeSigNames = [&](const std::vector<std::string>& full_paths){
        auto names = ExtractFileNames(full_paths);
        return std::set<std::string>(names.begin(), names.end());
    };
    // ★修改结束

    std::vector<PendingUpload> pending_uploads; // ★修改：锁外执行上传与清理


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

        // ★优先级开始：事件优先 —— 若任一测点存在事件补招（recall_list）尚未清空，则本轮跳过文件补招
        {
            bool has_event_pending = false;
            for (const auto& dev : terminal_devlist) {
                for (const auto& lm : dev.line) {
                    if (!lm.recall_list.empty()) {   // 只要存在事件待处理就让位
                        has_event_pending = true;
                        break;
                    }
                }
                if (has_event_pending) break;
            }
            if (has_event_pending) {
                std::cout << "[check_recall_file] skip this round: event recall pending, give priority to check_recall_event()\n";
                return; // 直接让位给 check_recall_event，本轮不处理文件补招
            }
        }
        // ★优先级结束

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

                // 标记这个测点在本轮开始时是否非空
                bool had_items_before = !lm.recall_list_static.empty();
                bool popped_any = false;                            // 本轮是否真的弹过

                while (!lm.recall_list_static.empty()) {

                    const RecallFile& front = lm.recall_list_static.front();

                    // ===== 弹掉首条前：打印首条内容与列表大小 =====
                    std::cout << "[check_recall_file] before pop | dev=" << dev.terminal_id
                                  << " mon=" << lm.monitor_id
                                  << " size=" << lm.recall_list_static.size()
                                  << " first{guid=" << front.recall_guid
                                  << ", status=" << front.recall_status
                                  << ", direct=" << (front.direct_mode ? 1 : 0)
                                  << ", phase=" << static_cast<int>(front.phase)
                                  << ", cur_dir=" << front.cur_dir
                                  << ", cur_file=" << front.downloading_file
                                  << ", list_result=" << static_cast<int>(front.list_result)
                                  << ", download_result=" << static_cast<int>(front.download_result)
                                  << ", download_queue=" << front.download_queue.size()
                                  << "}" << std::endl;
                        // ===== 打印 download_queue 内的全部文件 =====
                        if (!front.download_queue.empty()) {
                            std::cout << "    [download_queue files] (" << front.download_queue.size() << "):" << std::endl;
                            int idx = 0;
                            for (const auto& path : front.download_queue) {
                                std::cout << "        [" << idx++ << "] " << path << std::endl;
                            }
                        } else {
                            std::cout << "    [download_queue files] (empty)" << std::endl;
                        }

                    if (front.recall_status == static_cast<int>(RecallStatus::DONE)) {
                        std::cout << "[check_recall_stat] DONE dev=" << dev.terminal_id
                                  << " monitor=" << lm.monitor_id << std::endl;

                        lm.recall_list_static.pop_front();

                        //弹出后再打印首条内容与列表大小
                        if (!lm.recall_list_static.empty()) {

                            const RecallFile& nf = lm.recall_list_static.front();
                            std::cout << "[check_recall_file] after pop | dev=" << dev.terminal_id
                                      << " mon=" << lm.monitor_id
                                      << " size=" << lm.recall_list_static.size()
                                      << " first{guid=" << nf.recall_guid
                                      << ", status=" << nf.recall_status
                                      << ", direct=" << (nf.direct_mode ? 1 : 0)
                                      << ", phase=" << static_cast<int>(nf.phase)
                                      << ", cur_dir=" << nf.cur_dir
                                      << ", cur_file=" << nf.downloading_file
                                      << ", list_result=" << static_cast<int>(nf.list_result)
                                      << ", download_result=" << static_cast<int>(nf.download_result)
                                      << ", download_queue=" << nf.download_queue.size()
                                      << "}" << std::endl;

                            // ===== 打印 download_queue 内的全部文件 =====
                            if (!nf.download_queue.empty()) {
                                std::cout << "    [download_queue files] (" << nf.download_queue.size() << "):" << std::endl;
                                int idx = 0;
                                for (const auto& path : nf.download_queue) {
                                    std::cout << "        [" << idx++ << "] " << path << std::endl;
                                }
                            } else {
                                std::cout << "    [download_queue files] (empty)" << std::endl;
                            }
                        }else {
                            std::cout << "[check_recall_file] after pop | list empty" << std::endl;
                        }

                        popped_any = true;
                        break;
                    } 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;

                        //20251218添加记录
                        std::string msg_fail;
                        if (front.direct_mode) {
                            msg_fail = std::string("监测点：") + lm.monitor_name
                                     + " 补招波形文件失败，目标时标："
                                     + front.target_filetimes;
                        } else {
                            msg_fail = std::string("监测点：") + lm.monitor_name
                                     + " 补招波形文件失败，时间范围："
                                     + front.StartTime + " ~ " + front.EndTime;
                        }
                        append_recall_record_line(dev.guid, lm.monitor_id, msg_fail);
                        //20251218添加记录
                                
                        lm.recall_list_static.pop_front();

                        //弹出后再打印首条内容与列表大小
                        if (!lm.recall_list_static.empty()) {

                            const RecallFile& nf = lm.recall_list_static.front();

                            std::cout << "[check_recall_file] after pop | dev=" << dev.terminal_id
                                      << " mon=" << lm.monitor_id
                                      << " size=" << lm.recall_list_static.size()
                                      << " first{guid=" << nf.recall_guid
                                      << ", status=" << nf.recall_status
                                      << ", direct=" << (nf.direct_mode ? 1 : 0)
                                      << ", phase=" << static_cast<int>(nf.phase)
                                      << ", cur_dir=" << nf.cur_dir
                                      << ", cur_file=" << nf.downloading_file
                                      << ", list_result=" << static_cast<int>(nf.list_result)
                                      << ", download_result=" << static_cast<int>(nf.download_result)
                                      << ", download_queue=" << nf.download_queue.size()
                                      << "}" << std::endl;

                            // ===== 打印 download_queue 内的全部文件 =====
                            if (!nf.download_queue.empty()) {
                                std::cout << "    [download_queue files] (" << nf.download_queue.size() << "):" << std::endl;
                                int idx = 0;
                                for (const auto& path : nf.download_queue) {
                                    std::cout << "        [" << idx++ << "] " << path << std::endl;
                                }
                            } else {
                                std::cout << "    [download_queue files] (empty)" << std::endl;
                            }

                        } else {
                            std::cout << "[check_recall_file] after pop | list empty" << std::endl;
                        }

                        popped_any = true;
                        break;
                    } else {
                        std::cout << "[check_recall_file] skip line=" << lm.monitor_name<< std::endl;
                        break;//找到第一条不是成功或失败的记录就退出循环
                    }
                }

                if (had_items_before && popped_any &&
                    (lm.recall_list_static.empty() ||
                    (!lm.recall_list_static.empty() && lm.recall_list_static.front().recall_guid != dev.guid))) {//处理后，当前测点补招记录为空
                    //通知补招全部完成
                    std::cout << "[check_recall_file] finish recall monitor=" << lm.monitor_id << std::endl;
                    //读取记录文件获取响应参数
                    std::string file_guid, terminalId, file_monitorId, startTime, endTime, msg;

                    if (get_recall_record_fields_by_guid_monitor(dev.guid, lm.monitor_id,
                                                                 file_guid, terminalId, file_monitorId,
                                                                 startTime, endTime, msg)) {
                        // 校验通过，拿到参数了
                        std::cout << "guid=" << file_guid << std::endl;
                        std::cout << "terminalId=" << terminalId << std::endl;
                        std::cout << "monitor_id=" << file_monitorId << std::endl;
                        std::cout << "start=" << startTime << std::endl;
                        std::cout << "end=" << endTime << std::endl;
                        std::cout << "msg:\n" << msg << std::endl;
                                                                
                        send_reply_to_kafka_recall(file_guid, 1, static_cast<int>(ResponseCode::OK),
                                                    msg, terminalId, file_monitorId, startTime, endTime);
                    }

                    delete_recall_record_file(dev.guid, lm.monitor_id);
                }

                //处理后，当前测点补招记录非空
                if (!lm.recall_list_static.empty()) 
                {
                    any_non_empty = true;  // 处理了成功/失败/无消息以后该装置只要有任一测点有一条记录就标记，可能是待处理或者正在处理的补招
                }

            }

            if (!any_non_empty && dev.busytype == static_cast<int>(DeviceState::READING_STATSFILE)) {
                std::cout << "[check_recall_file] finish recall dev=" << dev.terminal_id << std::endl;
                dev.guid.clear();
                dev.busytype = static_cast<int>(DeviceState::IDLE);
                dev.isbusy = 0;
                dev.busytimecount = 0;

                dircache_clear_device(dev.terminal_id); // ★新增：该装置的目录缓存生命周期到此结束，清空

                continue;//这个装置处理下一个装置
            }

            // pop后判断是否仍有 RUNNING（pop后应该都为unstarted，没pop的才会是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;
                }
            }

            if (!has_running && dev.busytype == static_cast<int>(DeviceState::READING_STATSFILE)) dev.busytimecount = 0;

            // ★新增：当存在 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;//跳过不是正在补招的记录

                    // 初始化阶段：补招分为两个阶段，读文件列表和下载文件，如果是刚进入 RUNNING 状态则初始化
                    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();//初始化下载文件队列

                        front.required_files.clear();
                        front.file_success.clear();

                        // 立即发起第一个目录请求
                        if (front.cur_dir_index < static_cast<int>(front.dir_candidates.size())) {
                            front.cur_dir = front.dir_candidates[front.cur_dir_index];//从第一个目录开始

                            // ★新增：先查设备级目录缓存
                            std::vector<tag_dir_info> cached;
                            if (dircache_try_get(dev.terminal_id, front.cur_dir, cached)) {
                                // 命中缓存：直接当作“目录 OK”，不下发请求
                                front.dir_files[front.cur_dir] = std::move(cached);
                                front.list_result = ActionResult::OK;
                                std::cout << "[dircache] HIT dev=" << dev.terminal_id
                                          << " dir=" << front.cur_dir
                                          << " entries=" << front.dir_files[front.cur_dir].size() << std::endl;
                                break;
                            } else {

                                // ★★ 只发一个目录请求，并等待外部线程回写结果与文件名列表
                                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;
                                break;
                            }
                        } else {
                            // 无目录可查
                            front.recall_status = static_cast<int>(RecallStatus::FAILED);

                            std::string msg_fail;
                                if (front.direct_mode) {
                                    msg_fail = std::string("监测点：") + lm.monitor_name
                                             + " 补招波形文件未找到，目标时标："
                                             + front.target_filetimes;
                                } else {
                                    msg_fail = std::string("监测点：") + lm.monitor_name
                                             + " 补招波形文件未找到，时间范围："
                                             + front.StartTime + " ~ " + front.EndTime;
                                }
                                append_recall_record_line(dev.guid, lm.monitor_id, msg_fail);

                            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）
                            break;  //跳出循环，一个装置一次只能处理一个测点的一个补招记录
                        }

                        if (front.list_result == ActionResult::FAIL) {
                            // 尝试下一个目录
                            front.cur_dir_index++;
                            front.list_result = ActionResult::PENDING;//重置状态
                            front.download_queue.clear();//重置下载队列

                            front.required_files.clear();
                            front.file_success.clear();

                            if (front.cur_dir_index < static_cast<int>(front.dir_candidates.size())) {
                                front.cur_dir = front.dir_candidates[front.cur_dir_index];

                                // ★新增：先查缓存
                                std::vector<tag_dir_info> cached;
                                if (dircache_try_get(dev.terminal_id, front.cur_dir, cached)) {
                                    front.dir_files[front.cur_dir] = std::move(cached);
                                    front.list_result = ActionResult::OK;
                                    std::cout << "[dircache] HIT dev=" << dev.terminal_id
                                              << " dir=" << front.cur_dir
                                              << " entries=" << front.dir_files[front.cur_dir].size() << std::endl;
                                    break;
                                } else {

                                    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;
                                    break;
                                }
                            } else {
                                // 所有目录都失败
                                front.recall_status = static_cast<int>(RecallStatus::FAILED);

                                std::string msg_fail;
                                if (front.direct_mode) {
                                    msg_fail = std::string("监测点：") + lm.monitor_name
                                             + " 补招波形文件未找到，目标时标："
                                             + front.target_filetimes;
                                } else {
                                    msg_fail = std::string("监测点：") + lm.monitor_name
                                             + " 补招波形文件未找到，时间范围："
                                             + front.StartTime + " ~ " + front.EndTime;
                                }
                                append_recall_record_line(dev.guid, lm.monitor_id, msg_fail);

                                std::cout << "[check_recall_stat] all dir failed, FAIL dev=" << dev.terminal_id
                                          << " monitor=" << lm.monitor_id << std::endl;
                            }
                            break; //跳出循环，一个装置一次只能处理一个测点的一个补招记录;如果失败，下个循环会弹出
                        }

                        // 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;
                                break;//跳出循环，一个装置一次只能处理一个测点的一个补招记录;如果失败，下个循环会弹出
                            }*/

                            //装置消息返回后通知成功的处理：
                            auto it = front.dir_files.find(front.cur_dir);//在map中查找当前目录名对应的目录下的文件名列表
                            if (it != front.dir_files.end()) {
                                if (front.direct_mode) {
                                    // 目标时间戳（不含毫秒、形如 yyyyMMdd_HHmmss）
                                    const std::string& want_ts = front.target_filetimes;

                                    for (const auto& ent : it->second) {
                                        // 打印目录下的所有条目
                                        std::cout << "[check_recall_file] dir file dev=" << dev.terminal_id
                                                  << " monitor=" << lm.monitor_id
                                                  << " dir=" << front.cur_dir
                                                  << " file=" << ent.name
                                                  << " flag=" << ent.flag
                                                  << std::endl;
                                    
                                        if (ent.flag == 0) continue; // 只要文件，跳过目录
                                    
                                        size_t n = ::strnlen(ent.name, sizeof(ent.name));
                                        std::string fname(ent.name, n);
                                    
                                        // 解析出 key = 监测点号_YYYYMMDD_HHMMSS（注意：确保你的 make_target_key_from_filename
                                        // 已经改成“去掉毫秒”的版本；若还带毫秒，请先调整该函数）
                                        std::string key;
                                        if (!make_target_key_from_filename(fname, key)) {
                                            std::cout << "[check_recall_file] dir file dev=" << dev.terminal_id
                                                      << " monitor=" << lm.monitor_id
                                                      << " dir=" << front.cur_dir
                                                      << " file=" << fname
                                                      << " key=" << key
                                                      << " ERR: invalid filename format, skip"
                                                      << std::endl;
                                            continue;
                                        }
                                    
                                        // key 形如 MON_YYYYMMDD_HHMMSS，目标是只按时间戳匹配：
                                        if (has_suffix(key,  want_ts)) {
                                            //打印放入的文件名
                                            std::cout << "[check_recall_file] dir file dev=" << dev.terminal_id
                                                      << " monitor=" << lm.monitor_id
                                                      << " dir=" << front.cur_dir
                                                      << " file=" << fname
                                                      << " key=" << key
                                                      << " MATCH, add to download queue"
                                                      << std::endl;

                                            front.download_queue.push_back(front.cur_dir + "/" + fname);
                                        }
                                    }
                                } 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];

                                // ★新增：先查缓存
                                std::vector<tag_dir_info> cached;
                                if (dircache_try_get(dev.terminal_id, front.cur_dir, cached)) {
                                    front.dir_files[front.cur_dir] = std::move(cached);
                                    front.list_result = ActionResult::OK;
                                    std::cout << "[dircache] HIT dev=" << dev.terminal_id
                                              << " dir=" << front.cur_dir
                                              << " entries=" << front.dir_files[front.cur_dir].size() << std::endl;
                                    break;
                                } else {

                                    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;
                                    break;
                                }
                            } else {
                                // 所有目录都“无匹配文件”
                                front.recall_status = static_cast<int>(RecallStatus::FAILED);

                                std::string msg_fail;
                                if (front.direct_mode) {
                                    msg_fail = std::string("监测点：") + lm.monitor_name
                                             + " 补招波形文件未找到，目标时标："
                                             + front.target_filetimes;
                                } else {
                                    msg_fail = std::string("监测点：") + lm.monitor_name
                                             + " 补招波形文件未找到，时间范围："
                                             + front.StartTime + " ~ " + front.EndTime;
                                }
                                append_recall_record_line(dev.guid, lm.monitor_id, msg_fail);

                                std::cout << "[check_recall_stat] no matched files in ALL dirs, FAIL dev=" << dev.terminal_id
                                          << " monitor=" << lm.monitor_id << std::endl;
                            }

                            break; //跳出循环，一个装置一次只能处理一个测点的一个补招记录;如果失败，下个循环会弹出
                            
                        } 
                        else {
                            // 进入下载阶段

                            front.required_files.clear();
                            //for (const auto& p : front.download_queue) front.required_files.insert(p);

                            //转成本地保存路径 download/<dev.addr_str>/<fname>
                            front.required_files.clear();
                            {
                                const std::string base_dir = std::string("download/") + sanitize(dev.addr_str);
                                for (const auto& p : front.download_queue) {
                                    // p 形如 "<cur_dir>/<fname>" —— 提取纯文件名
                                    std::string fname = sanitize(extract_filename1(p));
                                    if (!fname.empty()) {
                                        front.required_files.insert(base_dir + "/" + fname);
                                    }
                                }
                            }

                            front.file_success.clear();

                            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()) {//下载队列无文件
                                // 所有文件下载完毕
                                std::cout << "dev=" << dev.terminal_id
                                        << " monitor=" << lm.monitor_id
                                        << " ok=" << front.file_success.size()
                                        << " total=" << front.required_files.size()
                                        << std::endl;
                                if (!front.required_files.empty()
                                    && front.file_success.size() == front.required_files.size()) {

                                    front.recall_status = static_cast<int>(RecallStatus::DONE);//两个文件都下好了标记为成功

                                    //更新事件
                                    // ★修改开始：替换“assign_qvvr_file_list + update_qvvr_file_download(有锁)”
                                    // 组装完整路径列表
                                    std::vector<std::string> fullFilenames;
                                    fullFilenames.reserve(front.required_files.size());
                                    for (const auto& f : front.required_files) fullFilenames.push_back(f);

                                    // 仅对“当前监测点”添加一组 qvvr_file（file_name=仅文件名，下载标记留到下行）
                                    {
                                        qvvr_file qfile;
                                        auto names = ExtractFileNames(fullFilenames);
                                        qfile.file_name.assign(names.begin(), names.end());
                                        qfile.is_download     = false;
                                        qfile.is_pair         = false;
                                        qfile.file_time_count = 0;
                                        qfile.used_status     = true;
                                        lm.qvvrevent.qvvrfile.push_back(std::move(qfile));
                                    }

                                    // 把这组标记为“已全部下载”，并尝试用 .cfg 匹配事件；准备锁外上传动作
                                    {
                                        // 找到刚刚 append 的那组：file_name 集合 == fullFilenames 的文件名集合
                                        auto want_names = MakeSigNames(fullFilenames);
                                        auto it_qf = std::find_if(lm.qvvrevent.qvvrfile.begin(),
                                                                  lm.qvvrevent.qvvrfile.end(),
                                                                  [&](const qvvr_file& x){
                                                                      std::set<std::string> s(x.file_name.begin(), x.file_name.end());
                                                                      return s == want_names;
                                                                  });
                                        if (it_qf != lm.qvvrevent.qvvrfile.end()) {
                                            // 原：it_qf->file_download = fullFilenames;
                                            it_qf->file_download.clear();
                                            it_qf->file_download.assign(fullFilenames.begin(), fullFilenames.end()); // ★修复：vector -> list
                                            it_qf->is_download   = true;

                                            // 寻找第一个 .cfg 做匹配
                                            qvvr_data matched{};
                                            qvvr_data mismatched{};
                                            bool has_matched = false;
                                            for (const auto& p : it_qf->file_download) {
                                                auto s = sanitize(p);
                                                auto dot = s.find_last_of('.');
                                                if (dot != std::string::npos) {
                                                    std::string ext = s.substr(dot);
                                                    for (auto& c : ext) c = (char)std::tolower((unsigned char)c);
                                                    if (ext == ".cfg") {
                                                        if (compare_qvvr_and_file(p, lm.qvvrevent.qvvrdata, matched)) {
                                                            it_qf->is_pair = true;
                                                            has_matched = true;
                                                        }
                                                        else {
                                                            it_qf->is_pair = false;

                                                            long long start_t;
                                                            long long trigger_t;
                                                            if (extract_timestamp_from_cfg_file(p, start_t, trigger_t)) {
                                                                mismatched.QVVR_time = trigger_t;
                                                            } else {
                                                                mismatched.QVVR_time = 0;     // 未能提取时间
                                                            }
                                                            // 其他字段置空/置零
                                                            mismatched.QVVR_Amg     = 0.0;
                                                            mismatched.QVVR_PerTime = 0.0;
                                                            mismatched.QVVR_type    = 0;
                                                            mismatched.phase        = 0;
                                                        }
                                                        break; // 只看一个 .cfg
                                                    }
                                                }
                                            }

                                            // 记录一个“待上传动作”，锁外执行上传、回写 JSON、删文件与回锁清理
                                            PendingUpload pu;
                                            pu.terminal_id = dev.terminal_id;
                                            try { pu.logical_seq = static_cast<unsigned short>(std::stoi(lm.logical_device_seq)); }
                                            catch (...) { pu.logical_seq = 0; }
                                            // 原：pu.files_to_send = it_qf->file_download;
                                            pu.files_to_send.clear();
                                            pu.files_to_send.assign(it_qf->file_download.begin(), it_qf->file_download.end()); // ★修复：list -> vector
                                            pu.has_matched   = has_matched;
                                            pu.matched       = matched;
                                            pu.mismatched    = mismatched;
                                            pu.sig_names     = want_names;
                                            pu.sig_downs     = std::set<std::string>(pu.files_to_send.begin(), pu.files_to_send.end());
                                            pending_uploads.push_back(std::move(pu));
                                        }
                                    }
                                    // ★修改结束

                                    std::cout << "[check_recall_stat] DONE dev=" << dev.terminal_id
                                        << " monitor=" << lm.monitor_id
                                        << " ok=" << front.file_success.size()
                                        << " total=" << front.required_files.size()
                                        << std::endl;
                                } else {
                                    front.recall_status = static_cast<int>(RecallStatus::FAILED);

                                    //20251218添加记录
                                    std::string msg_fail;
                                    if (front.direct_mode) {
                                        msg_fail = std::string("监测点：") + lm.monitor_name
                                                 + " 补招波形文件下载失败，目标时标："
                                                 + front.target_filetimes;
                                    } else {
                                        msg_fail = std::string("监测点：") + lm.monitor_name
                                                 + " 补招波形文件下载失败，时间范围："
                                                 + front.StartTime + " ~ " + front.EndTime;
                                    }
                                    append_recall_record_line(dev.guid, lm.monitor_id, msg_fail);
                                    //20251218添加记录

                                    std::cout << "[check_recall_stat] some files failed, FAIL dev=" << dev.terminal_id
                                              << " monitor=" << lm.monitor_id
                                              << " ok=" << front.file_success.size()
                                              << " need=" << front.required_files.size() << std::endl;
                                }
                                break; //跳出循环，一个装置一次只能处理一个测点的一个补招记录
                            }
                            // 发起下一文件下载
                            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;
                            break; //跳出循环，一个装置一次只能处理一个测点的一个补招记录
                        } else {
                            // 等待 download_result
                            if (front.download_result == ActionResult::PENDING) {
                                break; // 仍在等待
                            }
                            if (front.download_result == ActionResult::OK) {

                                front.file_success.insert(front.downloading_file);

                                std::string msg_ok = std::string("监测点：") + lm.monitor_name
                                    + " 补招波形文件：" + front.downloading_file
                                    + " 执行完成";
                                append_recall_record_line(dev.guid, lm.monitor_id, msg_ok);

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

                                front.download_result = ActionResult::PENDING;

                                break; //跳出循环，一个装置一次只能处理一个测点的一个补招记录

                            } else {

                                std::string msg_fail = std::string("监测点：") + lm.monitor_name
                                    + " 补招波形文件：" + front.downloading_file
                                    + " 执行失败";
                                append_recall_record_line(dev.guid, lm.monitor_id, msg_fail);

                                // 失败：直接尝试下一个文件（不中断整条补招）
                                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();

                                front.download_result = ActionResult::PENDING;

                                break; //跳出循环，一个装置一次只能处理一个测点的一个补招记录
                            }
                        }
                    }

                    // 一个终端只推进“一个监测点的首条”状态机，避免并行
                    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;    //装置由idle标记为忙
                    dev.busytype      = static_cast<int>(DeviceState::READING_STATSFILE);//装置状态刷新为正在补招文件
                    dev.busytimecount = 0;  //清空业务超时计数
                    dev.guid          = front.recall_guid;

                    // 初始化状态机并发出第一个目录请求
                    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;  //目录状态：等待返回

                        // ★新增：先查缓存
                        std::vector<tag_dir_info> cached;
                        if (dircache_try_get(dev.terminal_id, front.cur_dir, cached)) {
                            front.dir_files[front.cur_dir] = std::move(cached);
                            front.list_result = ActionResult::OK;
                            std::cout << "[dircache] HIT dev=" << dev.terminal_id
                                      << " dir=" << front.cur_dir
                                      << " entries=" << front.dir_files[front.cur_dir].size() << std::endl;

                            break; // ★新增：命中缓存后，本轮就此结束（每终端本轮仅推进一条）

                        } else {

                            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;

                            break; // ★新增：已下发请求，本轮结束，等回执/下一轮
                        }
                    } else {
                        front.recall_status = static_cast<int>(RecallStatus::FAILED);   //目录列表空，失败

                        std::string msg_fail;
                        if (front.direct_mode) {
                            msg_fail = std::string("监测点：") + lm.monitor_name
                                     + " 补招波形文件未找到，目标时标："
                                     + front.target_filetimes;
                        } else {
                            msg_fail = std::string("监测点：") + lm.monitor_name
                                     + " 补招波形文件未找到，时间范围："
                                     + front.StartTime + " ~ " + front.EndTime;
                        }
                        append_recall_record_line(dev.guid, lm.monitor_id, msg_fail);

                        std::cout << "[check_recall_stat] empty dir_candidates, FAIL dev=" << dev.terminal_id
                                  << " monitor=" << lm.monitor_id << std::endl;
                    }

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

    // ★修改开始：锁外执行上传、回写 JSON、删除本地文件，并回锁做台账清理
    for (auto& pu : pending_uploads) {
        // 1) 上传所有文件：构造临时 qvvr_file 给现有 SendAllQvvrFiles 使用
        qvvr_file tmp;
        // 原：tmp.file_download = pu.files_to_send;
        tmp.file_download.clear();
        tmp.file_download.assign(pu.files_to_send.begin(), pu.files_to_send.end()); // ★修复：vector -> list

        std::string wavepath;
        bool sent_ok = SendAllQvvrFiles(tmp, wavepath);

        if (sent_ok) {
            // 2) 回写 JSON（若匹配到事件）
            if (pu.has_matched) {
                transfer_json_qvvr_data(pu.terminal_id,
                                        pu.logical_seq,
                                        pu.matched.QVVR_Amg,
                                        pu.matched.QVVR_PerTime,
                                        pu.matched.QVVR_time,
                                        pu.matched.QVVR_type,
                                        pu.matched.phase,
                                        wavepath);
            }
            else{
                //打印提示：没有从装置读到匹配的事件，使用文件中的数据进行更新
                std::cout << "[Upload] No matched QVVR event from device for terminal="
                          << pu.terminal_id << " seq=" << pu.logical_seq
                          << ", using data extracted from .cfg file for JSON update.\n";

                transfer_json_qvvr_data(pu.terminal_id,
                                        pu.logical_seq,
                                        //从文件中获取
                                        pu.mismatched.QVVR_Amg,
                                        pu.mismatched.QVVR_PerTime,
                                        pu.mismatched.QVVR_time,
                                        pu.mismatched.QVVR_type,
                                        pu.mismatched.phase,
                                        wavepath);
            }

            // 3) 删除本地文件
            for (const auto& f : pu.files_to_send) {
                if (std::remove(f.c_str()) != 0) {
                    std::cerr << "[Cleanup] Failed to delete file: " << f << "\n";
                } else {
                    std::cout << "[Cleanup] Deleted uploaded file: " << f << "\n";
                }
            }

            // 4) 回锁做台账清理：删除对应 qvvr_file 组与匹配到的事件
            {
                std::lock_guard<std::mutex> lk(ledgermtx);
                for (auto& dev : terminal_devlist) {
                    if (dev.terminal_id != pu.terminal_id) continue;
                    for (auto& lm : dev.line) {
                        unsigned short seq = 0;
                        try { seq = static_cast<unsigned short>(std::stoi(lm.logical_device_seq)); } catch (...) { seq = 0; }
                        if (seq != pu.logical_seq) continue;

                        // 定位并删除相同签名的组
                        auto it_qf = std::find_if(lm.qvvrevent.qvvrfile.begin(),
                                                  lm.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==pu.sig_names && d==pu.sig_downs;
                                                  });
                        if (it_qf != lm.qvvrevent.qvvrfile.end()) {
                            lm.qvvrevent.qvvrfile.erase(it_qf);
                        } else {
                            std::cerr << "[Cleanup] qvvrfile changed; target group not found, skip erase\n";
                        }

                        // 如有匹配事件，按时间删除
                        if (pu.has_matched) {
                            auto it_evt = std::find_if(lm.qvvrevent.qvvrdata.begin(),
                                                       lm.qvvrevent.qvvrdata.end(),
                                                       [&](const qvvr_data& d){ return d.QVVR_time == pu.matched.QVVR_time; });
                            if (it_evt != lm.qvvrevent.qvvrdata.end()) {
                                lm.qvvrevent.qvvrdata.erase(it_evt);
                            }
                        }
                    }
                }
            }
        } else {
            std::cerr << "[check_recall_file][Upload] SendAllQvvrFiles failed for terminal="
                      << pu.terminal_id << " seq=" << pu.logical_seq << std::endl;
        }
    }
    // ★修改结束

    // ★说明：本函数不主动把 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& filetime,
                             const std::string& guid)
{
    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_guid     = guid;
    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_filetimes=filetime;      // ▲单个文件时间入“列表”

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

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

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_file_list] queued: " << j.dump() << std::endl;

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

    return true;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////运行日志相应处理


void runninginfo_cache_put(const std::string& dev_id, const RunningInformation& info)
{
    std::lock_guard<std::mutex> lk(g_runninginfo_cache_mtx);
    g_runninginfo_cache[dev_id] = info;
}

bool runninginfo_cache_take(const std::string& dev_id, RunningInformation& out)
{
    std::lock_guard<std::mutex> lk(g_runninginfo_cache_mtx);
    auto it = g_runninginfo_cache.find(dev_id);
    if (it == g_runninginfo_cache.end()) {
        return false;
    }

    out = std::move(it->second);
    g_runninginfo_cache.erase(it);
    return true;
}

bool send_running_info(terminal_dev* dev, const RunningInformation& info) {

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

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

    // 构造 JSON 报文
    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);

    // 构造 result
    nlohmann::json result;
    result["Time"]           = info.Time;
    result["CpuLoad"]        = info.CpuLoad;
    result["FreeMemory"]     = info.FreeMemory;
    result["TotalMemory"]    = info.TotalMemory;
    result["FreeStorage"]    = info.FreeStorage;
    result["TotalStorage"]   = info.TotalStorage;
    result["HardTimeSync"]   = info.HardTimeSync;
    result["SntpTimeSync"]   = info.SntpTimeSync;
    result["CloudTimeSync"]  = info.CloudTimeSync;
    result["SignalStrength"] = info.SignalStrength;

    // 构造 Detail 部分
    nlohmann::json detail;
    detail["Type"] = 1111; // 读取运行状态
    detail["Msg"]  = { {"result", result} };
    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_running_info] queued: " << j.dump() << std::endl;

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

    return true;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////版本信息处理

void versioninfo_cache_put(const std::string& dev_id, const DeviceVersionInfo& info)
{
    std::lock_guard<std::mutex> lk(g_versioninfo_cache_mtx);
    g_versioninfo_cache[dev_id] = info;
}

bool versioninfo_cache_take(const std::string& dev_id, DeviceVersionInfo& out)
{
    std::lock_guard<std::mutex> lk(g_versioninfo_cache_mtx);
    auto it = g_versioninfo_cache.find(dev_id);
    if (it == g_versioninfo_cache.end()) {
        return false;
    }

    out = std::move(it->second);
    g_versioninfo_cache.erase(it);
    return true;
}

bool send_version_info(terminal_dev* dev, const DeviceVersionInfo& info) {

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

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

    // 构造 JSON 报文
    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);

    // 构造 VersionInfo
    nlohmann::json ver;
    ver["BaseModel"]            = info.BaseModel;
    ver["CloudProtocolVer"]     = info.CloudProtocolVer;
    ver["AppVersion"]           = info.AppVersion;
    ver["AppDate"]              = info.AppDate;
    ver["AppChecksum"]          = info.AppChecksum;
    ver["VoltageWiring"]        = info.VoltageWiring;
    ver["CurrentBSynthetic"]    = info.CurrentBSynthetic;
    ver["DataStatInterval"]     = info.DataStatInterval;
    ver["RatedVoltage"]         = info.RatedVoltage;
    ver["PTRatio"]              = info.PTRatio;
    ver["CTRatio"]              = info.CTRatio;
    ver["SntpIP"]               = info.SntpIP;
    ver["SntpPort"]             = info.SntpPort;
    ver["SntpInterval"]         = info.SntpInterval;
    ver["WebPort"]              = info.WebPort;
    ver["FtpPort"]              = info.FtpPort;
    ver["PqdifInterval"]        = info.PqdifInterval;
    ver["WaveFileTypeCount"]    = info.WaveFileTypeCount;
    ver["SpecialVersion"]       = info.SpecialVersion;
    ver["DeviceModel"]          = info.DeviceModel;
    ver["HarmonicEnergyFlag"]   = info.HarmonicEnergyFlag;
    ver["PhysicalName"]         = info.PhysicalName;
    ver["WaveLDName"]           = info.WaveLDName;
    ver["HighFreqHarmonicFlag"] = info.HighFreqHarmonicFlag;
    ver["CommProtocols"]        = info.CommProtocols;
    ver["TimeSyncMethods"]      = info.TimeSyncMethods;
    ver["DeviceFunctions"]      = info.DeviceFunctions;

    // 构造 Detail 部分
    nlohmann::json detail;
    detail["Type"] = 1112; // 读取版本信息
    detail["Msg"]  = { {"VersionInfo", ver} };
    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_version_info] queued: " << j.dump() << std::endl;

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

    return true;
}

/////////////////////////////////////////////////////////////////////////////////////////////
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) 找到 RecallStatus::RUNNING 的 monitor（优先）
        //    若没有，则回退到按 cid -> logical_device_seq 匹配
        ledger_monitor* matched_monitor = nullptr;

        //优先：遍历查找首条补招项处于 RUNNING 的监测点
        for (auto& lm : dev->line) {
            if (!lm.recall_list.empty()) {
                const RecallMonitor& head = lm.recall_list.front();
                if (head.recall_status == static_cast<int>(RecallStatus::RUNNING)) {
                    matched_monitor = &lm;
                    break;
                }
            }
        }

        //未找到 RUNNING 的监测点时，按原逻辑用 cid 匹配 logical_device_seq
        if (!matched_monitor) {
            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 (no RUNNING item, no cid match), cid="
                      << static_cast<int>(cid) << 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
                                  << " recall_status=" << front.recall_status
                                  << std::endl;
                    } else if (response_code == 404) {    //补招无数据
                        front.recall_status = static_cast<int>(RecallStatus::EMPTY);
                        std::cout << "[RESP][EVENTLOG][WARN] dev=" << id
                                  << " mon=" << matched_monitor->monitor_id
                                  << " " << front.StartTime << "~" << front.EndTime
                                  << " rc=" << response_code << " recall_status=" << front.recall_status << 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 << " recall_status=" << front.recall_status<< std::endl;  //错误响应码

                        //记录日志
                        DIY_ERRORLOG_CODE(matched_monitor->monitor_id.c_str(),2,LOG_CODE_RECALL,"补招数据失败,失败时间点:%s 至 %s",front.StartTime.c_str(),front.EndTime.c_str());
                    }
                    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;
        }

        std::vector<tag_dir_info> names;
        // 2) 按该终端当前 busytype 分支处理
        const int bt = dev->busytype;
        if (bt == static_cast<int>(DeviceState::READING_FILEMENU)) {
            // ====== 分支 A：当前业务就是“读取文件目录” ======
            if (ok) {
                
                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);
                    send_reply_to_queue(dev->guid, static_cast<int>(ResponseCode::BAD_REQUEST),
                        "终端 id: " + dev->terminal_id + "进行业务:" + get_type_by_state(dev->busytype) +"失败，停止该业务处理");
                    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);
                send_reply_to_queue(dev->guid, static_cast<int>(ResponseCode::BAD_REQUEST),
                    "终端 id: " + dev->terminal_id + "进行业务:" + get_type_by_state(dev->busytype) +"失败，停止该业务处理");

                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() 正在运行且处于 LISTING 的监测点”
            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::LISTING) {//正在请求目录
                    running_monitor = &lm;  //补招的测点
                    running_front   = &f;   //补招记录
                    break;
                }
                else {
                    // 不是正在补招的测点，跳过//打印跳过的信息
                    std::cout << "[check_recall_file] skip non-running monitor dev=" << dev->terminal_id
                              << " monitor=" << lm.monitor_id
                              << " status=" << static_cast<int>(f.recall_status)
                              << " phase=" << static_cast<int>(f.phase)
                              << std::endl;
                    continue;
                }
            }
        
            if (!running_monitor || !running_front) {   //该装置没有正在补招的测点和补招记录，退出处理
                std::cout << "[RESP][FILEMENU->(EVENT/STATS)FILE][WARN] dev=" << id
                          << " no RUNNING/LISTING recall on this device, ignore resp"
                          << " rc=" << response_code << std::endl;
                break;
            }
        
            // 根据回执结果，回写目录结果；状态机会在下一轮推进到下一个目录/结束
            if (ok) {
                if(filemenu_cache_take(id, names)) { // 从缓存取目录列表
                    running_front->dir_files[running_front->cur_dir] = std::move(names);
                    running_front->list_result = ActionResult::OK;

                    dircache_store(id, running_front->cur_dir, running_front->dir_files[running_front->cur_dir]); // ★新增：缓存（非空或空都按实际存）

                    std::cout << "[RESP][FILEMENU->(EVENT/STATS)FILE][OK] dev=" << id
                        << " monitor=" << running_monitor->monitor_id
                        << " dir=" << running_front->cur_dir
                        << " entries=" << running_front->dir_files[running_front->cur_dir].size()
                        << std::endl;
                } else {
                    running_front->dir_files[running_front->cur_dir] = {};
                    running_front->list_result = ActionResult::OK;

                    dircache_store(id, running_front->cur_dir, running_front->dir_files[running_front->cur_dir]); // ★新增：缓存空列表

                    std::cout << "[RESP][FILEMENU->(EVENT/STATS)FILE][WARN] dev=" << id
                        << " monitor=" << running_monitor->monitor_id
                        << " dir=" << running_front->cur_dir
                        << " cache miss -> treat as empty OK" << std::endl;
                }
            } else {
                running_front->list_result = ActionResult::FAIL;
                std::cout << "[RESP][FILEMENU->(EVENT/STATS)FILE][FAIL] dev=" << id
                          << " monitor=" << running_monitor->monitor_id
                          << " dir=" << running_front->cur_dir
                          << " 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);
                send_reply_to_queue(dev->guid, static_cast<int>(ResponseCode::OK),
                    "终端 id: " + dev->terminal_id + "进行业务:" + get_type_by_state(dev->busytype) +"成功，停止该业务处理");

                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);
                send_reply_to_queue(dev->guid, static_cast<int>(ResponseCode::BAD_REQUEST),
                    "终端 id: " + dev->terminal_id + "进行业务:" + get_type_by_state(dev->busytype) +"失败，停止该业务处理");

                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：当前业务为“下载事件文件/统计文件”（两者处理相同） ======
        
            //优先：找“首条处于 RUNNING 且 phase==DOWNLOADING”的监测点
            ledger_monitor* matched_monitor = nullptr;
            RecallFile*     running_front   = nullptr;
        
            for (auto& lm : dev->line) {
                if (lm.recall_list_static.empty()) continue;
                RecallFile& f = lm.recall_list_static.front();

                std::cout << "[RESP][FILEDATA][SCAN] dev=" << id
              << " monitor=" << lm.monitor_id
              << " status=" << static_cast<int>(f.recall_status)
              << " phase="  << static_cast<int>(f.phase)
              << std::endl;

                if (f.recall_status == static_cast<int>(RecallStatus::RUNNING) &&
                    f.phase         == RecallPhase::DOWNLOADING) {
                    matched_monitor = &lm;
                    running_front   = &f;
                    break;
                }
            }
        
            // 若没找到 RUNNING/DOWNLOADING，用 cid 精确匹配测点号
            if (!matched_monitor) {
                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()) {
                    RecallFile& f = matched_monitor->recall_list_static.front();
                    if (f.recall_status == static_cast<int>(RecallStatus::RUNNING) &&
                        f.phase         == RecallPhase::DOWNLOADING) {
                        running_front = &f;
                        std::cout << "[RESP][FILEDATA][FALLBACK-CID] dev=" << id
                                  << " cid=" << static_cast<int>(cid)
                                  << " monitor=" << matched_monitor->monitor_id << std::endl;
                    } else {
                        std::cout << "[RESP][FILEDATA][WARN] dev=" << id
                                  << " cid matched monitor=" << matched_monitor->monitor_id
                                  << " but status=" << f.recall_status
                                  << " phase="  << static_cast<int>(f.phase)
                                  << " — ignore this resp" << std::endl;
                        matched_monitor = nullptr;
                    }
                }
            }
        
            if (!matched_monitor || !running_front) {
                std::cout << "[RESP][FILEDATA->(EVENT/STATS)FILE][WARN] dev=" << id
                          << " no RUNNING/DOWNLOADING recall to accept filedata"
                          << " rc=" << response_code
                          << " cid=" << static_cast<int>(cid) << std::endl;
                break;
            }
        
            // 到这里一定是 RUNNING/DOWNLOADING 的 front；按回执设置下载结果
            if (ok) {
                running_front->download_result = ActionResult::OK;
                std::cout << "[RESP][FILEDATA->(EVENT/STATS)FILE][OK] dev=" << id
                          << " monitor=" << matched_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=" << matched_monitor->monitor_id
                          << " file=" << running_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;
    }

    // ================= 特殊：读取运行信息 =================
    case DeviceState::READING_RUNNINGINFO: {
        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][RUNNINGINFO] dev not found, id=" << id
                      << " rc=" << response_code << std::endl;
            break;
        }

        RunningInformation info;
        const int bt = dev->busytype;

        if (bt == static_cast<int>(DeviceState::READING_RUNNINGINFORMATION_1)) {
            if (ok) {
                if (runninginfo_cache_take(id, info)) {

                    // 发送运行信息
                    send_running_info(dev, info);

                } else {
                    send_reply_to_queue(dev->guid, static_cast<int>(ResponseCode::BAD_REQUEST),
                        "终端 id: " + dev->terminal_id + "进行业务:" +
                        get_type_by_state(dev->busytype) + "失败，运行状态缓存不存在");

                    std::cout << "[RESP][RUNNINGINFO][WARN] dev=" << id
                              << " running info missing in cache" << std::endl;
                }

                dev->guid.clear();
                dev->isbusy        = 0;
                dev->busytype      = 0;
                dev->busytimecount = 0;

                std::cout << "[RESP][RUNNINGINFO][OK] dev=" << id << std::endl;
            } else {
                send_reply_to_queue(dev->guid, static_cast<int>(ResponseCode::BAD_REQUEST),
                    "终端 id: " + dev->terminal_id + "进行业务:" +
                    get_type_by_state(dev->busytype) + "失败，停止该业务处理");

                dev->guid.clear();
                dev->isbusy        = 0;
                dev->busytype      = 0;
                dev->busytimecount = 0;

                std::cout << "[RESP][RUNNINGINFO][FAIL] dev=" << id
                          << " rc=" << response_code << std::endl;
            }
        }
        break;
    }

    // ================= 特殊：读取版本信息 =================

    case DeviceState::READING_DEVVERSION: {
        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][VERSIONINFO] dev not found, id=" << id
                      << " rc=" << response_code << std::endl;
            break;
        }

        DeviceVersionInfo info;
        const int bt = dev->busytype;

        if (bt == static_cast<int>(DeviceState::READING_DEVVERSION)) {
            if (ok) {
                if (versioninfo_cache_take(id, info)) {

                    // 发送版本信息
                    send_version_info(dev, info);

                } else {
                    send_reply_to_queue(dev->guid, static_cast<int>(ResponseCode::BAD_REQUEST),
                        "终端 id: " + dev->terminal_id + "进行业务:" +
                        get_type_by_state(dev->busytype) + "失败，版本信息缓存不存在");

                    std::cout << "[RESP][VERSIONINFO][WARN] dev=" << id
                              << " version info missing in cache" << std::endl;
                }

                dev->guid.clear();
                dev->isbusy        = 0;
                dev->busytype      = 0;
                dev->busytimecount = 0;

                std::cout << "[RESP][VERSIONINFO][OK] dev=" << id << std::endl;
            } else {
                send_reply_to_queue(dev->guid, static_cast<int>(ResponseCode::BAD_REQUEST),
                    "终端 id: " + dev->terminal_id + "进行业务:" +
                    get_type_by_state(dev->busytype) + "失败，停止该业务处理");

                dev->guid.clear();
                dev->isbusy        = 0;
                dev->busytype      = 0;
                dev->busytimecount = 0;

                std::cout << "[RESP][VERSIONINFO][FAIL] dev=" << id
                          << " rc=" << response_code << 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);
            send_reply_to_queue(dev->guid, response_code,
                    "终端 id: " + dev->terminal_id + "进行业务:" + get_type_by_state(dev->busytype) + "," + ResponseCodeToString(response_code) + "停止该业务处理");
            //其他的错误和成功都会结束业务
            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;

            //lnk20251030
            q.is_pair = false;

            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;

            //lnk20251030
            q.is_pair = false;

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

    //lnk20251030
    q.is_pair = false;

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

/////////////////////////////////////////////////////////////////////////////////////////////////////////////实时数据封装发送
void enqueue_realtime_pq(const RealtagPqDate_float& realdata,
                         int nPTType,
                         unsigned char cid,
                         const std::string& mac,
                         const std::string& devid)
{
    // 先根据 devIdxMap 的配置决定编码分支：
    // 2: 基础数据  3: 谐波电压含有率  4: 谐波电流有效值  5: 间谐波电压含有率
    int idx = 0;
    std::string base64;

    // 这里尝试用 mac 作为 key 获取 idx；如果项目里 devIdxMap 的 key 不是 mac，
    // 你可以把这里改成对应的设备 id（devid）。未命中则再尝试用规范化后的 mac。
    if (devidx_get(devid, idx)) {
        switch (idx) {
            case 2: // 基础数据（根据接线方式选择转换方法 数据全集解析）
                base64 = realdata.ConvertToBase64(nPTType);
                break;
            case 3: // 谐波电压含有率
                base64 = realdata.ConvertToBase64_RtHarmV(nPTType);
                break;
            case 4: // 谐波电流有效值（幅值）
                base64 = realdata.ConvertToBase64_RtHarmI();
                break;
            case 5: // 间谐波电压含有率
                base64 = realdata.ConvertToBase64_RtInHarmV();
                break;
            default:
                // 未知 idx，回退到基础数据
                base64 = realdata.ConvertToBase64(nPTType);
                break;
        }
    } else {
        // 未配置 idx，回退到基础数据
        base64 = realdata.ConvertToBase64(nPTType);
    }
    //std::cout << base64 << std::endl;

    //lnk实时数据使用接口发送20250711
    time_t data_time = ConvertToTimestamp(realdata.time);
    {
        std::lock_guard<std::mutex> lk(g_last_ts_mtx);
        auto it = g_last_ts_by_devid.find(devid);
        if (it != g_last_ts_by_devid.end() && it->second == data_time) {

            std::cout << "[enqueue_realtime_pq] duplicate timestamp, devid="
                      << devid << " time=" << data_time << std::endl;
            // 同一设备与上次时间相同 → 丢弃本次
            return;
        }
        // 记录本次时间
        g_last_ts_by_devid[devid] = data_time;

        std::cout << "[enqueue_realtime_pq] record timestamp, devid="
                  << devid << " time=" << data_time << std::endl;
    }

    std::vector<DataArrayItem> arr;
    arr.push_back({1,   //数据属性  -1-无， 0-“Rt”,1-“Max”,2-“Min”,3-“Avg”,4-“Cp95”
        data_time,     //数据转换出来的时间，数据时标，相对1970年的秒，无效填入“-1”
        0,             //数据时标，微秒钟，无效填入“-1”
        0,              //数据标识，1-标识数据异常
        base64});
    std::string js = generate_json(
        normalize_mac(mac),
        -1,                 //需应答的报文订阅者收到后需以此ID应答，无需应答填入“-1”
        1,                  //设备唯一标识Ldid，填入0代表Ndid,后续根据商议决定填id还是数字
        1,                  //报文处理的优先级：1 I类紧急请求/响应 2 II类紧急请求/响应 3 普通请求/响应 4 广播报文
        0x1302,               //设备数据主动上送的数据类型
        cid,                //逻辑子设备ID，0-逻辑设备本身，无填-1
        0x04,               //数据类型固定为电能质量数据
        1,                      //数据属性：无“0”、实时“1”、统计“2”等
        idx,                      //数据集序号（以数据集方式上送），无填-1
        arr                     //数据数组
    );
    //std::cout << js << std::en
    queue_data_t data;
    data.monitor_no = 1;    //上送的实时数据没有测点序号，统一填1
    data.strTopic = TOPIC_RTDATA;   //实时topic
    data.strText = js;
    data.mp_id = "";    //监测点id，暂时不需要
    data.tag = G_RT_TAG;    //实时tag
    data.key = G_RT_KEY;    //实时key
    std::lock_guard<std::mutex> lock(queue_data_list_mutex);
    queue_data_list.push_back(data);
}

////////////////////////////////////////////////////////////////////////////////统计数据打包发送

// 封装：组装统计数据并入队发送
void enqueue_stat_pq(const std::string& max_base64Str,
                     const std::string& min_base64Str,
                     const std::string& avg_base64Str,
                     const std::string& cp95_base64Str,
                     time_t data_time,
                     const std::string& mac,
                     short cid)
{
    std::vector<DataArrayItem> arr;
    arr.push_back({1,   //数据属性  -1-无， 0-“Rt”,1-“Max”,2-“Min”,3-“Avg”，4-“Cp95”
        data_time,     //数据转换出来的时间，数据时标，相对1970年的秒，无效填入“-1”
        0,             //数据时标，微秒钟，无效填入“-1”
        0,              //数据标识，1-标识数据异常
        max_base64Str});
    arr.push_back({2, data_time, 0, 0, min_base64Str});
    arr.push_back({3, data_time, 0, 0, avg_base64Str});
    arr.push_back({4, data_time, 0, 0, cp95_base64Str});

    std::string js = generate_json(
        normalize_mac(mac),
        -1,                 //需应答的报文订阅者收到后需以此ID应答，无需应答填入“-1”
        1,                  //设备唯一标识Ldid，填入0代表Ndid,后续根据商议决定填id还是数字
        1,                  //报文处理的优先级：1 I类紧急请求/响应 2 II类紧急请求/响应 3 普通请求/响应 4 广播报文
        0x1302,               //设备数据主动上送的数据类型
        cid,                //逻辑子设备ID，0-逻辑设备本身，无填-1（原：avg_data.name）
        0x04,               //数据类型固定为电能质量
        2,                      //数据属性：无“0”、实时“1”、统计“2”等
        1,                      //数据集序号（以数据集方式上送），无填-1
        arr                     //数据数组
    );

    //std::cout << js << std::endl;

    queue_data_t data;
    data.monitor_no = cid;    //监测点序号（原：avg_data.name）
    data.strTopic = TOPIC_STAT;//统计topic
    data.strText = js;
    data.mp_id = "";    //监测点id，暂时不需要
    data.tag = G_ROCKETMQ_TAG;  //统计tag
    data.key = G_ROCKETMQ_KEY;  //统计key
    std::lock_guard<std::mutex> lock(queue_data_list_mutex);
    queue_data_list.push_back(data);

    std::cout << "Successfully assembled tagPqData for line: "
        << cid << std::endl;
}

/////////////////////////////////////////////////////////////////////////////////////清空一个装置运行数据
size_t erase_one_terminals_by_id(const std::string& terminal_id) {

    // 先对所有匹配项做日志清理
    for (const auto& d : terminal_devlist) {
        if (d.terminal_id == terminal_id) {
            remove_loggers_by_terminal_id(terminal_id);
        }
    }
    // 再统一擦除
    const auto old_size = terminal_devlist.size();
    terminal_devlist.erase(
        std::remove_if(terminal_devlist.begin(), terminal_devlist.end(),
                       [&](const terminal_dev& d){ return d.terminal_id == terminal_id; }),
        terminal_devlist.end());
    return old_size - terminal_devlist.size();
}

///////////////////////////////////////////////////////////////////////////////////////////////
DeviceInfo make_device_from_terminal(const terminal_dev& t) {
    DeviceInfo d;

    // 基本信息
    d.device_id = t.terminal_id;
    d.name      = t.terminal_name;
    d.model     = t.dev_type;
    d.mac       = t.mac;

    // status：优先按数字解析，其次按 online/true/yes/on 识别；默认 0
    int status = 0;
    if (!t.tmnl_status.empty()) {
        bool parsed_num = false;
        // 尝试解析为整数
        char* endp = nullptr;
        long v = std::strtol(t.tmnl_status.c_str(), &endp, 10);
        if (endp && *endp == '\0') { parsed_num = true; status = (v != 0) ? 1 : 0; }

        if (!parsed_num) {
            std::string s = t.tmnl_status;
            for (char& c : s) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
            status = (s == "online" || s == "true" || s == "yes" || s == "on" || s == "1") ? 1 : 0;
        }
    }
    d.status = status;

    // righttime：支持 "1/true/yes/on"；默认 false
    bool rt = false;
    if (!t.Righttime.empty()) {
        std::string s = t.Righttime;
        for (char& c : s) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
        rt = (s == "1" || s == "true" || s == "yes" || s == "on");
    }
    d.righttime = rt;

    // points
    d.points.clear();
    d.points.reserve(t.line.size());
    for (const auto& m : t.line) {
        PointInfo p;
        p.point_id  = m.monitor_id;
        p.name      = m.monitor_name;
        p.device_id = t.terminal_id;

        // nCpuNo：从 logical_device_seq 转 ushort，非法则置 0
        unsigned short cpu_no = 0;
        if (!m.logical_device_seq.empty()) {
            char* endp = nullptr;
            long v = std::strtol(m.logical_device_seq.c_str(), &endp, 10);
            if (endp && *endp == '\0' && v >= 0) {
                if (v > 0xFFFF) v = 0xFFFF;
                cpu_no = static_cast<unsigned short>(v);
            }
        }
        p.nCpuNo = cpu_no;

        // 变比与电压等级
        p.PT1 = m.PT1;
        p.PT2 = m.PT2;
        p.CT1 = m.CT1;
        p.CT2 = m.CT2;
        p.strScale = m.voltage_level;

        // 接线方式：0-星型 1-角型；支持 "0/1"、包含“角/三角/delta/Δ”
        int pttype = 0; // 默认星型
        if (!m.terminal_connect.empty()) {
            std::string s = m.terminal_connect;
            for (char& c : s) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
            if (s == "1" || s.find("角") != std::string::npos || s.find("三角") != std::string::npos
                || s.find("delta") != std::string::npos || s.find("△") != std::string::npos || s.find("Δ") != std::string::npos) {
                pttype = 1;
            }
        }
        p.nPTType = pttype;

        d.points.push_back(std::move(p));
    }

    return d;
}


/////////////////////////////////////////////////////////////////////////////////////////////////////////////补招文件
// --------- mkdir -p ----------
static bool mkdir_p(const std::string& path, mode_t mode = 0755) {
    if (path.empty()) return false;
    if (access(path.c_str(), F_OK) == 0) return true;

    std::string cur;
    for (size_t i = 0; i < path.size(); ++i) {
        cur.push_back(path[i]);
        if (path[i] == '/' && !cur.empty()) {
            if (access(cur.c_str(), F_OK) != 0) {
                if (mkdir(cur.c_str(), mode) != 0 && errno != EEXIST) {
                    return false;
                }
            }
        }
    }
    // 最后一层（不以 / 结尾时）
    if (access(path.c_str(), F_OK) != 0) {
        if (mkdir(path.c_str(), mode) != 0 && errno != EEXIST) {
            return false;
        }
    }
    return true;
}

static std::string build_recall_filepath(const std::string& guid,
                                         const std::string& monitorId,
                                         const std::string& start,
                                         const std::string& end) {
    std::string dir = "/FeProject/dat/recall/";
    ensure_dir_exists("/FeProject");
    ensure_dir_exists("/FeProject/dat");
    ensure_dir_exists(dir);

    // 替换非法字符（例如空格、冒号）
    auto sanitize = [](std::string s) {
        for (auto& c : s) {
            if (c == ' ' || c == ':' || c == '/' || c == '\\') c = '_';
        }
        return s;
    };

    std::string filename = sanitize(guid) + "_" + sanitize(monitorId) + "_" +
                           sanitize(start) + "_" + sanitize(end) + ".txt";
    return dir + filename;
}

std::string sanitize_for_filename(std::string s) {
    // 把空格->'_'，冒号/波浪号/斜杠去掉，避免非法字符
    for (char& c : s) {
        if (c == ' ') c = '_';
        else if (c == ':') c = '-';
        else if (c == '~' || c == '/' || c == '\\') c = '-';
    }
    return s;
}

bool init_recall_record_file(const std::string& guid,
                             const std::string& terminalId,
                             const std::string& monitorId,
                             const std::string& start,
                             const std::string& end)
{
    try {
        std::string path = build_recall_filepath(guid, monitorId, start, end);

        // 建立索引：guid + monitorId -> path
        {
            std::lock_guard<std::mutex> lk(g_recall_file_mtx);
            g_recall_file_index[std::make_pair(guid, monitorId)] = path;
        }

        std::ofstream ofs(path.c_str(), std::ios::out | std::ios::trunc);
        if (!ofs.is_open()) {
            std::cerr << "[recall_file] ERROR: cannot open file: " << path
                      << " errno=" << errno << " (" << strerror(errno) << ")"
                      << std::endl;
            return false;
        }

        // 写入头
        ofs << "guid: "        << guid       << "\n";
        ofs << "terminalId: "  << terminalId << "\n";
        ofs << "monitor_id: "  << monitorId  << "\n";
        ofs << "start_time: "  << start      << "\n";
        ofs << "end_time: "    << end        << "\n";
        ofs << "msglist:\n";
        ofs.close();

        std::cout << "[recall_file] created file OK: " << path << std::endl;
        return true;
    }
    catch (const std::exception& e) {
        std::cerr << "[recall_file] Exception in init_recall_record_file: "
                  << e.what() << std::endl;
        return false;
    }
    catch (...) {
        std::cerr << "[recall_file] Unknown exception in init_recall_record_file"
                  << std::endl;
        return false;
    }
}

bool append_recall_record_line(const std::string& guid,
                               const std::string& monitorId,
                               const std::string& msg) {
    std::string path;
    {
        std::lock_guard<std::mutex> lk(g_recall_file_mtx);
        auto it = g_recall_file_index.find(std::make_pair(guid, monitorId));
        if (it == g_recall_file_index.end()) return false;
        path = it->second;
    }

    std::ofstream ofs(path.c_str(), std::ios::out | std::ios::app);
    if (!ofs.is_open()) return false;
    ofs << msg << "\n";
    ofs.close();
    return true;
}

bool delete_recall_record_file(const std::string& guid,
                               const std::string& monitorId) {
    std::string path;
    {
        std::lock_guard<std::mutex> lk(g_recall_file_mtx);
        auto it = g_recall_file_index.find(std::make_pair(guid, monitorId));
        if (it == g_recall_file_index.end()) return false;
        path = it->second;
        g_recall_file_index.erase(it);
    }
    // 删除文件
    if (access(path.c_str(), F_OK) == 0) {
        ::remove(path.c_str());
    }
    return true;
}

static std::string find_recall_file(const std::string& guid,
                                    const std::string& monitorId) {
    std::string dir = "/FeProject/dat/recall/";
    ensure_dir_exists("/FeProject");
    ensure_dir_exists("/FeProject/dat");
    ensure_dir_exists(dir);
    std::string prefix = sanitize_for_filename(guid) + "_" +
                         sanitize_for_filename(monitorId) + "_";
    DIR* d = opendir(dir.c_str());
    if (!d) return "";

    std::string found;
    struct dirent* ent;
    while ((ent = readdir(d)) != nullptr) {
        if (ent->d_type == DT_REG) {
            std::string name = ent->d_name;
            if (name.compare(0, prefix.size(), prefix) == 0) {
                found = dir + name;
                break;
            }
        }
    }
    closedir(d);
    return found;
}

bool get_recall_record_fields_by_guid_monitor(const std::string& guid,
                                              const std::string& monitorId,
                                              std::string& outGuid,
                                              std::string& terminalId,
                                              std::string& outMonitorId,
                                              std::string& startTime,
                                              std::string& endTime,
                                              std::string& msg)
{
    std::string filepath = find_recall_file(guid, monitorId);
    if (filepath.empty()) {
        std::cerr << "[recall_file] 未找到匹配文件 guid=" << guid
                  << " monitorId=" << monitorId << std::endl;
        return false;
    }

    std::ifstream ifs(filepath.c_str());
    if (!ifs.is_open()) {
        std::cerr << "[recall_file] 打开文件失败: " << filepath << std::endl;
        return false;
    }

    std::string line;
    bool inMsglist = false;
    std::ostringstream msgbuf;

    while (std::getline(ifs, line)) {
        line = trim_copy(line);
        if (line.empty()) continue;

        if (!inMsglist) {
            parse_kv_line(line, "guid", outGuid);
            parse_kv_line(line, "terminalId", terminalId);
            parse_kv_line(line, "monitor_id", outMonitorId);
            parse_kv_line(line, "start_time", startTime);
            parse_kv_line(line, "end_time", endTime);
            if (line == "msglist:" || line == "msglist：") {
                inMsglist = true;
            }
        } else {
            msgbuf << line << "\n";
        }
    }
    ifs.close();
    msg = msgbuf.str();
    if (!msg.empty() && msg.back() == '\n') msg.pop_back();

    // ===== 校验 guid 和 monitor_id =====
    if (outGuid != guid || outMonitorId != monitorId) {
        std::cerr << "[recall_file] 文件头与请求不一致: guid=" << outGuid
                  << " monitor_id=" << outMonitorId << std::endl;
        return false;
    }

    // 简单检查字段完整性
    if (terminalId.empty()) {
        std::cerr << "[recall_file] 文件字段不完整: " << filepath << std::endl;
        return false;
    }

    //将 msg 内的换行符替换为逗号，避免 JSON 解析出错
    {
        for (auto& ch : msg) {
            if (ch == '\n' || ch == '\r') {
                ch = ',';  // 统一替换为逗号
            }
        }

        // 连续逗号去重（例如 "\n\n" -> ",," -> ",")
        while (msg.find(",,") != std::string::npos) {
            msg.replace(msg.find(",,"), 2, ",");
        }

        // 去除首尾多余的逗号
        if (!msg.empty() && msg.front() == ',') msg.erase(msg.begin());
        if (!msg.empty() && msg.back() == ',') msg.pop_back();
    }

    std::cout << "[recall_file] 读取成功: " << filepath << std::endl;
    return true;
}

///////////////////////////////////////////////////////////////////////////////////////////上送文件选择
// ========================= 自动选择上传URL的封装函数 =========================
// 功能：根据终端 id 判断当前业务类型（busytype），自动选择不同上传接口。
// 入参和 SendFileWeb 完全一致。
bool SendFileWebAuto(const std::string& id,
                     const std::string& local_path,
                     const std::string& remote_path,
                     std::string& out_filename)
{

    try {
        // 查找对应装置并判断状态
        std::lock_guard<std::mutex> lk(ledgermtx);
        const terminal_dev* dev_ptr = nullptr;
        for (const auto& d : terminal_devlist) {
            if (d.terminal_id == id) {
                dev_ptr = &d;
                break;
            }
        }

        std::string file_cloudpath;

        if (dev_ptr) {
            const int bt = dev_ptr->busytype;

            // 若处于“事件文件/统计文件”补招阶段，则使用补招专用上传目录：comtrade/wave/...
            if (bt == static_cast<int>(DeviceState::READING_EVENTFILE) ||
                bt == static_cast<int>(DeviceState::READING_STATSFILE)) {
                
                std::string rel = dirname_with_slash(local_path); // 例如：download/00:B7:.../
                // 将 download/ 前缀替换为 wave/
                if (!replace_prefix(rel, "download/", "wave/")) {
                    // 若不是以 download/ 开头，兜底拼 wave/ + 原目录
                    rel = "wave/" + rel;
                }
            
                file_cloudpath = "comtrade/" + rel;  // 目标：comtrade/wave/00:B7:.../
            
                std::cout << "[SendFileWebAuto] dev=" << id
                          << " busytype=" << bt
                          << " -> use recall upload URL (cloud path=" << file_cloudpath << ")\n";
            } else {
                // 非补招场景沿用原来的 download 目录
                file_cloudpath = dirname_with_slash(local_path); // 保持原逻辑
                std::cout << "[SendFileWebAuto] dev=" << id
                          << " busytype=" << bt
                          << " -> use default upload URL (cloud path=" << file_cloudpath << ")\n";
            }
        } else {
            std::cout << "[SendFileWebAuto][WARN] device not found for id=" << id
                      << ", fallback to default URL\n";
            file_cloudpath = dirname_with_slash(local_path);
        }

        // 实际上传调用
        SendFileWeb(WEB_FILEUPLOAD, local_path, file_cloudpath, out_filename);

        std::cout << "[SendFileWebAuto] File upload complete: " << out_filename << std::endl;
        return true;

    } catch (const std::exception& e) {
        std::cerr << "[SendFileWebAuto][ERROR] Exception: " << e.what() << std::endl;
    } catch (...) {
        std::cerr << "[SendFileWebAuto][ERROR] Unknown exception" << std::endl;
    }

    return false;
}

