add qvvr interface

This commit is contained in:
lnk
2025-08-05 20:00:20 +08:00
parent 0eab1e6fb5
commit afc079465e
3 changed files with 151 additions and 70 deletions

View File

@@ -2703,7 +2703,26 @@ bool extract_timestamp_from_cfg_file(const std::string& cfg_path, long long& sta
return start_tm > 0 && trig_tm > 0; return start_tm > 0 && trig_tm > 0;
} }
bool compare_qvvr_and_file(const std::string& cfg_path, const std::vector<qvvr_data>& data_list) {
long long start_tm = 0;
long long trig_tm = 0;
// 提取 .cfg 文件中的时间戳
if (!extract_timestamp_from_cfg_file(cfg_path, start_tm, trig_tm)) {
std::cerr << "Failed to extract timestamp from cfg file: " << cfg_path << "\n";
return false;
}
// 遍历所有暂态事件,查找与 trig_tm 匹配的
for (const auto& data : data_list) {
long long diff = static_cast<long long>(data.QVVR_time) - trig_tm;
if (std::abs(diff) <= 1) {
return true;
}
}
return false;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////数据转换函数 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////数据转换函数
// DataArrayItem to_json // DataArrayItem to_json
void to_json(nlohmann::json& j, const DataArrayItem& d) { void to_json(nlohmann::json& j, const DataArrayItem& d) {
@@ -2844,7 +2863,7 @@ bool assign_qvvr_file_list(const std::string& id, ushort nCpuNo, const std::vect
qfile.is_download = false; qfile.is_download = false;
qfile.is_pair = false; qfile.is_pair = false;
qfile.file_time_count = 0; qfile.file_time_count = 0;
qfile.file_start =false; qfile.used_status =true;
// 添加到唯一的 qvvrevent // 添加到唯一的 qvvrevent
monitor.qvvrevent.qvvrfile.push_back(std::move(qfile)); //记录暂态文件组 monitor.qvvrevent.qvvrfile.push_back(std::move(qfile)); //记录暂态文件组
@@ -2861,72 +2880,158 @@ bool assign_qvvr_file_list(const std::string& id, ushort nCpuNo, const std::vect
} }
////////////////////////////////////////////////////////////////////////////////////////////////////////////////下载成功通知 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////下载成功通知
//提取下载路径的文件名
std::string extract_filename(const std::string& path) {
size_t pos = path.find_last_of("/\\");
return (pos != std::string::npos) ? path.substr(pos + 1) : path;
}
//发送匹配的所有录波文件
bool SendAllQvvrFiles(qvvr_file& qfile, std::string& out_wavepath) {
std::vector<std::string> wavepaths;
std::string first_wavepath;
bool send_success = true;
for (const auto& file_localpath : qfile.file_download) {
std::string file_cloudpath = "comtrade/" + file_localpath;
std::string wavepath_result;
// 发送本地文件到远端,返回 wavepath
SOEFileWeb(const_cast<std::string&>(file_localpath), file_cloudpath, wavepath_result);
// 如果失败,重发一次
if (wavepath_result.empty()) {
std::cerr << "[SOEFileWeb] Warning: first send failed for file: " << file_localpath << ", retrying...\n";
SOEFileWeb(const_cast<std::string&>(file_localpath), file_cloudpath, wavepath_result);
}
if (wavepath_result.empty()) {
send_success = false;
std::cerr << "[SOEFileWeb] Failed: wavepath empty for file: " << file_localpath << "\n";
break;
}
if (wavepaths.empty()) {
first_wavepath = wavepath_result;
} else if (wavepath_result != first_wavepath) {
send_success = false;
std::cerr << "[SOEFileWeb] Mismatch wavepath: " << wavepath_result
<< " vs " << first_wavepath << "\n";
break;
}
wavepaths.push_back(wavepath_result);
}
// 检查数量是否一致
if (!send_success || wavepaths.size() != qfile.file_download.size()) {
std::cerr << "[SOEFileWeb] Failed to send all qvvr files. "
<< "Sent: " << wavepaths.size()
<< ", Expected: " << qfile.file_download.size() << "\n";
return false;
}
out_wavepath = first_wavepath; // 返回统一的 wavepath
std::cout << "[SOEFileWeb] Success: all files sent for qfile. Wavepath = "
<< first_wavepath << "\n";
return true;
}
//文件下载结束接口
bool update_qvvr_file_download(const std::string& filename_with_mac, const std::string& terminal_id) { bool update_qvvr_file_download(const std::string& filename_with_mac, const std::string& terminal_id) {
// 去除 mac 路径前缀 // 去除 mac 路径前缀,仅保留文件名
size_t pos = filename_with_mac.find_last_of("/\\"); std::string filename = extract_filename(filename_with_mac);
std::string filename = (pos != std::string::npos) ? filename_with_mac.substr(pos + 1) : filename_with_mac;
// 提取逻辑序号(如 PQM1 → 1 // 提取逻辑序号(如 PQM1 → 1
size_t under_pos = filename.find('_'); size_t under_pos = filename.find('_');
if (under_pos == std::string::npos) return false; if (under_pos == std::string::npos) return false;
std::string type_part = filename.substr(0, under_pos); //PQMonitor_PQM1 std::string type_part = filename.substr(0, under_pos); // PQMonitor_PQM1
size_t num_start = type_part.find_last_not_of("0123456789"); size_t num_start = type_part.find_last_not_of("0123456789");
if (num_start == std::string::npos || num_start + 1 >= type_part.size()) return false; if (num_start == std::string::npos || num_start + 1 >= type_part.size()) return false;
std::string seq_str = type_part.substr(num_start + 1); std::string seq_str = type_part.substr(num_start + 1);
ushort logical_seq = static_cast<ushort>(std::stoi(seq_str)); ushort logical_seq = static_cast<ushort>(std::stoi(seq_str)); // 逻辑序号
for (auto& dev : terminal_devlist) { for (auto& dev : terminal_devlist) {
if (dev.terminal_id != terminal_id) continue; if (dev.terminal_id != terminal_id) continue;
for (auto& monitor : dev.line) { for (auto& monitor : dev.line) {
try { try {
// 将监测点台账中的 logical_device_seq 转换为数字进行匹配
ushort monitor_seq = static_cast<ushort>(std::stoi(monitor.logical_device_seq)); ushort monitor_seq = static_cast<ushort>(std::stoi(monitor.logical_device_seq));
if (monitor_seq != logical_seq) continue; if (monitor_seq != logical_seq) continue;
} catch (...) { } catch (...) {
continue; continue; // logical_device_seq 非法,跳过
} }
// 匹配监测点下 qvvrfile 中的 file_name // 匹配监测点下 qvvrfile 中的 file_name
for (auto& qfile : monitor.qvvrevent.qvvrfile) { for (auto& qfile : monitor.qvvrevent.qvvrfile) {
// file_name 中是文件名,需与提取的 filename 比较
auto it = std::find(qfile.file_name.begin(), qfile.file_name.end(), filename); auto it = std::find(qfile.file_name.begin(), qfile.file_name.end(), filename);
if (it != qfile.file_name.end()) { if (it != qfile.file_name.end()) {
// 添加到 file_download去重 // 添加到 file_download记录完整路径,避免重复
if (std::find(qfile.file_download.begin(), qfile.file_download.end(), filename) == qfile.file_download.end()) { if (std::find(qfile.file_download.begin(), qfile.file_download.end(), filename_with_mac) == qfile.file_download.end()) {
qfile.file_download.push_back(filename); qfile.file_download.push_back(filename_with_mac);
} }
qfile.file_time_count = 0; qfile.file_time_count = 0; // 文件下载开始后,计时归零
qfile.file_start = true; //开始下载文件
// 检查 file_download 是否与 file_name 完全一致(集合相同)//每次下载都会对比 // file_download 中是完整路径,需提取文件名后与 file_name 做集合比较
std::set<std::string> s_name(qfile.file_name.begin(), qfile.file_name.end()); std::set<std::string> s_name(qfile.file_name.begin(), qfile.file_name.end());
std::set<std::string> s_down(qfile.file_download.begin(), qfile.file_download.end()); std::set<std::string> s_down;
for (const auto& path : qfile.file_download) {
s_down.insert(extract_filename(path)); // 提取每个路径中的文件名
}
// 检查 file_download 是否与 file_name 完全一致(集合相同)
if (s_name == s_down) { if (s_name == s_down) {
qfile.is_download = true; //全部下载完成 qfile.is_download = true; // 全部下载完成
// 找到其中的 .cfg 文件进行匹配 // 找到其中的 .cfg 文件进行匹配
for (const auto& f : qfile.file_download) { for (const auto& fpath : qfile.file_download) {
if (f.size() >= 4 && f.substr(f.size() - 4) == ".cfg") { std::string fname = extract_filename(fpath);
if (compare_qvvr_and_file(f)) {//提取文件时标和监测点事件的时标匹配 if (fname.size() >= 4 && fname.substr(fname.size() - 4) == ".cfg") {
qfile.is_pair = true; // 提取文件时标和监测点事件的时标匹配
//发送所有文件 if (compare_qvvr_and_file(fpath, monitor.qvvrevent.qvvrdata)) {
qfile.is_pair = true; // 文件与事件匹配成功
//发送暂态事件 // 发送所有文件(已下载完成)
std::string wavepath;
if (SendAllQvvrFiles(qfile, wavepath)) {
//文件发送成功后更新事件
transfer_json_qvvr_data(terminal_id,
logical_seq,
monitor.qvvrevent.qvvrdata.QVVR_Amg,
monitor.qvvrevent.qvvrdata.QVVR_PerTime,
monitor.qvvrevent.qvvrdata.QVVR_time,
monitor.qvvrevent.qvvrdata.QVVR_type,
monitor.qvvrevent.qvvrdata.phase,
wavepath);
// 清除暂态数据
monitor.qvvrevent.qvvrfile.clear();
monitor.qvvrevent.qvvrdata.clear();
// 删除上传成功的文件
for (const auto& uploaded_file : qfile.file_download) {
if (std::remove(uploaded_file.c_str()) != 0) {
std::cerr << "[Cleanup] Failed to delete file: " << uploaded_file << "\n";
} else {
std::cout << "[Cleanup] Deleted uploaded file: " << uploaded_file << "\n";
}
}
}
} }
break; // 只处理第一个 cfg 文件 break; // 只处理第一个 cfg 文件
} }
} }
} }
return true; // 当前文件处理成功
return true;
} }
} }
} }
} }
return false; // 未匹配到终端ID或逻辑序号对应的监测点
return false;
} }

View File

@@ -1149,32 +1149,24 @@ static void scanAndResendOfflineFiles(const std::string& dirPath)
} }
} }
int transfer_json_qvvr_data(unsigned int func_type, int monitor_id, int transfer_json_qvvr_data(const std::string& dev_id, ushort monitor_id,
double mag, double dur, long long start_tm, long long end_tm, int dis_kind, double mag, double dur, long long start_tm, int dis_kind,int phase,
const std::string& uuid_cfg, const std::string& uuid_dat, const std::string& wavepath) {
const std::string& mp_id, const std::string& Qvvr_rptname, const std::string& devtype) {
// 监测点日志的 key, lnk20250526 // 监测点日志的 key, lnk20250526
std::string full_key_m_c = "monitor." + mp_id + ".COM"; std::string full_key_m_c = "monitor." + std::to_string(monitor_id) + ".COM";
std::string full_key_m_d = "monitor." + mp_id + ".DATA"; std::string full_key_m_d = "monitor." + std::to_string(monitor_id) + ".DATA";
// 监测点日志的 key, lnk20250526 // 监测点日志的 key, lnk20250526
// 获取装置类型的映射配置 if (dev_id.empty()) {
XmlConfig c_xmlcfg; std::cout << "dev_id is null" << std::endl;
if (xmlinfo_list.count(devtype)) {
c_xmlcfg = xmlinfo_list[devtype]->xmlcfg;
} else {
c_xmlcfg = xmlcfg;
}
if (mp_id.empty()) {
std::cout << "mp_id is null" << std::endl;
return 0; return 0;
} }
// 构造 JSON 对象 // 构造 JSON 对象
json root; json root;
root["monitorId"] = mp_id; root["devId"] = dev_id;
root["CpuNo"] = monitor_id;
root["amplitude"] = mag; root["amplitude"] = mag;
root["duration"] = dur; root["duration"] = dur;
root["eventType"] = dis_kind; root["eventType"] = dis_kind;
@@ -1189,21 +1181,9 @@ int transfer_json_qvvr_data(unsigned int func_type, int monitor_id,
std::string start_time_str = start_time_stream.str(); std::string start_time_str = start_time_stream.str();
root["startTime"] = start_time_str; root["startTime"] = start_time_str;
root["wavePath"] = uuid_dat; //接口提供了两个文件名入参,实际上名字一样只用一个,可优化 root["wavePath"] = wavepath;
root["phase"] = phase;
if (c_xmlcfg.WavePhasicFlag == "1") { //映射配置分相
if (Qvvr_rptname.find(c_xmlcfg.WavePhasicA) != std::string::npos) {
root["phase"] = "A";
} else if (Qvvr_rptname.find(c_xmlcfg.WavePhasicB) != std::string::npos) {
root["phase"] = "B";
} else if (Qvvr_rptname.find(c_xmlcfg.WavePhasicC) != std::string::npos) {
root["phase"] = "C";
} else {
root["phase"] = "unknow";
}
} else {
root["phase"] = "unknow"; //不分相
}
std::string json_string = root.dump(4); std::string json_string = root.dump(4);
std::cout << json_string << std::endl; std::cout << json_string << std::endl;
@@ -1212,7 +1192,7 @@ int transfer_json_qvvr_data(unsigned int func_type, int monitor_id,
std::string response; std::string response;
SendJsonAPI_web(WEB_EVENT, "", json_string, response); SendJsonAPI_web(WEB_EVENT, "", json_string, response);
// ================ 插入新功能 ========================= // ================ 暂态重发功能 =========================
if (!response.empty()) { if (!response.empty()) {
try { try {
json j_r = json::parse(response); json j_r = json::parse(response);
@@ -1254,13 +1234,7 @@ int transfer_json_qvvr_data(unsigned int func_type, int monitor_id,
void qvvr_test() void qvvr_test()
{ {
char uuid_cfg[] = {"/comtrade/"};
char uuid_dat[] = {"/comtrade/"};
char mp_id[] = {"qvvrtest123"};
char Qvvr_rptname[] = {"unknow"};
char devtype[] = {"01"};
transfer_json_qvvr_data(1, 123456789, 220, 180, 1730894400.123, 1730894580, 1210001,uuid_cfg,uuid_dat,mp_id,Qvvr_rptname,devtype);
} }
/////////////////////////////////////////////////////////////////////////////////////////////////////////通用接口响应 /////////////////////////////////////////////////////////////////////////////////////////////////////////通用接口响应

View File

@@ -52,7 +52,7 @@ public:
//录波文件和暂态事件 //录波文件和暂态事件
class qvvr_data class qvvr_data
{ {
int used_status; //是否占用 bool used_status; //是否占用
int QVVR_type; //暂态类型 int QVVR_type; //暂态类型
uint64_t QVVR_time; //暂态开始时间 unsigned longlong uint64_t QVVR_time; //暂态开始时间 unsigned longlong
double QVVR_PerTime; //暂态持续时间 double QVVR_PerTime; //暂态持续时间
@@ -62,7 +62,7 @@ class qvvr_data
class qvvr_file class qvvr_file
{ {
bool file_start; bool used_status;
int file_time_count; //组内文件下载时间计数第一个文件下载后十分钟内如果其他文件没下载全或者下载全了没匹配事件则将已下载的文件都移到备份区comtrade_bak int file_time_count; //组内文件下载时间计数第一个文件下载后十分钟内如果其他文件没下载全或者下载全了没匹配事件则将已下载的文件都移到备份区comtrade_bak
bool is_download; //文件是否下载完全,最后一个文件下载成功后对比成功则更新这个标志 bool is_download; //文件是否下载完全,最后一个文件下载成功后对比成功则更新这个标志
bool is_pair; //文件是否和事件匹配从comtrade/mac/路径下取file_download中的cfg文件提取时间和持续时间来匹配匹配后接口发送这组file_download全部文件发送成功后删除这组文件然后更新事件中的文件列表 bool is_pair; //文件是否和事件匹配从comtrade/mac/路径下取file_download中的cfg文件提取时间和持续时间来匹配匹配后接口发送这组file_download全部文件发送成功后删除这组文件然后更新事件中的文件列表
@@ -454,14 +454,16 @@ std::string generate_json( //构造装置主动上送数据的报文
const std::vector<DataArrayItem>& dataArray //数据数组。 const std::vector<DataArrayItem>& dataArray //数据数组。
); );
int transfer_json_qvvr_data(unsigned int func_type, int monitor_id, //暂态事件接口 //暂态事件接口
double mag, double dur, long long start_tm, long long end_tm, int dis_kind, int transfer_json_qvvr_data(const std::string& dev_id, ushort monitor_id,
const std::string& uuid_cfg, const std::string& uuid_dat, double mag, double dur, long long start_tm, int dis_kind,int phase,
const std::string& mp_id, const std::string& Qvvr_rptname, const std::string& devtype); const std::string& wavepath);
//录波文件目录接口 //录波文件目录接口
bool assign_qvvr_file_list(const std::string& id, ushort nCpuNo, const std::vector<std::string>& file_list_raw);
//录波文件下载完成通知接口 //录波文件下载完成通知接口
bool update_qvvr_file_download(const std::string& filename_with_mac, const std::string& terminal_id);
////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////