diff --git a/LFtid1056/cloudfront/code/cfg_parser.cpp b/LFtid1056/cloudfront/code/cfg_parser.cpp index e8cd548..0c348f7 100644 --- a/LFtid1056/cloudfront/code/cfg_parser.cpp +++ b/LFtid1056/cloudfront/code/cfg_parser.cpp @@ -73,6 +73,10 @@ extern std::map xmlinfo_list2;//保存所有型号角形 extern time_t ConvertToTimestamp(const tagTime& time); //////////////////////////////////////////////////////////////////////////////////////////////////// +//补招记录文件 +std::mutex g_recall_file_mtx; +std::map, std::string> g_recall_file_index; + //实时数据时间记录 std::mutex g_last_ts_mtx; std::unordered_map g_last_ts_by_devid; @@ -1123,8 +1127,10 @@ static std::string compact_ts_for_filename(const std::string& ts) { } 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_%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 ""; @@ -1317,6 +1323,9 @@ int recall_json_handle_from_mq(const std::string& body) << " file=" << fn << std::endl; } } + + init_recall_record_file(guid, terminalId, monId, "", ""); + } } else if (dt == 0 || dt == 1) { //一个装置对应一个guid对应多个监测点的多个时间段 // ▲保持老逻辑(与“对象+data”一致):timeInterval 数组 @@ -1357,6 +1366,9 @@ int recall_json_handle_from_mq(const std::string& body) 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_list_hour; Get_Recall_Time_Char(start, end, recallinfo_list_hour); @@ -1370,7 +1382,7 @@ int recall_json_handle_from_mq(const std::string& body) 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); @@ -3490,7 +3502,7 @@ void check_device_busy_timeout() { dev.busytimecount++; - if (dev.busytype == static_cast(DeviceState::READING_FILEDATA)) //下载文件业务 + if (dev.busytype == static_cast(DeviceState::READING_FILEDATA) || dev.busytype == static_cast(DeviceState::READING_STATSFILE)) //下载文件业务 { if (dev.busytimecount > 60) { @@ -3498,19 +3510,47 @@ void check_device_busy_timeout() << " busytype=READING_FILEDATA 超时(" << dev.busytimecount << "s)" << std::endl; + //标记当前 RUNNING 的首条为 FAILED(或 TIMEOUT),而不是只清设备状态 + bool marked = false; + if (dev.busytype == static_cast(DeviceState::READING_STATSFILE)) { + for (auto& lm : dev.line) { + if (!lm.recall_list_static.empty() && + lm.recall_list_static.front().recall_status == static_cast(RecallStatus::RUNNING)) { + lm.recall_list_static.front().recall_status = static_cast(RecallStatus::FAILED); + marked = true; + break; + } + } + } + //发送超时响应 //send_reply_to_cloud(static_cast(ResponseCode::TIMEOUT),dev.terminal_id,get_type_by_state(dev.busytype),dev.guid,dev.mac); send_reply_to_queue(dev.guid, static_cast(ResponseCode::TIMEOUT), "终端 id: " + dev.terminal_id + "进行业务:" + get_type_by_state(dev.busytype) +"超时,停止该业务处理"); // 超时清空状态 - dev.guid.clear(); // 清空进行中的 guid - dev.busytype = 0; // 复位业务类型 - dev.isbusy = 0; // 标记空闲 - dev.busytimecount = 0; // 计时清零 + //若还有未处理条目,则仅复位计时,不清设备状态,交给 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(DeviceState::READING_STATSFILE)) { + dev.busytimecount = 0; // 复位计时 + dev.isbusy = 1; // 仍视为在补招流程中 + dev.busytype = static_cast(DeviceState::READING_STATSFILE); + //不要清 dev.guid,让回调/通知能“对上号” //因为超时后发送结果需要guid,所以不能清,在补招任务中pop就行 + } else { + //队列都空了,或者是正在进行其他业务才清设备状态 + dev.guid.clear(); + dev.busytype = static_cast(DeviceState::IDLE); + dev.isbusy = 0; + dev.busytimecount = 0; + } } } - else //其他业务 + else //其他业务,包括补招日志都是20s一条,一问一答的时间 { if (dev.busytimecount > 20) { @@ -3518,16 +3558,44 @@ void check_device_busy_timeout() << " busytype=" << dev.busytype << " 超时(" << dev.busytimecount << "s)" << std::endl; + //标记当前 RUNNING 的首条为 FAILED(或 TIMEOUT),而不是只清设备状态 + bool marked = false; + if (dev.busytype == static_cast(DeviceState::READING_EVENTLOG)) { + for (auto& lm : dev.line) { + if (!lm.recall_list.empty() && + lm.recall_list.front().recall_status == static_cast(RecallStatus::RUNNING)) { + lm.recall_list.front().recall_status = static_cast(RecallStatus::FAILED); + marked = true; + break; + } + } + } + //发送超时响应 //send_reply_to_cloud(static_cast(ResponseCode::TIMEOUT),dev.terminal_id,get_type_by_state(dev.busytype),dev.guid,dev.mac); send_reply_to_queue(dev.guid, static_cast(ResponseCode::TIMEOUT), "终端 id: " + dev.terminal_id + "进行业务:" + get_type_by_state(dev.busytype) +"超时,停止该业务处理"); // 超时清空状态 - dev.guid.clear(); - dev.busytype = 0; - dev.isbusy = 0; - dev.busytimecount = 0; + //若还有未处理条目,则仅复位计时,不清设备状态,交给 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(DeviceState::READING_EVENTLOG)) { + dev.busytimecount = 0; // 复位计时 + dev.isbusy = 1; // 仍视为在补招流程中 + dev.busytype = static_cast(DeviceState::READING_EVENTLOG); + //不要清 dev.guid,让回调/通知能“对上号” //因为超时后发送结果需要guid,所以不能清,在补招任务中pop就行 + } else { + //队列都空了,或者是正在进行其他业务才清设备状态 + dev.guid.clear(); + dev.busytype = static_cast(DeviceState::IDLE); + dev.isbusy = 0; + dev.busytimecount = 0; + } } } } @@ -4102,11 +4170,23 @@ void check_recall_event() { continue; } // 对正在补招或idle终端的所有监测点的待补招列表进行处理 - // 1) 先弹掉首条为 DONE/FAILED 的记录(所有 monitor 都要处理首条) + // 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(RecallStatus::DONE)) { std::cout << "[check_recall_event] DONE dev=" << dev.terminal_id << " monitor=" << lm.monitor_id @@ -4117,9 +4197,24 @@ void check_recall_event() { + " 补招时间范围:" + front.StartTime + " ~ " + front.EndTime + " 补招执行完成"; - send_reply_to_kafka_recall(dev.guid,1,static_cast(ResponseCode::OK),msg,dev.terminal_id,lm.monitor_id,front.StartTime,front.EndTime); + //send_reply_to_kafka_recall(dev.guid,1,static_cast(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(RecallStatus::EMPTY)) { @@ -4132,9 +4227,24 @@ void check_recall_event() { + " 补招时间范围:" + front.StartTime + " ~ " + front.EndTime + " 补招无事件日志"; - send_reply_to_kafka_recall(dev.guid,1,static_cast(ResponseCode::NOT_FOUND),msg,dev.terminal_id,lm.monitor_id,front.StartTime,front.EndTime); + //send_reply_to_kafka_recall(dev.guid,1,static_cast(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(RecallStatus::FAILED)) { @@ -4147,16 +4257,61 @@ void check_recall_event() { + " 补招时间范围:" + front.StartTime + " ~ " + front.EndTime + " 补招执行失败"; - send_reply_to_kafka_recall(dev.guid,1,static_cast(ResponseCode::BAD_REQUEST),msg,dev.terminal_id,lm.monitor_id,front.StartTime,front.EndTime); + //send_reply_to_kafka_recall(dev.guid,1,static_cast(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 { + } else {//如果每个测点都有补招记录,但是首条不是 DONE/FAILED/EMPTY 则跳过该测点,检查下一个测点 std::cout << "[check_recall_event] skip line=" << lm.monitor_name<< std::endl; break; // 首条不是 2/3/4,停,如果是正在处理其他业务或者idle的装置写入了待补招列表,应该都是0;如果是正在补招的装置,新增的部分不会影响原有顺序 } } - if (!lm.recall_list.empty()) any_non_empty = true; // 处理了成功和失败的以后只要有一条非空就标记,可能是待处理或者正在处理的补招 + + 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(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) @@ -4169,7 +4324,11 @@ void check_recall_event() { } } - // 有条目但目前存在 RUNNING:继续等待该 RUNNING 完成,本轮不新发 + if (!has_running && dev.busytype == static_cast(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; @@ -4180,7 +4339,7 @@ void check_recall_event() { if (!any_non_empty && dev.busytype == static_cast(DeviceState::READING_EVENTLOG)) { // 该终端本轮已无任何补招条目,且处于补招暂态事件的状态清空运行态 std::cout << "[check_recall_event] finish recall dev=" << dev.terminal_id << std::endl; - //通知补招全部完成 + dev.guid.clear(); // 清空 guid dev.busytype = static_cast(DeviceState::IDLE); // 业务类型归零 @@ -4191,8 +4350,8 @@ void check_recall_event() { - //有待补招任务,且idle或者在补招继续补招处理 - std::cout << "[check_recall_event] idle or continue recall dev=" << dev.terminal_id << std::endl; + //有待补招任务,且idle或者在补招,继续补招处理 + //std::cout << "[check_recall_event] idle or continue recall dev=" << dev.terminal_id << std::endl; // 若无 RUNNING,则说明该终端空闲,可以挑选新的补招任务 if (any_non_empty && !has_running) { @@ -4385,9 +4544,11 @@ static bool make_target_key_from_filename(const std::string& fname, std::string& 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 + 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).append("_").append(mmm); + out_key.append(monitor).append("_").append(ymd).append("_").append(hms); return true; } ////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -4409,53 +4570,183 @@ void check_recall_file() { // 1) 清理首条 DONE/FAILED bool any_non_empty = false; - bool has_running = 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(front.phase) + << ", cur_dir=" << front.cur_dir + << ", cur_file=" << front.downloading_file + << ", list_result=" << static_cast(front.list_result) + << ", download_result=" << static_cast(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(RecallStatus::DONE)) { std::cout << "[check_recall_stat] DONE dev=" << dev.terminal_id - << " monitor=" << lm.monitor_id - << " " << front.StartTime << " ~ " << front.EndTime << std::endl; + << " 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(nf.phase) + << ", cur_dir=" << nf.cur_dir + << ", cur_file=" << nf.downloading_file + << ", list_result=" << static_cast(nf.list_result) + << ", download_result=" << static_cast(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(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(); + + //弹出后再打印首条内容与列表大小 + 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(nf.phase) + << ", cur_dir=" << nf.cur_dir + << ", cur_file=" << nf.downloading_file + << ", list_result=" << static_cast(nf.list_result) + << ", download_result=" << static_cast(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 (!lm.recall_list_static.empty()) { - any_non_empty = true;//弹出后是否为空 - if (lm.recall_list_static.front().recall_status == static_cast(RecallStatus::RUNNING)) { - has_running = true; //存在测点正在补招 + + if (had_items_before && popped_any && lm.recall_list_static.empty()) {//处理后,当前测点补招记录为空 + //通知补招全部完成 + 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(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(DeviceState::READING_STATSFILE)) { - // 处于“文件补招”的状态且无条目 -> 清空运行态 + std::cout << "[check_recall_file] finish recall dev=" << dev.terminal_id << std::endl; dev.guid.clear(); - dev.busytype = 0; + dev.busytype = static_cast(DeviceState::IDLE); dev.isbusy = 0; dev.busytimecount = 0; - continue; - } else if (!any_non_empty && dev.busytype != static_cast(DeviceState::IDLE)) {//其他运行态不处理,idle往下执行 - continue; + continue;//这个装置处理下一个装置 } - // 2) 若任一 monitor 的首条为 RUNNING,则该终端正在补招中 -> 不下发新的任务(但需要推进状态机!) - /*bool has_running = false; + // 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(RecallStatus::RUNNING)) { - has_running = true; //存在测点正在补招 + has_running = true; break; } - }*/ + } + + if (!has_running && dev.busytype == static_cast(DeviceState::READING_STATSFILE)) dev.busytimecount = 0; // ★新增:当存在 RUNNING 时,推进“该终端的首条补招记录”的两步状态机 if (has_running) { @@ -4467,14 +4758,18 @@ void check_recall_file() { // 初始化阶段:补招分为两个阶段,读文件列表和下载文件,如果是刚进入 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.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(front.dir_candidates.size())) { - front.cur_dir = front.dir_candidates[front.cur_dir_index]; + 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 @@ -4495,14 +4790,18 @@ void check_recall_file() { if (front.list_result == ActionResult::PENDING) { // 还在等目录回执,本轮不再发任何请求 // (外部线程拿到目录文件列表后,应写入:front.dir_files[front.cur_dir] = {a,b,c...} 并置 list_result=OK/FAIL) - continue; + break; //跳出循环,一个装置一次只能处理一个测点的一个补招记录 } if (front.list_result == ActionResult::FAIL) { // 尝试下一个目录 front.cur_dir_index++; - front.list_result = ActionResult::PENDING; - front.download_queue.clear(); + front.list_result = ActionResult::PENDING;//重置状态 + front.download_queue.clear();//重置下载队列 + + front.required_files.clear(); + front.file_success.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); @@ -4512,47 +4811,81 @@ void check_recall_file() { } 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; + 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(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; - } + break;//跳出循环,一个装置一次只能处理一个测点的一个补招记录;如果失败,下个循环会弹出 + }*/ - auto it = front.dir_files.find(front.cur_dir); + //装置消息返回后通知成功的处理: + auto it = front.dir_files.find(front.cur_dir);//在map中查找当前目录名对应的目录下的文件名列表 if (it != front.dir_files.end()) { if (front.direct_mode) { - // ▲直下:支持“目标名列表”(元素形如:监测点号_YYYYMMDD_HHMMSS_mmm) - std::unordered_set want(front.target_filetimes.begin(), front.target_filetimes.end()); + // 目标时间戳(不含毫秒、形如 yyyyMMdd_HHmmss) + const std::string& want_ts = front.target_filetimes; for (const auto& ent : it->second) { - if (ent.flag != 1) continue; // 只要文件 + // 打印目录下的所有条目 + 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); - std::string key; // 解析得到的 "监测点号_YYYYMMDD_HHMMSS_mmm" + // 解析出 key = 监测点号_YYYYMMDD_HHMMSS(注意:确保你的 make_target_key_from_filename + // 已经改成“去掉毫秒”的版本;若还带毫秒,请先调整该函数) + std::string key; if (!make_target_key_from_filename(fname, key)) { - continue; // 不符合命名规范,跳过 + 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; } - if (want.find(key) != want.end()) { + // 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 { + } else { //稳态文件 // ☆原有:按时间窗筛选 long long beg = parse_time_to_epoch(front.StartTime); long long end = parse_time_to_epoch(front.EndTime); @@ -4588,12 +4921,33 @@ void check_recall_file() { } else { // 所有目录都“无匹配文件” front.recall_status = static_cast(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; } - continue; - } else { + + break; //跳出循环,一个装置一次只能处理一个测点的一个补招记录;如果失败,下个循环会弹出 + + } + else { // 进入下载阶段 + + front.required_files.clear(); + for (const auto& p : front.download_queue) front.required_files.insert(p); + front.file_success.clear(); + front.phase = RecallPhase::DOWNLOADING; front.download_result = ActionResult::PENDING; front.downloading_file.clear(); @@ -4603,19 +4957,38 @@ void check_recall_file() { } } + //在上面的处理中已经找到要下载的文件列表,进入下载阶段 + // DOWNLOADING 阶段:一次只下一个文件,等待外部线程填充 download_result if (front.phase == RecallPhase::DOWNLOADING) { - if (front.downloading_file.empty()) { - if (front.download_queue.empty()) { + 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; + 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(RecallStatus::DONE); + 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(RecallStatus::FAILED); + 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_queue.pop_front();//弹出队首 front.download_result = ActionResult::PENDING; // ★★ 只发一个下载请求,等待外部线程回写结果 @@ -4623,28 +4996,49 @@ void check_recall_file() { std::cout << "[check_recall_stat] DL req dev=" << dev.terminal_id << " monitor=" << lm.monitor_id << " file=" << front.downloading_file << std::endl; - continue; + break; //跳出循环,一个装置一次只能处理一个测点的一个补招记录 } else { // 等待 download_result if (front.download_result == ActionResult::PENDING) { - continue; // 仍在等待 + break; // 仍在等待 } if (front.download_result == ActionResult::OK) { - // 记录成功文件 - front.file_paths.push_back(front.downloading_file); + + 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(); - continue; + + 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(); - continue; + + front.download_result = ActionResult::PENDING; + + break; //跳出循环,一个装置一次只能处理一个测点的一个补招记录 } } } @@ -4653,7 +5047,7 @@ void check_recall_file() { break; } - // 本终端已有 RUNNING 项,且已推进;不再挑选新的 NOT_STARTED + // 本终端已有 RUNNING 项,且已推进;不再挑选新的 NOT_STARTED,开始下一个终端的处理 continue; } @@ -4668,6 +5062,7 @@ void check_recall_file() { dev.isbusy = 1; //装置由idle标记为忙 dev.busytype = static_cast(DeviceState::READING_STATSFILE);//装置状态刷新为正在补招文件 dev.busytimecount = 0; //清空业务超时计数 + dev.guid = front.recall_guid; // 初始化状态机并发出第一个目录请求 front.reset_runtime(true);//保留直下文件信息 @@ -4733,7 +5128,7 @@ bool enqueue_direct_download(const std::string& dev_id, rf.EndTime = "1970-01-01 00:00:01"; //rf.dir_candidates = dir_candidates; // 要检索的目录列表和默认的一致 rf.direct_mode = true; // ★关键:直下文件 - rf.target_filetimes.push_back(filetime); // ▲单个文件时间入“列表” + rf.target_filetimes=filetime; // ▲单个文件时间入“列表” lm_it->recall_list_static.push_back(std::move(rf)); @@ -4810,7 +5205,7 @@ void on_device_response_minimal(int response_code, // 若没有,则回退到按 cid -> logical_device_seq 匹配 ledger_monitor* matched_monitor = nullptr; - // [MOD] 优先:遍历查找首条补招项处于 RUNNING 的监测点 + //优先:遍历查找首条补招项处于 RUNNING 的监测点 for (auto& lm : dev->line) { if (!lm.recall_list.empty()) { const RecallMonitor& head = lm.recall_list.front(); @@ -4821,7 +5216,7 @@ void on_device_response_minimal(int response_code, } } - // [MOD] 回退方案:未找到 RUNNING 的监测点时,按原逻辑用 cid 匹配 logical_device_seq + //未找到 RUNNING 的监测点时,按原逻辑用 cid 匹配 logical_device_seq if (!matched_monitor) { const std::string cid_str = std::to_string(static_cast(cid)); for (auto& lm : dev->line) { @@ -4966,6 +5361,15 @@ void on_device_response_minimal(int response_code, 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(f.recall_status) + << " phase=" << static_cast(f.phase) + << std::endl; + continue; + } } if (!running_monitor || !running_front) { //该装置没有正在补招的测点和补招记录,退出处理 @@ -4977,11 +5381,19 @@ void on_device_response_minimal(int response_code, // 根据回执结果,回写目录结果;状态机会在下一轮推进到下一个目录/结束 if (ok) { - running_front->dir_files[running_front->cur_dir] = std::move(names); - running_front->list_result = ActionResult::OK; - std::cout << "[RESP][FILEMENU->(EVENT/STATS)FILE][OK] dev=" << id - << " monitor=" << running_monitor->monitor_id - << " dir=" << running_front->cur_dir << std::endl; + if(filemenu_cache_take(id, names)) { // 从缓存取目录列表 + running_front->dir_files[running_front->cur_dir] = std::move(names); + running_front->list_result = ActionResult::OK; + std::cout << "[RESP][FILEMENU->(EVENT/STATS)FILE][OK] dev=" << id + << " monitor=" << running_monitor->monitor_id + << " dir=" << running_front->cur_dir << std::endl; + } else { + running_front->list_result = ActionResult::FAIL; + std::cout << "[RESP][FILEMENU->(EVENT/STATS)FILE][WARN] dev=" << id + << " monitor=" << running_monitor->monitor_id + << " dir=" << running_front->cur_dir + << " names missing in cache" << std::endl; + } } else { running_front->list_result = ActionResult::FAIL; std::cout << "[RESP][FILEMENU->(EVENT/STATS)FILE][FAIL] dev=" << id @@ -4989,6 +5401,7 @@ void on_device_response_minimal(int response_code, << " dir=" << running_front->cur_dir << " rc=" << response_code << std::endl; } + break; } else { @@ -5054,46 +5467,73 @@ 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:当前业务为“下载事件文件/统计文件”(两者处理相同) ====== - // ★新增:通过 cid 精确找到监测点(logical_device_seq == cid) + + //优先:找“首条处于 RUNNING 且 phase==DOWNLOADING”的监测点 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; } + RecallFile* running_front = nullptr; + + 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) { + matched_monitor = &lm; + running_front = &f; + break; } } - if (!matched_monitor || matched_monitor->recall_list_static.empty()) { + // 若没找到 RUNNING/DOWNLOADING,用 cid 精确匹配测点号 + if (!matched_monitor) { + 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()) { + RecallFile& f = matched_monitor->recall_list_static.front(); + if (f.recall_status == static_cast(RecallStatus::RUNNING) && + f.phase == RecallPhase::DOWNLOADING) { + running_front = &f; // 回退成功 + std::cout << "[RESP][FILEDATA][FALLBACK-CID] dev=" << id + << " cid=" << static_cast(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(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 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; + << " no RUNNING/DOWNLOADING recall to accept filedata" + << " rc=" << response_code + << " cid=" << static_cast(cid) << std::endl; break; } + // 到这里一定是 RUNNING/DOWNLOADING 的 front;按回执设置下载结果 if (ok) { - // ★新增:下载成功 -> 通知状态机推进到下一个文件(真正入账 file_paths 在 check_recall_stat 里做) - front.download_result = ActionResult::OK; + running_front->download_result = ActionResult::OK; std::cout << "[RESP][FILEDATA->(EVENT/STATS)FILE][OK] dev=" << id - << " file=" << front.downloading_file << std::endl; + << " monitor=" << matched_monitor->monitor_id + << " file=" << running_front->downloading_file << std::endl; } else { - // ★新增:下载失败 -> 让状态机尝试下一个文件 - front.download_result = ActionResult::FAIL; + running_front->download_result = ActionResult::FAIL; std::cout << "[RESP][FILEDATA->(EVENT/STATS)FILE][FAIL] dev=" << id - << " file=" << front.downloading_file - << " rc=" << response_code << std::endl; + << " monitor=" << matched_monitor->monitor_id + << " file=" << running_front->downloading_file + << " rc=" << response_code << std::endl; } break; } @@ -5535,4 +5975,295 @@ DeviceInfo make_device_from_terminal(const terminal_dev& t) { } 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 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 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 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; + } + + 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 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; + + // 若处于“事件文件/统计文件”补招阶段,则使用补招专用上传接口 + if (bt == static_cast(DeviceState::READING_EVENTFILE) || + bt == static_cast(DeviceState::READING_STATSFILE)) { + file_cloudpath = "comtrade/" + dirname_with_slash(local_path); + + std::cout << "[SendFileWebAuto] dev=" << id + << " busytype=" << bt + << " -> use recall upload URL" << std::endl; + } else { + file_cloudpath = "download/" + dirname_with_slash(local_path); + std::cout << "[SendFileWebAuto] dev=" << id + << " busytype=" << bt + << " -> use default upload URL" << std::endl; + } + } else { + std::cout << "[SendFileWebAuto][WARN] device not found for id=" << id + << ", fallback to default URL" << std::endl; + } + + // 实际上传调用 + 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; } \ No newline at end of file diff --git a/LFtid1056/cloudfront/code/interface.h b/LFtid1056/cloudfront/code/interface.h index a4ed23d..298d1ef 100644 --- a/LFtid1056/cloudfront/code/interface.h +++ b/LFtid1056/cloudfront/code/interface.h @@ -10,6 +10,8 @@ #include #include +#include + /////////////////////////////////////////////////////////////////////////////////////////// #include "nlohmann/json.hpp" @@ -78,15 +80,13 @@ public: //暂态文件用 bool direct_mode = false; // 直下文件开关:true 表示不按时间窗,仅按目标文件名 - std::vector target_filetimes; // 直下文件匹配时间 - - std::list file_paths; // 已下载/要上报的完整路径(用于最终结果) + std::string target_filetimes; // 直下文件匹配时间点(yyyyMMdd_HHmmss),仅 direct_mode=true 时有效 // ★新增:按“目录名 -> 文件名列表”的映射;由“其他线程”在目录请求成功后回填 std::map> dir_files; // ★新增:候选目录(可扩展) - std::vector dir_candidates{ + std::vector dir_candidates{ "/cf/COMTRADE", "/bd0/COMTRADE", "/sd0/COMTRADE", @@ -103,9 +103,12 @@ public: ActionResult download_result = ActionResult::PENDING; // 当前文件的下载结果 // ★新增:下载队列(已筛选出在时间窗内的文件,含完整路径) - std::list download_queue; + std::list download_queue; //一个时间可能对应多个文件 std::string downloading_file; // 当前正在下载的文件(完整路径) + std::unordered_set required_files; // 本次应当下载成功的文件全集 + std::unordered_set file_success; // 已下载成功的文件集合 + // ★新增:一个便捷复位 void reset_runtime(bool keep_direct = false) { @@ -117,6 +120,10 @@ public: download_queue.clear(); downloading_file.clear(); dir_files.clear(); + + required_files.clear(); + file_success.clear(); + // ★新增:按需保留直下文件开关和目标名 if (!keep_direct) { direct_mode = false; @@ -807,6 +814,12 @@ inline void print_terminal(const update_dev& tmnl) { print_terminal_common(tmnl) inline void print_terminal(const terminal_dev& tmnl) { print_terminal_common(tmnl); } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////小工具 +// 小工具:判断 s 是否以 suffix 结尾 +static inline bool has_suffix(const std::string& s, const std::string& suffix) { + if (suffix.size() > s.size()) return false; + return std::equal(suffix.rbegin(), suffix.rend(), s.rbegin()); +} + inline std::string trim_cstr(const char* s, size_t n) { if (!s) return {}; size_t end = 0; @@ -817,6 +830,15 @@ inline std::string trim_cstr(const char* s, size_t n) { return out; } +// 去首尾空格 +inline std::string trim_copy(const std::string& s) { + size_t b = s.find_first_not_of(" \t\r\n"); + if (b == std::string::npos) return ""; + size_t e = s.find_last_not_of(" \t\r\n"); + return s.substr(b, e - b + 1); +} + +//清洗字符串 inline std::string sanitize(std::string s) { // 截断第一个 NUL 及其后内容 size_t z = s.find('\0'); @@ -844,6 +866,22 @@ inline std::string now_yyyy_mm_dd_hh_mm_ss() { return oss.str(); } +// 简单键值提取(兼容半角/全角冒号) +inline bool parse_kv_line(const std::string& line, + const std::string& key, + std::string& out) { + std::string k1 = key + ":"; + std::string k2 = key + ":"; + if (line.compare(0, k1.size(), k1) == 0) { + out = trim_copy(line.substr(k1.size())); + return true; + } else if (line.compare(0, k2.size(), k2) == 0) { + out = trim_copy(line.substr(k2.size())); + return true; + } + return false; +} + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////实时数据用 // === 专用锁 + 数据表(仅管实时 idx 映射) === extern std::mutex devidx_lock; // 新锁(不要用 ledgermtx) @@ -924,4 +962,35 @@ static bool parse_datetime_tm(const std::string& s, std::tm& out) { } #endif +/////////////////////////////////////////////////////////////////////////////补招文件记录 +// 记录 (guid, monitorId) -> 文件完整路径 +extern std::mutex g_recall_file_mtx; +extern std::map, std::string> g_recall_file_index; +// 初始化 / 追加 / 删除 +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); + +bool append_recall_record_line(const std::string& guid, + const std::string& monitorId, + const std::string& msg); + +bool delete_recall_record_file(const std::string& guid, + const std::string& monitorId); + +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); + +bool SendFileWebAuto(const std::string& id, + const std::string& local_path, + const std::string& remote_path, + std::string& out_filename); \ No newline at end of file diff --git a/LFtid1056/cloudfront/code/worker.cpp b/LFtid1056/cloudfront/code/worker.cpp index 138b71b..9842051 100644 --- a/LFtid1056/cloudfront/code/worker.cpp +++ b/LFtid1056/cloudfront/code/worker.cpp @@ -684,16 +684,9 @@ void Worker::printLedgerinshell(const terminal_dev& dev, int fd) { // ★新增:直下模式与目标时间列表 os << "\r\x1B[K |-- direct_mode=" << (rf.direct_mode ? "true" : "false") - << ", target_filetimes(" << rf.target_filetimes.size() << ")\n"; + << ", target_filetimes(" << rf.target_filetimes << ")\n"; { - size_t c = 0; - for (const auto& t : rf.target_filetimes) { - if (c++ >= MAX_ITEMS) break; - os << "\r\x1B[K |-- " << t << "\n"; - } - if (rf.target_filetimes.size() > MAX_ITEMS) { - os << "\r\x1B[K |.. (+" << (rf.target_filetimes.size() - MAX_ITEMS) << " more)\n"; - } + os << "\r\x1B[K |.. " << rf.target_filetimes << "\n"; } // ★新增:状态机运行态 @@ -747,19 +740,6 @@ void Worker::printLedgerinshell(const terminal_dev& dev, int fd) { if (!rf.downloading_file.empty()) { os << "\r\x1B[K |-- downloading: " << rf.downloading_file << "\n"; } - - // ★新增:已下载/待上报的完整路径(file_paths) - os << "\r\x1B[K |-- file_paths(" << rf.file_paths.size() << ")\n"; - { - size_t c = 0; - for (const auto& p : rf.file_paths) { - if (c++ >= MAX_ITEMS) break; - os << "\r\x1B[K |-- " << p << "\n"; - } - if (rf.file_paths.size() > MAX_ITEMS) { - os << "\r\x1B[K |.. (+" << (rf.file_paths.size() - MAX_ITEMS) << " more)\n"; - } - } } if (ld.recall_list_static.size() > MAX_ITEMS) { os << "\r\x1B[K |.. (+" << (ld.recall_list_static.size() - MAX_ITEMS) << " more)\n"; diff --git a/LFtid1056/dealMsg.cpp b/LFtid1056/dealMsg.cpp index 018b14f..2c5116e 100644 --- a/LFtid1056/dealMsg.cpp +++ b/LFtid1056/dealMsg.cpp @@ -785,6 +785,8 @@ void process_received_message(string mac, string id,const char* data, size_t len //send_file_list(id,FileList);//lnk20250813 filemenu_cache_put(id,FileList); + on_device_response_minimal(static_cast(ResponseCode::OK), id, 0, static_cast(DeviceState::READING_FILEMENU)); + // 处理完成后重置状态 ClientManager::instance().change_device_state(id, DeviceState::IDLE); } @@ -873,7 +875,7 @@ 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);//如果是补招文件的下载,下载后也是直接上传,上传成功后更新补招状态即可 + SendFileWebAuto(id, file_path, file_path, filename);//如果是补招文件的下载,下载后也是直接上传,上传成功后更新补招状态即可 std::cout << "File upload: " << filename << std::endl; //通知文件上传 @@ -2078,9 +2080,9 @@ void process_received_message(string mac, string id,const char* data, size_t len << ", 特征幅值: " << record.fMagntitude << " pu" << ", 时间戳: " << record.triggerTimeMs << "ms" << std::endl; - //记录补招上来的暂态事件 - append_qvvr_event(id,event.head.name, - record.nType,record.fPersisstime,record.fMagntitude,record.triggerTimeMs,record.phase); + //记录补招上来的暂态事件,如果需要前置自行下载波形才需要这个接口 + /*append_qvvr_event(id,event.head.name, + record.nType,record.fPersisstime,record.fMagntitude,record.triggerTimeMs,record.phase);*/ //直接发走暂态事件 transfer_json_qvvr_data(id,event.head.name, record.fMagntitude,record.fPersisstime,record.triggerTimeMs,record.nType,record.phase,""); @@ -2145,7 +2147,7 @@ void process_received_message(string mac, string id,const char* data, size_t len else { //其余错误码代表异常情况 //lnk20251023 - on_device_response_minimal(static_cast(ResponseCode::BAD_REQUEST), id, 0, static_cast(DeviceState::CUSTOM_ACTION)); + on_device_response_minimal(static_cast(ResponseCode::BAD_REQUEST), id, 0, static_cast(DeviceState::READING_EVENTLOG)); } // 装置否定 // 补招装置日志失败,调整为空闲状态,处理下一项工作。