From 0acc58bbe1f27ee385bcaa162821047a48ff7ac1 Mon Sep 17 00:00:00 2001 From: lnk Date: Thu, 12 Mar 2026 15:28:17 +0800 Subject: [PATCH] add function:upload and download device file ,modify interface function fix memleak --- cfg_parse/cfg_parser.cpp | 451 +++++++++++++++++------------ json/create_json.cpp | 46 +-- json/save2json.cpp | 595 ++++++++++++++++++++++++++++++++++++++- mms/db_interface.h | 15 + mms/mms_process.c | 3 + mms/mmsclient.c | 53 ++++ mms/rdb_client.h | 2 + set_process.sh | 17 +- 8 files changed, 980 insertions(+), 202 deletions(-) diff --git a/cfg_parse/cfg_parser.cpp b/cfg_parse/cfg_parser.cpp index 6775201..7752c8c 100644 --- a/cfg_parse/cfg_parser.cpp +++ b/cfg_parse/cfg_parser.cpp @@ -322,6 +322,11 @@ std::string Topic_Reply_Topic = ""; std::string Topic_Reply_Tag = ""; std::string Topic_Reply_Key = ""; +//lnk20260310添加文件管理的topic和tag +std::string G_MQCONSUMER_TOPIC_FILE = "";//consumer topie +std::string G_MQCONSUMER_TAG_FILE = "";//consumer tag +std::string G_MQCONSUMER_KEY_FILE = "";//consumer key + int G_TEST_FLAG = 0; int G_TEST_NUM = 0; int G_TEST_TYPE = 0; @@ -709,6 +714,16 @@ void init_config() { G_CONNECT_TAG = strdup(ba.data()); ba = settings.value("RocketMq/CONNECTKey", "").toString().toLatin1(); G_CONNECT_KEY = strdup(ba.data()); + + //lnk20260310添加文件管理的topic和tag + ba = settings.value("RocketMq/ConsumerTopicFile", "").toString().toLatin1(); + G_MQCONSUMER_TOPIC_FILE = strdup(ba.data()); + ba = settings.value("RocketMq/ConsumerTagFile", "").toString().toLatin1(); + G_MQCONSUMER_TAG_FILE = strdup(ba.data()); + ba = settings.value("RocketMq/ConsumerKeyFile", "").toString().toLatin1(); + G_MQCONSUMER_KEY_FILE = strdup(ba.data()); + + //MQ测试 G_TEST_FLAG = settings.value("RocketMq/Testflag", 0).toInt(); G_TEST_NUM = settings.value("RocketMq/Testnum", 0).toInt(); @@ -3198,8 +3213,21 @@ size_t req_reply_http(void* contents, size_t size, size_t nmemb, void* userp) { } void SendJsonAPI_web(const std::string& strUrl, const char* code, const std::string& json, char** ptr) { + //【修改点1】先校验 ptr 本身,避免空指针解引用 + if (ptr == NULL) { + printf("SendJsonAPI_web error: ptr is NULL\n"); + return; + } + + //【修改点2】如果上层传进来的 *ptr 非空,先释放并置空,避免旧内存残留 + if (*ptr != NULL) { + free(*ptr); + *ptr = NULL; + } + CURL* curl = curl_easy_init(); CURLcode res; + long http_code = 0; //【修改点3】增加 HTTP 状态码 // 初始化 *ptr 并分配空字符串 *ptr = (char*)malloc(1); @@ -3211,8 +3239,8 @@ void SendJsonAPI_web(const std::string& strUrl, const char* code, const std::str if (curl) { char url[256]; - snprintf(url, sizeof(url), "%s?%s", strUrl.c_str(), code); - //printf(">>>json %s\n", url);//减少不必要的打印 + //【修改点5】避免 code 为 NULL 时 snprintf 异常 + snprintf(url, sizeof(url), "%s?%s", strUrl.c_str(), (code != NULL ? code : "")); curl_easy_setopt(curl, CURLOPT_URL, url); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, req_reply_http); @@ -3220,6 +3248,9 @@ void SendJsonAPI_web(const std::string& strUrl, const char* code, const std::str curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10); curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10); + //【修改点6】建议禁用 signal,防止多线程里超时信号影响别的线程 + curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L); + if (!json.empty()) { curl_easy_setopt(curl, CURLOPT_POST, 1L); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json.c_str()); @@ -3231,9 +3262,31 @@ void SendJsonAPI_web(const std::string& strUrl, const char* code, const std::str res = curl_easy_perform(curl); + //【修改点7】先取 HTTP 状态码 + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + if (res != CURLE_OK) { printf("web failed res code: %s\n", curl_easy_strerror(res)); - } else { + } + //【修改点9】即使 curl 成功,HTTP 非 200 也按失败处理 + /*else if (http_code != 200) { + printf("web http failed, http_code: %ld\n", http_code); + + if (*ptr != NULL) { + free(*ptr); + *ptr = NULL; + } + }*/ + //【修改点10】即使 HTTP 200,但返回内容为空串,也按失败处理 + else if (*ptr == NULL || (*ptr)[0] == '\0') { + printf("web response is empty\n"); + + if (*ptr != NULL) { + free(*ptr); + *ptr = NULL; + } + } + else { //printf(">>> web return str: %s \n", *ptr); } @@ -3241,70 +3294,14 @@ void SendJsonAPI_web(const std::string& strUrl, const char* code, const std::str curl_easy_cleanup(curl); } else { printf(">>> web curl init failed\n"); + + //【修改点11】curl 初始化失败时,也要释放之前分配的空串并置 NULL + if (*ptr != NULL) { + free(*ptr); + *ptr = NULL; + } } } -/*void SendJsonAPI_web(const std::string& strUrl, const char* code, const std::string& json, char** ptr) { - // curl 初始化 - CURL* curl = curl_easy_init(); - CURLcode res; - - if (curl) { - char url[100]; - sprintf(url, "%s?%s", strUrl.c_str(), code); - printf(">>>json %s\n", url); - - // 设置 URL - curl_easy_setopt(curl, CURLOPT_URL, url); - - // 设置数据接收和写入函数 - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, req_reply_web); - - // 数据接收 - std::string resPost0; - curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&resPost0); - - // 设置超时时间 - curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10); - curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10); - - if(json != ""){ - // 设置 HTTP 方法为 POST - curl_easy_setopt(curl, CURLOPT_POST, 1L); - - // 设置 JSON 格式的 body 数据 - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json.c_str()); - } - - // 设置请求头 - struct curl_slist* headers = NULL; - headers = curl_slist_append(headers, "Content-Type: application/json"); - curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); - - // 执行请求 - res = curl_easy_perform(curl); - - // 检查请求是否成功 - if (res != CURLE_OK) { - printf("web failed res code: %s\n", curl_easy_strerror(res)); - } else { - printf(">>> web return str: %s \n", resPost0.c_str()); - - // 分配内存并复制返回结果 - *ptr = (char*)malloc(resPost0.size() + 1); // 分配足够的内存空间 - if (*ptr != NULL) { - strcpy(*ptr, resPost0.c_str()); - } else { - printf("Memory allocation failed!\n"); - } - } - - // 清理请求头 - curl_slist_free_all(headers); - } else { - printf(">>> web curl init failed"); - } - curl_easy_cleanup(curl); -}*/ // 打印 terminal_dev_map 中所有内容的函数 void printTerminalDevMap(const QMap& terminal_dev_map) { @@ -3796,90 +3793,23 @@ int terminal_ledger_web(QMap* terminal_dev_map, cJSON* root = NULL; - // 发送 API 请求 - /*SendJsonAPI_web(WEB_DEVICE, "",parm.c_str(), &ptr); - - if (ptr == NULL) { - std::cerr << "Error: Received NULL response from SendJsonAPI_web." << std::endl; - return 1; - } - - // 调试用 - printf("ptr:%s\n", ptr); - - cJSON* root = cJSON_Parse(ptr); //json格式序列化 - - int retry = 0; - - if (root == NULL) { - printf("web error %s\n", cJSON_GetErrorPtr()); - - //重发多次 - while(root == NULL){ - // 在重试前释放之前的 ptr 以避免内存泄漏 - if (ptr != NULL) { - free(ptr); - ptr = NULL; - } - //测试用url参数 - //SendJsonAPI_web("http://192.168.1.149:8091/powerQuality/getProperties", parm.c_str(), "",&ptr); - SendJsonAPI_web(WEB_DEVICE, "",parm.c_str(), &ptr); - - if(ptr == NULL){ - std::cerr << "Error: Received NULL response from SendJsonAPI_web in retry" << std::endl; - return 1; - } - - root = cJSON_Parse(ptr); - retry++; - if(retry > 3){ - break; - } - } - // 如果重试后仍然失败,确保退出前释放任何已分配的内存 - if (root == NULL) { - printf("web error after 3 retry\n"); - //return 1; // 根据需要返回适当的错误码 - //三次重试过后尝试读取本地台账文件 - char* ledger = NULL; - read_latest_ledger_file(&ledger); - if (ledger != NULL) { - root = cJSON_Parse(ledger); - free(ledger); - } - - //记录上送日志 - - //读取台账文件也失败则启用定时器5分钟等待下一次读取台账和文件,不要退出死掉 - if (root == NULL) { - printf("read local ledger failed, wait 5 min to retry...\n"); - apr_sleep(apr_time_from_sec(300)); // 睡眠 5 分钟 - - // 重新请求 - if (ptr != NULL) { - free(ptr); - ptr = NULL; - } - SendJsonAPI_web(WEB_DEVICE, "", parm.c_str(), &ptr); - if (ptr != NULL) { - root = cJSON_Parse(ptr); - if (root == NULL) { - printf("still failed after sleep retry\n"); - return 1; - } - } else { - printf("no response after sleep retry\n"); - return 1; - } - } - //记录上送日志 - } - }*/ while (1) { + + //防止泄露lnk20260312 + if (root != NULL) { + cJSON_Delete(root); + root = NULL; + } + + if (ptr != NULL) { + free(ptr); + ptr = NULL; + } + // 请求接口 SendJsonAPI_web(WEB_DEVICE, "", parm.c_str(), &ptr); - if (ptr != NULL) { + if (ptr != NULL && ptr[0] != '\0') { // 调试用 printf("ptr:%s\n", ptr); @@ -3909,7 +3839,7 @@ int terminal_ledger_web(QMap* terminal_dev_map, } // 解析失败,尝试重试 - printf("web error %s\n", cJSON_GetErrorPtr()); + //printf("web error %s\n", cJSON_GetErrorPtr()); retry++; if (retry > 3) { @@ -3922,6 +3852,11 @@ int terminal_ledger_web(QMap* terminal_dev_map, } // 读取本地台账文件 + if (root != NULL) { + cJSON_Delete(root); + root = NULL; + } + char* ledger = NULL; read_latest_ledger_file(&ledger); if (ledger != NULL) { @@ -3959,6 +3894,9 @@ int terminal_ledger_web(QMap* terminal_dev_map, retry = 0; // 重置 retry 重新开始循环 continue; } + + //每次重试等2秒 + apr_sleep(apr_time_from_sec(2)); } // 获取 "code" 和 "msg" @@ -4011,64 +3949,79 @@ int terminal_ledger_web(QMap* terminal_dev_map, cJSON* id = cJSON_GetObjectItem(item, "id"); // terminal_id if (id && id->type == cJSON_String) strncpy(dev->terminal_id, id->valuestring, sizeof(dev->terminal_id) - 1); else strncpy(dev->terminal_id, "N/A", sizeof(dev->terminal_id) - 1); + dev->terminal_id[sizeof(dev->terminal_id) - 1] = '\0'; cJSON* ip = cJSON_GetObjectItem(item, "ip"); // addr_str if (ip && ip->type == cJSON_String) strncpy(dev->addr_str, ip->valuestring, sizeof(dev->addr_str) - 1); else strncpy(dev->addr_str, "N/A", sizeof(dev->addr_str) - 1); + dev->addr_str[sizeof(dev->addr_str) - 1] = '\0'; cJSON* terminalCode = cJSON_GetObjectItem(item, "name"); // terminal_code if (terminalCode && terminalCode->type == cJSON_String) strncpy(dev->terminal_code, terminalCode->valuestring, sizeof(dev->terminal_code) - 1); else strncpy(dev->terminal_code, "N/A", sizeof(dev->terminal_code) - 1); + dev->terminal_code[sizeof(dev->terminal_code) - 1] = '\0'; cJSON* orgName = cJSON_GetObjectItem(item, "org_name"); // org_name if (orgName && orgName->type == cJSON_String) strncpy(dev->org_name, orgName->valuestring, sizeof(dev->org_name) - 1); else strncpy(dev->org_name, "N/A", sizeof(dev->org_name) - 1); + dev->org_name[sizeof(dev->org_name) - 1] = '\0'; cJSON* maintName = cJSON_GetObjectItem(item, "maint_name"); // maint_name if (maintName && maintName->type == cJSON_String) strncpy(dev->maint_name, maintName->valuestring, sizeof(dev->maint_name) - 1); else strncpy(dev->maint_name, "N/A", sizeof(dev->maint_name) - 1); + dev->maint_name[sizeof(dev->maint_name) - 1] = '\0'; cJSON* stationName = cJSON_GetObjectItem(item, "stationName"); // station_name if (stationName && stationName->type == cJSON_String) strncpy(dev->station_name, stationName->valuestring, sizeof(dev->station_name) - 1); else strncpy(dev->station_name, "N/A", sizeof(dev->station_name) - 1); + dev->station_name[sizeof(dev->station_name) - 1] = '\0'; cJSON* manufacturer = cJSON_GetObjectItem(item, "manufacturer"); // tmnl_factory if (manufacturer && manufacturer->type == cJSON_String) strncpy(dev->tmnl_factory, manufacturer->valuestring, sizeof(dev->tmnl_factory) - 1); else strncpy(dev->tmnl_factory, "N/A", sizeof(dev->tmnl_factory) - 1); + dev->tmnl_factory[sizeof(dev->tmnl_factory) - 1] = '\0'; cJSON* status = cJSON_GetObjectItem(item, "status"); // tmnl_status if (status && status->type == cJSON_String) strncpy(dev->tmnl_status, status->valuestring, sizeof(dev->tmnl_status) - 1); else strncpy(dev->tmnl_status, "N/A", sizeof(dev->tmnl_status) - 1); + dev->tmnl_status[sizeof(dev->tmnl_status) - 1] = '\0'; cJSON* devType = cJSON_GetObjectItem(item, "devType"); // dev_type if (devType && devType->type == cJSON_String) strncpy(dev->dev_type, devType->valuestring, sizeof(dev->dev_type) - 1); else strncpy(dev->dev_type, "N/A", sizeof(dev->dev_type) - 1); + dev->dev_type[sizeof(dev->dev_type) - 1] = '\0'; cJSON* devKey = cJSON_GetObjectItem(item, "devKey"); // dev_key if (devKey && devKey->type == cJSON_String) strncpy(dev->dev_key, devKey->valuestring, sizeof(dev->dev_key) - 1); else strncpy(dev->dev_key, "N/A", sizeof(dev->dev_key) - 1); + dev->dev_key[sizeof(dev->dev_key) - 1] = '\0'; cJSON* series = cJSON_GetObjectItem(item, "series"); // dev_series if (series && series->type == cJSON_String) strncpy(dev->dev_series, series->valuestring, sizeof(dev->dev_series) - 1); else strncpy(dev->dev_series, "N/A", sizeof(dev->dev_series) - 1); + dev->dev_series[sizeof(dev->dev_series) - 1] = '\0'; //lnk20250210台账进程号 cJSON* processNo = cJSON_GetObjectItem(item, "processNo"); // processNo转为字符串 if (processNo && processNo->type == cJSON_Number) snprintf(dev->processNo, sizeof(dev->processNo), "%d", processNo->valueint); else strncpy(dev->processNo, "N/A", sizeof(dev->processNo) - 1); + dev->processNo[sizeof(dev->processNo) - 1] = '\0'; //20250513进程数量 cJSON* maxProcessNum = cJSON_GetObjectItem(item, "maxProcessNum"); // maxProcessNum转为字符串 if (maxProcessNum && maxProcessNum->type == cJSON_Number) snprintf(dev->maxProcessNum, sizeof(dev->maxProcessNum), "%d", maxProcessNum->valueint); else strncpy(dev->maxProcessNum, "N/A", sizeof(dev->maxProcessNum) - 1); + dev->maxProcessNum[sizeof(dev->maxProcessNum) - 1] = '\0'; cJSON* port = cJSON_GetObjectItem(item, "port"); // port if (port && port->type == cJSON_String) strncpy(dev->port, port->valuestring, sizeof(dev->port) - 1); else strncpy(dev->port, "N/A", sizeof(dev->port) - 1); + dev->port[sizeof(dev->port) - 1] = '\0'; cJSON* updateTime = cJSON_GetObjectItem(item, "updateTime"); // timestamp if (updateTime && updateTime->type == cJSON_String) strncpy(dev->timestamp, updateTime->valuestring, sizeof(dev->timestamp) - 1); else strncpy(dev->timestamp, "N/A", sizeof(dev->timestamp) - 1); + dev->timestamp[sizeof(dev->timestamp) - 1] = '\0'; cJSON* logLevel = cJSON_GetObjectItem(item, "log_level"); // log_level int tmp_level = -1; @@ -4100,29 +4053,35 @@ int terminal_ledger_web(QMap* terminal_dev_map, cJSON* monitor_id = cJSON_GetObjectItem(monitorItem, "id"); // monitor_id if (monitor_id && monitor_id->type == cJSON_String) strncpy(dev->line[j].monitor_id, monitor_id->valuestring, sizeof(dev->line[j].monitor_id) - 1); else strncpy(dev->line[j].monitor_id, "N/A", sizeof(dev->line[j].monitor_id) - 1); + dev->line[j].monitor_id[sizeof(dev->line[j].monitor_id) - 1] = '\0'; cJSON* monitor_name = cJSON_GetObjectItem(monitorItem, "name"); // monitor_name if (monitor_name && monitor_name->type == cJSON_String) strncpy(dev->line[j].monitor_name, monitor_name->valuestring, sizeof(dev->line[j].monitor_name) - 1); else strncpy(dev->line[j].monitor_name, "N/A", sizeof(dev->line[j].monitor_name) - 1); + dev->line[j].monitor_name[sizeof(dev->line[j].monitor_name) - 1] = '\0'; cJSON* lineNo = cJSON_GetObjectItem(monitorItem, "lineNo"); // logical_device_seq if (lineNo && lineNo->type == cJSON_String) strncpy(dev->line[j].logical_device_seq, lineNo->valuestring, sizeof(dev->line[j].logical_device_seq) - 1); else strncpy(dev->line[j].logical_device_seq, "N/A", sizeof(dev->line[j].logical_device_seq) - 1); + dev->line[j].logical_device_seq[sizeof(dev->line[j].logical_device_seq) - 1] = '\0'; cJSON* voltageLevel = cJSON_GetObjectItem(monitorItem, "voltageLevel"); // voltage_level if (voltageLevel && voltageLevel->type == cJSON_String) strncpy(dev->line[j].voltage_level, voltageLevel->valuestring, sizeof(dev->line[j].voltage_level) - 1); else strncpy(dev->line[j].voltage_level, "N/A", sizeof(dev->line[j].voltage_level) - 1); + dev->line[j].voltage_level[sizeof(dev->line[j].voltage_level) - 1] = '\0'; cJSON* ptType = cJSON_GetObjectItem(monitorItem, "ptType"); // terminal_connect if (ptType && ptType->type == cJSON_String) strncpy(dev->line[j].terminal_connect, ptType->valuestring, sizeof(dev->line[j].terminal_connect) - 1); else strncpy(dev->line[j].terminal_connect, "N/A", sizeof(dev->line[j].terminal_connect) - 1); + dev->line[j].terminal_connect[sizeof(dev->line[j].terminal_connect) - 1] = '\0'; // 添加监测点状态 cJSON* monitorstatus = cJSON_GetObjectItem(monitorItem, "status"); // status if (monitorstatus && monitorstatus->type == cJSON_String) strncpy(dev->line[j].status, monitorstatus->valuestring, sizeof(dev->line[j].status) - 1); else strncpy(dev->line[j].status, "N/A", sizeof(dev->line[j].status) - 1); + dev->line[j].status[sizeof(dev->line[j].status) - 1] = '\0'; - cJSON* logLevel_m = cJSON_GetObjectItem(item, "log_level"); // log_level + cJSON* logLevel_m = cJSON_GetObjectItem(monitorItem, "log_level"); // log_level int tmp_level = -1; if (logLevel_m && logLevel_m->type == cJSON_Number) { tmp_level = logLevel_m->valueint; @@ -4134,7 +4093,7 @@ int terminal_ledger_web(QMap* terminal_dev_map, if (tmp_level >= 0 && tmp_level <= 3) { dev->line[j].log_level = tmp_level; } else { - dev->line[j].log_level = 1; // 默认 WARN + dev->line[j].log_level = dev->log_level; // 默认 WARN } printf("line[%d].log_level: %d\n", j, dev->line[j].log_level); @@ -4145,6 +4104,9 @@ int terminal_ledger_web(QMap* terminal_dev_map, // 准备键 QString key = QString(dev->terminal_id);//用id而不是code区分:有的code存在乱码 + //lnk20260312防止内存泄漏 + bool inserted = false; + // 检查是否存在重复键 if (terminal_dev_map->contains(key)) { std::cerr << "Duplicate terminal_code found: " << key.toStdString() << std::endl; @@ -4159,23 +4121,31 @@ int terminal_ledger_web(QMap* terminal_dev_map, if(atoi(dev->processNo) == g_front_seg_index || g_front_seg_index == 0){//lnk20250210匹配进程号 //调试用 std::cout<< "process num match" << std::endl; - terminal_dev_map->insert(key, dev);}//后续修改为只有进程号匹配上index才录入当前进程 + terminal_dev_map->insert(key, dev); + inserted = true; + }//后续修改为只有进程号匹配上index才录入当前进程 } else { // 插入新的 terminal_dev 对象 if(atoi(dev->processNo) == g_front_seg_index || g_front_seg_index == 0){//lnk20250210匹配进程号 //调试用 std::cout<< "process num match" << std::endl; - terminal_dev_map->insert(key, dev);}//后续修改为只有进程号匹配上index才录入当前进程 + terminal_dev_map->insert(key, dev); + inserted = true; + }//后续修改为只有进程号匹配上index才录入当前进程 //调试用 //std::cout << "i = " << i << std::endl; //std::cout << "terminal_dev_map.size:" << terminal_dev_map->size() << std::endl; }//如果出现重复项,日志要有体现方便排查 - + if (!inserted) { + delete dev; + dev = NULL; + } + } //读取台账有效,保存或更新到台账文件20250513lnk - if(g_node_id == STAT_DATA_BASE_NODE_ID && g_front_seg_index == 1){ + if(g_node_id == STAT_DATA_BASE_NODE_ID && g_front_seg_index == 1 && ptr != NULL && ptr[0] != '\0'){ save_ledger_json(ptr); //普通日志,更新本地台账 } @@ -4621,14 +4591,17 @@ int parse_model_web(QMap* icd_model_map,const std::vector* icd_model_map,const std::vector3)break; continue; } root = cJSON_Parse(ptr); - retry++;if(retry>3)break; + retry++; + if(retry>3)break; } if (root == NULL) { + if (ptr != NULL) { + free(ptr); + ptr = NULL; + } printf("web error %s\n", cJSON_GetErrorPtr()); return 1; } @@ -4669,8 +4648,8 @@ int parse_model_web(QMap* icd_model_map,const std::vectorvaluestring : "not found"; - std::string msg = (msgItem != NULL) ? msgItem->valuestring : "not found"; + std::string code = (codeItem != NULL && codeItem->valuestring != NULL) ? codeItem->valuestring : "not found"; + std::string msg = (msgItem != NULL && msgItem->valuestring != NULL) ? msgItem->valuestring : "not found"; // 打印结果 std::cout << "code: " << code << std::endl; @@ -4680,30 +4659,72 @@ int parse_model_web(QMap* icd_model_map,const std::vectortype == cJSON_Array) { cJSON* item; cJSON_ArrayForEach(item, data) { - icd_model* model = new icd_model; + icd_model* model = new icd_model(); cJSON* id = cJSON_GetObjectItem(item, "id");//model_id - if (id && id->type == cJSON_String) strncpy(model->model_id, id->valuestring, sizeof(model->model_id) - 1); - + if (id && id->type == cJSON_String) { + strncpy(model->model_id, id->valuestring, sizeof(model->model_id) - 1); + model->model_id[sizeof(model->model_id) - 1] = '\0'; + } + cJSON* fileName = cJSON_GetObjectItem(item, "fileName");//file_name - if (fileName && fileName->type == cJSON_String) strncpy(model->file_name, fileName->valuestring, sizeof(model->file_name) - 1); + if (fileName && fileName->type == cJSON_String) { + strncpy(model->file_name, fileName->valuestring, sizeof(model->file_name) - 1); + model->file_name[sizeof(model->file_name) - 1] = '\0'; + } cJSON* filePath = cJSON_GetObjectItem(item, "filePath");//新增 - if (filePath && filePath->type == cJSON_String) strncpy(model->file_path, filePath->valuestring, sizeof(model->file_path) - 1); + if (filePath && filePath->type == cJSON_String) { + strncpy(model->file_path, filePath->valuestring, sizeof(model->file_path) - 1); + model->file_path[sizeof(model->file_path) - 1] = '\0'; + } cJSON* devType = cJSON_GetObjectItem(item, "devType");//tmnl_type - if (devType && devType->type == cJSON_String) strncpy(model->tmnl_type, devType->valuestring, sizeof(model->tmnl_type) - 1); + if (devType && devType->type == cJSON_String) { + strncpy(model->tmnl_type, devType->valuestring, sizeof(model->tmnl_type) - 1); + model->tmnl_type[sizeof(model->tmnl_type) - 1] = '\0'; + } cJSON* updateTime = cJSON_GetObjectItem(item, "updateTime");//timestamp - if (updateTime && updateTime->type == cJSON_String) strncpy(model->timestamp, updateTime->valuestring, sizeof(model->timestamp) - 1); + if (updateTime && updateTime->type == cJSON_String) { + strncpy(model->timestamp, updateTime->valuestring, sizeof(model->timestamp) - 1); + model->timestamp[sizeof(model->timestamp) - 1] = '\0'; + } // 添加到 QMap - icd_model_map->insert(model->model_id, model); + if (model->model_id[0] != '\0') { + icd_model_map->insert(model->model_id, model); + } else { + delete model; + model = NULL; + } } } + else{ + std::cout << "Error: 'data' is not an array or is missing" << std::endl; + cJSON_Delete(root); + if (ptr != NULL) { + free(ptr); + ptr = NULL; + } + return 1; + } + + if (cJSON_GetArraySize(data) == 0) { + std::cout << "Error: 'data' array is empty" << std::endl; + cJSON_Delete(root); + if (ptr != NULL) { + free(ptr); + ptr = NULL; + } + return 1; + } cJSON_Delete(root); - free(ptr); // 如果 SendJsonAPI_web 分配了内存,记得释放 + if (ptr != NULL) { + free(ptr); + ptr = NULL; + } return 0; // 确保函数有返回值 } @@ -4776,7 +4797,7 @@ int parse_model_cfg_web() char tmnl_type[64]; char file_name[128]; char file_path[128]; - otl_datetime timestamp; + otl_datetime timestamp = {}; // 遍历终端台账容器 QMap::iterator it; @@ -4791,6 +4812,12 @@ int parse_model_cfg_web() strncpy(file_path, value->file_path, sizeof(file_path) - 1); strncpy(file_name, value->file_name, sizeof(file_name) - 1); + //lnk20260311 + model_id[sizeof(model_id) - 1] = '\0'; + tmnl_type[sizeof(tmnl_type) - 1] = '\0'; + file_path[sizeof(file_path) - 1] = '\0'; + file_name[sizeof(file_name) - 1] = '\0'; + std::cout << "model_id" << model_id << std::endl; std::cout << "tmnl_type" << tmnl_type << std::endl; std::cout << "filepath" << file_path << std::endl; @@ -5445,6 +5472,84 @@ void SOEFileWeb_test() SOEFileWeb(localpath,cloudpath,wavepath); std::cout << "wavepath:" << wavepath << std::endl; } +////////////////////////////////////////////////////////////////////////////////////////////////////lnk20260310 +static size_t write_file_callback(void* ptr, size_t size, size_t nmemb, void* stream) +{ + FILE* fp = (FILE*)stream; + return fwrite(ptr, size, nmemb, fp); +} +/* + * 从 WEB_FILEDOWNLOAD 下载 path 对应的文件到 localpath + * 成功返回 0,失败返回 -1 + */ +int DownloadFileWeb(const std::string& strUrl, + const char* remotePath, + const char* localpath) +{ + if (remotePath == NULL || localpath == NULL) + return -1; + + CURL* curl = curl_easy_init(); + if (!curl) + { + std::cerr << "curl init failed" << std::endl; + return -1; + } + + FILE* fp = fopen(localpath, "wb"); + if (fp == NULL) + { + std::cerr << "open local file failed: " << localpath << std::endl; + curl_easy_cleanup(curl); + return -1; + } + + char* encodedPath = curl_easy_escape(curl, remotePath, 0); + if (encodedPath == NULL) + { + fclose(fp); + curl_easy_cleanup(curl); + return -1; + } + + std::string fullUrl = strUrl; + if (fullUrl.find('?') == std::string::npos) + fullUrl += "?path="; + else + fullUrl += "&path="; + fullUrl += encodedPath; + + curl_easy_setopt(curl, CURLOPT_URL, fullUrl.c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10L); + curl_easy_setopt(curl, CURLOPT_TIMEOUT, 60L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_file_callback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp); + + CURLcode res = curl_easy_perform(curl); + + long http_code = 0; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + + curl_free(encodedPath); + fclose(fp); + curl_easy_cleanup(curl); + + if (res != CURLE_OK) + { + std::cerr << "DownloadFileWeb failed: " << curl_easy_strerror(res) << std::endl; + return -1; + } + + if (http_code != 200) + { + std::cerr << "DownloadFileWeb http code: " << http_code << std::endl; + return -1; + } + + return 0; +} + /*/////////////////////////////////////////////////////////lnk10-24根据web接口修改/////////////////////////////////////////////////////////////*/ /*封装C可调用的台账更新函数 *///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/json/create_json.cpp b/json/create_json.cpp index b09f5bf..8f7373f 100644 --- a/json/create_json.cpp +++ b/json/create_json.cpp @@ -2931,6 +2931,7 @@ void Set_xml_databaseinfo(char* MODEL_ID, char* TMNL_TYPE, char* FILE_PATH, char { Xmldata* config2 = new Xmldata(); xmlinfo_list2.insert(type, config2); + xmlinfo_list2[type]->updataflag = true; } else { @@ -2952,35 +2953,30 @@ void Set_xml_databaseinfo(char* MODEL_ID, char* TMNL_TYPE, char* FILE_PATH, char char file_name[256]; memset(file_name, 0, 256); - sprintf(file_name, "%s", FILE_NAME); + snprintf(file_name, sizeof(file_name), "%s", FILE_NAME); + file_name[sizeof(file_name) - 1] = '\0'; QString Qsavename; Qsavename.append("/FeProject/dat/").append(id).append(".xml"); //本地保存路径 char save_name[256]; memset(save_name, 0, 256); - sprintf(save_name, "%s", Qsavename.toAscii().data()); + snprintf(save_name, sizeof(save_name), "%s", Qsavename.toAscii().data()); + save_name[sizeof(save_name) - 1] = '\0'; cout << file_name << "!!!!!!!!!!!!!!!!!!!!!!!!!!" << save_name << endl; //mq日志 DIY_WARNLOG_CODE("process",LOG_CODE_ICD_AND_DOWNLOAD,"【WARN】前置获取到终端类型%s,该终端类型对应的映射文件为%s,映射文件将下载并保存在本地为%s",TMNL_TYPE,FILE_PATH,save_name); - //20241028 lnk 替换为文件下载web接口 - //构造文件下载接口参数 - //接口示例http://192.168.1.125:10215/file/download?filePath=/path/xxx.txt + // 调用web获取文件内容 char* fileContent = NULL; - //测试下载 - //char downpath[128] = {"/home/pq/FeProject/src/pt61850netd_pqfe_lnk/download/123.txt"}; - //char download[128] = {"{\"filename\":\"file_test.txt\"}"}; - //SendJsonAPI_web("http://192.168.1.149:8091/file/download", "", download, &fileContent); - std::string fullPath = std::string("filePath=") + std::string(FILE_PATH); //调试用 std::cout << "fullpath" << fullPath << std::endl; SendJsonAPI_web(WEB_FILEDOWNLOAD, fullPath.c_str(), "", &fileContent); - if (fileContent != NULL) { + if (fileContent != NULL && fileContent[0] != '\0') { // 创建并打开文件 //判断返回的是不是错误json响应 @@ -3459,18 +3455,19 @@ static void scanAndResendOfflineFiles(const std::string &dirPath) // 尝试发送 char* ptr = NULL; // 接收返回 SendJsonAPI_web(WEB_EVENT, "", jsonContent.c_str(), &ptr); - if (ptr != NULL) { + if (ptr != NULL && ptr[0] != '\0') { cJSON* j_r = cJSON_Parse(ptr); if (j_r == NULL) { std::cout << "old file send fail" << std::endl; - // 表示有响应,则可视为成功;根据项目需要可加更精细的判断 - handleCommentResponse(std::string(ptr)); + DIY_WARNLOG_CODE("process",LOG_CODE_TRANSIENT_COMM,"【WARN】前置重发暂态事件失败"); } else{ + // 表示有响应,则可视为成功;根据项目需要可加更精细的判断 + handleCommentResponse(std::string(ptr)); DIY_WARNLOG_CODE("process",LOG_CODE_TRANSIENT_COMM,"【WARN】前置重发暂态事件成功"); @@ -3478,7 +3475,7 @@ static void scanAndResendOfflineFiles(const std::string &dirPath) // 删除文件 remove(fileList[i].fileName.c_str()); - free(j_r); + cJSON_Delete(j_r); } } @@ -3524,7 +3521,7 @@ char* mp_id,char* Qvvr_rptname,char* devtype) c_xmlcfg = xmlcfg; } - if (strlen(mp_id) == 0) { + if (NULL == mp_id || strlen(mp_id) == 0 ) { std::cout << "mp_id is null" << std::endl; return 0; } @@ -3567,6 +3564,12 @@ char* mp_id,char* Qvvr_rptname,char* devtype) } char* json_string = cJSON_Print(root); + if (json_string == NULL) { + DIY_ERRORLOG_CODE(full_key_m_d,LOG_CODE_TRANSIENT_COMM,"【ERROR】监测点:%s暂态事件生成JSON字符串失败",mp_id); + std::cerr << "Failed to print JSON object." << std::endl; + cJSON_Delete(root); + return 0; + } printf("%s\n", json_string); // 输出 JSON 字符串 // 发送到暂态接口 @@ -3575,7 +3578,7 @@ char* mp_id,char* Qvvr_rptname,char* devtype) // ================ 插入新功能 ========================= // ********** 新增功能开始 ********** - if(ptr != NULL) + if(ptr != NULL && ptr[0] != '\0') { cJSON* j_r = cJSON_Parse(ptr); // 如果发送失败(j_r == NULL),则把当前 json 存入指定目录(/FeProject/dat/qvvr/) @@ -3604,13 +3607,13 @@ char* mp_id,char* Qvvr_rptname,char* devtype) // 把 json_string 写入文件 if(!writeJsonToFile(fileName.c_str(), json_string)){ - DIY_ERRORLOG_CODE(full_key_m_d,LOG_CODE_TRANSIENT_COMM,"【ERROR】监测点:%s无法将暂态时间为%lld的暂态事件写入本地缓存",start_tm,mp_id); + DIY_ERRORLOG_CODE(full_key_m_d,LOG_CODE_TRANSIENT_COMM,"【ERROR】监测点:%s无法将暂态时间为%lld的暂态事件写入本地缓存",mp_id,start_tm); } checkAndRemoveOldestIfNeeded(qvvrDir, 10LL * 1024 * 1024); } else{ - free(j_r); + cJSON_Delete(j_r); //后续处理 } } @@ -3623,10 +3626,11 @@ char* mp_id,char* Qvvr_rptname,char* devtype) // ********** 新增功能结束 ********** // 下面继续原逻辑,不动,处理本次发送 - if (ptr != NULL) { + if (ptr != NULL && ptr[0] != '\0') { std::cout << "current qvvr handle response" << std::endl; handleCommentResponse(std::string(ptr)); free(ptr); + ptr = NULL; } else { // 处理 ptr 为 NULL 的情况,例如日志记录或错误处理 std::cout << "Error: Received NULL response" << std::endl; @@ -3652,7 +3656,7 @@ char* mp_id,char* Qvvr_rptname,char* devtype) fileName += ".txt"; // 把 json_string 写入文件 if(!writeJsonToFile(fileName.c_str(), json_string)){ - DIY_ERRORLOG_CODE(full_key_m_d,LOG_CODE_TRANSIENT_COMM,"【ERROR】监测点:%s无法将暂态时间为%lld的暂态事件写入本地缓存",start_tm,mp_id); + DIY_ERRORLOG_CODE(full_key_m_d,LOG_CODE_TRANSIENT_COMM,"【ERROR】监测点:%s无法将暂态时间为%lld的暂态事件写入本地缓存",mp_id,start_tm); } checkAndRemoveOldestIfNeeded(qvvrDir, 10LL * 1024 * 1024); diff --git a/json/save2json.cpp b/json/save2json.cpp index 354dcfb..2a7a18a 100644 --- a/json/save2json.cpp +++ b/json/save2json.cpp @@ -46,6 +46,18 @@ int StringToInt(const std::string& str); extern pthread_mutex_t mtx;//lnk20250115 + +extern void SendFileWeb(const std::string& strUrl, + const char* localpath, + const char* cloudpath, + char* wavepath); + +extern int DownloadFileWeb(const std::string& strUrl, + const char* remotePath, + const char* localpath); + + + #ifdef __cplusplus extern "C" { @@ -64,6 +76,13 @@ extern "C" { extern node_t* g_node; //lnk20241223 extern LD_info_t* find_LD_info_only_from_mp_id(char* mp_id);//lnk20241223 extern void print_terminal(const terminal* tmnl); + + extern ST_RET mms_mvla_obtfile(MVL_NET_INFO *net_info, + ST_CHAR *srcfilename, + ST_CHAR *destfilename, + int iTimeout); + + extern pt61850app_t *g_pt61850app; #ifdef __cplusplus } #endif @@ -121,13 +140,21 @@ extern std::string G_MQCONSUMER_KEY_RC;//key extern std::string G_MQCONSUMER_TOPIC_SET;//topie_recall extern std::string G_MQCONSUMER_TAG_SET;//tag extern std::string G_MQCONSUMER_KEY_SET;//key - extern std::string G_MQCONSUMER_TOPIC_LOG;//topie_log extern std::string G_MQCONSUMER_TAG_LOG;//tag extern std::string G_MQCONSUMER_KEY_LOG;//key extern std::string G_LOG_TOPIC;//topie extern std::string G_LOG_TAG;//tag extern std::string G_LOG_KEY;//key +extern std::string G_MQCONSUMER_TOPIC_FILE;//topie_file +extern std::string G_MQCONSUMER_TAG_FILE;//tag +extern std::string G_MQCONSUMER_KEY_FILE;//key +extern std::string Topic_Reply_Topic; +extern std::string Topic_Reply_Tag; +extern std::string Topic_Reply_Key; + +extern std::string WEB_FILEUPLOAD; +extern std::string WEB_FILEDOWNLOAD; bool showinshellflag =false; @@ -143,6 +170,10 @@ static QMap json_data_map;//CZY 2023-08-17 ww 2023年 static QMap json_flicker_data_map;//CZY 2023-09-11 展Map,用于保存各条线路的闪变数据 static QMap json_pst_data_map;//CZY 2023-09-11 展Map,用于保存各条线路的闪变数据 +//////////////////////////////////////////////////////////////////////////////lnk20260310文件控制 +pthread_mutex_t g_file_req_mutex = PTHREAD_MUTEX_INITIALIZER; +std::list g_file_dir_req_list; + bool is_blank(const std::string& str) { for (std::string::const_iterator it = str.begin(); it != str.end(); ++it) @@ -581,7 +612,8 @@ std::string extractDataJson(const char* inputJson) { // 提取 "guid" 部分 cJSON* guidstr = cJSON_GetObjectItem(messageBody, "guid"); if (guidstr == NULL || guidstr->type != cJSON_String) { - std::cerr << "'guid' is missing or is not an array" << std::endl; + std::cerr << "'guid' is missing or is not an string" << std::endl; + cJSON_Delete(messageBody); cJSON_Delete(root); return ""; } @@ -593,12 +625,19 @@ std::string extractDataJson(const char* inputJson) { cJSON* data = cJSON_GetObjectItem(messageBody, "data"); if (data == NULL || data->type != cJSON_Array) { std::cerr << "'data' is missing or is not an array" << std::endl; + cJSON_Delete(messageBody); cJSON_Delete(root); return ""; } // 创建新的 JSON 数组对象,只包含 "data" 部分 cJSON* newJson = cJSON_CreateArray(); // 创建一个新的数组 + if (newJson == NULL) { + std::cerr << "Failed to create new JSON array" << std::endl; + cJSON_Delete(messageBody); + cJSON_Delete(root); + return ""; + } // 将 "data" 数组中的元素逐个添加到新数组中 cJSON* dataItem = NULL; @@ -610,6 +649,7 @@ std::string extractDataJson(const char* inputJson) { char* newJsonString = cJSON_Print(newJson); if (newJsonString == NULL) { std::cerr << "Error printing new JSON" << std::endl; + cJSON_Delete(messageBody); cJSON_Delete(root); cJSON_Delete(newJson); return ""; @@ -620,6 +660,7 @@ std::string extractDataJson(const char* inputJson) { // 清理内存 free(newJsonString); + cJSON_Delete(messageBody); cJSON_Delete(root); cJSON_Delete(newJson); @@ -684,10 +725,12 @@ bool parseJsonMessageRT(const std::string& body, std::string& devSeries, std::st } else { std::cerr << "Missing expected fields in JSON message." << std::endl; + cJSON_Delete(messageBody); cJSON_Delete(root); return false; } + cJSON_Delete(messageBody); cJSON_Delete(root); // 清理 JSON 对象 return true; } @@ -917,6 +960,7 @@ int parse_set(const std::string& json_str) { cJSON* guidstr = cJSON_GetObjectItem(messageBody, "guid"); if (guidstr == nullptr) { std::cout << "Missing 'guid' in JSON." << std::endl; + cJSON_Delete(messageBody); cJSON_Delete(root); return 1; } @@ -928,6 +972,7 @@ int parse_set(const std::string& json_str) { cJSON* code = cJSON_GetObjectItem(messageBody, "code"); if (code == nullptr) { std::cout << "Missing 'code' in JSON." << std::endl; + cJSON_Delete(messageBody); cJSON_Delete(root); return 1; } @@ -938,6 +983,7 @@ int parse_set(const std::string& json_str) { cJSON* processNo = cJSON_GetObjectItem(messageBody, "processNo"); if (processNo == nullptr) { std::cout << "Missing 'processNo' in JSON." << std::endl; + cJSON_Delete(messageBody); cJSON_Delete(root); return 1; } @@ -948,6 +994,7 @@ int parse_set(const std::string& json_str) { cJSON* funtion = cJSON_GetObjectItem(messageBody, "fun"); if (funtion == nullptr) { std::cout << "Missing 'fun' in JSON." << std::endl; + cJSON_Delete(messageBody); cJSON_Delete(root); return 1; } @@ -957,6 +1004,7 @@ int parse_set(const std::string& json_str) { cJSON* front = cJSON_GetObjectItem(messageBody, "frontType"); if (front == nullptr) { std::cout << "Missing 'frontType' in JSON." << std::endl; + cJSON_Delete(messageBody); cJSON_Delete(root); return 1; } @@ -965,6 +1013,7 @@ int parse_set(const std::string& json_str) { if (index_value != g_front_seg_index && g_front_seg_index != 0) { std::cout << "msg index:"<< index_value <<"doesnt match self index:" << g_front_seg_index << std::endl; + cJSON_Delete(messageBody); cJSON_Delete(root); return 0; } @@ -979,6 +1028,7 @@ int parse_set(const std::string& json_str) { cJSON* num = cJSON_GetObjectItem(messageBody, "processNum"); if (num == nullptr) { std::cout << "Missing 'processNum' in JSON." << std::endl; + cJSON_Delete(messageBody); cJSON_Delete(root); return 1; } @@ -1037,6 +1087,7 @@ int parse_set(const std::string& json_str) { cJSON* onlyip = cJSON_GetObjectItem(messageBody, "ip"); if (onlyip == nullptr) { std::cout << "Missing 'ip' in JSON." << std::endl; + cJSON_Delete(messageBody); cJSON_Delete(root); return 1; } @@ -1046,6 +1097,7 @@ int parse_set(const std::string& json_str) { cJSON* index_item = cJSON_GetObjectItem(messageBody, "proindex"); if (index_item == nullptr) { std::cout << "Missing 'proindex' in JSON." << std::endl; + cJSON_Delete(messageBody); cJSON_Delete(root); return 1; } @@ -1078,6 +1130,7 @@ int parse_set(const std::string& json_str) { } // 释放 JSON 对象 + cJSON_Delete(messageBody); cJSON_Delete(root); return 0; } @@ -1316,6 +1369,7 @@ int parse_log(const std::string& json_str) { cJSON* guidstr = cJSON_GetObjectItem(messageBody, "guid"); if (guidstr == nullptr) { std::cout << "Missing 'guid' in JSON." << std::endl; + cJSON_Delete(messageBody); cJSON_Delete(root); return 1; } @@ -1327,6 +1381,7 @@ int parse_log(const std::string& json_str) { cJSON* code = cJSON_GetObjectItem(messageBody, "code"); if (code == nullptr) { std::cout << "Missing 'code' in JSON." << std::endl; + cJSON_Delete(messageBody); cJSON_Delete(root); return 1; } @@ -1338,6 +1393,7 @@ int parse_log(const std::string& json_str) { cJSON* process = cJSON_GetObjectItem(messageBody, "processNo"); if (process == nullptr) { std::cout << "Missing 'processNo' in JSON." << std::endl; + cJSON_Delete(messageBody); cJSON_Delete(root); return 1; } @@ -1349,6 +1405,7 @@ int parse_log(const std::string& json_str) { cJSON* idstr = cJSON_GetObjectItem(messageBody, "id"); if (idstr == nullptr) { std::cout << "Missing 'id' in JSON." << std::endl; + cJSON_Delete(messageBody); cJSON_Delete(root); return 1; } @@ -1359,6 +1416,7 @@ int parse_log(const std::string& json_str) { cJSON* levelstr = cJSON_GetObjectItem(messageBody, "level"); if (levelstr == nullptr) { std::cout << "Missing 'level' in JSON." << std::endl; + cJSON_Delete(messageBody); cJSON_Delete(root); return 1; } @@ -1369,6 +1427,7 @@ int parse_log(const std::string& json_str) { cJSON* gradestr = cJSON_GetObjectItem(messageBody, "grade"); if (gradestr == nullptr) { std::cout << "Missing 'grade' in JSON." << std::endl; + cJSON_Delete(messageBody); cJSON_Delete(root); return 1; } @@ -1379,6 +1438,7 @@ int parse_log(const std::string& json_str) { cJSON* logtypestr = cJSON_GetObjectItem(messageBody, "logtype"); if (logtypestr == nullptr) { std::cout << "Missing 'logtype' in JSON." << std::endl; + cJSON_Delete(messageBody); cJSON_Delete(root); return 1; } @@ -1389,6 +1449,7 @@ int parse_log(const std::string& json_str) { cJSON* frontTypestr = cJSON_GetObjectItem(messageBody, "frontType"); if (frontTypestr == nullptr) { std::cout << "Missing 'frontType' in JSON." << std::endl; + cJSON_Delete(messageBody); cJSON_Delete(root); return 1; } @@ -1397,12 +1458,14 @@ int parse_log(const std::string& json_str) { if (processNo != g_front_seg_index) { std::cout << "msg index:"<< processNo <<"doesnt match self index:" << g_front_seg_index << std::endl; + cJSON_Delete(messageBody); cJSON_Delete(root); return 0; } if (frontType != subdir) { std::cout << "msg frontType:"<< frontType <<"doesnt match self frontType:" << subdir << std::endl; + cJSON_Delete(messageBody); cJSON_Delete(root); return 0; } @@ -1439,6 +1502,7 @@ int parse_log(const std::string& json_str) { } // 释放 JSON 对象 + cJSON_Delete(messageBody); cJSON_Delete(root); return 0; } @@ -1479,6 +1543,7 @@ int parse_control(const std::string& json_str, const std::string& output_dir) { cJSON* code = cJSON_GetObjectItem(messageBody, "code"); if (code == nullptr) { std::cout << "Missing 'code' in JSON." << std::endl; + cJSON_Delete(messageBody); cJSON_Delete(root); return 1; } @@ -1490,6 +1555,7 @@ int parse_control(const std::string& json_str, const std::string& output_dir) { cJSON* process = cJSON_GetObjectItem(messageBody, "processNo"); if (process == nullptr) { std::cout << "Missing 'processNo' in JSON." << std::endl; + cJSON_Delete(messageBody); cJSON_Delete(root); return 1; } @@ -1501,6 +1567,7 @@ int parse_control(const std::string& json_str, const std::string& output_dir) { cJSON* guidstr = cJSON_GetObjectItem(messageBody, "guid"); if (guidstr == nullptr) { std::cout << "Missing 'guid' in JSON." << std::endl; + cJSON_Delete(messageBody); cJSON_Delete(root); return 1; } @@ -1511,6 +1578,7 @@ int parse_control(const std::string& json_str, const std::string& output_dir) { //进程号为0的进程处理所有台账更新消息 if (process_No != g_front_seg_index && g_front_seg_index !=0) { std::cout << "msg index:"<< process_No <<"doesnt match self index:" << g_front_seg_index << std::endl; + cJSON_Delete(messageBody); cJSON_Delete(root); return 0; } @@ -1773,10 +1841,492 @@ int parse_control(const std::string& json_str, const std::string& output_dir) { } // 释放 JSON 对象 + cJSON_Delete(messageBody); cJSON_Delete(root); return 0; } +////////////////////////////////////////////////////////////////////////////////////////////////////////// +//处理文件请求 +static int ParseFileDirReq(const char *body, file_dir_req_t *req) +{ + if (body == NULL || req == NULL) + return -1; + + cJSON *root = cJSON_Parse(body); + if (root == NULL) + return -1; + + cJSON *guid = cJSON_GetObjectItem(root, "guid"); + cJSON *frontid = cJSON_GetObjectItem(root, "frontid"); + cJSON *processNo = cJSON_GetObjectItem(root, "ProcessNo"); + cJSON *devid = cJSON_GetObjectItem(root, "devid"); + cJSON *type = cJSON_GetObjectItem(root, "type"); + cJSON *path = cJSON_GetObjectItem(root, "Path"); + + if (!guid || guid->type != cJSON_String || + !frontid || frontid->type != cJSON_String || + !processNo || processNo->type != cJSON_Number || + !devid || devid->type != cJSON_String || + !type || type->type != cJSON_Number || + !path || path->type != cJSON_String) + { + cJSON_Delete(root); + return -1; + } + + memset(req, 0, sizeof(file_dir_req_t)); + snprintf(req->guid, sizeof(req->guid), "%s", guid->valuestring); + snprintf(req->frontid, sizeof(req->frontid), "%s", frontid->valuestring); + req->processNo = processNo->valueint; + snprintf(req->devid, sizeof(req->devid), "%s", devid->valuestring); + req->type = type->valueint; + snprintf(req->path, sizeof(req->path), "%s", path->valuestring); + req->create_time = time(NULL); + + cJSON_Delete(root); + return 0; +} + +static void PushFileDirReq(const file_dir_req_t *req) +{ + if (req == NULL) + return; + + file_dir_req_t *node = new file_dir_req_t; + *node = *req; + + pthread_mutex_lock(&g_file_req_mutex); + + g_file_dir_req_list.push_back(node); + pthread_mutex_unlock(&g_file_req_mutex); +} + +static file_dir_req_t* PopMatchedFileDirReq(const char *terminal_id) +{ + if (terminal_id == NULL || terminal_id[0] == 0) + return NULL; + + file_dir_req_t *match = NULL; + + pthread_mutex_lock(&g_file_req_mutex); + + for (std::list::iterator it = g_file_dir_req_list.begin(); + it != g_file_dir_req_list.end(); + ++it) + { + file_dir_req_t *node = *it; + if (node != NULL && strcmp(node->devid, terminal_id) == 0) + { + match = node; + g_file_dir_req_list.erase(it); + break; + } + } + + pthread_mutex_unlock(&g_file_req_mutex); + return match; +} + +static std::string BuildFileDirRespJsonEx(const file_dir_req_t *req, + char **names, + const char **itemTypes, + int *itemSizes, + int itemNum, + int result) +{ + cJSON *root = cJSON_CreateObject(); + cJSON *dirInfo = cJSON_CreateArray(); + + cJSON_AddStringToObject(root, "guid", req ? req->guid : ""); + cJSON_AddStringToObject(root, "frontid", req ? req->frontid : ""); + cJSON_AddNumberToObject(root, "processNo", req ? req->processNo : 0); + cJSON_AddStringToObject(root, "devid", req ? req->devid : ""); + cJSON_AddNumberToObject(root, "type", req ? req->type : 0); + + for (int i = 0; i < itemNum; ++i) + { + cJSON *item = cJSON_CreateObject(); + cJSON_AddStringToObject(item, "name", (names && names[i]) ? names[i] : ""); + cJSON_AddStringToObject(item, "type", (itemTypes && itemTypes[i]) ? itemTypes[i] : "file"); + cJSON_AddNumberToObject(item, "size", (itemSizes ? itemSizes[i] : 1)); + cJSON_AddItemToArray(dirInfo, item); + } + + cJSON_AddItemToObject(root, "dirInfo", dirInfo); + cJSON_AddNumberToObject(root, "result", result); + + char *json = cJSON_PrintUnformatted(root); + std::string jsonStr = json ? json : ""; + + if (json) free(json); + cJSON_Delete(root); + return jsonStr; +} + +static std::string BuildFileDirRespJson(const file_dir_req_t *req, + char **filenames, + int filenum, + int result) +{ + if (filenum <= 0) + return BuildFileDirRespJsonEx(req, NULL, NULL, NULL, 0, result); + + const char **types = new const char*[filenum]; + int *sizes = new int[filenum]; + + for (int i = 0; i < filenum; ++i) + { + types[i] = "dir"; + sizes[i] = 1; + } + + std::string jsonStr = BuildFileDirRespJsonEx(req, filenames, types, sizes, filenum, result); + + delete [] types; + delete [] sizes; + return jsonStr; +} + +static std::string BuildSingleFileRespJson(const file_dir_req_t *req, + const char *name, + const char *itemType, + int size, + int result) +{ + char *names[1]; + const char *types[1]; + int sizes[1]; + + if (name == NULL || result != 0) + { + return BuildFileDirRespJsonEx(req, NULL, NULL, NULL, 0, result); + } + + names[0] = (char *)name; + types[0] = (itemType ? itemType : "file"); + sizes[0] = size; + + return BuildFileDirRespJsonEx(req, names, types, sizes, 1, result); +} + +////////////////////////下载 +static const char* GetFileNameOnly(const char* fullpath) +{ + if (fullpath == NULL) + return ""; + + const char* p1 = strrchr(fullpath, '/'); + const char* p2 = strrchr(fullpath, '\\'); + const char* p = p1 > p2 ? p1 : p2; + + return (p ? p + 1 : fullpath); +} + +static int MakeDirRecursive(const char* dirPath) +{ + if (dirPath == NULL || dirPath[0] == '\0') + return -1; + + char tmp[512] = {0}; + snprintf(tmp, sizeof(tmp), "%s", dirPath); + + int len = strlen(tmp); + if (len <= 0) + return -1; + + /* 去掉末尾 '/' */ + if (tmp[len - 1] == '/') + tmp[len - 1] = '\0'; + + for (char* p = tmp + 1; *p; ++p) + { + if (*p == '/') + { + *p = '\0'; + if (access(tmp, F_OK) != 0) + { + if (mkdir(tmp, 0777) != 0 && errno != EEXIST) + { + printf("mkdir failed: %s, err=%s\n", tmp, strerror(errno)); + return -1; + } + } + *p = '/'; + } + } + + if (access(tmp, F_OK) != 0) + { + if (mkdir(tmp, 0777) != 0 && errno != EEXIST) + { + printf("mkdir failed: %s, err=%s\n", tmp, strerror(errno)); + return -1; + } + } + + return 0; +} + +static void SafePathName(const char* src, char* dst, size_t dstSize) +{ + if (dst == NULL || dstSize == 0) + return; + + dst[0] = '\0'; + + if (src == NULL) + return; + + size_t j = 0; + for (size_t i = 0; src[i] != '\0' && j + 1 < dstSize; ++i) + { + char c = src[i]; + if ((c >= '0' && c <= '9') || + (c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + c == '.' || c == '_' || c == '-') + { + dst[j++] = c; + } + else + { + dst[j++] = '_'; + } + } + dst[j] = '\0'; +} + +static int BuildTempLocalPath(char* outPath, + size_t outSize, + chnl_usr_t* chnl_usr, + const char* remotePath) +{ + if (outPath == NULL || outSize == 0 || chnl_usr == NULL) + return -1; + + const char* fileName = GetFileNameOnly(remotePath); + if (fileName == NULL || fileName[0] == '\0') + fileName = "tmp_file.dat"; + + const char* ipStr = chnl_usr->ip_str; + if (ipStr == NULL || ipStr[0] == '\0') + ipStr = "unknown_ip"; + + char safeIp[128] = {0}; + SafePathName(ipStr, safeIp, sizeof(safeIp)); + + char dirPath[512] = {0}; + snprintf(dirPath, sizeof(dirPath), "/tmp/%s", safeIp); + + /* 目录不存在则创建 */ + if (MakeDirRecursive(dirPath) != 0) + { + printf("BuildTempLocalPath mkdir failed: %s\n", dirPath); + return -1; + } + + snprintf(outPath, outSize, "%s/%s", dirPath, fileName); + return 0; +} + +static int HandleTypeDownloadAndUpload(chnl_usr_t* chnl_usr, + file_dir_req_t* req, + std::string& jsonString) +{ + if (chnl_usr == NULL || req == NULL || chnl_usr->net_info == NULL) + return -1; + + char localpath[512] = {0}; + if (BuildTempLocalPath(localpath, sizeof(localpath), chnl_usr, req->path) != 0) + { + DIY_ERRORLOG_CODE("process", LOG_CODE_TRANSIENT_COMM, + "【ERROR】构造本地临时路径失败 devid=%s path=%s", + req->devid, req->path); + + jsonString = BuildSingleFileRespJson(req, NULL, "file", 1, -1); + return -1; + } + + ST_RET ret = mms_getFile(chnl_usr->net_info, + (ST_CHAR*)localpath, + (ST_CHAR*)req->path, + 3 * g_pt61850app->mmsOpTimeout); + + if (ret != SD_SUCCESS) + { + DIY_ERRORLOG_CODE("process", LOG_CODE_TRANSIENT_COMM, + "【ERROR】装置文件下载失败 devid=%s, rem=%s, ret=0x%X", + req->devid, req->path, ret); + + jsonString = BuildSingleFileRespJson(req, NULL, "file", 1, ret); + return -1; + } + + DIY_INFOLOG("process", + "【NORMAL】装置文件下载成功 devid=%s, rem=%s, local=%s", + req->devid, req->path, localpath); + + char wavepath[512] = {0}; + SendFileWeb(WEB_FILEUPLOAD, localpath, req->path, wavepath); + + jsonString = BuildSingleFileRespJson(req, + (wavepath[0] != 0 ? wavepath : req->path), + "file", + 1, + 0); + remove(localpath); + + return 0; +} + +///////////传输 +static int HandleTypeTransferToDevice(chnl_usr_t* chnl_usr, + file_dir_req_t* req, + std::string& jsonString) +{ + if (chnl_usr == NULL || req == NULL || chnl_usr->net_info == NULL) + return -1; + + char localpath[512] = {0}; + if (BuildTempLocalPath(localpath, sizeof(localpath), chnl_usr, req->path) != 0) + { + DIY_ERRORLOG_CODE("process", LOG_CODE_TRANSIENT_COMM, + "【ERROR】构造本地临时路径失败 devid=%s path=%s", + req->devid, req->path); + + jsonString = BuildSingleFileRespJson(req, NULL, "file", 1, -1); + return -1; + } + + int dlRet = DownloadFileWeb(WEB_FILEDOWNLOAD, req->path, localpath); + if (dlRet != 0) + { + DIY_ERRORLOG_CODE("process", LOG_CODE_TRANSIENT_COMM, + "【ERROR】Web 文件下载失败 devid=%s, path=%s", + req->devid, req->path); + + jsonString = BuildSingleFileRespJson(req, NULL, "file", 1, -1); + return -1; + } + + DIY_INFOLOG("process", + "【NORMAL】Web 文件下载成功 devid=%s, webpath=%s, local=%s", + req->devid, req->path, localpath); + + char destfilename[512] = {0}; + const char* fileName = GetFileNameOnly(req->path); + snprintf(destfilename, sizeof(destfilename), "/etc/%s", + (fileName && fileName[0]) ? fileName : "tmp_file.dat"); + + ST_RET ret = mms_mvla_obtfile(chnl_usr->net_info, + (ST_CHAR*)localpath, + (ST_CHAR*)destfilename, + 3 * g_pt61850app->mmsOpTimeout); + + if (ret != SD_SUCCESS) + { + DIY_ERRORLOG_CODE("process", LOG_CODE_TRANSIENT_COMM, + "【ERROR】文件传送到装置失败 devid=%s, src=%s, dest=%s, ret=0x%X", + req->devid, localpath, destfilename, ret); + + jsonString = BuildSingleFileRespJson(req, NULL, "file", 1, ret); + return -1; + } + + DIY_INFOLOG("process", + "【NORMAL】文件传送到装置成功 devid=%s, src=%s, dest=%s", + req->devid, localpath, destfilename); + + jsonString = BuildSingleFileRespJson(req, + destfilename, + "file", + 1, + 0); + + remove(localpath); + return 0; +} + +void HandleFileDirReqForChannel(chnl_usr_t *chnl_usr) +{ + if (chnl_usr == NULL || chnl_usr->chnl == NULL || chnl_usr->chnl->ied == NULL) + return; + + ied_t *ied = chnl_usr->chnl->ied; + ied_usr_t *ied_usr = GET_IEDEXT_ADDR(ied); + if (ied_usr == NULL) + return; + + if (ied_usr->terminal_id[0] == 0) + return; + + file_dir_req_t *req = PopMatchedFileDirReq(ied_usr->terminal_id); + if (req == NULL) + return; // 当前连接没有文件请求 + + DIY_INFOLOG("process", + "【NORMAL】处理文件请求 terminal_id=%s type=%d path=%s", + req->devid, req->type, req->path); + + std::string jsonString; + int handleRet = -1; + + if (req->type == 0) + { + /* 目录查询 */ + char **filenames = NULL; + int filenum = 0; + + ST_RET ret = mms_mvla_fdir(chnl_usr->net_info, + (ST_CHAR*)req->path, + 3 * g_pt61850app->mmsOpTimeout, + &filenames, + &filenum, + g_pt61850app->tmp_pool); + + jsonString = BuildFileDirRespJson(req, + filenames, + filenum, + (ret == SD_SUCCESS) ? 0 : ret); + + DIY_INFOLOG("process", + "【NORMAL】目录请求处理完成 terminal_id=%s ret=0x%X filenum=%d", + req->devid, ret, filenum); + + handleRet = (ret == SD_SUCCESS) ? 0 : -1; + } + else if (req->type == 1) + { + /* 装置下载到本地,再上传 Web */ + handleRet = HandleTypeDownloadAndUpload(chnl_usr, req, jsonString); + } + else if (req->type == 2) + { + /* Web 下载到本地,再传送到装置 /etc */ + handleRet = HandleTypeTransferToDevice(chnl_usr, req, jsonString); + } + else + { + DIY_WARNLOG("process", + "【WARN】未知文件请求类型 type=%d devid=%s path=%s", + req->type, req->devid, req->path); + + jsonString = BuildSingleFileRespJson(req, NULL, "file", 1, 1);//1是失败 + handleRet = -1; + } + + /* 统一回 Kafka */ + Ckafka_data_t dir_info; + dir_info.strTopic = QString::fromStdString(Topic_Reply_Topic); + dir_info.strText = QString::fromStdString(jsonString); + + kafka_data_list_mutex.lock(); + kafka_data_list.append(dir_info); + kafka_data_list_mutex.unlock(); + + delete req; +} ///////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -2048,6 +2598,44 @@ int myMessageCallbackrecall(CPushConsumer* consumer, CMessageExt* msg) return E_CONSUME_SUCCESS; } +int myMessageCallbackfile(CPushConsumer* consumer, CMessageExt* msg) +{ + if (INITFLAG != 1) return 1; + + if (msg == NULL) { + std::cerr << "Received null message." << std::endl; + return E_RECONSUME_LATER; + } + + const char* body = GetMessageBody(msg); + const char* key = GetMessageKeys(msg); + + if (body == NULL) { + std::cerr << "Message body is NULL." << std::endl; + return E_RECONSUME_LATER; + } + + DIY_INFOLOG("process","【NORMAL】前置消费topic:%s_%s的文件控制消息", + FRONT_INST.c_str(), G_MQCONSUMER_TOPIC_FILE.c_str()); + + std::cout << "file Callback received message: " << body << std::endl; + std::cout << "Message Key: " << (key ? key : "N/A") << std::endl; + + file_dir_req_t req; + if (ParseFileDirReq(body, &req) != 0) + { + DIY_WARNLOG("process", "【WARN】文件控制消息解析失败: %s", body); + return E_CONSUME_SUCCESS; + } + + PushFileDirReq(&req); + + DIY_INFOLOG("process", + "【NORMAL】文件目录请求已入队 guid=%s devid=%s path=%s", + req.guid, req.devid, req.path); + + return E_CONSUME_SUCCESS; +} void mqconsumerThread::run() { @@ -2060,6 +2648,9 @@ void mqconsumerThread::run() std::vector subscriptions; // 初始化消费者1 //lnk20241230只有实时进程会订阅实时topic,不订阅实时topic的进程无法触发实时数据 if(g_node_id == THREE_SECS_DATA_BASE_NODE_ID){ + + //lnk20260310添加文件管理 + subscriptions.push_back(Subscription(std::string(FRONT_INST) + "_" + G_MQCONSUMER_TOPIC_FILE, G_MQCONSUMER_TAG_FILE, myMessageCallbackfile)); subscriptions.push_back(Subscription(std::string(FRONT_INST) + "_" + G_MQCONSUMER_TOPIC_RT, G_MQCONSUMER_TAG_RT, myMessageCallbackrtdata)); } diff --git a/mms/db_interface.h b/mms/db_interface.h index 20c308e..7da0958 100644 --- a/mms/db_interface.h +++ b/mms/db_interface.h @@ -171,6 +171,21 @@ typedef struct { bool get_xml_config_by_dev_type(const char* dev_type, XmlConfigC* out_cfg); +//////////////////////////////////////////////////////////////////////////////////////文件控制请求参数 +typedef struct file_dir_req_t +{ + struct file_dir_req_t *next; + struct file_dir_req_t *prev; + + char guid[128]; + char frontid[128]; + int processNo; + char devid[128]; + int type; + char path[256]; + time_t create_time; +} file_dir_req_t; + #ifdef __cplusplus } #endif diff --git a/mms/mms_process.c b/mms/mms_process.c index 19a42f8..029ffd4 100644 --- a/mms/mms_process.c +++ b/mms/mms_process.c @@ -1551,6 +1551,9 @@ void CheckAllConnectedChannel() if(chnl_usr->m_state == CHANNEL_CONNECTED) { + if(g_node_id == THREE_SECS_DATA_BASE_NODE_ID) { + HandleFileDirReqForChannel(chnl_usr);//文件目录请求 + } ChannelCheckIECReports(chnl_usr);//报告 if ( (g_node_id == SOE_COMTRADE_BASE_NODE_ID) || (g_node_id == HIS_DATA_BASE_NODE_ID) || (g_node_id == NEW_HIS_DATA_BASE_NODE_ID) || (g_node_id == RECALL_HIS_DATA_BASE_NODE_ID) || (g_node_id == RECALL_ALL_DATA_BASE_NODE_ID)) diff --git a/mms/mmsclient.c b/mms/mmsclient.c index b849336..156f4a6 100644 --- a/mms/mmsclient.c +++ b/mms/mmsclient.c @@ -1546,6 +1546,59 @@ ERR: } } +//***************************lnk20260309下发文件到装置*********************/ +ST_RET mms_putFile(MVL_NET_INFO *clientNetInfo, + ST_CHAR *src_file, + ST_CHAR *dest_file, + ST_INT iTimeout) +{ + MVL_REQ_PEND *reqCtrl = NULL; + ST_RET ret = SD_FAILURE; + + if (clientNetInfo == NULL) + { + printf("\n mms_putFile failed: clientNetInfo is NULL"); + return SD_FAILURE; + } + + if (src_file == NULL || src_file[0] == '\0') + { + printf("\n mms_putFile failed: src_file is NULL or empty"); + return SD_FAILURE; + } + + if (dest_file == NULL || dest_file[0] == '\0') + { + printf("\n mms_putFile failed: dest_file is NULL or empty"); + return SD_FAILURE; + } + + ret = mvla_obtfile(clientNetInfo, src_file, dest_file, &reqCtrl); + if (ret == SD_SUCCESS) + ret = waitReqDone(reqCtrl, iTimeout); + + if (ret != SD_SUCCESS) + { + printf("\n mms_putFile failed, src='%s', dest='%s', ret=0x%X", + src_file, dest_file, ret); + } + else + { + printf("\n mms_putFile OK, src='%s', dest='%s'", + src_file, dest_file); + } + + if (reqCtrl != NULL) + { + mvl_free_req_ctrl(reqCtrl); + reqCtrl = NULL; + } + + return ret; +} +//************************************************************************/ +/* putFile */ + /************************************************************************/ /* init_mem */ diff --git a/mms/rdb_client.h b/mms/rdb_client.h index c628692..a87a1a7 100644 --- a/mms/rdb_client.h +++ b/mms/rdb_client.h @@ -532,6 +532,8 @@ int extract_timestamp_from_cfg_file(char *comtrade_fn,long long *start_tm,long l int parse_file_names_by_fltnum(int fltnum, char* domname, char** filenames, int filenum, int* cfg_idx, int* dat_idx, char* file_base_name, char* file_yyyymm); QVVR_t* find_qvvr_by_trig_tm(LD_info_t* LD_info,long long trig_tm); +void HandleFileDirReqForChannel(chnl_usr_t *chnl_usr); + ////////////////////////////////////////////////////////////////// #ifdef __cplusplus diff --git a/set_process.sh b/set_process.sh index a548ec8..e5956f1 100644 --- a/set_process.sh +++ b/set_process.sh @@ -51,11 +51,13 @@ kill_process_by_name() { if [ -n "$PID" ]; then echo "Found process '$PROCESS_NAME' with PID: $PID" echo "Killing process..." - kill -9 $PID - if [ $? -eq 0 ]; then - echo "Process '$PROCESS_NAME' killed successfully." - else - echo "Failed to kill the process '$PROCESS_NAME'." + kill -15 $PID + sleep 3 + + PID2=$(ps -ef | grep "$PROCESS_NAME" | grep -v "grep" | awk '{print $2}') + if [ -n "$PID2" ]; then + echo "Process still exists, force kill: $PID2" + kill -9 $PID2 fi else echo "Process '$PROCESS_NAME' not found." @@ -86,7 +88,7 @@ handle_reset() { kill_process_by_name "/FeProject/bin/pt61850netd_pqfe -d cfg_soe_comtrade" #关闭进程后等待一段时间,防止端口占用 - #sleep 5 + sleep 1 # 清空 runtime.cf 中的所有进程配置 sed -i '/cfg_stat_data/d' /home/pq/FeProject/etc/runtime.cf @@ -119,6 +121,9 @@ handle_reset() { sed -i "2a\\$(printf '/FeProject/bin/ ^ pt61850netd_pqfe -d cfg_soe_comtrade^ ^ ^ 1 ^ ^\n')" /home/pq/FeProject/etc/runtime.cf fi + + # 修改后等一下 + sleep 1 # 确保文件已被写入并刷新 sync