diff --git a/LFtid1056/cloudfront/code/cfg_parser.cpp b/LFtid1056/cloudfront/code/cfg_parser.cpp index 0c348f7..f1b003c 100644 --- a/LFtid1056/cloudfront/code/cfg_parser.cpp +++ b/LFtid1056/cloudfront/code/cfg_parser.cpp @@ -242,6 +242,29 @@ bool is_blank(const std::string& str) return true; } +// 获取本地当天 00:00:00 的毫秒时间戳 +static inline uint64_t get_local_midnight_ms_today() { + std::time_t now = std::time(nullptr); + std::tm local_tm{}; +#if defined(_WIN32) || defined(_WIN64) + localtime_s(&local_tm, &now); +#else + local_tm = *std::localtime(&now); +#endif + local_tm.tm_hour = 0; + local_tm.tm_min = 0; + local_tm.tm_sec = 0; + std::time_t midnight_sec = std::mktime(&local_tm); // 本地时区 + return static_cast(midnight_sec) * 1000ULL; +} + +// 获取“本地前一天 00:00:00”的毫秒时间戳 +static inline uint64_t get_local_midnight_ms_prev_day() { + uint64_t today_ms = get_local_midnight_ms_today(); + const uint64_t one_day_ms = 24ULL * 60ULL * 60ULL * 1000ULL; + return today_ms - one_day_ms; +} + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////配置文件读取 //将 G_TEST_LIST 按逗号分割,填充 TESTARRAY @@ -1147,12 +1170,22 @@ static std::vector build_direct_filenames(const std::string& monito } static long long parse_time_to_epoch(const std::string& time_str) { + std::string s = time_str; + + // ★去掉可能存在的微秒部分(例如 .000000) + size_t dot_pos = s.find('.'); + if (dot_pos != std::string::npos) { + s = s.substr(0, dot_pos); + } + std::tm tm = {}; - std::istringstream ss(time_str); + std::istringstream ss(s); ss >> std::get_time(&tm, "%Y-%m-%d %H:%M:%S"); if (ss.fail()) { - return 0; + std::cerr << "[parse_time_to_epoch] failed to parse time string: " << s << std::endl; + return -1; // ★返回 -1 表示失败(便于判断) } + return static_cast(std::mktime(&tm)); } @@ -1308,8 +1341,55 @@ int recall_json_handle_from_mq(const std::string& body) std::string digits = get_monitor_digits_from_terminal_list(terminalId, monId);//有锁 if (digits.empty()) { std::cout << "monitorId数字提取失败: " << monId << std::endl; continue; } + // 根据 monitorId 和提取的数字初始化补招记录 + init_recall_record_file(guid, terminalId, monId, "", ""); + + //根据时间戳单独补招事件 + // ★新增(dt==2 同步生成“按小时”的事件补招到 recall_list,与 dt==1 逻辑一致)——开始 + { + std::lock_guard lock2(ledgermtx); // 复用与 dt==1 相同的加锁粒度 + // 找终端 + terminal_dev* dev_nc = NULL; + for (auto& d : terminal_devlist) if (d.terminal_id == terminalId) { dev_nc = &d; break; } + if (!dev_nc) { /* 理论上前面已校验,这里兜底 */ continue; } + + // 找监测点 + ledger_monitor* lm = NULL; + for (auto itLm = dev_nc->line.begin(); itLm != dev_nc->line.end(); ++itLm) { + if (!itLm->monitor_id.empty() && itLm->monitor_id == monId) { lm = &(*itLm); break; } + } + if (!lm) { std::cout << "monitorId未在terminal内找到(直下事件回灌): " << monId << " @ " << terminalId << std::endl; continue; } + + // 将 timeList 的每个时间点映射为 [该整点, 下一整点) 的一小时窗口 + for (const auto& t : rec["timeList"]) { + if (!t.is_string()) continue; + std::string tstr = t.get(); + + long long ep = parse_time_to_epoch(tstr); // 需与系统时间解析格式一致(已有同名函数) + if (ep < 0) { + std::cout << "[direct->event] parse_time_to_epoch fail: " << tstr << std::endl; + continue; + } + long long hour_start = ep - (ep % 3600); + long long hour_end = hour_start + 3600 - 1; // 与 Get_Recall_Time_Char 产物风格保持一致(闭区间) + + RecallMonitor rm; + rm.recall_guid = guid; + rm.recall_status = 0; + rm.StartTime = epoch_to_datetime_str(hour_start); + rm.EndTime = epoch_to_datetime_str(hour_end); + rm.STEADY = "0"; // 与 dt==1 一致:事件(波形) + rm.VOLTAGE = "1"; // 与 dt==1 一致 + + lm->recall_list.push_back(rm); + } + } + // ★新增(dt==2 同步生成“按小时”的事件补招到 recall_list,与 dt==1 逻辑一致)——结束 + //根据时间戳单独补招事件 + for (const auto& t : rec["timeList"]) { if (!t.is_string()) continue; + std::string ts_compact = compact_ts_for_filename(t.get()); if (ts_compact.empty()) { std::cout << "时间解析失败: " << t << std::endl; continue; } @@ -1324,8 +1404,6 @@ int recall_json_handle_from_mq(const std::string& body) } } - init_recall_record_file(guid, terminalId, monId, "", ""); - } } else if (dt == 0 || dt == 1) { //一个装置对应一个guid对应多个监测点的多个时间段 // ▲保持老逻辑(与“对象+data”一致):timeInterval 数组 @@ -2831,7 +2909,7 @@ bool extract_timestamp_from_cfg_file(const std::string& cfg_path, long long& sta return start_tm > 0 && trig_tm > 0; } -bool compare_qvvr_and_file(const std::string& cfg_path, const std::vector& data_list,qvvr_data& matched_data) { +bool compare_qvvr_and_file(const std::string& cfg_path, std::vector& data_list,qvvr_data& matched_data) { long long start_tm = 0; long long trig_tm = 0; @@ -2845,9 +2923,12 @@ bool compare_qvvr_and_file(const std::string& cfg_path, const std::vector(data.QVVR_time) - trig_tm; if (std::abs(diff) <= 1) { + + data.is_pair = true; // 标记为已匹配 + matched_data = data; // 返回匹配到的事件 return true; } @@ -3683,6 +3764,41 @@ void check_and_backup_qvvr_files() { } } +/////////////////////////////////////////////////////////////////////////////////////////定时清理内存的暂态事件 +// 每天执行一次;删除“is_pair=false 且 QVVR_time < 昨天 00:00:00(毫秒)”的 qvvr_data +void cleanup_old_unpaired_qvvr_events() { + const uint64_t cutoff_ms = get_local_midnight_ms_prev_day(); + + std::lock_guard lk(ledgermtx); // ★新增:加锁保护台账 + + size_t total_removed = 0; + for (auto& dev : terminal_devlist) { + for (auto& lm : dev.line) { + auto& vec = lm.qvvrevent.qvvrdata; + const size_t before = vec.size(); + + // 删除条件:未配对 且 发生时间 < 昨天 00:00:00(本地时区毫秒) + vec.erase(std::remove_if(vec.begin(), vec.end(), + [&](const qvvr_data& d){ + return (d.is_pair == false) && (d.QVVR_time < cutoff_ms); + }), + vec.end()); + + const size_t removed = before - vec.size(); + if (removed > 0) { + total_removed += removed; + std::cout << "[cleanup_qvvr] dev=" << dev.terminal_id + << " mon=" << lm.monitor_id + << " removed=" << removed + << " cutoff_ms=" << cutoff_ms + << " (prev 00:00 local)" << std::endl; + } + } + } + + std::cout << "[cleanup_qvvr] total_removed=" << total_removed << std::endl; +} + /////////////////////////////////////////////////////////////////////////////////////////定值信息发送接口函数 bool save_set_value(const std::string &dev_id, unsigned char mp_index, const std::vector &fabsf) { std::lock_guard lock(ledgermtx); @@ -4551,16 +4667,71 @@ static bool make_target_key_from_filename(const std::string& fname, std::string& out_key.append(monitor).append("_").append(ymd).append("_").append(hms); return true; } -////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////补招文件匹配事件 +//////////////////////////////////////////////////////////////////////////////////////////////////////////////补招文件逻辑 // ====== ★修改:check_recall_stat —— 加入“两步法”状态机 ====== void check_recall_file() { std::vector tasks; // 本轮要发送的“每终端一条”(目录请求 或 文件下载 请求) + // ★修改开始:新增“待上传动作”容器与两个小工具(局部作用域,函数私有) + struct PendingUpload { + std::string terminal_id; + unsigned short logical_seq = 0; + std::vector files_to_send; // 完整路径(含MAC目录) + qvvr_data matched{}; // 与 .cfg 匹配到的事件(如有) + bool has_matched = false; + // 用于回锁后在台账中定位并删掉对应 qvvr_file 组 + std::set sig_names; // 仅文件名集合 + std::set sig_downs; // 完整路径集合(与 files_to_send 相同内容的 set) + }; + + auto ExtractFileNames = [](const std::vector& full_paths) { + std::vector names; + names.reserve(full_paths.size()); + for (const auto& p : full_paths) { + auto s = sanitize(p); + size_t pos = s.find_last_of("/\\"); + std::string name = (pos == std::string::npos) ? s : s.substr(pos + 1); + names.push_back(sanitize(name)); + } + std::sort(names.begin(), names.end()); + names.erase(std::unique(names.begin(), names.end()), names.end()); + return names; + }; + + auto MakeSigNames = [&](const std::vector& full_paths){ + auto names = ExtractFileNames(full_paths); + return std::set(names.begin(), names.end()); + }; + // ★修改结束 + + std::vector pending_uploads; // ★修改:锁外执行上传与清理 + + { //锁作用域 std::lock_guard lock(ledgermtx); + // ★优先级开始:事件优先 —— 若任一测点存在事件补招(recall_list)尚未清空,则本轮跳过文件补招 + { + bool has_event_pending = false; + for (const auto& dev : terminal_devlist) { + for (const auto& lm : dev.line) { + if (!lm.recall_list.empty()) { // 只要存在事件待处理就让位 + has_event_pending = true; + break; + } + } + if (has_event_pending) break; + } + if (has_event_pending) { + std::cout << "[check_recall_file] skip this round: event recall pending, give priority to check_recall_event()\n"; + return; // 直接让位给 check_recall_event,本轮不处理文件补招 + } + } + // ★优先级结束 + for (auto& dev : terminal_devlist) { // 仅处理“正在补招/空闲”终端,与你原逻辑一致 if (dev.busytype != static_cast(DeviceState::READING_STATSFILE) && @@ -4778,6 +4949,19 @@ 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 dir candidates, FAIL dev=" << dev.terminal_id << " monitor=" << lm.monitor_id << std::endl; } @@ -4812,7 +4996,17 @@ void check_recall_file() { // 所有目录都失败 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] all dir failed, FAIL dev=" << dev.terminal_id << " monitor=" << lm.monitor_id << std::endl; @@ -4945,7 +5139,21 @@ void check_recall_file() { // 进入下载阶段 front.required_files.clear(); - for (const auto& p : front.download_queue) front.required_files.insert(p); + //for (const auto& p : front.download_queue) front.required_files.insert(p); + + //转成本地保存路径 download// + front.required_files.clear(); + { + const std::string base_dir = std::string("download/") + sanitize(dev.addr_str); + for (const auto& p : front.download_queue) { + // p 形如 "/" —— 提取纯文件名 + std::string fname = sanitize(extract_filename1(p)); + if (!fname.empty()) { + front.required_files.insert(base_dir + "/" + fname); + } + } + } + front.file_success.clear(); front.phase = RecallPhase::DOWNLOADING; @@ -4971,7 +5179,80 @@ void check_recall_file() { << std::endl; if (!front.required_files.empty() && front.file_success.size() == front.required_files.size()) { - front.recall_status = static_cast(RecallStatus::DONE); + + front.recall_status = static_cast(RecallStatus::DONE);//两个文件都下好了标记为成功 + + //更新事件 + // ★修改开始:替换“assign_qvvr_file_list + update_qvvr_file_download(有锁)” + // 组装完整路径列表 + std::vector fullFilenames; + fullFilenames.reserve(front.required_files.size()); + for (const auto& f : front.required_files) fullFilenames.push_back(f); + + // 仅对“当前监测点”添加一组 qvvr_file(file_name=仅文件名,下载标记留到下行) + { + qvvr_file qfile; + auto names = ExtractFileNames(fullFilenames); + qfile.file_name.assign(names.begin(), names.end()); + qfile.is_download = false; + qfile.is_pair = false; + qfile.file_time_count = 0; + qfile.used_status = true; + lm.qvvrevent.qvvrfile.push_back(std::move(qfile)); + } + + // 把这组标记为“已全部下载”,并尝试用 .cfg 匹配事件;准备锁外上传动作 + { + // 找到刚刚 append 的那组:file_name 集合 == fullFilenames 的文件名集合 + auto want_names = MakeSigNames(fullFilenames); + auto it_qf = std::find_if(lm.qvvrevent.qvvrfile.begin(), + lm.qvvrevent.qvvrfile.end(), + [&](const qvvr_file& x){ + std::set s(x.file_name.begin(), x.file_name.end()); + return s == want_names; + }); + if (it_qf != lm.qvvrevent.qvvrfile.end()) { + // 原:it_qf->file_download = fullFilenames; + it_qf->file_download.clear(); + it_qf->file_download.assign(fullFilenames.begin(), fullFilenames.end()); // ★修复:vector -> list + it_qf->is_download = true; + + // 寻找第一个 .cfg 做匹配 + qvvr_data matched{}; + bool has_matched = false; + for (const auto& p : it_qf->file_download) { + auto s = sanitize(p); + auto dot = s.find_last_of('.'); + if (dot != std::string::npos) { + std::string ext = s.substr(dot); + for (auto& c : ext) c = (char)std::tolower((unsigned char)c); + if (ext == ".cfg") { + if (compare_qvvr_and_file(p, lm.qvvrevent.qvvrdata, matched)) { + it_qf->is_pair = true; + has_matched = true; + } + break; // 只看一个 .cfg + } + } + } + + // 记录一个“待上传动作”,锁外执行上传、回写 JSON、删文件与回锁清理 + PendingUpload pu; + pu.terminal_id = dev.terminal_id; + try { pu.logical_seq = static_cast(std::stoi(lm.logical_device_seq)); } + catch (...) { pu.logical_seq = 0; } + // 原:pu.files_to_send = it_qf->file_download; + pu.files_to_send.clear(); + pu.files_to_send.assign(it_qf->file_download.begin(), it_qf->file_download.end()); // ★修复:list -> vector + pu.has_matched = has_matched; + pu.matched = matched; + pu.sig_names = want_names; + pu.sig_downs = std::set(pu.files_to_send.begin(), pu.files_to_send.end()); + pending_uploads.push_back(std::move(pu)); + } + } + // ★修改结束 + std::cout << "[check_recall_stat] DONE dev=" << dev.terminal_id << " monitor=" << lm.monitor_id << " ok=" << front.file_success.size() @@ -5079,6 +5360,19 @@ void check_recall_file() { << " end=" << front.EndTime << std::endl; } 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] empty dir_candidates, FAIL dev=" << dev.terminal_id << " monitor=" << lm.monitor_id << std::endl; } @@ -5090,6 +5384,82 @@ void check_recall_file() { } // end for dev } // 解锁 + // ★修改开始:锁外执行上传、回写 JSON、删除本地文件,并回锁做台账清理 + for (auto& pu : pending_uploads) { + // 1) 上传所有文件:构造临时 qvvr_file 给现有 SendAllQvvrFiles 使用 + qvvr_file tmp; + // 原:tmp.file_download = pu.files_to_send; + tmp.file_download.clear(); + tmp.file_download.assign(pu.files_to_send.begin(), pu.files_to_send.end()); // ★修复:vector -> list + + std::string wavepath; + bool sent_ok = SendAllQvvrFiles(tmp, wavepath); + + if (sent_ok) { + // 2) 回写 JSON(若匹配到事件) + if (pu.has_matched) { + transfer_json_qvvr_data(pu.terminal_id, + pu.logical_seq, + pu.matched.QVVR_Amg, + pu.matched.QVVR_PerTime, + pu.matched.QVVR_time, + pu.matched.QVVR_type, + pu.matched.phase, + wavepath); + } + + // 3) 删除本地文件 + for (const auto& f : pu.files_to_send) { + if (std::remove(f.c_str()) != 0) { + std::cerr << "[Cleanup] Failed to delete file: " << f << "\n"; + } else { + std::cout << "[Cleanup] Deleted uploaded file: " << f << "\n"; + } + } + + // 4) 回锁做台账清理:删除对应 qvvr_file 组与匹配到的事件 + { + std::lock_guard lk(ledgermtx); + for (auto& dev : terminal_devlist) { + if (dev.terminal_id != pu.terminal_id) continue; + for (auto& lm : dev.line) { + unsigned short seq = 0; + try { seq = static_cast(std::stoi(lm.logical_device_seq)); } catch (...) { seq = 0; } + if (seq != pu.logical_seq) continue; + + // 定位并删除相同签名的组 + auto it_qf = std::find_if(lm.qvvrevent.qvvrfile.begin(), + lm.qvvrevent.qvvrfile.end(), + [&](const qvvr_file& x){ + std::set n(x.file_name.begin(), x.file_name.end()); + std::set d(x.file_download.begin(), x.file_download.end()); + return n==pu.sig_names && d==pu.sig_downs; + }); + if (it_qf != lm.qvvrevent.qvvrfile.end()) { + lm.qvvrevent.qvvrfile.erase(it_qf); + } else { + std::cerr << "[Cleanup] qvvrfile changed; target group not found, skip erase\n"; + } + + // 如有匹配事件,按时间删除 + if (pu.has_matched) { + auto it_evt = std::find_if(lm.qvvrevent.qvvrdata.begin(), + lm.qvvrevent.qvvrdata.end(), + [&](const qvvr_data& d){ return d.QVVR_time == pu.matched.QVVR_time; }); + if (it_evt != lm.qvvrevent.qvvrdata.end()) { + lm.qvvrevent.qvvrdata.erase(it_evt); + } + } + } + } + } + } else { + std::cerr << "[check_recall_file][Upload] SendAllQvvrFiles failed for terminal=" + << pu.terminal_id << " seq=" << pu.logical_seq << std::endl; + } + } + // ★修改结束 + // ★说明:本函数不主动把 RUNNING 改 DONE/FAILED; // 由“其他线程”在目录/下载结果返回后,找到对应 dev/monitor 的 lm.recall_list_static.front(), // 写入: @@ -5385,14 +5755,17 @@ void on_device_response_minimal(int response_code, 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; + << " monitor=" << running_monitor->monitor_id + << " dir=" << running_front->cur_dir + << " entries=" << running_front->dir_files[running_front->cur_dir].size() + << std::endl; } else { - running_front->list_result = ActionResult::FAIL; + running_front->dir_files[running_front->cur_dir] = {}; + running_front->list_result = ActionResult::OK; 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; + << " monitor=" << running_monitor->monitor_id + << " dir=" << running_front->cur_dir + << " cache miss -> treat as empty OK" << std::endl; } } else { running_front->list_result = ActionResult::FAIL; @@ -5478,6 +5851,13 @@ void on_device_response_minimal(int response_code, for (auto& lm : dev->line) { if (lm.recall_list_static.empty()) continue; RecallFile& f = lm.recall_list_static.front(); + + std::cout << "[RESP][FILEDATA][SCAN] dev=" << id + << " monitor=" << lm.monitor_id + << " status=" << static_cast(f.recall_status) + << " phase=" << static_cast(f.phase) + << std::endl; + if (f.recall_status == static_cast(RecallStatus::RUNNING) && f.phase == RecallPhase::DOWNLOADING) { matched_monitor = &lm; @@ -5499,7 +5879,7 @@ void on_device_response_minimal(int response_code, RecallFile& f = matched_monitor->recall_list_static.front(); if (f.recall_status == static_cast(RecallStatus::RUNNING) && f.phase == RecallPhase::DOWNLOADING) { - running_front = &f; // 回退成功 + running_front = &f; std::cout << "[RESP][FILEDATA][FALLBACK-CID] dev=" << id << " cid=" << static_cast(cid) << " monitor=" << matched_monitor->monitor_id << std::endl; @@ -5674,6 +6054,9 @@ bool append_qvvr_event(const std::string& terminal_id, << ", phase=" << phase << "}" << std::endl; + //lnk20251030 + q.is_pair = false; + q.used_status = true; q.QVVR_PerTime = fPersisstime_sec; q.QVVR_Amg = fMagnitude_pu; @@ -5699,6 +6082,9 @@ bool append_qvvr_event(const std::string& terminal_id, << ", phase=" << phase << "}" << std::endl; + //lnk20251030 + q.is_pair = false; + q.used_status = true; q.QVVR_type = nType; q.QVVR_time = triggerTimeMs; @@ -5714,6 +6100,10 @@ bool append_qvvr_event(const std::string& terminal_id, // 5) 直接追加 qvvr_data q{}; + + //lnk20251030 + q.is_pair = false; + q.used_status = true; q.QVVR_type = nType; q.QVVR_time = triggerTimeMs; // ms @@ -6234,23 +6624,33 @@ bool SendFileWebAuto(const std::string& id, if (dev_ptr) { const int bt = dev_ptr->busytype; - // 若处于“事件文件/统计文件”补招阶段,则使用补招专用上传接口 + // 若处于“事件文件/统计文件”补招阶段,则使用补招专用上传目录:comtrade/wave/... if (bt == static_cast(DeviceState::READING_EVENTFILE) || bt == static_cast(DeviceState::READING_STATSFILE)) { - file_cloudpath = "comtrade/" + dirname_with_slash(local_path); + std::string rel = dirname_with_slash(local_path); // 例如:download/00:B7:.../ + // 将 download/ 前缀替换为 wave/ + if (!replace_prefix(rel, "download/", "wave/")) { + // 若不是以 download/ 开头,兜底拼 wave/ + 原目录 + rel = "wave/" + rel; + } + + file_cloudpath = "comtrade/" + rel; // 目标:comtrade/wave/00:B7:.../ + std::cout << "[SendFileWebAuto] dev=" << id << " busytype=" << bt - << " -> use recall upload URL" << std::endl; + << " -> use recall upload URL (cloud path=" << file_cloudpath << ")\n"; } else { - file_cloudpath = "download/" + dirname_with_slash(local_path); + // 非补招场景沿用原来的 download 目录 + file_cloudpath = dirname_with_slash(local_path); // 保持原逻辑 std::cout << "[SendFileWebAuto] dev=" << id << " busytype=" << bt - << " -> use default upload URL" << std::endl; + << " -> use default upload URL (cloud path=" << file_cloudpath << ")\n"; } } else { std::cout << "[SendFileWebAuto][WARN] device not found for id=" << id - << ", fallback to default URL" << std::endl; + << ", fallback to default URL\n"; + file_cloudpath = dirname_with_slash(local_path); } // 实际上传调用 @@ -6266,4 +6666,5 @@ bool SendFileWebAuto(const std::string& id, } return false; -} \ No newline at end of file +} + diff --git a/LFtid1056/cloudfront/code/interface.h b/LFtid1056/cloudfront/code/interface.h index 298d1ef..8369108 100644 --- a/LFtid1056/cloudfront/code/interface.h +++ b/LFtid1056/cloudfront/code/interface.h @@ -164,6 +164,7 @@ public: class qvvr_data { public: + bool is_pair; bool used_status; //是否占用 int QVVR_type; //暂态类型 uint64_t QVVR_time; //暂态开始时间 unsigned longlong @@ -815,11 +816,20 @@ inline void print_terminal(const terminal_dev& tmnl) { print_terminal_common(tmn ///////////////////////////////////////////////////////////////////////////////////////////////////////////////小工具 // 小工具:判断 s 是否以 suffix 结尾 -static inline bool has_suffix(const std::string& s, const std::string& suffix) { +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 bool replace_prefix(std::string& s, const std::string& from, const std::string& to) { + if (s.rfind(from, 0) == 0) { // 以 from 为前缀 + s.replace(0, from.size(), to); + return true; + } + return false; +} + inline std::string trim_cstr(const char* s, size_t n) { if (!s) return {}; size_t end = 0; @@ -993,4 +1003,6 @@ bool get_recall_record_fields_by_guid_monitor(const std::string& guid, 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 + std::string& out_filename); + +void cleanup_old_unpaired_qvvr_events(); \ No newline at end of file diff --git a/LFtid1056/cloudfront/code/main.cpp b/LFtid1056/cloudfront/code/main.cpp index b9726e5..524dd23 100644 --- a/LFtid1056/cloudfront/code/main.cpp +++ b/LFtid1056/cloudfront/code/main.cpp @@ -85,6 +85,18 @@ std::unique_ptr make_unique(Args&&... args) { return std::unique_ptr(new T(std::forward(args)...)); } +// 把“今天”做成年月日整数(YYYYMMDD),用于“每天只清理一次”的判定 +static inline int local_ymd_today() { + std::time_t now = std::time(nullptr); + std::tm local_tm{}; +#if defined(_WIN32) || defined(_WIN64) + localtime_s(&local_tm, &now); +#else + local_tm = *std::localtime(&now); +#endif + return (local_tm.tm_year + 1900) * 10000 + (local_tm.tm_mon + 1) * 100 + local_tm.tm_mday; +} + //处理参数 bool parse_param(int argc, char* argv[]) { for (int i = 1; i < argc; ++i) { @@ -138,35 +150,6 @@ bool parse_param(int argc, char* argv[]) { return true; } -//获取前置类型 -/*void init_global_function_enable() { - if (subdir == "cfg_stat_data") { // 历史稳态 - g_node_id = STAT_DATA_BASE_NODE_ID; - auto_register_report_enabled = 1; - } else if (subdir == "cfg_3s_data") { // 实时 - g_node_id = THREE_SECS_DATA_BASE_NODE_ID; - three_secs_enabled = 1; - } else if (subdir == "cfg_soe_comtrade") { // 告警、录波、暂态 - g_node_id = SOE_COMTRADE_BASE_NODE_ID; - } else if (subdir == "cfg_recallhis_data") { // 补招 - g_node_id = RECALL_HIS_DATA_BASE_NODE_ID; - } -}*/ - -//获取功能名称 -/*std::string get_front_msg_from_subdir() { - if (subdir.find("cfg_3s_data") != std::string::npos) - return "实时数据进程"; - else if (subdir.find("cfg_soe_comtrade") != std::string::npos) - return "暂态和告警进程"; - else if (subdir.find("cfg_recallhis_data") != std::string::npos) - return "稳态补招进程"; - else if (subdir.find("cfg_stat_data") != std::string::npos) - return "稳态统计进程"; - else - return "unknown"; -}*/ - //获取前置路径 std::string get_parent_directory() { // 获取当前工作目录 @@ -456,10 +439,9 @@ void Front::FrontThread() { try { while (!m_bIsFrontThreadCancle) { - check_recall_file(); //处理补招文件-稳态和暂态 check_recall_event(); // 处理补招事件,从list中读取然后直接调用接口,每一条可能都不同测点,每个测点自己做好记录 - //check_ledger_update(); // 触发台账更新 - + check_recall_file(); //处理补招文件-稳态和暂态 + std::this_thread::sleep_for(std::chrono::milliseconds(100)); } } catch (const std::exception& e) { @@ -490,6 +472,9 @@ void Front::OnTimerThread() send_heartbeat_to_queue("1"); + //记录“上次做日清理”的日期(YYYYMMDD),确保每天只做一次 + static int s_lastCleanupYMD = -1; + while (!m_IsTimerCancel) { update_log_entries_countdown(); @@ -509,6 +494,18 @@ void Front::OnTimerThread() backupCounter = 0; } + // 按天清理 —— 发现“日期变更”则执行一次清理 + { + const int todayYMD = local_ymd_today(); // YYYYMMDD(本地时区) + if (todayYMD != s_lastCleanupYMD) { + // 说明进入了新的一天:执行清理(删除前日及更早的未配对事件) + std::cout << "[OnTimerThread] daily cleanup start, today=" << todayYMD << std::endl; + cleanup_old_unpaired_qvvr_events(); // 调用清理内存的暂态事件 + s_lastCleanupYMD = todayYMD; + std::cout << "[OnTimerThread] daily cleanup done" << std::endl; + } + } + hbCounter++; backupCounter++; diff --git a/LFtid1056/cloudfront/code/rocketmq.cpp b/LFtid1056/cloudfront/code/rocketmq.cpp index be5ff3e..551bf83 100644 --- a/LFtid1056/cloudfront/code/rocketmq.cpp +++ b/LFtid1056/cloudfront/code/rocketmq.cpp @@ -756,7 +756,7 @@ bool parseJsonMessageUD(const std::string& json_str, const std::string& output_d //int procNum = item.value("maxProcessNum", -1); //json_data.maxProcessNum = std::to_string(procNum); - //json_data.addr_str = item.value("ip", ""); + json_data.mac = item.value("ip", ""); //json_data.port = item.value("port", ""); //json_data.timestamp = item.value("updateTime", ""); json_data.Righttime = item.value("Righttime", ""); diff --git a/LFtid1056/dealMsg.cpp b/LFtid1056/dealMsg.cpp index 2c5116e..16e6f95 100644 --- a/LFtid1056/dealMsg.cpp +++ b/LFtid1056/dealMsg.cpp @@ -817,7 +817,7 @@ void process_received_message(string mac, string id,const char* data, size_t len //提取数据 const uint8_t* data_ptr = parser.RecvData.data() + 14; - size_t data_size = parser.RecvData.size() - 14; + size_t data_size = parser.RecvData.size() - 14; // 如果是第一帧,记录文件名 if (current_frame == 1) { @@ -873,10 +873,43 @@ void process_received_message(string mac, string id,const char* data, size_t len file_data.size()); std::cout << "File saved: " << file_path << std::endl; + //调试用 + // 若是 .cfg,先查看并打印内容(限长) + { + auto dot = file_path.find_last_of('.'); + std::string ext = (dot == std::string::npos) ? "" : file_path.substr(dot); + // 转小写比较 + for (auto& c : ext) c = static_cast(std::tolower(static_cast(c))); + if (ext == ".cfg") { + std::ifstream fin(file_path, std::ios::binary); + if (!fin) { + std::cerr << "[CFG] open failed: " << file_path << " (" << std::strerror(errno) << ")\n"; + } else { + // 读取前 8KB 作为预览,避免日志过大 + constexpr size_t kMaxPreview = 8 * 1024; + std::string buf; + buf.resize(kMaxPreview); + fin.read(&buf[0], static_cast(kMaxPreview)); + std::streamsize got = fin.gcount(); + buf.resize(static_cast(got)); + + std::cout << "================ [CFG PREVIEW BEGIN] ================\n"; + std::cout << "path=" << file_path << ", size=" << file_data.size() + << " bytes, preview=" << got << " bytes\n"; + // 直接打印文本预览;如果包含不可见字符,会按原样输出 + std::cout.write(buf.data(), static_cast(buf.size())); + if (static_cast(got) == kMaxPreview && fin.peek() != EOF) { + std::cout << "\n...[truncated]\n"; + } + std::cout << "\n================ [CFG PREVIEW END] ==================\n"; + } + } + } + //使用接口上送文件lnk20250826 std::string filename; - SendFileWebAuto(id, file_path, file_path, filename);//如果是补招文件的下载,下载后也是直接上传,上传成功后更新补招状态即可 - std::cout << "File upload: " << filename << std::endl; + //SendFileWebAuto(id, file_path, file_path, filename);//如果是补招文件的下载,下载后也是直接上传,这样单纯补招波形也可以保证传文件 + std::cout << "File download success wait for upload: " << filename << std::endl; //通知文件上传 on_device_response_minimal(static_cast(ResponseCode::OK), id, 0, static_cast(DeviceState::READING_FILEDATA)); @@ -889,7 +922,7 @@ void process_received_message(string mac, string id,const char* data, size_t len } //当前文件下载完毕,调整为空闲处理下一项工作(如果这里后续有新文件等待下载,一般已经存入等待队列等候处理了,调成空闲状态后直接就会开始新文件的下载工作) - ClientManager::instance().change_device_state(id, DeviceState::READING_FILEDATA); + ClientManager::instance().change_device_state(id, DeviceState::IDLE); } } @@ -2080,9 +2113,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,""); @@ -2092,9 +2125,6 @@ void process_received_message(string mac, string id,const char* data, size_t len recordlist.push_back(record); } - - - //暂时移除CRC校验相关 //// ========== 新增 CRC 验证 ==========