finish file recall

This commit is contained in:
lnk
2025-09-12 17:08:25 +08:00
parent 58ffb3a6aa
commit e434f66986
4 changed files with 722 additions and 64 deletions

View File

@@ -69,6 +69,8 @@ extern std::list<CTopic*> topicList2; //角型接线发送主题链表
extern std::map<std::string, Xmldata*> xmlinfo_list2;//保存所有型号角形接线对应的icd映射文件解析数据
////////////////////////////////////////////////////////////////////////////////////////////////////
static std::mutex g_filemenu_cache_mtx;
std::map<std::string, std::vector<tag_dir_info>> g_filemenu_cache;
//补招
std::list<JournalRecall> g_StatisticLackList; //日志补招结构类链表
@@ -1201,6 +1203,18 @@ int recall_json_handle(const std::string& jstr) {
std::string start = ti.substr(0, pos);
std::string end = ti.substr(pos + 1);
// 仅对 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);
// 仅当需要事件补招voltage==1才进行 1 小时拆分并压入 recall_list
if (voltage == 1) {
// 拆分时间段为 1 小时一段,并存入 recall_list
std::vector<RecallInfo> recallinfo_list_hour;
Get_Recall_Time_Char(start, end, recallinfo_list_hour);
@@ -1208,23 +1222,49 @@ int recall_json_handle(const std::string& jstr) {
for (auto& info : recallinfo_list_hour) {
RecallMonitor rm;
rm.recall_status = 0; // 初始状态:未补招
rm.StartTime = epoch_to_datetime_str(info.starttime); // 需要你已有的工具函数
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)); // ★逐一压入
lm->recall_list.push_back(std::move(rm));
// 可选:调试打印
// 事件补招列表recall_list调试打印
std::cout << "[recall_json_handle] terminal=" << terminalId
<< " monitor=" << monitorId
<< " start=" << lm->recall_list.back().StartTime
<< " [recall_list] start=" << lm->recall_list.back().StartTime
<< " end=" << lm->recall_list.back().EndTime
<< " steady="<< lm->recall_list.back().STEADY
<< " voltage="<< lm->recall_list.back().VOLTAGE
<< std::endl;
}
}
// 仅当需要稳态补招stat==1不拆分直接压入 recall_list_static
if (stat == 1) {
lm->recall_list_static.push_back(rm_all); // 不拆分,整体区间
// 稳态补招列表recall_list_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<RecallTask> tasks; // 本轮要发送的“每终端一条”
std::vector<std::string> empty_devs_to_clear; // 本轮发现已无任务的终端,解锁后统一清理
{ //锁作用域
std::lock_guard<std::mutex> lock(ledgermtx);
// 遍历所有 terminal —— 每个 terminal 只挑一条
// 遍历所有 terminal —— 每个 terminal 只挑一条,先不判断运行状态,因为正在处理其他事务的装置也可以记录待补招信息
for (auto& dev : terminal_devlist) {
//如果该终端不是正在补招或者idle则直接跳过节省运行时间
if(dev.busytype != static_cast<int>(DeviceState::READING_EVENTLOG) && dev.busytype != static_cast<int>(DeviceState::IDLE)){
continue;
}
// 对正在补招或idle终端的所有监测点的待补招列表进行处理
// 1) 先弹掉首条为 DONE/FAILED 的记录(所有 monitor 都要处理首条)
bool any_non_empty = false;
for (auto& lm : dev.line) {
while (!lm.recall_list.empty()) {
const RecallMonitor& front = lm.recall_list.front();
if (front.recall_status == static_cast<int>(RecallStatus::DONE)) {
std::cout << "[check_recall_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<int>(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<int>(DeviceState::READING_EVENTLOG)) {
// 该终端本轮已无任何补招条目,且处于补招暂态事件的状态清空运行态
//通知补招全部完成
dev.guid.clear(); // 清空 guid
dev.busytype = 0; // 业务类型归零
dev.isbusy = 0; // 清空业务标志
dev.busytimecount = 0; // 计时归零
continue;
}
//如果是idle又没有待补招任务了应该跳过
else if(!any_non_empty && dev.busytype == static_cast<int>(DeviceState::IDLE)){
continue;
}
else{//有待补招任务且处于补招状态或者idle状态
// 继续补招处理
}
// 2) 若任一 monitor 的首条为 RUNNING则该终端正在补招中 -> 跳过该终端
// 2) 若任一 monitor 的首条为 RUNNING则该终端正在补招中 -> 跳过该终端,不会下发新的补招请求
bool has_running = false;
for (auto& lm : dev.line) {
if (!lm.recall_list.empty() &&
@@ -3842,17 +3898,16 @@ 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<int>(RecallStatus::NOT_STARTED)) {
// 标记为 RUNNING并设置终端忙状态
front.recall_status = static_cast<int>(RecallStatus::RUNNING);
front.recall_status = static_cast<int>(RecallStatus::RUNNING);//该补招记录刷新为补招中
dev.isbusy = 1;
dev.busytype = static_cast<int>(DeviceState::READING_EVENTLOG);
dev.busytimecount = 0;
// 如需 guid可在此生成 dev.guid = new_guid();
dev.isbusy = 1; //标记为忙
dev.busytype = static_cast<int>(DeviceState::READING_EVENTLOG);//装置状态正在补招和idle的都刷新为正在补招
dev.busytimecount = 0; //刷新业务超时计数
// 记录任务(每终端只取这一条)
tasks.push_back(RecallTask{
@@ -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<uint8_t>(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<std::string> parts;
parts.reserve(8);
size_t start = 0, pos;
while ((pos = name.find('_', start)) != std::string::npos) {
parts.emplace_back(name.substr(start, pos - start));
start = pos + 1;
}
parts.emplace_back(name.substr(start)); // 最后一段(含扩展名)
if (parts.size() < 4) return false;
// 第二段序号是倒数第4段
const std::string& seq_str = parts[parts.size() - 4];
// 允许前导 0把字符串转 int 后比较
for (char c : seq_str) if (!std::isdigit(static_cast<unsigned char>(c))) return false;
int seq_val = 0;
try {
seq_val = std::stoi(seq_str);
} catch (...) {
return false;
}
if (seq_val != logical_device_seq) return false;
// 其余与上面相同
const std::string& date_str = parts[parts.size() - 3];
const std::string& time_str = parts[parts.size() - 2];
std::string ms_str = parts.back();
size_t dot = ms_str.find('.');
if (dot != std::string::npos) {
ms_str.erase(dot);
}
if (date_str.size() != 8 || time_str.size() != 6) return false;
for (char c : date_str) if (!std::isdigit(static_cast<unsigned char>(c))) return false;
for (char c : time_str) if (!std::isdigit(static_cast<unsigned char>(c))) return false;
for (char c : ms_str) if (!std::isdigit(static_cast<unsigned char>(c))) return false;
int year = std::stoi(date_str.substr(0, 4));
int month = std::stoi(date_str.substr(4, 2));
int day = std::stoi(date_str.substr(6, 2));
int hour = std::stoi(time_str.substr(0, 2));
int min = std::stoi(time_str.substr(2, 2));
int sec = std::stoi(time_str.substr(4, 2));
// int msec = std::stoi(ms_str);
std::tm tm{}; tm.tm_isdst = -1;
tm.tm_year = year - 1900;
tm.tm_mon = month - 1;
tm.tm_mday = day;
tm.tm_hour = hour;
tm.tm_min = min;
tm.tm_sec = sec;
time_t t = timegm(&tm);
if (t < 0) return false;
out_epoch = static_cast<long long>(t); // 秒级
return true;
}
// ====== ★修改check_recall_stat —— 加入“两步法”状态机 ======
void check_recall_file() {
std::vector<RecallTask> tasks; // 本轮要发送的“每终端一条”(目录请求 或 文件下载 请求)
{ //锁作用域
std::lock_guard<std::mutex> lock(ledgermtx);
for (auto& dev : terminal_devlist) {
// 仅处理“正在补招/空闲”终端,与你原逻辑一致
if (dev.busytype != static_cast<int>(DeviceState::READING_STATSFILE) &&
dev.busytype != static_cast<int>(DeviceState::IDLE)) {
continue;
}
// 1) 清理首条 DONE/FAILED
bool any_non_empty = false;
for (auto& lm : dev.line) {
while (!lm.recall_list_static.empty()) {
const RecallFile& front = lm.recall_list_static.front();
if (front.recall_status == static_cast<int>(RecallStatus::DONE)) {
std::cout << "[check_recall_stat] DONE dev=" << dev.terminal_id
<< " monitor=" << lm.monitor_id
<< " " << front.StartTime << " ~ " << front.EndTime << std::endl;
lm.recall_list_static.pop_front();
} else if (front.recall_status == static_cast<int>(RecallStatus::FAILED)) {
std::cout << "[check_recall_stat] FAILED dev=" << dev.terminal_id
<< " monitor=" << lm.monitor_id
<< " " << front.StartTime << " ~ " << front.EndTime << std::endl;
lm.recall_list_static.pop_front();
} else {
break;
}
}
if (!lm.recall_list_static.empty()) any_non_empty = true;
}
// 无条目时的装置态收尾
if (!any_non_empty && dev.busytype == static_cast<int>(DeviceState::READING_EVENTLOG)) {
// (保持你原注释)处于“暂态补招”的状态且无条目 -> 清空运行态
dev.guid.clear();
dev.busytype = 0;
dev.isbusy = 0;
dev.busytimecount = 0;
continue;
} else if (!any_non_empty && dev.busytype == static_cast<int>(DeviceState::IDLE)) {
continue;
}
// 2) 若任一 monitor 的首条为 RUNNING则该终端正在补招中 -> 不下发新的任务(但需要推进状态机!)
bool has_running = false;
for (auto& lm : dev.line) {
if (!lm.recall_list_static.empty() &&
lm.recall_list_static.front().recall_status == static_cast<int>(RecallStatus::RUNNING)) {
has_running = true;
break;
}
}
// ★新增:当存在 RUNNING 时,推进“该终端的首条补招记录”的两步状态机
if (has_running) {
for (auto& lm : dev.line) {
if (lm.recall_list_static.empty()) continue;
RecallFile& front = lm.recall_list_static.front();
if (front.recall_status != static_cast<int>(RecallStatus::RUNNING)) continue;
// 初始化阶段:从 NOT_STARTED->RUNNING 已完成,此处保证 phase 就绪
if (front.phase == RecallPhase::IDLE) {
front.phase = RecallPhase::LISTING;
front.cur_dir_index = 0;
front.cur_dir.clear();
front.list_result = ActionResult::PENDING;
front.download_result = ActionResult::PENDING;
front.download_queue.clear();
// 立即发起第一个目录请求
if (front.cur_dir_index < static_cast<int>(front.dir_candidates.size())) {
front.cur_dir = front.dir_candidates[front.cur_dir_index];
// ★★ 只发一个目录请求,并等待外部线程回写结果与文件名列表
ClientManager::instance().add_file_menu_action_to_device(dev.terminal_id, front.cur_dir);
std::cout << "[check_recall_stat] LIST req dev=" << dev.terminal_id
<< " monitor=" << lm.monitor_id
<< " dir=" << front.cur_dir << std::endl;
} else {
// 无目录可查
front.recall_status = static_cast<int>(RecallStatus::FAILED);
std::cout << "[check_recall_stat] no dir candidates, FAIL dev=" << dev.terminal_id
<< " monitor=" << lm.monitor_id << std::endl;
}
// 一个终端只推进首条,跳出
break;
}
// LISTING 阶段:等待其他线程回写 list_result + dir_files[cur_dir]
if (front.phase == RecallPhase::LISTING) {
if (front.list_result == ActionResult::PENDING) {
// 还在等目录回执,本轮不再发任何请求
// 外部线程拿到目录文件列表后应写入front.dir_files[front.cur_dir] = {a,b,c...} 并置 list_result=OK/FAIL
continue;
}
if (front.list_result == ActionResult::FAIL) {
// 尝试下一个目录
front.cur_dir_index++;
front.list_result = ActionResult::PENDING;
front.download_queue.clear();
if (front.cur_dir_index < static_cast<int>(front.dir_candidates.size())) {
front.cur_dir = front.dir_candidates[front.cur_dir_index];
ClientManager::instance().add_file_menu_action_to_device(dev.terminal_id, front.cur_dir);
std::cout << "[check_recall_stat] LIST retry dev=" << dev.terminal_id
<< " monitor=" << lm.monitor_id
<< " dir=" << front.cur_dir << std::endl;
} else {
// 所有目录都失败
front.recall_status = static_cast<int>(RecallStatus::FAILED);
std::cout << "[check_recall_stat] all dir failed, FAIL dev=" << dev.terminal_id
<< " monitor=" << lm.monitor_id << std::endl;
}
continue;
}
// OK根据起止时间筛选文件
{
long long beg = parse_time_to_epoch(front.StartTime);
long long end = parse_time_to_epoch(front.EndTime);
if (beg < 0 || end < 0 || beg > end) {
front.recall_status = static_cast<int>(RecallStatus::FAILED);
std::cout << "[check_recall_stat] time parse ERR, FAIL dev=" << dev.terminal_id
<< " monitor=" << lm.monitor_id
<< " start=" << front.StartTime
<< " end=" << front.EndTime << std::endl;
continue;
}
auto it = front.dir_files.find(front.cur_dir);
if (it != front.dir_files.end()) {
if (front.direct_mode) {
// ★新增:直下文件(精确匹配文件名)
for (const auto& ent : it->second) {
if (ent.flag != 1) continue; // 只要文件
// 安全从 char[64] 转成 std::string
size_t n = ::strnlen(ent.name, sizeof(ent.name));
std::string fname(ent.name, n);
if (fname == front.target_filename) {
front.download_queue.push_back(front.cur_dir + "/" + fname);
break; // 找到一个就足够
}
}
} else {
// ☆原有:按时间窗筛选
long long beg = parse_time_to_epoch(front.StartTime);
long long end = parse_time_to_epoch(front.EndTime);
for (const auto& ent : it->second) {
if (ent.flag != 1) continue; // 只要文件
// 文件名
size_t n = ::strnlen(ent.name, sizeof(ent.name));
std::string fname(ent.name, n);
long long fe = -1;
int seq = 0;
try { seq = std::stoi(lm.logical_device_seq); } catch (...) { seq = 0; }
if (!extract_epoch_from_filename(fname, fe, seq)) continue;
if (fe >= beg && fe <= end) {
front.download_queue.push_back(front.cur_dir + "/" + fname);
}
}
}
}
}
if (front.download_queue.empty()) {
// 当前目录无匹配文件 -> 试下一个目录
front.cur_dir_index++;
front.list_result = ActionResult::PENDING;
if (front.cur_dir_index < static_cast<int>(front.dir_candidates.size())) {
front.cur_dir = front.dir_candidates[front.cur_dir_index];
ClientManager::instance().add_file_menu_action_to_device(dev.terminal_id, front.cur_dir);
std::cout << "[check_recall_stat] LIST next dev=" << dev.terminal_id
<< " monitor=" << lm.monitor_id
<< " dir=" << front.cur_dir << std::endl;
} else {
// 所有目录都“无匹配文件”
front.recall_status = static_cast<int>(RecallStatus::FAILED);
std::cout << "[check_recall_stat] no matched files in ALL dirs, FAIL dev=" << dev.terminal_id
<< " monitor=" << lm.monitor_id << std::endl;
}
continue;
} else {
// 进入下载阶段
front.phase = RecallPhase::DOWNLOADING;
front.download_result = ActionResult::PENDING;
front.downloading_file.clear();
std::cout << "[check_recall_stat] enter DOWNLOADING dev=" << dev.terminal_id
<< " monitor=" << lm.monitor_id
<< " count=" << front.download_queue.size() << std::endl;
}
}
// DOWNLOADING 阶段:一次只下一个文件,等待外部线程填充 download_result
if (front.phase == RecallPhase::DOWNLOADING) {
if (front.downloading_file.empty()) {
if (front.download_queue.empty()) {
// 所有文件下载完毕
front.recall_status = static_cast<int>(RecallStatus::DONE);
std::cout << "[check_recall_stat] DONE dev=" << dev.terminal_id
<< " monitor=" << lm.monitor_id << std::endl;
continue;
}
// 发起下一文件下载
front.downloading_file = front.download_queue.front();
front.download_queue.pop_front();
front.download_result = ActionResult::PENDING;
// ★★ 只发一个下载请求,等待外部线程回写结果
ClientManager::instance().add_file_download_action_to_device(dev.terminal_id, front.downloading_file);
std::cout << "[check_recall_stat] DL req dev=" << dev.terminal_id
<< " monitor=" << lm.monitor_id
<< " file=" << front.downloading_file << std::endl;
continue;
} else {
// 等待 download_result
if (front.download_result == ActionResult::PENDING) {
continue; // 仍在等待
}
if (front.download_result == ActionResult::OK) {
// 记录成功文件
front.file_paths.push_back(front.downloading_file);
std::cout << "[check_recall_stat] DL ok dev=" << dev.terminal_id
<< " monitor=" << lm.monitor_id
<< " file=" << front.downloading_file << std::endl;
// 清空当前文件标志,进入下一轮取队首
front.downloading_file.clear();
continue;
} else {
// 失败:直接尝试下一个文件(不中断整条补招)
std::cout << "[check_recall_stat] DL fail dev=" << dev.terminal_id
<< " monitor=" << lm.monitor_id
<< " file=" << front.downloading_file << std::endl;
front.downloading_file.clear();
continue;
}
}
}
// 一个终端只推进“一个监测点的首条”状态机,避免并行
break;
}
// 本终端已有 RUNNING 项,且已推进;不再挑选新的 NOT_STARTED
continue;
}
// 3) 没有 RUNNING挑选第一条 NOT_STARTED并发起“首个目录”的请求
for (auto& lm : dev.line) {
if (lm.recall_list_static.empty()) continue;
RecallFile& front = lm.recall_list_static.front();
if (front.recall_status == static_cast<int>(RecallStatus::NOT_STARTED)) {
// 标记为 RUNNING并设置终端忙状态
front.recall_status = static_cast<int>(RecallStatus::RUNNING);
dev.isbusy = 1;
dev.busytype = static_cast<int>(DeviceState::READING_STATSFILE);
dev.busytimecount = 0;
// 初始化状态机并发出第一个目录请求
front.reset_runtime(true);//保留直下文件信息
front.phase = RecallPhase::LISTING;
if (!front.dir_candidates.empty()) {
front.cur_dir_index = 0;
front.cur_dir = front.dir_candidates[0];
front.list_result = ActionResult::PENDING;
ClientManager::instance().add_file_menu_action_to_device(dev.terminal_id, front.cur_dir);
std::cout << "[check_recall_stat] LIST start dev=" << dev.terminal_id
<< " monitor=" << lm.monitor_id
<< " dir=" << front.cur_dir
<< " start=" << front.StartTime
<< " end=" << front.EndTime << std::endl;
} else {
front.recall_status = static_cast<int>(RecallStatus::FAILED);
std::cout << "[check_recall_stat] empty dir_candidates, FAIL dev=" << dev.terminal_id
<< " monitor=" << lm.monitor_id << std::endl;
}
// 每终端本轮仅取一条
break;
}
}
} // end for dev
} // 解锁
// ★说明:本函数不主动把 RUNNING 改 DONE/FAILED
// 由“其他线程”在目录/下载结果返回后,找到对应 dev/monitor 的 lm.recall_list_static.front()
// 写入:
// - 目录请求front.dir_files[front.cur_dir] = {...}; front.list_result = OK/FAIL;
// - 文件下载front.download_result = OK/FAIL;
// 下一轮本函数会推进状态机(继续本目录的文件或切到下一个目录)。
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////根据下发的指令直接补招文件
// ★新增:直下文件的任务入队(终端、测点、文件名)
// 约定:外部线程的回调,照旧把目录返回值写到 lm.recall_list_static.front().dir_files[dir]
// 并置 front.list_result=OK/FAIL下载回执置 front.download_result=OK/FAIL。
// 处理指令部分将文件名拼接出来调用这个函数
bool enqueue_direct_download(const std::string& dev_id,
const std::string& monitor_id,
const std::string& filename,
const std::vector<std::string>& dir_candidates)
{
std::lock_guard<std::mutex> lk(ledgermtx);
// 找终端
auto dev_it = std::find_if(terminal_devlist.begin(), terminal_devlist.end(),
[&](const terminal_dev& d){ return d.terminal_id == dev_id; });
if (dev_it == terminal_devlist.end()) return false;
// 找监测点
auto lm_it = std::find_if(dev_it->line.begin(), dev_it->line.end(),
[&](const ledger_monitor& lm){ return lm.monitor_id == monitor_id; });
if (lm_it == dev_it->line.end()) return false;
// 组装一条 RecallFile
RecallFile rf;
rf.recall_status = static_cast<int>(RecallStatus::NOT_STARTED);
rf.StartTime = "1970-01-01 00:00:00"; // 仅占位,直下文件不会用到时间窗
rf.EndTime = "1970-01-01 00:00:01";
rf.dir_candidates = dir_candidates; // 传入要检索的目录列表
rf.direct_mode = true; // ★关键:直下文件
rf.target_filename = filename; // ★关键:匹配的目标文件名
lm_it->recall_list_static.push_back(std::move(rf));
// 若设备空闲,可直接置忙(可选,视你的流程而定)
if (dev_it->busytype == static_cast<int>(DeviceState::IDLE)) {
dev_it->isbusy = 1;
dev_it->busytype = static_cast<int>(DeviceState::READING_STATSFILE); // 或 READING_EVENTFILE
dev_it->busytimecount = 0;
}
return true;
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////通讯响应处理
void filemenu_cache_put(const std::string& dev_id,
std::vector<tag_dir_info> FileList)
{
std::lock_guard<std::mutex> lk(g_filemenu_cache_mtx);
g_filemenu_cache[dev_id] = std::move(FileList); // 直接存 vector<tag_dir_info>
}
bool filemenu_cache_take(const std::string& dev_id, std::vector<tag_dir_info>& out)
{
std::lock_guard<std::mutex> lk(g_filemenu_cache_mtx);
auto it = g_filemenu_cache.find(dev_id);
if (it == g_filemenu_cache.end()) return false;
out = std::move(it->second); // 转移 vector<tag_dir_info>
g_filemenu_cache.erase(it); // 删除缓存
return true;
}
// 提取文件名列表(仅 flag==1
static inline void build_file_name_list(const std::vector<tag_dir_info>& in,
std::list<std::string>& out)
{
out.clear();
for (const auto& e : in) {
if (e.flag == 1) {
// 安全地从固定长度 char[64] 转成 std::string
size_t n = ::strnlen(e.name, sizeof(e.name));
out.emplace_back(std::string(e.name, n));
}
}
}
void on_device_response_minimal(int response_code,
const std::string& id,
unsigned char cid,
@@ -4020,6 +4515,19 @@ void on_device_response_minimal(int response_code,
if (bt == static_cast<int>(DeviceState::READING_FILEMENU)) {
// ====== 分支 A当前业务就是“读取文件目录” ======
if (ok) {
std::vector<tag_dir_info> names;
if (filemenu_cache_take(id, names)) {
//发送目录
send_file_list(id,names);
} else {
// 失败响应web并复位为空闲
send_reply_to_cloud(static_cast<int>(ResponseCode::BAD_REQUEST), id, static_cast<int>(DeviceState::READING_FILEMENU));
std::cout << "[RESP][FILEMENU->FILEMENU][WARN] dev=" << id
<< " names missing in cache" << std::endl;
}
// 成功:复位
dev->guid.clear();
dev->isbusy = 0;
@@ -4043,16 +4551,43 @@ void on_device_response_minimal(int response_code,
|| bt == static_cast<int>(DeviceState::READING_STATSFILE)
) {
// ====== 分支 B当前业务为“下载事件文件/统计文件”(两者处理相同) ======
// 一个装置同一时刻只会有一个监测点在下载
ledger_monitor* running_monitor = nullptr;
RecallFile* running_front = nullptr;
// 在该终端下,找到“首条 recall_list_static.front() 正在运行且处于 DOWNLOADING 的监测点”
for (auto& lm : dev->line) {
if (lm.recall_list_static.empty()) continue;
RecallFile& f = lm.recall_list_static.front();
if (f.recall_status == static_cast<int>(RecallStatus::RUNNING)
&& f.phase == RecallPhase::DOWNLOADING) {
running_monitor = &lm;
running_front = &f;
break;
}
}
if (!running_monitor || !running_front) {
std::cout << "[RESP][FILEDATA->(EVENT/STATS)FILE][WARN] dev=" << id
<< " no RUNNING/DOWNLOADING recall on this device, ignore resp"
<< " rc=" << response_code << std::endl;
break;
}
// 根据回执结果,回写下载结果;状态机会在下一轮推进到下一个文件/结束
if (ok) {
// 成功:通常紧接着进入读取文件数据阶段
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<int>(DeviceState::READING_STATSFILE)
) {
// ====== 分支 B当前业务为“下载事件文件/统计文件”(两者处理相同) ======
// ★新增:通过 cid 精确找到监测点logical_device_seq == cid
ledger_monitor* matched_monitor = nullptr;
{
const std::string cid_str = std::to_string(static_cast<int>(cid));
for (auto& lm : dev->line) {
if (lm.logical_device_seq == cid_str) { matched_monitor = &lm; break; }
}
}
if (!matched_monitor || matched_monitor->recall_list_static.empty()) {
std::cout << "[RESP][FILEDATA->(EVENT/STATS)FILE][WARN] dev=" << id
<< " no matched monitor or empty recall list, cid="
<< static_cast<int>(cid) << std::endl;
break;
}
RecallFile& front = matched_monitor->recall_list_static.front();
if (front.recall_status != static_cast<int>(RecallStatus::RUNNING)
|| front.phase != RecallPhase::DOWNLOADING) {
std::cout << "[RESP][FILEDATA->(EVENT/STATS)FILE][WARN] dev=" << id
<< " monitor=" << matched_monitor->monitor_id
<< " ignore resp: status=" << front.recall_status
<< " phase=" << static_cast<int>(front.phase) << std::endl;
break;
}
if (ok) {
// 成功:通常紧接着进入读取文件数据阶段
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 响应,统一处理 打印提示======
@@ -4157,3 +4721,10 @@ void on_device_response_minimal(int response_code,
}
}
}

View File

@@ -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<std::string> file_paths; // 已下载/要上报的完整路径(用于最终结果)
// ★新增:按“目录名 -> 文件名列表”的映射;由“其他线程”在目录请求成功后回填
std::map<std::string, std::vector<tag_dir_info>> dir_files;
// ★新增:候选目录(可扩展)
std::vector<std::string> 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<std::string> 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<RecallMonitor> recall_list;
std::list<RecallMonitor> recall_list; //事件
std::list<RecallFile> recall_list_static;//稳态文件
//定值list
std::list<float> 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<tag_dir_info> FileList);
//提取目录信息
bool filemenu_cache_take(const std::string& dev_id, std::vector<tag_dir_info>& out);
//小工具
inline std::string trim_cstr(const char* s, size_t n) {

View File

@@ -318,6 +318,7 @@ void Front::FrontThread() {
try {
while (!m_bIsFrontThreadCancle) {
check_recall_file(); //处理补招文件-稳态和暂态
check_recall_event(); // 处理补招事件从list中读取然后直接调用接口,每一条可能都不同测点,每个测点自己做好记录
check_ledger_update(); // 触发台账更新

View File

@@ -753,7 +753,8 @@ void process_received_message(string mac, string id,const char* data, size_t len
}
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ӷ<EFBFBD><D3B7><EFBFBD><EFBFBD>ļ<EFBFBD><C4BC>б<EFBFBD><D0B1><EFBFBD><EFBFBD>߼<EFBFBD>
send_file_list(id,FileList);//lnk20250813
//send_file_list(id,FileList);//lnk20250813
filemenu_cache_put(id,FileList);
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ɺ<EFBFBD><C9BA><EFBFBD><EFBFBD><EFBFBD>״̬
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
//ʹ<>ýӿ<C3BD><D3BF><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD>lnk20250826
std::string filename;
SendFileWeb(WEB_FILEUPLOAD, file_path, file_path, filename);
SendFileWeb(WEB_FILEUPLOAD, file_path, file_path, filename);//<2F><><EFBFBD><EFBFBD><EFBFBD>Dz<EFBFBD><C7B2><EFBFBD><EFBFBD>ļ<EFBFBD><C4BC><EFBFBD><EFBFBD><EFBFBD><EFBFBD>أ<EFBFBD><D8A3><EFBFBD><EFBFBD>غ<EFBFBD>Ҳ<EFBFBD><D2B2>ֱ<EFBFBD><D6B1><EFBFBD>ϴ<EFBFBD><CFB4><EFBFBD><EFBFBD>ϴ<EFBFBD><CFB4>ɹ<EFBFBD><C9B9><EFBFBD><EFBFBD><EFBFBD><EFBFBD>²<EFBFBD><C2B2><EFBFBD>״̬<D7B4><CCAC><EFBFBD><EFBFBD>
std::cout << "File upload: " << filename << std::endl;
//֪ͨ<CDA8>ļ<EFBFBD><C4BC>ϴ<EFBFBD>
on_device_response_minimal(static_cast<int>(ResponseCode::OK), id, 0, static_cast<int>(DeviceState::READING_FILEDATA));
}
else {
on_device_response_minimal(static_cast<int>(ResponseCode::INTERNAL_ERROR), id, 0, static_cast<int>(DeviceState::READING_FILEDATA));