From e434f66986c94cb69c9e863c63ec8979a17e8808 Mon Sep 17 00:00:00 2001 From: lnk Date: Fri, 12 Sep 2025 17:08:25 +0800 Subject: [PATCH] finish file recall --- LFtid1056/cloudfront/code/cfg_parser.cpp | 693 +++++++++++++++++++++-- LFtid1056/cloudfront/code/interface.h | 84 ++- LFtid1056/cloudfront/code/main.cpp | 1 + LFtid1056/dealMsg.cpp | 8 +- 4 files changed, 722 insertions(+), 64 deletions(-) diff --git a/LFtid1056/cloudfront/code/cfg_parser.cpp b/LFtid1056/cloudfront/code/cfg_parser.cpp index cabd5d3..7f3cd32 100644 --- a/LFtid1056/cloudfront/code/cfg_parser.cpp +++ b/LFtid1056/cloudfront/code/cfg_parser.cpp @@ -69,6 +69,8 @@ extern std::list topicList2; //角型接线发送主题链表 extern std::map xmlinfo_list2;//保存所有型号角形接线对应的icd映射文件解析数据 //////////////////////////////////////////////////////////////////////////////////////////////////// +static std::mutex g_filemenu_cache_mtx; +std::map> g_filemenu_cache; //补招 std::list g_StatisticLackList; //日志补招结构类链表 @@ -1201,28 +1203,66 @@ int recall_json_handle(const std::string& jstr) { std::string start = ti.substr(0, pos); std::string end = ti.substr(pos + 1); - // 拆分时间段为 1 小时一段,并存入 recall_list - std::vector recallinfo_list_hour; - Get_Recall_Time_Char(start, end, recallinfo_list_hour); - - for (auto& info : recallinfo_list_hour) { - RecallMonitor rm; - rm.recall_status = 0; // 初始状态:未补招 - rm.StartTime = epoch_to_datetime_str(info.starttime); // 需要你已有的工具函数 - rm.EndTime = epoch_to_datetime_str(info.endtime); - rm.STEADY = std::to_string(stat); - rm.VOLTAGE = std::to_string(voltage); + // 仅对 recall_list(事件)进行 1 小时拆分;recall_list_stat(稳态)不拆分 + { + // 公共字段(整体区间,不拆分)用于 recall_list_stat + RecallFile rm_all; + rm_all.recall_status = 0; // 初始状态:未补招 + rm_all.StartTime = start; + rm_all.EndTime = end; + rm_all.STEADY = std::to_string(stat); + rm_all.VOLTAGE = std::to_string(voltage); - lm->recall_list.push_back(std::move(rm)); // ★逐一压入 + // 仅当需要事件补招(voltage==1)时,才进行 1 小时拆分并压入 recall_list + if (voltage == 1) { + // 拆分时间段为 1 小时一段,并存入 recall_list + std::vector recallinfo_list_hour; + Get_Recall_Time_Char(start, end, recallinfo_list_hour); + + for (auto& info : recallinfo_list_hour) { + RecallMonitor rm; + rm.recall_status = 0; // 初始状态:未补招 + rm.StartTime = epoch_to_datetime_str(info.starttime); + rm.EndTime = epoch_to_datetime_str(info.endtime); + rm.STEADY = std::to_string(stat); + rm.VOLTAGE = std::to_string(voltage); + + lm->recall_list.push_back(std::move(rm)); + + // 事件补招列表(recall_list)调试打印 + std::cout << "[recall_json_handle] terminal=" << terminalId + << " monitor=" << monitorId + << " [recall_list] start=" << lm->recall_list.back().StartTime + << " end=" << lm->recall_list.back().EndTime + << " steady="<< lm->recall_list.back().STEADY + << " voltage="<< lm->recall_list.back().VOLTAGE + << std::endl; + } + } - // 可选:调试打印 - std::cout << "[recall_json_handle] terminal=" << terminalId - << " monitor=" << monitorId - << " start=" << lm->recall_list.back().StartTime - << " end=" << lm->recall_list.back().EndTime - << " steady="<< lm->recall_list.back().STEADY - << " voltage="<< lm->recall_list.back().VOLTAGE - << std::endl; + // 仅当需要稳态补招(stat==1)时,不拆分,直接压入 recall_list_static + if (stat == 1) { + lm->recall_list_static.push_back(rm_all); // 不拆分,整体区间 + + // 稳态补招列表(recall_list_stat)调试打印 + std::cout << "[recall_json_handle] terminal=" << terminalId + << " monitor=" << monitorId + << " [recall_list_static] start=" << lm->recall_list_static.back().StartTime + << " end=" << lm->recall_list_static.back().EndTime + << " steady="<< lm->recall_list_static.back().STEADY + << " voltage="<< lm->recall_list_static.back().VOLTAGE + << std::endl; + } + + // 非法输入保护 + if (stat == 0 && voltage == 0) { + std::cout << "[recall_json_handle] skip: stat=0 && voltage=0, monitor=" << monitorId + << " terminal=" << terminalId + << " start=" << rm_all.StartTime + << " end=" << rm_all.EndTime + << std::endl; + return 10003; // 不可能进入这个逻辑,错误退出 + } } } } @@ -3785,50 +3825,66 @@ void clear_terminal_runtime_state(const std::string& id) { void check_recall_event() { std::vector tasks; // 本轮要发送的“每终端一条” - std::vector empty_devs_to_clear; // 本轮发现已无任务的终端,解锁后统一清理 { //锁作用域 std::lock_guard lock(ledgermtx); - // 遍历所有 terminal —— 每个 terminal 只挑一条 + // 遍历所有 terminal —— 每个 terminal 只挑一条,先不判断运行状态,因为正在处理其他事务的装置也可以记录待补招信息 for (auto& dev : terminal_devlist) { + //如果该终端不是正在补招或者idle则直接跳过,节省运行时间 + if(dev.busytype != static_cast(DeviceState::READING_EVENTLOG) && dev.busytype != static_cast(DeviceState::IDLE)){ + continue; + } + // 对正在补招或idle终端的所有监测点的待补招列表进行处理 // 1) 先弹掉首条为 DONE/FAILED 的记录(所有 monitor 都要处理首条) bool any_non_empty = false; for (auto& lm : dev.line) { while (!lm.recall_list.empty()) { const RecallMonitor& front = lm.recall_list.front(); if (front.recall_status == static_cast(RecallStatus::DONE)) { - std::cout << "[check_recall_work] DONE dev=" << dev.terminal_id + std::cout << "[check_recall_event] DONE dev=" << dev.terminal_id << " monitor=" << lm.monitor_id << " " << front.StartTime << " ~ " << front.EndTime << std::endl; - //调用reply接口通知web端补招完成 + //调用reply接口通知web端该时间段补招完成 lm.recall_list.pop_front(); // 弹掉首条 } else if (front.recall_status == static_cast(RecallStatus::FAILED)) { - std::cout << "[check_recall_work] FAILED dev=" << dev.terminal_id + std::cout << "[check_recall_event] FAILED dev=" << dev.terminal_id << " monitor=" << lm.monitor_id << " " << front.StartTime << " ~ " << front.EndTime << std::endl; - //调用reply接口通知web端补招失败 + //调用reply接口通知web端该时间段补招失败 lm.recall_list.pop_front(); // 弹掉首条 } else { - break; // 首条不是 2/3,停 + break; // 首条不是 2/3,停,如果是正在处理其他业务或者idle的装置写入了待补招列表,应该都是0;如果是正在补招的装置,新增的部分不会影响原有顺序 } } if (!lm.recall_list.empty()) any_non_empty = true; // 处理了成功和失败的以后只要有一条非空就标记,可能是待处理或者正在处理的补招 } - if (!any_non_empty) { - // 该终端本轮已无任何补招条目,等解锁后清空运行态 - empty_devs_to_clear.push_back(dev.terminal_id); + if (!any_non_empty && dev.busytype == static_cast(DeviceState::READING_EVENTLOG)) { + // 该终端本轮已无任何补招条目,且处于补招暂态事件的状态清空运行态 + //通知补招全部完成 + + dev.guid.clear(); // 清空 guid + dev.busytype = 0; // 业务类型归零 + dev.isbusy = 0; // 清空业务标志 + dev.busytimecount = 0; // 计时归零 continue; } + //如果是idle又没有待补招任务了,应该跳过 + else if(!any_non_empty && dev.busytype == static_cast(DeviceState::IDLE)){ + continue; + } + else{//有待补招任务且处于补招状态或者idle状态 + // 继续补招处理 + } - // 2) 若任一 monitor 的首条为 RUNNING,则该终端正在补招中 -> 跳过该终端 + // 2) 若任一 monitor 的首条为 RUNNING,则该终端正在补招中 -> 跳过该终端,不会下发新的补招请求 bool has_running = false; for (auto& lm : dev.line) { if (!lm.recall_list.empty() && @@ -3842,18 +3898,17 @@ void check_recall_event() { // 3) 选择该终端的“第一条 NOT_STARTED(0)”作为本终端本轮任务 bool picked = false; for (auto& lm : dev.line) { - if (lm.recall_list.empty()) continue; + if (lm.recall_list.empty()) continue; //跳过空的监测点 - RecallMonitor& front = lm.recall_list.front(); + RecallMonitor& front = lm.recall_list.front(); //取非空测点的列表的第一条 if (front.recall_status == static_cast(RecallStatus::NOT_STARTED)) { // 标记为 RUNNING,并设置终端忙状态 - front.recall_status = static_cast(RecallStatus::RUNNING); - - dev.isbusy = 1; - dev.busytype = static_cast(DeviceState::READING_EVENTLOG); - dev.busytimecount = 0; - // 如需 guid,可在此生成 dev.guid = new_guid(); + front.recall_status = static_cast(RecallStatus::RUNNING);//该补招记录刷新为补招中 + dev.isbusy = 1; //标记为忙 + dev.busytype = static_cast(DeviceState::READING_EVENTLOG);//装置状态正在补招和idle的都刷新为正在补招 + dev.busytimecount = 0; //刷新业务超时计数 + // 记录任务(每终端只取这一条) tasks.push_back(RecallTask{ dev.terminal_id, @@ -3861,7 +3916,7 @@ void check_recall_event() { front.EndTime, lm.monitor_id }); - picked = true; + picked = true; //该装置已取 break; } } @@ -3870,26 +3925,27 @@ void check_recall_event() { } } // 解锁 - // 先处理“已空终端”的运行态清理,如果在上面循环中直接清理,可能会与正在处理的补招冲突。而且就算上面解锁后写了新的补招,也会在下一轮被执行。 - for (const auto& id : empty_devs_to_clear) { - clear_terminal_runtime_state(id); - } - // 对于本轮挑选到的任务,逐终端下发一条,这些终端的状态都已设为补招 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(std::stoi(t.monitor_index)); - } catch (...) { /* 保持默认1或根据需要处理 */ } + } 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是暂态事件 @@ -3902,10 +3958,449 @@ void check_recall_event() { // 重要:本函数不把 RUNNING 改成 DONE/FAILED; // 应由设备回调/结果处理逻辑在完成后调用通知函数,把对应 monitor.front().recall_status 从1置为 2/3, - // 下一轮本函数会弹掉 2/3,并在该终端所有 recall_list 全部清空后调用 clear_terminal_runtime_state()。 + // 下一轮本函数会弹掉 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 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(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(c))) return false; + for (char c : time_str) if (!std::isdigit(static_cast(c))) return false; + for (char c : ms_str) if (!std::isdigit(static_cast(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(t); // 秒级 + return true; +} + +// ====== ★修改:check_recall_stat —— 加入“两步法”状态机 ====== +void check_recall_file() { + + std::vector tasks; // 本轮要发送的“每终端一条”(目录请求 或 文件下载 请求) + + { //锁作用域 + std::lock_guard lock(ledgermtx); + + for (auto& dev : terminal_devlist) { + // 仅处理“正在补招/空闲”终端,与你原逻辑一致 + if (dev.busytype != static_cast(DeviceState::READING_STATSFILE) && + dev.busytype != static_cast(DeviceState::IDLE)) { + continue; + } + + // 1) 清理首条 DONE/FAILED + bool any_non_empty = false; + for (auto& lm : dev.line) { + while (!lm.recall_list_static.empty()) { + const RecallFile& front = lm.recall_list_static.front(); + if (front.recall_status == static_cast(RecallStatus::DONE)) { + std::cout << "[check_recall_stat] DONE dev=" << dev.terminal_id + << " monitor=" << lm.monitor_id + << " " << front.StartTime << " ~ " << front.EndTime << std::endl; + lm.recall_list_static.pop_front(); + } else if (front.recall_status == static_cast(RecallStatus::FAILED)) { + std::cout << "[check_recall_stat] FAILED dev=" << dev.terminal_id + << " monitor=" << lm.monitor_id + << " " << front.StartTime << " ~ " << front.EndTime << std::endl; + lm.recall_list_static.pop_front(); + } else { + break; + } + } + if (!lm.recall_list_static.empty()) any_non_empty = true; + } + + // 无条目时的装置态收尾 + if (!any_non_empty && dev.busytype == static_cast(DeviceState::READING_EVENTLOG)) { + // (保持你原注释)处于“暂态补招”的状态且无条目 -> 清空运行态 + dev.guid.clear(); + dev.busytype = 0; + dev.isbusy = 0; + dev.busytimecount = 0; + continue; + } else if (!any_non_empty && dev.busytype == static_cast(DeviceState::IDLE)) { + continue; + } + + // 2) 若任一 monitor 的首条为 RUNNING,则该终端正在补招中 -> 不下发新的任务(但需要推进状态机!) + bool has_running = false; + for (auto& lm : dev.line) { + if (!lm.recall_list_static.empty() && + lm.recall_list_static.front().recall_status == static_cast(RecallStatus::RUNNING)) { + has_running = true; + break; + } + } + + // ★新增:当存在 RUNNING 时,推进“该终端的首条补招记录”的两步状态机 + if (has_running) { + for (auto& lm : dev.line) { + if (lm.recall_list_static.empty()) continue; + RecallFile& front = lm.recall_list_static.front(); + if (front.recall_status != static_cast(RecallStatus::RUNNING)) continue; + + // 初始化阶段:从 NOT_STARTED->RUNNING 已完成,此处保证 phase 就绪 + if (front.phase == RecallPhase::IDLE) { + front.phase = RecallPhase::LISTING; + front.cur_dir_index = 0; + front.cur_dir.clear(); + front.list_result = ActionResult::PENDING; + front.download_result = ActionResult::PENDING; + front.download_queue.clear(); + // 立即发起第一个目录请求 + if (front.cur_dir_index < static_cast(front.dir_candidates.size())) { + front.cur_dir = front.dir_candidates[front.cur_dir_index]; + // ★★ 只发一个目录请求,并等待外部线程回写结果与文件名列表 + ClientManager::instance().add_file_menu_action_to_device(dev.terminal_id, front.cur_dir); + std::cout << "[check_recall_stat] LIST req dev=" << dev.terminal_id + << " monitor=" << lm.monitor_id + << " dir=" << front.cur_dir << std::endl; + } else { + // 无目录可查 + front.recall_status = static_cast(RecallStatus::FAILED); + std::cout << "[check_recall_stat] no dir candidates, FAIL dev=" << dev.terminal_id + << " monitor=" << lm.monitor_id << std::endl; + } + // 一个终端只推进首条,跳出 + break; + } + + // LISTING 阶段:等待其他线程回写 list_result + dir_files[cur_dir] + if (front.phase == RecallPhase::LISTING) { + if (front.list_result == ActionResult::PENDING) { + // 还在等目录回执,本轮不再发任何请求 + // (外部线程拿到目录文件列表后,应写入:front.dir_files[front.cur_dir] = {a,b,c...} 并置 list_result=OK/FAIL) + continue; + } + + if (front.list_result == ActionResult::FAIL) { + // 尝试下一个目录 + front.cur_dir_index++; + front.list_result = ActionResult::PENDING; + front.download_queue.clear(); + if (front.cur_dir_index < static_cast(front.dir_candidates.size())) { + front.cur_dir = front.dir_candidates[front.cur_dir_index]; + ClientManager::instance().add_file_menu_action_to_device(dev.terminal_id, front.cur_dir); + std::cout << "[check_recall_stat] LIST retry dev=" << dev.terminal_id + << " monitor=" << lm.monitor_id + << " dir=" << front.cur_dir << std::endl; + } else { + // 所有目录都失败 + front.recall_status = static_cast(RecallStatus::FAILED); + std::cout << "[check_recall_stat] all dir failed, FAIL dev=" << dev.terminal_id + << " monitor=" << lm.monitor_id << std::endl; + } + continue; + } + + // OK:根据起止时间筛选文件 + { + long long beg = parse_time_to_epoch(front.StartTime); + long long end = parse_time_to_epoch(front.EndTime); + if (beg < 0 || end < 0 || beg > end) { + front.recall_status = static_cast(RecallStatus::FAILED); + std::cout << "[check_recall_stat] time parse ERR, FAIL dev=" << dev.terminal_id + << " monitor=" << lm.monitor_id + << " start=" << front.StartTime + << " end=" << front.EndTime << std::endl; + continue; + } + + auto it = front.dir_files.find(front.cur_dir); + if (it != front.dir_files.end()) { + if (front.direct_mode) { + // ★新增:直下文件(精确匹配文件名) + for (const auto& ent : it->second) { + if (ent.flag != 1) continue; // 只要文件 + // 安全从 char[64] 转成 std::string + size_t n = ::strnlen(ent.name, sizeof(ent.name)); + std::string fname(ent.name, n); + if (fname == front.target_filename) { + front.download_queue.push_back(front.cur_dir + "/" + fname); + break; // 找到一个就足够 + } + } + } else { + // ☆原有:按时间窗筛选 + long long beg = parse_time_to_epoch(front.StartTime); + long long end = parse_time_to_epoch(front.EndTime); + + for (const auto& ent : it->second) { + if (ent.flag != 1) continue; // 只要文件 + // 文件名 + size_t n = ::strnlen(ent.name, sizeof(ent.name)); + std::string fname(ent.name, n); + + long long fe = -1; + int seq = 0; + try { seq = std::stoi(lm.logical_device_seq); } catch (...) { seq = 0; } + if (!extract_epoch_from_filename(fname, fe, seq)) continue; + if (fe >= beg && fe <= end) { + front.download_queue.push_back(front.cur_dir + "/" + fname); + } + } + } + } + } + + if (front.download_queue.empty()) { + // 当前目录无匹配文件 -> 试下一个目录 + front.cur_dir_index++; + front.list_result = ActionResult::PENDING; + if (front.cur_dir_index < static_cast(front.dir_candidates.size())) { + front.cur_dir = front.dir_candidates[front.cur_dir_index]; + ClientManager::instance().add_file_menu_action_to_device(dev.terminal_id, front.cur_dir); + std::cout << "[check_recall_stat] LIST next dev=" << dev.terminal_id + << " monitor=" << lm.monitor_id + << " dir=" << front.cur_dir << std::endl; + } else { + // 所有目录都“无匹配文件” + front.recall_status = static_cast(RecallStatus::FAILED); + std::cout << "[check_recall_stat] no matched files in ALL dirs, FAIL dev=" << dev.terminal_id + << " monitor=" << lm.monitor_id << std::endl; + } + continue; + } else { + // 进入下载阶段 + front.phase = RecallPhase::DOWNLOADING; + front.download_result = ActionResult::PENDING; + front.downloading_file.clear(); + std::cout << "[check_recall_stat] enter DOWNLOADING dev=" << dev.terminal_id + << " monitor=" << lm.monitor_id + << " count=" << front.download_queue.size() << std::endl; + } + } + + // DOWNLOADING 阶段:一次只下一个文件,等待外部线程填充 download_result + if (front.phase == RecallPhase::DOWNLOADING) { + if (front.downloading_file.empty()) { + if (front.download_queue.empty()) { + // 所有文件下载完毕 + front.recall_status = static_cast(RecallStatus::DONE); + std::cout << "[check_recall_stat] DONE dev=" << dev.terminal_id + << " monitor=" << lm.monitor_id << std::endl; + continue; + } + // 发起下一文件下载 + front.downloading_file = front.download_queue.front(); + front.download_queue.pop_front(); + front.download_result = ActionResult::PENDING; + + // ★★ 只发一个下载请求,等待外部线程回写结果 + ClientManager::instance().add_file_download_action_to_device(dev.terminal_id, front.downloading_file); + std::cout << "[check_recall_stat] DL req dev=" << dev.terminal_id + << " monitor=" << lm.monitor_id + << " file=" << front.downloading_file << std::endl; + continue; + } else { + // 等待 download_result + if (front.download_result == ActionResult::PENDING) { + continue; // 仍在等待 + } + if (front.download_result == ActionResult::OK) { + // 记录成功文件 + front.file_paths.push_back(front.downloading_file); + std::cout << "[check_recall_stat] DL ok dev=" << dev.terminal_id + << " monitor=" << lm.monitor_id + << " file=" << front.downloading_file << std::endl; + // 清空当前文件标志,进入下一轮取队首 + front.downloading_file.clear(); + continue; + } else { + // 失败:直接尝试下一个文件(不中断整条补招) + std::cout << "[check_recall_stat] DL fail dev=" << dev.terminal_id + << " monitor=" << lm.monitor_id + << " file=" << front.downloading_file << std::endl; + front.downloading_file.clear(); + continue; + } + } + } + + // 一个终端只推进“一个监测点的首条”状态机,避免并行 + break; + } + + // 本终端已有 RUNNING 项,且已推进;不再挑选新的 NOT_STARTED + continue; + } + + // 3) 没有 RUNNING:挑选第一条 NOT_STARTED,并发起“首个目录”的请求 + for (auto& lm : dev.line) { + if (lm.recall_list_static.empty()) continue; + + RecallFile& front = lm.recall_list_static.front(); + if (front.recall_status == static_cast(RecallStatus::NOT_STARTED)) { + // 标记为 RUNNING,并设置终端忙状态 + front.recall_status = static_cast(RecallStatus::RUNNING); + dev.isbusy = 1; + dev.busytype = static_cast(DeviceState::READING_STATSFILE); + dev.busytimecount = 0; + + // 初始化状态机并发出第一个目录请求 + front.reset_runtime(true);//保留直下文件信息 + front.phase = RecallPhase::LISTING; + if (!front.dir_candidates.empty()) { + front.cur_dir_index = 0; + front.cur_dir = front.dir_candidates[0]; + front.list_result = ActionResult::PENDING; + ClientManager::instance().add_file_menu_action_to_device(dev.terminal_id, front.cur_dir); + std::cout << "[check_recall_stat] LIST start dev=" << dev.terminal_id + << " monitor=" << lm.monitor_id + << " dir=" << front.cur_dir + << " start=" << front.StartTime + << " end=" << front.EndTime << std::endl; + } else { + front.recall_status = static_cast(RecallStatus::FAILED); + std::cout << "[check_recall_stat] empty dir_candidates, FAIL dev=" << dev.terminal_id + << " monitor=" << lm.monitor_id << std::endl; + } + + // 每终端本轮仅取一条 + break; + } + } + } // end for dev + } // 解锁 + + // ★说明:本函数不主动把 RUNNING 改 DONE/FAILED; + // 由“其他线程”在目录/下载结果返回后,找到对应 dev/monitor 的 lm.recall_list_static.front(), + // 写入: + // - 目录请求:front.dir_files[front.cur_dir] = {...}; front.list_result = OK/FAIL; + // - 文件下载:front.download_result = OK/FAIL; + // 下一轮本函数会推进状态机(继续本目录的文件或切到下一个目录)。 +} + +///////////////////////////////////////////////////////////////////////////////////////////////////////////根据下发的指令直接补招文件 +// ★新增:直下文件的任务入队(终端、测点、文件名) +// 约定:外部线程的回调,照旧把目录返回值写到 lm.recall_list_static.front().dir_files[dir], +// 并置 front.list_result=OK/FAIL;下载回执置 front.download_result=OK/FAIL。 +// 处理指令部分将文件名拼接出来调用这个函数 +bool enqueue_direct_download(const std::string& dev_id, + const std::string& monitor_id, + const std::string& filename, + const std::vector& dir_candidates) +{ + std::lock_guard lk(ledgermtx); + + // 找终端 + auto dev_it = std::find_if(terminal_devlist.begin(), terminal_devlist.end(), + [&](const terminal_dev& d){ return d.terminal_id == dev_id; }); + if (dev_it == terminal_devlist.end()) return false; + + // 找监测点 + auto lm_it = std::find_if(dev_it->line.begin(), dev_it->line.end(), + [&](const ledger_monitor& lm){ return lm.monitor_id == monitor_id; }); + if (lm_it == dev_it->line.end()) return false; + + // 组装一条 RecallFile + RecallFile rf; + rf.recall_status = static_cast(RecallStatus::NOT_STARTED); + rf.StartTime = "1970-01-01 00:00:00"; // 仅占位,直下文件不会用到时间窗 + rf.EndTime = "1970-01-01 00:00:01"; + rf.dir_candidates = dir_candidates; // 传入要检索的目录列表 + rf.direct_mode = true; // ★关键:直下文件 + rf.target_filename = filename; // ★关键:匹配的目标文件名 + + lm_it->recall_list_static.push_back(std::move(rf)); + + // 若设备空闲,可直接置忙(可选,视你的流程而定) + if (dev_it->busytype == static_cast(DeviceState::IDLE)) { + dev_it->isbusy = 1; + dev_it->busytype = static_cast(DeviceState::READING_STATSFILE); // 或 READING_EVENTFILE + dev_it->busytimecount = 0; + } + return true; } ///////////////////////////////////////////////////////////////////////////////////////////////////////////通讯响应处理 + +void filemenu_cache_put(const std::string& dev_id, + std::vector FileList) +{ + std::lock_guard lk(g_filemenu_cache_mtx); + g_filemenu_cache[dev_id] = std::move(FileList); // 直接存 vector +} + +bool filemenu_cache_take(const std::string& dev_id, std::vector& out) +{ + std::lock_guard 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 + g_filemenu_cache.erase(it); // 删除缓存 + return true; +} + +// 提取文件名列表(仅 flag==1) +static inline void build_file_name_list(const std::vector& in, + std::list& out) +{ + out.clear(); + for (const auto& e : in) { + if (e.flag == 1) { + // 安全地从固定长度 char[64] 转成 std::string + size_t n = ::strnlen(e.name, sizeof(e.name)); + out.emplace_back(std::string(e.name, n)); + } + } +} + void on_device_response_minimal(int response_code, const std::string& id, unsigned char cid, @@ -4020,6 +4515,19 @@ void on_device_response_minimal(int response_code, if (bt == static_cast(DeviceState::READING_FILEMENU)) { // ====== 分支 A:当前业务就是“读取文件目录” ====== if (ok) { + std::vector names; + if (filemenu_cache_take(id, names)) { + + //发送目录 + send_file_list(id,names); + + } else { + // 失败:响应web并复位为空闲 + send_reply_to_cloud(static_cast(ResponseCode::BAD_REQUEST), id, static_cast(DeviceState::READING_FILEMENU)); + std::cout << "[RESP][FILEMENU->FILEMENU][WARN] dev=" << id + << " names missing in cache" << std::endl; + } + // 成功:复位 dev->guid.clear(); dev->isbusy = 0; @@ -4040,19 +4548,46 @@ void on_device_response_minimal(int response_code, } else if ( bt == static_cast(DeviceState::READING_EVENTFILE) - || bt == static_cast(DeviceState::READING_STATSFILE) + || bt == static_cast(DeviceState::READING_STATSFILE) ) { // ====== 分支 B:当前业务为“下载事件文件/统计文件”(两者处理相同) ====== + // 一个装置同一时刻只会有一个监测点在下载 + ledger_monitor* running_monitor = nullptr; + RecallFile* running_front = nullptr; + + // 在该终端下,找到“首条 recall_list_static.front() 正在运行且处于 DOWNLOADING 的监测点” + for (auto& lm : dev->line) { + if (lm.recall_list_static.empty()) continue; + RecallFile& f = lm.recall_list_static.front(); + if (f.recall_status == static_cast(RecallStatus::RUNNING) + && f.phase == RecallPhase::DOWNLOADING) { + running_monitor = &lm; + running_front = &f; + break; + } + } + + if (!running_monitor || !running_front) { + std::cout << "[RESP][FILEDATA->(EVENT/STATS)FILE][WARN] dev=" << id + << " no RUNNING/DOWNLOADING recall on this device, ignore resp" + << " rc=" << response_code << std::endl; + break; + } + + // 根据回执结果,回写下载结果;状态机会在下一轮推进到下一个文件/结束 if (ok) { - // 成功:通常紧接着进入读取文件数据阶段 - - std::cout << "[RESP][FILEMENU->FILEDATA][OK] dev=" << id << std::endl; + running_front->download_result = ActionResult::OK; + std::cout << "[RESP][FILEDATA->(EVENT/STATS)FILE][OK] dev=" << id + << " monitor=" << running_monitor->monitor_id + << " file=" << running_front->downloading_file << std::endl; } else { - // 失败:尝试下一个目录 - - std::cout << "[RESP][FILEMENU->(EVENT/STATS)FILE][FAIL] dev=" << id + running_front->download_result = ActionResult::FAIL; + std::cout << "[RESP][FILEDATA->(EVENT/STATS)FILE][FAIL] dev=" << id + << " monitor=" << running_monitor->monitor_id + << " file=" << running_front->downloading_file << " rc=" << response_code << std::endl; } + break; } else { // ====== 分支 C:其他业务场景下收到 FILEMENU 响应,统一处理 打印提示====== @@ -4116,16 +4651,45 @@ void on_device_response_minimal(int response_code, || bt == static_cast(DeviceState::READING_STATSFILE) ) { // ====== 分支 B:当前业务为“下载事件文件/统计文件”(两者处理相同) ====== + // ★新增:通过 cid 精确找到监测点(logical_device_seq == cid) + ledger_monitor* matched_monitor = nullptr; + { + const std::string cid_str = std::to_string(static_cast(cid)); + for (auto& lm : dev->line) { + if (lm.logical_device_seq == cid_str) { matched_monitor = &lm; break; } + } + } + + if (!matched_monitor || matched_monitor->recall_list_static.empty()) { + std::cout << "[RESP][FILEDATA->(EVENT/STATS)FILE][WARN] dev=" << id + << " no matched monitor or empty recall list, cid=" + << static_cast(cid) << std::endl; + break; + } + + RecallFile& front = matched_monitor->recall_list_static.front(); + if (front.recall_status != static_cast(RecallStatus::RUNNING) + || front.phase != RecallPhase::DOWNLOADING) { + std::cout << "[RESP][FILEDATA->(EVENT/STATS)FILE][WARN] dev=" << id + << " monitor=" << matched_monitor->monitor_id + << " ignore resp: status=" << front.recall_status + << " phase=" << static_cast(front.phase) << std::endl; + break; + } + if (ok) { - // 成功:通常紧接着进入读取文件数据阶段 - - std::cout << "[RESP][FILEDATA->(EVENT/STATS)FILE][OK] dev=" << id << std::endl; + // ★新增:下载成功 -> 通知状态机推进到下一个文件(真正入账 file_paths 在 check_recall_stat 里做) + front.download_result = ActionResult::OK; + std::cout << "[RESP][FILEDATA->(EVENT/STATS)FILE][OK] dev=" << id + << " file=" << front.downloading_file << std::endl; } else { - // 失败:尝试下一个目录 - + // ★新增:下载失败 -> 让状态机尝试下一个文件 + front.download_result = ActionResult::FAIL; std::cout << "[RESP][FILEDATA->(EVENT/STATS)FILE][FAIL] dev=" << id + << " file=" << front.downloading_file << " rc=" << response_code << std::endl; } + break; } else { // ====== 分支 C:其他业务场景下收到 FILEDATA 响应,统一处理 打印提示====== @@ -4156,4 +4720,11 @@ void on_device_response_minimal(int response_code, break; } } -} \ No newline at end of file +} + + + + + + + diff --git a/LFtid1056/cloudfront/code/interface.h b/LFtid1056/cloudfront/code/interface.h index be0323d..4556bf3 100644 --- a/LFtid1056/cloudfront/code/interface.h +++ b/LFtid1056/cloudfront/code/interface.h @@ -49,6 +49,78 @@ public: std::string VOLTAGE; //补招暂态事件标识 0-不补招;1-补招 }; +// ====== ★新增:下载/列目录阶段枚举与结果枚举 ====== +enum class RecallPhase { + IDLE = 0, + LISTING, // 正在请求并等待“目录文件名列表” + DOWNLOADING // 正在按队列逐个下载 +}; + +enum class ActionResult { + PENDING = -1, // 还未返回 + FAIL = 0, + OK = 1 +}; + +// ====== ★修改:扩展 RecallFile,支持“多目录 + 文件筛选 + 串行下载”的状态机 ====== +class RecallFile +{ +public: + int recall_status; // 补招状态 0-未补招 1-补招中 2-补招完成 3-补招失败 + std::string StartTime; // 数据补招起始时间(yyyy-MM-dd HH:mm:ss) + std::string EndTime; // 数据补招结束时间(yyyy-MM-dd HH:mm:ss) + std::string STEADY; // 补招历史统计数据标识 0-不补招;1-补招 + std::string VOLTAGE; // 补招暂态事件标识 0-不补招;1-补招 + + //暂态文件用 + bool direct_mode = false; // 直下文件开关:true 表示不按时间窗,仅按目标文件名 + std::string target_filename; // 直下文件名(不含目录) + + std::list file_paths; // 已下载/要上报的完整路径(用于最终结果) + + // ★新增:按“目录名 -> 文件名列表”的映射;由“其他线程”在目录请求成功后回填 + std::map> dir_files; + + // ★新增:候选目录(可扩展) + std::vector dir_candidates{ + "/cf/COMTRADE", + "/bd0/COMTRADE", + "/sd0/COMTRADE", + "/sd0:1/COMTRADE" + }; + + // ★新增:状态机运行时变量 + RecallPhase phase = RecallPhase::IDLE; + int cur_dir_index = 0; // 正在尝试的目录下标 + std::string cur_dir; // 正在处理的目录 + + // ★新增:列目录/下载请求“回执位”,由其他线程置位 + ActionResult list_result = ActionResult::PENDING; // 当前目录的列举结果 + ActionResult download_result = ActionResult::PENDING; // 当前文件的下载结果 + + // ★新增:下载队列(已筛选出在时间窗内的文件,含完整路径) + std::list download_queue; + std::string downloading_file; // 当前正在下载的文件(完整路径) + + // ★新增:一个便捷复位 + void reset_runtime(bool keep_direct = false) + { + phase = RecallPhase::IDLE; + cur_dir_index = 0; + cur_dir.clear(); + list_result = ActionResult::PENDING; + download_result = ActionResult::PENDING; + download_queue.clear(); + downloading_file.clear(); + dir_files.clear(); + // ★新增:按需保留直下文件开关和目标名 + if (!keep_direct) { + direct_mode = false; + target_filename.clear(); + } + } +}; + enum class RecallStatus { NOT_STARTED = 0, // 未补招 RUNNING = 1, // 补招中 @@ -127,7 +199,8 @@ public: qvvr_event qvvrevent; //补招列表 - std::list recall_list; + std::list recall_list; //事件 + std::list recall_list_static;//稳态文件 //定值list std::list set_values; @@ -579,6 +652,15 @@ void on_device_response_minimal(int response_code, //处理补招的任务 void check_recall_event(); +void check_recall_file(); + + +//缓存目录信息 +void filemenu_cache_put(const std::string& dev_id, + std::vector FileList); +//提取目录信息 +bool filemenu_cache_take(const std::string& dev_id, std::vector& out); + //小工具 inline std::string trim_cstr(const char* s, size_t n) { diff --git a/LFtid1056/cloudfront/code/main.cpp b/LFtid1056/cloudfront/code/main.cpp index ed10c90..ad32671 100644 --- a/LFtid1056/cloudfront/code/main.cpp +++ b/LFtid1056/cloudfront/code/main.cpp @@ -318,6 +318,7 @@ void Front::FrontThread() { try { while (!m_bIsFrontThreadCancle) { + check_recall_file(); //处理补招文件-稳态和暂态 check_recall_event(); // 处理补招事件,从list中读取然后直接调用接口,每一条可能都不同测点,每个测点自己做好记录 check_ledger_update(); // 触发台账更新 diff --git a/LFtid1056/dealMsg.cpp b/LFtid1056/dealMsg.cpp index 1059b41..0075091 100644 --- a/LFtid1056/dealMsg.cpp +++ b/LFtid1056/dealMsg.cpp @@ -753,7 +753,8 @@ void process_received_message(string mac, string id,const char* data, size_t len } // ӷļб߼ - send_file_list(id,FileList);//lnk20250813 + //send_file_list(id,FileList);//lnk20250813 + filemenu_cache_put(id,FileList); // ɺ״̬ ClientManager::instance().change_device_state(id, DeviceState::IDLE); @@ -843,9 +844,12 @@ void process_received_message(string mac, string id,const char* data, size_t len //ʹýӿļlnk20250826 std::string filename; - SendFileWeb(WEB_FILEUPLOAD, file_path, file_path, filename); + SendFileWeb(WEB_FILEUPLOAD, file_path, file_path, filename);//DzļأغҲֱϴϴɹ²״̬ std::cout << "File upload: " << filename << std::endl; + //֪ͨļϴ + on_device_response_minimal(static_cast(ResponseCode::OK), id, 0, static_cast(DeviceState::READING_FILEDATA)); + } else { on_device_response_minimal(static_cast(ResponseCode::INTERNAL_ERROR), id, 0, static_cast(DeviceState::READING_FILEDATA));