fix recall

This commit is contained in:
lnk
2025-10-30 20:57:19 +08:00
parent 06a2f3a75b
commit 5e63adc8f9
5 changed files with 509 additions and 69 deletions

View File

@@ -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<uint64_t>(midnight_sec) * 1000ULL;
}
// 获取“本地前一天 00:00:00”的毫秒时间戳
static inline uint64_t get_local_midnight_ms_prev_day() {
uint64_t today_ms = get_local_midnight_ms_today();
const uint64_t one_day_ms = 24ULL * 60ULL * 60ULL * 1000ULL;
return today_ms - one_day_ms;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////配置文件读取
//将 G_TEST_LIST 按逗号分割,填充 TESTARRAY
@@ -1147,12 +1170,22 @@ static std::vector<std::string> 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<long long>(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<std::mutex> lock2(ledgermtx); // 复用与 dt==1 相同的加锁粒度
// 找终端
terminal_dev* dev_nc = NULL;
for (auto& d : terminal_devlist) if (d.terminal_id == terminalId) { dev_nc = &d; break; }
if (!dev_nc) { /* 理论上前面已校验,这里兜底 */ continue; }
// 找监测点
ledger_monitor* lm = NULL;
for (auto itLm = dev_nc->line.begin(); itLm != dev_nc->line.end(); ++itLm) {
if (!itLm->monitor_id.empty() && itLm->monitor_id == monId) { lm = &(*itLm); break; }
}
if (!lm) { std::cout << "monitorId未在terminal内找到(直下事件回灌): " << monId << " @ " << terminalId << std::endl; continue; }
// 将 timeList 的每个时间点映射为 [该整点, 下一整点) 的一小时窗口
for (const auto& t : rec["timeList"]) {
if (!t.is_string()) continue;
std::string tstr = t.get<std::string>();
long long ep = parse_time_to_epoch(tstr); // 需与系统时间解析格式一致(已有同名函数)
if (ep < 0) {
std::cout << "[direct->event] parse_time_to_epoch fail: " << tstr << std::endl;
continue;
}
long long hour_start = ep - (ep % 3600);
long long hour_end = hour_start + 3600 - 1; // 与 Get_Recall_Time_Char 产物风格保持一致(闭区间)
RecallMonitor rm;
rm.recall_guid = guid;
rm.recall_status = 0;
rm.StartTime = epoch_to_datetime_str(hour_start);
rm.EndTime = epoch_to_datetime_str(hour_end);
rm.STEADY = "0"; // 与 dt==1 一致:事件(波形)
rm.VOLTAGE = "1"; // 与 dt==1 一致
lm->recall_list.push_back(rm);
}
}
// ★新增dt==2 同步生成“按小时”的事件补招到 recall_list与 dt==1 逻辑一致)——结束
//根据时间戳单独补招事件
for (const auto& t : rec["timeList"]) {
if (!t.is_string()) continue;
std::string ts_compact = compact_ts_for_filename(t.get<std::string>());
if (ts_compact.empty()) { std::cout << "时间解析失败: " << t << std::endl; continue; }
@@ -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<qvvr_data>& data_list,qvvr_data& matched_data) {
bool compare_qvvr_and_file(const std::string& cfg_path, std::vector<qvvr_data>& data_list,qvvr_data& matched_data) {
long long start_tm = 0;
long long trig_tm = 0;
@@ -2845,9 +2923,12 @@ bool compare_qvvr_and_file(const std::string& cfg_path, const std::vector<qvvr_d
std::cout << "[调试] 提取到的起始时间戳: " << start_tm << ", 触发时间戳: " << trig_tm << "\n";
// 遍历所有暂态事件,查找与 trig_tm 匹配的
for (const auto& data : data_list) {
for (auto& data : data_list) {
long long diff = static_cast<long long>(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<std::mutex> lk(ledgermtx); // ★新增:加锁保护台账
size_t total_removed = 0;
for (auto& dev : terminal_devlist) {
for (auto& lm : dev.line) {
auto& vec = lm.qvvrevent.qvvrdata;
const size_t before = vec.size();
// 删除条件:未配对 且 发生时间 < 昨天 00:00:00本地时区毫秒
vec.erase(std::remove_if(vec.begin(), vec.end(),
[&](const qvvr_data& d){
return (d.is_pair == false) && (d.QVVR_time < cutoff_ms);
}),
vec.end());
const size_t removed = before - vec.size();
if (removed > 0) {
total_removed += removed;
std::cout << "[cleanup_qvvr] dev=" << dev.terminal_id
<< " mon=" << lm.monitor_id
<< " removed=" << removed
<< " cutoff_ms=" << cutoff_ms
<< " (prev 00:00 local)" << std::endl;
}
}
}
std::cout << "[cleanup_qvvr] total_removed=" << total_removed << std::endl;
}
/////////////////////////////////////////////////////////////////////////////////////////定值信息发送接口函数
bool save_set_value(const std::string &dev_id, unsigned char mp_index, const std::vector<float> &fabsf) {
std::lock_guard<std::mutex> lock(ledgermtx);
@@ -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<RecallTask> tasks; // 本轮要发送的“每终端一条”(目录请求 或 文件下载 请求)
// ★修改开始:新增“待上传动作”容器与两个小工具(局部作用域,函数私有)
struct PendingUpload {
std::string terminal_id;
unsigned short logical_seq = 0;
std::vector<std::string> files_to_send; // 完整路径含MAC目录
qvvr_data matched{}; // 与 .cfg 匹配到的事件(如有)
bool has_matched = false;
// 用于回锁后在台账中定位并删掉对应 qvvr_file 组
std::set<std::string> sig_names; // 仅文件名集合
std::set<std::string> sig_downs; // 完整路径集合(与 files_to_send 相同内容的 set
};
auto ExtractFileNames = [](const std::vector<std::string>& full_paths) {
std::vector<std::string> names;
names.reserve(full_paths.size());
for (const auto& p : full_paths) {
auto s = sanitize(p);
size_t pos = s.find_last_of("/\\");
std::string name = (pos == std::string::npos) ? s : s.substr(pos + 1);
names.push_back(sanitize(name));
}
std::sort(names.begin(), names.end());
names.erase(std::unique(names.begin(), names.end()), names.end());
return names;
};
auto MakeSigNames = [&](const std::vector<std::string>& full_paths){
auto names = ExtractFileNames(full_paths);
return std::set<std::string>(names.begin(), names.end());
};
// ★修改结束
std::vector<PendingUpload> pending_uploads; // ★修改:锁外执行上传与清理
{ //锁作用域
std::lock_guard<std::mutex> lock(ledgermtx);
// ★优先级开始:事件优先 —— 若任一测点存在事件补招recall_list尚未清空则本轮跳过文件补招
{
bool has_event_pending = false;
for (const auto& dev : terminal_devlist) {
for (const auto& lm : dev.line) {
if (!lm.recall_list.empty()) { // 只要存在事件待处理就让位
has_event_pending = true;
break;
}
}
if (has_event_pending) break;
}
if (has_event_pending) {
std::cout << "[check_recall_file] skip this round: event recall pending, give priority to check_recall_event()\n";
return; // 直接让位给 check_recall_event本轮不处理文件补招
}
}
// ★优先级结束
for (auto& dev : terminal_devlist) {
// 仅处理“正在补招/空闲”终端,与你原逻辑一致
if (dev.busytype != static_cast<int>(DeviceState::READING_STATSFILE) &&
@@ -4778,6 +4949,19 @@ void check_recall_file() {
} else {
// 无目录可查
front.recall_status = static_cast<int>(RecallStatus::FAILED);
std::string msg_fail;
if (front.direct_mode) {
msg_fail = std::string("监测点:") + lm.monitor_name
+ " 补招波形文件未找到,目标时标:"
+ front.target_filetimes;
} else {
msg_fail = std::string("监测点:") + lm.monitor_name
+ " 补招波形文件未找到,时间范围:"
+ front.StartTime + " ~ " + front.EndTime;
}
append_recall_record_line(dev.guid, lm.monitor_id, msg_fail);
std::cout << "[check_recall_stat] no dir candidates, FAIL dev=" << dev.terminal_id
<< " monitor=" << lm.monitor_id << std::endl;
}
@@ -4812,7 +4996,17 @@ void check_recall_file() {
// 所有目录都失败
front.recall_status = static_cast<int>(RecallStatus::FAILED);
std::string msg_fail;
if (front.direct_mode) {
msg_fail = std::string("监测点:") + lm.monitor_name
+ " 补招波形文件未找到,目标时标:"
+ front.target_filetimes;
} else {
msg_fail = std::string("监测点:") + lm.monitor_name
+ " 补招波形文件未找到,时间范围:"
+ front.StartTime + " ~ " + front.EndTime;
}
append_recall_record_line(dev.guid, lm.monitor_id, msg_fail);
std::cout << "[check_recall_stat] all dir failed, FAIL dev=" << dev.terminal_id
<< " monitor=" << lm.monitor_id << std::endl;
@@ -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/<dev.addr_str>/<fname>
front.required_files.clear();
{
const std::string base_dir = std::string("download/") + sanitize(dev.addr_str);
for (const auto& p : front.download_queue) {
// p 形如 "<cur_dir>/<fname>" —— 提取纯文件名
std::string fname = sanitize(extract_filename1(p));
if (!fname.empty()) {
front.required_files.insert(base_dir + "/" + fname);
}
}
}
front.file_success.clear();
front.phase = RecallPhase::DOWNLOADING;
@@ -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<int>(RecallStatus::DONE);
front.recall_status = static_cast<int>(RecallStatus::DONE);//两个文件都下好了标记为成功
//更新事件
// ★修改开始替换“assign_qvvr_file_list + update_qvvr_file_download(有锁)”
// 组装完整路径列表
std::vector<std::string> fullFilenames;
fullFilenames.reserve(front.required_files.size());
for (const auto& f : front.required_files) fullFilenames.push_back(f);
// 仅对“当前监测点”添加一组 qvvr_filefile_name=仅文件名,下载标记留到下行)
{
qvvr_file qfile;
auto names = ExtractFileNames(fullFilenames);
qfile.file_name.assign(names.begin(), names.end());
qfile.is_download = false;
qfile.is_pair = false;
qfile.file_time_count = 0;
qfile.used_status = true;
lm.qvvrevent.qvvrfile.push_back(std::move(qfile));
}
// 把这组标记为“已全部下载”,并尝试用 .cfg 匹配事件;准备锁外上传动作
{
// 找到刚刚 append 的那组file_name 集合 == fullFilenames 的文件名集合
auto want_names = MakeSigNames(fullFilenames);
auto it_qf = std::find_if(lm.qvvrevent.qvvrfile.begin(),
lm.qvvrevent.qvvrfile.end(),
[&](const qvvr_file& x){
std::set<std::string> s(x.file_name.begin(), x.file_name.end());
return s == want_names;
});
if (it_qf != lm.qvvrevent.qvvrfile.end()) {
// 原it_qf->file_download = fullFilenames;
it_qf->file_download.clear();
it_qf->file_download.assign(fullFilenames.begin(), fullFilenames.end()); // ★修复vector -> list
it_qf->is_download = true;
// 寻找第一个 .cfg 做匹配
qvvr_data matched{};
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<unsigned short>(std::stoi(lm.logical_device_seq)); }
catch (...) { pu.logical_seq = 0; }
// 原pu.files_to_send = it_qf->file_download;
pu.files_to_send.clear();
pu.files_to_send.assign(it_qf->file_download.begin(), it_qf->file_download.end()); // ★修复list -> vector
pu.has_matched = has_matched;
pu.matched = matched;
pu.sig_names = want_names;
pu.sig_downs = std::set<std::string>(pu.files_to_send.begin(), pu.files_to_send.end());
pending_uploads.push_back(std::move(pu));
}
}
// ★修改结束
std::cout << "[check_recall_stat] DONE dev=" << dev.terminal_id
<< " monitor=" << lm.monitor_id
<< " ok=" << front.file_success.size()
@@ -5079,6 +5360,19 @@ void check_recall_file() {
<< " end=" << front.EndTime << std::endl;
} else {
front.recall_status = static_cast<int>(RecallStatus::FAILED); //目录列表空,失败
std::string msg_fail;
if (front.direct_mode) {
msg_fail = std::string("监测点:") + lm.monitor_name
+ " 补招波形文件未找到,目标时标:"
+ front.target_filetimes;
} else {
msg_fail = std::string("监测点:") + lm.monitor_name
+ " 补招波形文件未找到,时间范围:"
+ front.StartTime + " ~ " + front.EndTime;
}
append_recall_record_line(dev.guid, lm.monitor_id, msg_fail);
std::cout << "[check_recall_stat] empty dir_candidates, FAIL dev=" << dev.terminal_id
<< " monitor=" << lm.monitor_id << std::endl;
}
@@ -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<std::mutex> lk(ledgermtx);
for (auto& dev : terminal_devlist) {
if (dev.terminal_id != pu.terminal_id) continue;
for (auto& lm : dev.line) {
unsigned short seq = 0;
try { seq = static_cast<unsigned short>(std::stoi(lm.logical_device_seq)); } catch (...) { seq = 0; }
if (seq != pu.logical_seq) continue;
// 定位并删除相同签名的组
auto it_qf = std::find_if(lm.qvvrevent.qvvrfile.begin(),
lm.qvvrevent.qvvrfile.end(),
[&](const qvvr_file& x){
std::set<std::string> n(x.file_name.begin(), x.file_name.end());
std::set<std::string> d(x.file_download.begin(), x.file_download.end());
return n==pu.sig_names && d==pu.sig_downs;
});
if (it_qf != lm.qvvrevent.qvvrfile.end()) {
lm.qvvrevent.qvvrfile.erase(it_qf);
} else {
std::cerr << "[Cleanup] qvvrfile changed; target group not found, skip erase\n";
}
// 如有匹配事件,按时间删除
if (pu.has_matched) {
auto it_evt = std::find_if(lm.qvvrevent.qvvrdata.begin(),
lm.qvvrevent.qvvrdata.end(),
[&](const qvvr_data& d){ return d.QVVR_time == pu.matched.QVVR_time; });
if (it_evt != lm.qvvrevent.qvvrdata.end()) {
lm.qvvrevent.qvvrdata.erase(it_evt);
}
}
}
}
}
} else {
std::cerr << "[check_recall_file][Upload] SendAllQvvrFiles failed for terminal="
<< pu.terminal_id << " seq=" << pu.logical_seq << std::endl;
}
}
// ★修改结束
// ★说明:本函数不主动把 RUNNING 改 DONE/FAILED
// 由“其他线程”在目录/下载结果返回后,找到对应 dev/monitor 的 lm.recall_list_static.front()
// 写入:
@@ -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<int>(f.recall_status)
<< " phase=" << static_cast<int>(f.phase)
<< std::endl;
if (f.recall_status == static_cast<int>(RecallStatus::RUNNING) &&
f.phase == RecallPhase::DOWNLOADING) {
matched_monitor = &lm;
@@ -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<int>(RecallStatus::RUNNING) &&
f.phase == RecallPhase::DOWNLOADING) {
running_front = &f; // 回退成功
running_front = &f;
std::cout << "[RESP][FILEDATA][FALLBACK-CID] dev=" << id
<< " cid=" << static_cast<int>(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<int>(DeviceState::READING_EVENTFILE) ||
bt == static_cast<int>(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;
}
}

View File

@@ -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);
std::string& out_filename);
void cleanup_old_unpaired_qvvr_events();

View File

@@ -85,6 +85,18 @@ std::unique_ptr<T> make_unique(Args&&... args) {
return std::unique_ptr<T>(new T(std::forward<Args>(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++;

View File

@@ -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", "");

View File

@@ -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<char>(std::tolower(static_cast<unsigned char>(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<std::streamsize>(kMaxPreview));
std::streamsize got = fin.gcount();
buf.resize(static_cast<size_t>(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<std::streamsize>(buf.size()));
if (static_cast<size_t>(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<int>(ResponseCode::OK), id, 0, static_cast<int>(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 验证 ==========