/** * network_client.cpp — 网络通信实现 */ #include "network_client.h" #include "feishu_client.h" #include "utils.h" #include "md5ex1.h" #include size_t WriteMemoryCallback(void* contents, size_t size, size_t nmemb, std::string* str); // ==================== 飞书配置 ==================== void InitCurlCommon(CURL* curl, std::string* response) { curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 15L); curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30L); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, response); } int parse_create_bill_response(const std::string& response, const std::string& plate, bool is_in, std::string& tb_num_out) { tb_num_out.clear(); cJSON* root = cJSON_Parse(response.c_str()); if (!root) { std::cerr << "[解析失败] 响应不是合法JSON: " << response << std::endl; return static_cast(ErrorCode::PARSE_ERROR); } if (DEBUG_LOG) { std::cout << response << std::endl; } cJSON* status = cJSON_GetObjectItem(root, "status"); cJSON* data = cJSON_GetObjectItem(root, "data"); cJSON* msg = cJSON_GetObjectItem(root, "msg"); if (msg && cJSON_IsString(msg) && strstr(msg->valuestring, "已确认上线") != NULL) { std::cout << "检测到Test环境已确认上线,自动切换到正式环境" << std::endl; TestFlag = 0; cJSON_Delete(root); return static_cast(ErrorCode::BUSINESS_ERROR); } // 业务错误在任何日志级别下都生效 if (DEBUG_LOG == 0)//调试模式DEBUG_LOG = 1,正式运行DEBUG_LOG =0屏蔽掉 { if (msg && cJSON_IsString(msg) && strstr(msg->valuestring, "非在网车辆") != NULL) { std::cerr << "车牌 " << plate << (is_in ? " 进站" : " 出站") << " 非在网车辆" << std::endl; cJSON_Delete(root); return static_cast(ErrorCode::BUSINESS_ERROR); } } if (status && status->valueint == BUSINESS_ERROR_CODE) { std::cerr << "车牌 " << plate << (is_in ? " 进站" : " 出站") << " 业务异常: " << (msg ? msg->valuestring : "未知") << std::endl; cJSON_Delete(root); return static_cast(ErrorCode::BUSINESS_ERROR); } if (DEBUG_LOG == 0)//调试模式DEBUG_LOG = 1,正式运行DEBUG_LOG =0屏蔽掉 { if (!status || status->valueint != HTTP_STATUS_OK) { std::cerr << "[创建工单失败] 服务器返回非200状态: " << (status ? status->valueint : -1) << std::endl; if (msg && cJSON_IsString(msg)) { std::cout << "错误信息: " << msg->valuestring << std::endl; } cJSON_Delete(root); return static_cast(ErrorCode::NETWORK_ERROR); } } if (!data) { std::cerr << "[解析失败] 响应缺少data字段" << std::endl; cJSON_Delete(root); return static_cast(ErrorCode::PARSE_ERROR); } cJSON* tbNo = cJSON_GetObjectItem(data, "tbNo"); if (!tbNo || !cJSON_IsString(tbNo) || strlen(tbNo->valuestring) == 0) { std::cerr << "[解析失败] 未找到有效的tbNo" << std::endl; cJSON_Delete(root); return static_cast(ErrorCode::PARSE_ERROR); } tb_num_out = tbNo->valuestring; update_bill_cache_tb_num(plate, is_in, tb_num_out); std::cout << "[解析成功] " << (is_in ? "进站" : "出站") << "联单编号: " << tb_num_out << std::endl; cJSON_Delete(root); g_metrics.record_createbill(true); return static_cast(ErrorCode::SUCCESS); } int parse_online_status_response(std::string resp) { std::cout << resp << std::endl; cJSON* root = cJSON_Parse(resp.c_str()); if (NULL == root) { std::cout << "parse_upload_image_response error!!!" << std::endl; return -1; } cJSON* status = cJSON_GetObjectItem(root, "status"); if (status && status->valueint == HTTP_STATUS_OK) { std::cout << "online status success!!!" << std::endl; } cJSON_Delete(root); return 0; } int parse_upload_image_response(std::string resp) { std::cout << resp << std::endl; cJSON* root = cJSON_Parse(resp.c_str()); if (NULL == root) { std::cout << "parse_upload_image_response error!!!" << std::endl; return -1; } cJSON* status = cJSON_GetObjectItem(root, "status"); if (status && status->valueint == HTTP_STATUS_OK) { std::cout << "upload image success!!!" << std::endl; cJSON_Delete(root); return 0; } std::cerr << "upload image failed" << std::endl; cJSON_Delete(root); return -1; } std::string json_create_bill_encode(std::shared_ptr plate_info) { CaptureInfo* plate_cap_info = plate_info->cap_info_copy.get(); if (!plate_cap_info) { std::cerr << "json_create_bill_encode: cap_info为空" << std::endl; return std::string(""); } cJSON* json = cJSON_CreateObject(); bool is_special = false; { std::lock_guard lock(g_special_plates_mtx); for (auto& p : g_special_plates) { if (p == plate_info->code) { is_special = true; break; } } } // fix24-v21: 使用照片抓拍时间(db_capture_time)替代当前时间,确保API上报时间与水印/DB一致 std::string capture_time_str; if (plate_info->db_capture_time > 0) { struct tm tm_buf; struct tm* tm_ptr = localtime_r(&plate_info->db_capture_time, &tm_buf); if (tm_ptr) { char buf[64]; strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", tm_ptr); capture_time_str = buf; } else { capture_time_str = get_format_time(); } } else { capture_time_str = get_format_time(); } if (g_wType == 1) { cJSON_AddStringToObject(json, "plateNumber", plate_info->code.c_str()); cJSON_AddStringToObject(json, "outWorkSiteDate", capture_time_str.c_str()); cJSON_AddStringToObject(json, "workSiteNo", point_number.c_str()); cJSON_AddStringToObject(json, "doorNo", throughway.c_str()); if (is_special) { cJSON_AddStringToObject(json, "platenumcolor", "黄色"); cJSON_AddStringToObject(json, "vehicleType", "渣土车"); } else { cJSON_AddStringToObject(json, "platenumcolor", plate_info->type.c_str()); cJSON_AddStringToObject(json, "vehicleType", ""); } cJSON_AddNumberToObject(json, "dType", plate_cap_info->dtype); } else if (g_wType == 3) { cJSON_AddStringToObject(json, "plateNumber", plate_info->code.c_str()); cJSON_AddStringToObject(json, "inDisposalDate", capture_time_str.c_str()); cJSON_AddStringToObject(json, "disposalNo", point_number.c_str()); cJSON_AddStringToObject(json, "doorNo", throughway.c_str()); if (is_special) { cJSON_AddStringToObject(json, "platenumcolor", "黄色"); } else { cJSON_AddStringToObject(json, "platenumcolor", plate_info->type.c_str()); } cJSON_AddNumberToObject(json, "dType", plate_cap_info->dtype); } char* string = cJSON_Print(json); cJSON_Delete(json); if (string == NULL) { return std::string(""); } std::string res = std::string(string); free(string); return res; } std::string json_online_status_encode(CaptureInfo* cap_info) { if (!cap_info) { std::cerr << "json_online_status_encode: cap_info为空" << std::endl; return std::string(""); } cJSON* json = cJSON_CreateObject(); cJSON_AddStringToObject(json, "workSiteNo", point_number.c_str()); cJSON_AddNumberToObject(json, "wType", g_wType); cJSON_AddNumberToObject(json, "dType", cap_info->dtype); cJSON_AddNumberToObject(json, "gType", cap_info->gtype); cJSON_AddNumberToObject(json, "status", cap_info->status); cJSON_AddStringToObject(json, "remark", ""); cJSON_AddStringToObject(json, "timestamp", get_format_time().c_str()); char* string = cJSON_Print(json); cJSON_Delete(json); if (string == NULL) { return std::string(""); } std::string res = std::string(string); free(string); return res; } // ============================================ // v41.2 新增: 批量状态上报 - 将4个摄像头合并为1个请求 // ============================================ std::string json_online_status_batch_encode() { cJSON* root = cJSON_CreateObject(); cJSON_AddStringToObject(root, "workSiteNo", point_number.c_str()); cJSON_AddNumberToObject(root, "wType", g_wType); cJSON_AddStringToObject(root, "timestamp", get_format_time().c_str()); // 创建摄像头状态数组 cJSON* cameraList = cJSON_CreateArray(); // 4个摄像头的状态 CaptureInfo* cameras[4] = { &plate_rec_app.front_info_in, // 进站低位 &plate_rec_app.front_info_out, // 出站低位 &plate_rec_app.side_info_in, // 进站高位 &plate_rec_app.side_info_out // 出站高位 }; for (int i = 0; i < 4; i++) { cJSON* cam = cJSON_CreateObject(); cJSON_AddNumberToObject(cam, "dType", cameras[i]->dtype); cJSON_AddNumberToObject(cam, "gType", cameras[i]->gtype); cJSON_AddNumberToObject(cam, "status", cameras[i]->status); cJSON_AddStringToObject(cam, "remark", ""); cJSON_AddItemToArray(cameraList, cam); } cJSON_AddItemToObject(root, "cameraList", cameraList); char* string = cJSON_PrintUnformatted(root); cJSON_Delete(root); if (string == NULL) { return std::string(""); } std::string res = std::string(string); free(string); return res; } // 前向声明 std::string get_new_timestamp(); // ==================== 签名计算 ==================== std::string calculate_sign(const std::string& app_key, const std::string& app_secret, const std::string& timestamp) { std::string sign_str = app_secret + "#app_key" + app_key + "timestamp" + timestamp + "#" + app_secret; char md5_hex[33]; md5_hexEx1(sign_str.c_str(), md5_hex); return std::string(md5_hex); } // ==================== libcurl初始化/清理 ==================== int lib_curl_init() { curl_global_init(CURL_GLOBAL_DEFAULT); return 0; } void lib_curl_deinit() { curl_global_cleanup(); } // ==================== 飞书Token缓存 ==================== std::string get_cached_tenant_token() { time_t now = time(NULL); if (!g_tenant_token.empty() && now < g_token_expire_time.load() - TOKEN_REFRESH_BUFFER_SECONDS) { return g_tenant_token; } std::lock_guard lock(g_token_mtx); now = time(NULL); if (!g_tenant_token.empty() && now < g_token_expire_time.load() - TOKEN_REFRESH_BUFFER_SECONDS) { return g_tenant_token; } std::string new_token = get_tenant_token(); if (!new_token.empty()) { g_tenant_token = new_token; g_token_expire_time.store(time(NULL) + TOKEN_EXPIRE_SECONDS); std::cout << "[Token刷新] 飞书租户Token已更新,过期时间: " << g_token_expire_time.load() << std::endl; } else { std::cerr << "[Token刷新] 获取飞书Token失败,使用旧Token(如果存在)" << std::endl; } return g_tenant_token; } // 批量状态上报请求 int lib_curl_online_status_batch_request() { std::string response; std::string timestamp = get_new_timestamp(); std::string sign = calculate_sign(app_key, app_secret, timestamp); std::string req_url = ""; if (TestFlag == 1) { req_url = app_api + "onlineStatusBatchTest?app_key=" + app_key + "&sign=" + sign + "×tamp=" + timestamp; } else { // 使用批量接口 req_url = app_api + "onlineStatusBatch?app_key=" + app_key + "&sign=" + sign + "×tamp=" + timestamp; } // v41.2: MAX_RETRY=4 表示 初始请求 + 3次重试 = 4次请求 const int MAX_RETRY = 4; for (int retry = 0; retry < MAX_RETRY; retry++) { if (retry > 0) { // 指数退让:2^retry = 2, 4, 8 秒 int backoff_seconds = (1 << retry); std::cerr << "[批量状态] 重试第" << retry << "次(共" << MAX_RETRY-1 << "次),等待" << backoff_seconds << "秒..." << std::endl; std::this_thread::sleep_for(std::chrono::seconds(backoff_seconds)); } CURL* curl = curl_easy_init(); if (!curl) { std::cerr << "curl_easy_init failed" << std::endl; return -1; } std::string postdata = json_online_status_batch_encode(); std::cout << "[批量状态] data: " << postdata << std::endl; struct curl_slist* headers = NULL; headers = curl_slist_append(headers, "Content-Type: application/json;charset=UTF-8"); curl_easy_setopt(curl, CURLOPT_URL, req_url.c_str()); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 15L);//3L curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30L); // 增加超时时间 curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postdata.c_str()); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); CURLcode last_res = curl_easy_perform(curl); if (last_res == CURLE_OK) { std::cout << "[批量状态] success: " << response << std::endl; curl_slist_free_all(headers); curl_easy_cleanup(curl); return 0; } std::cerr << "[批量状态] Error: " << curl_easy_strerror(last_res) << std::endl; curl_slist_free_all(headers); curl_easy_cleanup(curl); if (last_res != CURLE_OPERATION_TIMEDOUT && last_res != CURLE_COULDNT_CONNECT) { break; } response.clear(); } return -1; } // ============================================ // ==================== 缺失的函数实现 ==================== std::string get_new_timestamp() { auto now = std::chrono::system_clock::now(); auto timestamp = std::chrono::duration_cast(now.time_since_epoch()).count(); return std::to_string(timestamp); } std::string get_format_time_s() { time_t now = time(nullptr); struct tm tm_now; localtime_r(&now, &tm_now); char buf[64]; strftime(buf, sizeof(buf), "%Y年%m月%d日%H:%M:%S", &tm_now); return std::string(buf); } std::string calculate_md5(const std::string& input) { char md5_hex[33]; md5_hexEx1(input.c_str(), md5_hex); return std::string(md5_hex); } bool lib_curl_image_upload_request(std::shared_ptr plate_info, GTYPE gtype) { CaptureInfo* cap_info = plate_info->cap_info_copy.get(); if (!cap_info) { std::cerr << "lib_curl_image_upload_request: cap_info为空" << std::endl; return false; } auto upload_start = std::chrono::steady_clock::now(); std::string timestamp = get_new_timestamp(); std::string sign = calculate_sign(app_key, app_secret, timestamp); std::string req_url = ""; if (TestFlag == 1) { req_url = app_api + "uploadImgTest?app_key=" + app_key + "&sign=" + sign + "×tamp=" + timestamp; } else { req_url = app_api + "uploadImg?app_key=" + app_key + "&sign=" + sign + "×tamp=" + timestamp; } std::string tb_no = plate_info->tb_num; std::string f_type = (g_wType == 3) ? "2" : "1"; std::string d_type = std::to_string(cap_info->dtype); std::string g_type = std::to_string(gtype); std::string filePath = ""; if (gtype == POSITION_LO) { if (plate_info->pic_path_front.empty()) { std::cerr << "pic_path_front 为空,跳过上传" << std::endl; return false; } filePath = std::string(pathComm + "PlateJPG/") + plate_info->pic_path_front; } else if (gtype == POSITION_HI) { if (plate_info->pic_path_side.empty()) { std::cerr << "pic_path_side 为空,跳过上传" << std::endl; return false; } filePath = std::string(pathComm + "PlateJPG/") + plate_info->pic_path_side; } std::cout << "dtype:" << d_type << " gtype:" << g_type << " file:" << filePath << std::endl; if (filePath.empty()) { std::cerr << "文件路径为空,跳过上传" << std::endl; return false; } std::string response; // v41.2: MAX_RETRY=4 表示 初始请求 + 3次重试 = 4次请求 const int MAX_RETRY = 4; for (int retry = 0; retry < MAX_RETRY; retry++) { if (retry > 0) { // 指数退让:2^retry = 2, 4, 8 秒 int backoff_seconds = (1 << retry); std::cerr << "[图片上传] 重试第" << retry << "次(共" << MAX_RETRY-1 << "次),等待" << backoff_seconds << "秒..." << std::endl; std::this_thread::sleep_for(std::chrono::seconds(backoff_seconds)); } CURL* curl = curl_easy_init(); if (!curl) { std::cerr << "curl_easy_init failed" << std::endl; return false; } curl_mime* mime = curl_mime_init(curl); if (!mime) { std::cerr << "curl_mime_init failed" << std::endl; curl_easy_cleanup(curl); return false; } curl_mimepart* part = nullptr; part = curl_mime_addpart(mime); curl_mime_name(part, "tbNo"); curl_mime_data(part, tb_no.c_str(), CURL_ZERO_TERMINATED); part = curl_mime_addpart(mime); curl_mime_name(part, "fType"); curl_mime_data(part, f_type.c_str(), CURL_ZERO_TERMINATED); part = curl_mime_addpart(mime); curl_mime_name(part, "dType"); curl_mime_data(part, d_type.c_str(), CURL_ZERO_TERMINATED); part = curl_mime_addpart(mime); curl_mime_name(part, "gType"); curl_mime_data(part, g_type.c_str(), CURL_ZERO_TERMINATED); std::string file_display_name = filePath; size_t last_slash = filePath.find_last_of("/"); if (last_slash != std::string::npos) { file_display_name = filePath.substr(last_slash + 1); } part = curl_mime_addpart(mime); curl_mime_name(part, "file"); curl_mime_filedata(part, filePath.c_str()); curl_mime_filename(part, file_display_name.c_str()); curl_mime_type(part, "image/jpeg"); curl_easy_setopt(curl, CURLOPT_URL, req_url.c_str()); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 15L); curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30L); curl_easy_setopt(curl, CURLOPT_MIMEPOST, mime); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); CURLcode last_res = curl_easy_perform(curl); if (last_res == CURLE_OK) { int parse_res = parse_upload_image_response(response); auto upload_end = std::chrono::steady_clock::now(); int duration_ms = std::chrono::duration_cast(upload_end - upload_start).count(); g_metrics.record_upload_time(duration_ms); curl_mime_free(mime); curl_easy_cleanup(curl); return (parse_res == 0); } std::cout << "[图片上传] Error curl_easy_perform" << std::endl; g_metrics.record_error(classify_upload_error(last_res, 0)); if (last_res != CURLE_OPERATION_TIMEDOUT && last_res != CURLE_COULDNT_CONNECT) { curl_mime_free(mime); curl_easy_cleanup(curl); return false; } curl_mime_free(mime); curl_easy_cleanup(curl); response.clear(); } auto upload_end = std::chrono::steady_clock::now(); int duration_ms = std::chrono::duration_cast(upload_end - upload_start).count(); g_metrics.record_upload_time(duration_ms); return false; } int lib_curl_create_bill_request(std::shared_ptr plate_info, std::string& tb_num_out) { tb_num_out.clear(); std::string response; std::string postdata = json_create_bill_encode(plate_info); std::string timestamp = get_new_timestamp(); std::string sign = calculate_sign(app_key, app_secret, timestamp); std::string req_url = ""; CaptureInfo* plate_cap_info = plate_info->cap_info_copy.get(); if (!plate_cap_info) { std::cerr << "lib_curl_create_bill_request: cap_info为空" << std::endl; return static_cast(ErrorCode::INVALID_PARAM); } bool is_in = (plate_cap_info->dtype == STATION_IN); bool is_business_error = false; if (TestFlag == 1) { req_url = app_api + "createBillTest?app_key=" + app_key + "&sign=" + sign + "×tamp=" + timestamp; } else { if (g_wType == 1) { req_url = app_api + "createBill?app_key=" + app_key + "&sign=" + sign + "×tamp=" + timestamp; } else if (g_wType == 3) { req_url = app_api + "consumeBill?app_key=" + app_key + "&sign=" + sign + "×tamp=" + timestamp; } else { req_url = app_api + "createBill?app_key=" + app_key + "&sign=" + sign + "×tamp=" + timestamp; } } std::cout << "url: " << req_url << std::endl; std::cout << "data: " << postdata << std::endl; const int MAX_RETRY = 3; // 初始请求 + 3次重试 = 4次请求 for (int retry = 0; retry <= MAX_RETRY; retry++) { int backoff_seconds = (retry > 0) ? (1 << retry) : 0; // 指数退让: 2, 4, 8秒 if (retry > 0) { std::cerr << "[创建工单] 重试第" << retry << "次(共" << MAX_RETRY << "次),等待" << backoff_seconds << "秒..." << std::endl; sleep(backoff_seconds); } CURL* curl = curl_easy_init(); if (!curl) { std::cerr << "curl_easy_init failed" << std::endl; return static_cast(ErrorCode::NETWORK_ERROR); } curl_easy_setopt(curl, CURLOPT_URL, req_url.c_str()); struct curl_slist* headers = NULL; headers = curl_slist_append(headers, "Content-Type: application/json;charset=UTF-8"); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 15L); curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30L); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postdata.c_str()); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); CURLcode last_res = curl_easy_perform(curl); if (last_res == CURLE_OK) { int parse_res = parse_create_bill_response(response, plate_info->code, is_in, tb_num_out); if (parse_res == static_cast(ErrorCode::SUCCESS)) { curl_slist_free_all(headers); curl_easy_cleanup(curl); increment_bill_count(plate_info->code, is_in); return static_cast(ErrorCode::SUCCESS); } if (parse_res == static_cast(ErrorCode::BUSINESS_ERROR)) { std::cerr << "[创建工单] 业务错误,不再重试" << std::endl; is_business_error = true; curl_slist_free_all(headers); curl_easy_cleanup(curl); break; } std::cerr << "[创建工单] 解析响应失败,准备重试" << std::endl; curl_slist_free_all(headers); curl_easy_cleanup(curl); response.clear(); continue; } std::cout << "[创建工单] Error curl_easy_perform" << std::endl; g_metrics.record_createbill(false); g_metrics.record_error(classify_upload_error(last_res, 0)); if (last_res != CURLE_OPERATION_TIMEDOUT && last_res != CURLE_COULDNT_CONNECT) { curl_slist_free_all(headers); curl_easy_cleanup(curl); return static_cast(ErrorCode::NETWORK_ERROR); } curl_slist_free_all(headers); curl_easy_cleanup(curl); response.clear(); } if (!is_business_error) { std::cerr << "[创建工单] 所有重试都失败" << std::endl; g_metrics.record_createbill(false); } else { g_metrics.record_createbill(false, true); } return is_business_error ? static_cast(ErrorCode::BUSINESS_ERROR) : static_cast(ErrorCode::NETWORK_ERROR); } int lib_curl_online_status_request(CaptureInfo* cap_info) { std::string response; std::string timestamp = get_new_timestamp(); std::string sign = calculate_sign(app_key, app_secret, timestamp); std::string req_url = ""; if (TestFlag == 1) { req_url = app_api + "onlineStatusTest?app_key=" + app_key + "&sign=" + sign + "×tamp=" + timestamp; } else { req_url = app_api + "onlineStatus?app_key=" + app_key + "&sign=" + sign + "×tamp=" + timestamp; } // v41.2优化:指数退让重试 (2秒、4秒、8秒),最多3次 // v41.2: MAX_RETRY=4 表示 初始请求 + 3次重试 = 4次请求 const int MAX_RETRY = 4; for (int retry = 0; retry < MAX_RETRY; retry++) { if (retry > 0) { // 指数退让:2^retry = 2, 4, 8 秒 int backoff_seconds = (1 << retry); std::cerr << "[在线状态] 重试第" << retry << "次(共" << MAX_RETRY-1 << "次),等待" << backoff_seconds << "秒..." << std::endl; std::this_thread::sleep_for(std::chrono::seconds(backoff_seconds)); } CURL* curl = curl_easy_init(); if (!curl) { std::cerr << "curl_easy_init failed" << std::endl; return -1; } std::string postdata = json_online_status_encode(cap_info); std::cout << "data: " << postdata << std::endl; curl_easy_setopt(curl, CURLOPT_URL, req_url.c_str()); struct curl_slist* headers = NULL; headers = curl_slist_append(headers, "Content-Type: application/json;charset=UTF-8"); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 15L); curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30L); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postdata.c_str()); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); CURLcode last_res = curl_easy_perform(curl); if (last_res == CURLE_OK) { parse_online_status_response(response); curl_slist_free_all(headers); curl_easy_cleanup(curl); return 0; } std::cout << "[在线状态] Error curl_easy_perform" << std::endl; if (last_res != CURLE_OPERATION_TIMEDOUT && last_res != CURLE_COULDNT_CONNECT) { curl_slist_free_all(headers); curl_easy_cleanup(curl); return -1; } curl_slist_free_all(headers); curl_easy_cleanup(curl); response.clear(); } return -1; }