network_client.cpp 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760
  1. /**
  2. * network_client.cpp — 网络通信实现
  3. */
  4. #include "network_client.h"
  5. #include "feishu_client.h"
  6. #include "utils.h"
  7. #include "md5ex1.h"
  8. #include <iostream>
  9. size_t WriteMemoryCallback(void* contents, size_t size, size_t nmemb, std::string* str);
  10. // ==================== 飞书配置 ====================
  11. void InitCurlCommon(CURL* curl, std::string* response) {
  12. curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 15L);
  13. curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30L);
  14. curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
  15. curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
  16. curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
  17. curl_easy_setopt(curl, CURLOPT_WRITEDATA, response);
  18. }
  19. int parse_create_bill_response(const std::string& response, const std::string& plate, bool is_in, std::string& tb_num_out) {
  20. tb_num_out.clear();
  21. cJSON* root = cJSON_Parse(response.c_str());
  22. if (!root) {
  23. std::cerr << "[解析失败] 响应不是合法JSON: " << response << std::endl;
  24. return static_cast<int>(ErrorCode::PARSE_ERROR);
  25. }
  26. if (DEBUG_LOG) {
  27. std::cout << response << std::endl;
  28. }
  29. cJSON* status = cJSON_GetObjectItem(root, "status");
  30. cJSON* data = cJSON_GetObjectItem(root, "data");
  31. cJSON* msg = cJSON_GetObjectItem(root, "msg");
  32. if (msg && cJSON_IsString(msg) && strstr(msg->valuestring, "已确认上线") != NULL) {
  33. std::cout << "检测到Test环境已确认上线,自动切换到正式环境" << std::endl;
  34. TestFlag = 0;
  35. cJSON_Delete(root);
  36. return static_cast<int>(ErrorCode::BUSINESS_ERROR);
  37. }
  38. // 业务错误在任何日志级别下都生效
  39. if (DEBUG_LOG == 0)//调试模式DEBUG_LOG = 1,正式运行DEBUG_LOG =0屏蔽掉
  40. {
  41. if (msg && cJSON_IsString(msg) && strstr(msg->valuestring, "非在网车辆") != NULL) {
  42. std::cerr << "车牌 " << plate << (is_in ? " 进站" : " 出站")
  43. << " 非在网车辆" << std::endl;
  44. cJSON_Delete(root);
  45. return static_cast<int>(ErrorCode::BUSINESS_ERROR);
  46. }
  47. }
  48. if (status && status->valueint == BUSINESS_ERROR_CODE) {
  49. std::cerr << "车牌 " << plate << (is_in ? " 进站" : " 出站")
  50. << " 业务异常: " << (msg ? msg->valuestring : "未知") << std::endl;
  51. cJSON_Delete(root);
  52. return static_cast<int>(ErrorCode::BUSINESS_ERROR);
  53. }
  54. if (DEBUG_LOG == 0)//调试模式DEBUG_LOG = 1,正式运行DEBUG_LOG =0屏蔽掉
  55. {
  56. if (!status || status->valueint != HTTP_STATUS_OK) {
  57. std::cerr << "[创建工单失败] 服务器返回非200状态: " << (status ? status->valueint : -1) << std::endl;
  58. if (msg && cJSON_IsString(msg)) {
  59. std::cout << "错误信息: " << msg->valuestring << std::endl;
  60. }
  61. cJSON_Delete(root);
  62. return static_cast<int>(ErrorCode::NETWORK_ERROR);
  63. }
  64. }
  65. if (!data) {
  66. std::cerr << "[解析失败] 响应缺少data字段" << std::endl;
  67. cJSON_Delete(root);
  68. return static_cast<int>(ErrorCode::PARSE_ERROR);
  69. }
  70. cJSON* tbNo = cJSON_GetObjectItem(data, "tbNo");
  71. if (!tbNo || !cJSON_IsString(tbNo) || strlen(tbNo->valuestring) == 0) {
  72. std::cerr << "[解析失败] 未找到有效的tbNo" << std::endl;
  73. cJSON_Delete(root);
  74. return static_cast<int>(ErrorCode::PARSE_ERROR);
  75. }
  76. tb_num_out = tbNo->valuestring;
  77. update_bill_cache_tb_num(plate, is_in, tb_num_out);
  78. std::cout << "[解析成功] " << (is_in ? "进站" : "出站") << "联单编号: " << tb_num_out << std::endl;
  79. cJSON_Delete(root);
  80. g_metrics.record_createbill(true);
  81. return static_cast<int>(ErrorCode::SUCCESS);
  82. }
  83. int parse_online_status_response(std::string resp)
  84. {
  85. std::cout << resp << std::endl;
  86. cJSON* root = cJSON_Parse(resp.c_str());
  87. if (NULL == root)
  88. {
  89. std::cout << "parse_upload_image_response error!!!" << std::endl;
  90. return -1;
  91. }
  92. cJSON* status = cJSON_GetObjectItem(root, "status");
  93. if (status && status->valueint == HTTP_STATUS_OK)
  94. {
  95. std::cout << "online status success!!!" << std::endl;
  96. }
  97. cJSON_Delete(root);
  98. return 0;
  99. }
  100. int parse_upload_image_response(std::string resp)
  101. {
  102. std::cout << resp << std::endl;
  103. cJSON* root = cJSON_Parse(resp.c_str());
  104. if (NULL == root)
  105. {
  106. std::cout << "parse_upload_image_response error!!!" << std::endl;
  107. return -1;
  108. }
  109. cJSON* status = cJSON_GetObjectItem(root, "status");
  110. if (status && status->valueint == HTTP_STATUS_OK)
  111. {
  112. std::cout << "upload image success!!!" << std::endl;
  113. cJSON_Delete(root);
  114. return 0;
  115. }
  116. std::cerr << "upload image failed" << std::endl;
  117. cJSON_Delete(root);
  118. return -1;
  119. }
  120. std::string json_create_bill_encode(std::shared_ptr<CarPlateInfo> plate_info)
  121. {
  122. CaptureInfo* plate_cap_info = plate_info->cap_info_copy.get();
  123. if (!plate_cap_info) {
  124. std::cerr << "json_create_bill_encode: cap_info为空" << std::endl;
  125. return std::string("");
  126. }
  127. cJSON* json = cJSON_CreateObject();
  128. bool is_special = false;
  129. {
  130. std::lock_guard<std::mutex> lock(g_special_plates_mtx);
  131. for (auto& p : g_special_plates) {
  132. if (p == plate_info->code) {
  133. is_special = true;
  134. break;
  135. }
  136. }
  137. }
  138. // fix24-v21: 使用照片抓拍时间(db_capture_time)替代当前时间,确保API上报时间与水印/DB一致
  139. std::string capture_time_str;
  140. if (plate_info->db_capture_time > 0) {
  141. struct tm tm_buf;
  142. struct tm* tm_ptr = localtime_r(&plate_info->db_capture_time, &tm_buf);
  143. if (tm_ptr) {
  144. char buf[64];
  145. strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", tm_ptr);
  146. capture_time_str = buf;
  147. } else {
  148. capture_time_str = get_format_time();
  149. }
  150. } else {
  151. capture_time_str = get_format_time();
  152. }
  153. if (g_wType == 1)
  154. {
  155. cJSON_AddStringToObject(json, "plateNumber", plate_info->code.c_str());
  156. cJSON_AddStringToObject(json, "outWorkSiteDate", capture_time_str.c_str());
  157. cJSON_AddStringToObject(json, "workSiteNo", point_number.c_str());
  158. cJSON_AddStringToObject(json, "doorNo", throughway.c_str());
  159. if (is_special) {
  160. cJSON_AddStringToObject(json, "platenumcolor", "黄色");
  161. cJSON_AddStringToObject(json, "vehicleType", "渣土车");
  162. }
  163. else {
  164. cJSON_AddStringToObject(json, "platenumcolor", plate_info->type.c_str());
  165. cJSON_AddStringToObject(json, "vehicleType", "");
  166. }
  167. cJSON_AddNumberToObject(json, "dType", plate_cap_info->dtype);
  168. }
  169. else if (g_wType == 3)
  170. {
  171. cJSON_AddStringToObject(json, "plateNumber", plate_info->code.c_str());
  172. cJSON_AddStringToObject(json, "inDisposalDate", capture_time_str.c_str());
  173. cJSON_AddStringToObject(json, "disposalNo", point_number.c_str());
  174. cJSON_AddStringToObject(json, "doorNo", throughway.c_str());
  175. if (is_special) {
  176. cJSON_AddStringToObject(json, "platenumcolor", "黄色");
  177. }
  178. else {
  179. cJSON_AddStringToObject(json, "platenumcolor", plate_info->type.c_str());
  180. }
  181. cJSON_AddNumberToObject(json, "dType", plate_cap_info->dtype);
  182. }
  183. char* string = cJSON_Print(json);
  184. cJSON_Delete(json);
  185. if (string == NULL)
  186. {
  187. return std::string("");
  188. }
  189. std::string res = std::string(string);
  190. free(string);
  191. return res;
  192. }
  193. std::string json_online_status_encode(CaptureInfo* cap_info)
  194. {
  195. if (!cap_info) {
  196. std::cerr << "json_online_status_encode: cap_info为空" << std::endl;
  197. return std::string("");
  198. }
  199. cJSON* json = cJSON_CreateObject();
  200. cJSON_AddStringToObject(json, "workSiteNo", point_number.c_str());
  201. cJSON_AddNumberToObject(json, "wType", g_wType);
  202. cJSON_AddNumberToObject(json, "dType", cap_info->dtype);
  203. cJSON_AddNumberToObject(json, "gType", cap_info->gtype);
  204. cJSON_AddNumberToObject(json, "status", cap_info->status);
  205. cJSON_AddStringToObject(json, "remark", "");
  206. cJSON_AddStringToObject(json, "timestamp", get_format_time().c_str());
  207. char* string = cJSON_Print(json);
  208. cJSON_Delete(json);
  209. if (string == NULL)
  210. {
  211. return std::string("");
  212. }
  213. std::string res = std::string(string);
  214. free(string);
  215. return res;
  216. }
  217. // ============================================
  218. // v41.2 新增: 批量状态上报 - 将4个摄像头合并为1个请求
  219. // ============================================
  220. std::string json_online_status_batch_encode()
  221. {
  222. cJSON* root = cJSON_CreateObject();
  223. cJSON_AddStringToObject(root, "workSiteNo", point_number.c_str());
  224. cJSON_AddNumberToObject(root, "wType", g_wType);
  225. cJSON_AddStringToObject(root, "timestamp", get_format_time().c_str());
  226. // 创建摄像头状态数组
  227. cJSON* cameraList = cJSON_CreateArray();
  228. // 4个摄像头的状态
  229. CaptureInfo* cameras[4] = {
  230. &plate_rec_app.front_info_in, // 进站低位
  231. &plate_rec_app.front_info_out, // 出站低位
  232. &plate_rec_app.side_info_in, // 进站高位
  233. &plate_rec_app.side_info_out // 出站高位
  234. };
  235. for (int i = 0; i < 4; i++) {
  236. cJSON* cam = cJSON_CreateObject();
  237. cJSON_AddNumberToObject(cam, "dType", cameras[i]->dtype);
  238. cJSON_AddNumberToObject(cam, "gType", cameras[i]->gtype);
  239. cJSON_AddNumberToObject(cam, "status", cameras[i]->status);
  240. cJSON_AddStringToObject(cam, "remark", "");
  241. cJSON_AddItemToArray(cameraList, cam);
  242. }
  243. cJSON_AddItemToObject(root, "cameraList", cameraList);
  244. char* string = cJSON_PrintUnformatted(root);
  245. cJSON_Delete(root);
  246. if (string == NULL) {
  247. return std::string("");
  248. }
  249. std::string res = std::string(string);
  250. free(string);
  251. return res;
  252. }
  253. // 前向声明
  254. std::string get_new_timestamp();
  255. // ==================== 签名计算 ====================
  256. std::string calculate_sign(const std::string& app_key, const std::string& app_secret, const std::string& timestamp) {
  257. std::string sign_str = app_secret + "#app_key" + app_key + "timestamp" + timestamp + "#" + app_secret;
  258. char md5_hex[33];
  259. md5_hexEx1(sign_str.c_str(), md5_hex);
  260. return std::string(md5_hex);
  261. }
  262. // ==================== libcurl初始化/清理 ====================
  263. int lib_curl_init()
  264. {
  265. curl_global_init(CURL_GLOBAL_DEFAULT);
  266. return 0;
  267. }
  268. void lib_curl_deinit()
  269. {
  270. curl_global_cleanup();
  271. }
  272. // ==================== 飞书Token缓存 ====================
  273. std::string get_cached_tenant_token() {
  274. time_t now = time(NULL);
  275. if (!g_tenant_token.empty() && now < g_token_expire_time.load() - TOKEN_REFRESH_BUFFER_SECONDS) {
  276. return g_tenant_token;
  277. }
  278. std::lock_guard<std::mutex> lock(g_token_mtx);
  279. now = time(NULL);
  280. if (!g_tenant_token.empty() && now < g_token_expire_time.load() - TOKEN_REFRESH_BUFFER_SECONDS) {
  281. return g_tenant_token;
  282. }
  283. std::string new_token = get_tenant_token();
  284. if (!new_token.empty()) {
  285. g_tenant_token = new_token;
  286. g_token_expire_time.store(time(NULL) + TOKEN_EXPIRE_SECONDS);
  287. std::cout << "[Token刷新] 飞书租户Token已更新,过期时间: " << g_token_expire_time.load() << std::endl;
  288. } else {
  289. std::cerr << "[Token刷新] 获取飞书Token失败,使用旧Token(如果存在)" << std::endl;
  290. }
  291. return g_tenant_token;
  292. }
  293. // 批量状态上报请求
  294. int lib_curl_online_status_batch_request()
  295. {
  296. std::string response;
  297. std::string timestamp = get_new_timestamp();
  298. std::string sign = calculate_sign(app_key, app_secret, timestamp);
  299. std::string req_url = "";
  300. if (TestFlag == 1) {
  301. req_url = app_api + "onlineStatusBatchTest?app_key=" +
  302. app_key + "&sign=" + sign + "&timestamp=" + timestamp;
  303. } else {
  304. // 使用批量接口
  305. req_url = app_api + "onlineStatusBatch?app_key=" +
  306. app_key + "&sign=" + sign + "&timestamp=" + timestamp;
  307. }
  308. // v41.2: MAX_RETRY=4 表示 初始请求 + 3次重试 = 4次请求
  309. const int MAX_RETRY = 4;
  310. for (int retry = 0; retry < MAX_RETRY; retry++) {
  311. if (retry > 0) {
  312. // 指数退让:2^retry = 2, 4, 8 秒
  313. int backoff_seconds = (1 << retry);
  314. std::cerr << "[批量状态] 重试第" << retry << "次(共" << MAX_RETRY-1 << "次),等待" << backoff_seconds << "秒..." << std::endl;
  315. std::this_thread::sleep_for(std::chrono::seconds(backoff_seconds));
  316. }
  317. CURL* curl = curl_easy_init();
  318. if (!curl) {
  319. std::cerr << "curl_easy_init failed" << std::endl;
  320. return -1;
  321. }
  322. std::string postdata = json_online_status_batch_encode();
  323. std::cout << "[批量状态] data: " << postdata << std::endl;
  324. struct curl_slist* headers = NULL;
  325. headers = curl_slist_append(headers, "Content-Type: application/json;charset=UTF-8");
  326. curl_easy_setopt(curl, CURLOPT_URL, req_url.c_str());
  327. curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
  328. curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
  329. curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
  330. curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 15L);//3L
  331. curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30L); // 增加超时时间
  332. curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postdata.c_str());
  333. curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
  334. curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
  335. CURLcode last_res = curl_easy_perform(curl);
  336. if (last_res == CURLE_OK) {
  337. std::cout << "[批量状态] success: " << response << std::endl;
  338. curl_slist_free_all(headers);
  339. curl_easy_cleanup(curl);
  340. return 0;
  341. }
  342. std::cerr << "[批量状态] Error: " << curl_easy_strerror(last_res) << std::endl;
  343. curl_slist_free_all(headers);
  344. curl_easy_cleanup(curl);
  345. if (last_res != CURLE_OPERATION_TIMEDOUT && last_res != CURLE_COULDNT_CONNECT) {
  346. break;
  347. }
  348. response.clear();
  349. }
  350. return -1;
  351. }
  352. // ============================================
  353. // ==================== 缺失的函数实现 ====================
  354. std::string get_new_timestamp() {
  355. auto now = std::chrono::system_clock::now();
  356. auto timestamp = std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch()).count();
  357. return std::to_string(timestamp);
  358. }
  359. std::string get_format_time_s() {
  360. time_t now = time(nullptr);
  361. struct tm tm_now;
  362. localtime_r(&now, &tm_now);
  363. char buf[64];
  364. strftime(buf, sizeof(buf), "%Y年%m月%d日%H:%M:%S", &tm_now);
  365. return std::string(buf);
  366. }
  367. std::string calculate_md5(const std::string& input) {
  368. char md5_hex[33];
  369. md5_hexEx1(input.c_str(), md5_hex);
  370. return std::string(md5_hex);
  371. }
  372. bool lib_curl_image_upload_request(std::shared_ptr<CarPlateInfo> plate_info, GTYPE gtype)
  373. {
  374. CaptureInfo* cap_info = plate_info->cap_info_copy.get();
  375. if (!cap_info) {
  376. std::cerr << "lib_curl_image_upload_request: cap_info为空" << std::endl;
  377. return false;
  378. }
  379. auto upload_start = std::chrono::steady_clock::now();
  380. std::string timestamp = get_new_timestamp();
  381. std::string sign = calculate_sign(app_key, app_secret, timestamp);
  382. std::string req_url = "";
  383. if (TestFlag == 1)
  384. {
  385. req_url = app_api + "uploadImgTest?app_key=" +
  386. app_key + "&sign=" + sign + "&timestamp=" + timestamp;
  387. }
  388. else
  389. {
  390. req_url = app_api + "uploadImg?app_key=" +
  391. app_key + "&sign=" + sign + "&timestamp=" + timestamp;
  392. }
  393. std::string tb_no = plate_info->tb_num;
  394. std::string f_type = (g_wType == 3) ? "2" : "1";
  395. std::string d_type = std::to_string(cap_info->dtype);
  396. std::string g_type = std::to_string(gtype);
  397. std::string filePath = "";
  398. if (gtype == POSITION_LO)
  399. {
  400. if (plate_info->pic_path_front.empty()) {
  401. std::cerr << "pic_path_front 为空,跳过上传" << std::endl;
  402. return false;
  403. }
  404. filePath = std::string(pathComm + "PlateJPG/") + plate_info->pic_path_front;
  405. }
  406. else if (gtype == POSITION_HI)
  407. {
  408. if (plate_info->pic_path_side.empty()) {
  409. std::cerr << "pic_path_side 为空,跳过上传" << std::endl;
  410. return false;
  411. }
  412. filePath = std::string(pathComm + "PlateJPG/") + plate_info->pic_path_side;
  413. }
  414. std::cout << "dtype:" << d_type << " gtype:" << g_type << " file:" << filePath << std::endl;
  415. if (filePath.empty()) {
  416. std::cerr << "文件路径为空,跳过上传" << std::endl;
  417. return false;
  418. }
  419. std::string response;
  420. // v41.2: MAX_RETRY=4 表示 初始请求 + 3次重试 = 4次请求
  421. const int MAX_RETRY = 4;
  422. for (int retry = 0; retry < MAX_RETRY; retry++) {
  423. if (retry > 0) {
  424. // 指数退让:2^retry = 2, 4, 8 秒
  425. int backoff_seconds = (1 << retry);
  426. std::cerr << "[图片上传] 重试第" << retry << "次(共" << MAX_RETRY-1 << "次),等待" << backoff_seconds << "秒..." << std::endl;
  427. std::this_thread::sleep_for(std::chrono::seconds(backoff_seconds));
  428. }
  429. CURL* curl = curl_easy_init();
  430. if (!curl) {
  431. std::cerr << "curl_easy_init failed" << std::endl;
  432. return false;
  433. }
  434. curl_mime* mime = curl_mime_init(curl);
  435. if (!mime) {
  436. std::cerr << "curl_mime_init failed" << std::endl;
  437. curl_easy_cleanup(curl);
  438. return false;
  439. }
  440. curl_mimepart* part = nullptr;
  441. part = curl_mime_addpart(mime);
  442. curl_mime_name(part, "tbNo");
  443. curl_mime_data(part, tb_no.c_str(), CURL_ZERO_TERMINATED);
  444. part = curl_mime_addpart(mime);
  445. curl_mime_name(part, "fType");
  446. curl_mime_data(part, f_type.c_str(), CURL_ZERO_TERMINATED);
  447. part = curl_mime_addpart(mime);
  448. curl_mime_name(part, "dType");
  449. curl_mime_data(part, d_type.c_str(), CURL_ZERO_TERMINATED);
  450. part = curl_mime_addpart(mime);
  451. curl_mime_name(part, "gType");
  452. curl_mime_data(part, g_type.c_str(), CURL_ZERO_TERMINATED);
  453. std::string file_display_name = filePath;
  454. size_t last_slash = filePath.find_last_of("/");
  455. if (last_slash != std::string::npos) {
  456. file_display_name = filePath.substr(last_slash + 1);
  457. }
  458. part = curl_mime_addpart(mime);
  459. curl_mime_name(part, "file");
  460. curl_mime_filedata(part, filePath.c_str());
  461. curl_mime_filename(part, file_display_name.c_str());
  462. curl_mime_type(part, "image/jpeg");
  463. curl_easy_setopt(curl, CURLOPT_URL, req_url.c_str());
  464. curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
  465. curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
  466. curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 15L);
  467. curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30L);
  468. curl_easy_setopt(curl, CURLOPT_MIMEPOST, mime);
  469. curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
  470. curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
  471. CURLcode last_res = curl_easy_perform(curl);
  472. if (last_res == CURLE_OK) {
  473. int parse_res = parse_upload_image_response(response);
  474. auto upload_end = std::chrono::steady_clock::now();
  475. int duration_ms = std::chrono::duration_cast<std::chrono::milliseconds>(upload_end - upload_start).count();
  476. g_metrics.record_upload_time(duration_ms);
  477. curl_mime_free(mime);
  478. curl_easy_cleanup(curl);
  479. return (parse_res == 0);
  480. }
  481. std::cout << "[图片上传] Error curl_easy_perform" << std::endl;
  482. g_metrics.record_error(classify_upload_error(last_res, 0));
  483. if (last_res != CURLE_OPERATION_TIMEDOUT && last_res != CURLE_COULDNT_CONNECT) {
  484. curl_mime_free(mime);
  485. curl_easy_cleanup(curl);
  486. return false;
  487. }
  488. curl_mime_free(mime);
  489. curl_easy_cleanup(curl);
  490. response.clear();
  491. }
  492. auto upload_end = std::chrono::steady_clock::now();
  493. int duration_ms = std::chrono::duration_cast<std::chrono::milliseconds>(upload_end - upload_start).count();
  494. g_metrics.record_upload_time(duration_ms);
  495. return false;
  496. }
  497. int lib_curl_create_bill_request(std::shared_ptr<CarPlateInfo> plate_info, std::string& tb_num_out)
  498. {
  499. tb_num_out.clear();
  500. std::string response;
  501. std::string postdata = json_create_bill_encode(plate_info);
  502. std::string timestamp = get_new_timestamp();
  503. std::string sign = calculate_sign(app_key, app_secret, timestamp);
  504. std::string req_url = "";
  505. CaptureInfo* plate_cap_info = plate_info->cap_info_copy.get();
  506. if (!plate_cap_info) {
  507. std::cerr << "lib_curl_create_bill_request: cap_info为空" << std::endl;
  508. return static_cast<int>(ErrorCode::INVALID_PARAM);
  509. }
  510. bool is_in = (plate_cap_info->dtype == STATION_IN);
  511. bool is_business_error = false;
  512. if (TestFlag == 1)
  513. {
  514. req_url = app_api + "createBillTest?app_key=" +
  515. app_key + "&sign=" + sign + "&timestamp=" + timestamp;
  516. }
  517. else
  518. {
  519. if (g_wType == 1)
  520. {
  521. req_url = app_api + "createBill?app_key=" +
  522. app_key + "&sign=" + sign + "&timestamp=" + timestamp;
  523. }
  524. else if (g_wType == 3)
  525. {
  526. req_url = app_api + "consumeBill?app_key=" +
  527. app_key + "&sign=" + sign + "&timestamp=" + timestamp;
  528. }
  529. else
  530. {
  531. req_url = app_api + "createBill?app_key=" +
  532. app_key + "&sign=" + sign + "&timestamp=" + timestamp;
  533. }
  534. }
  535. std::cout << "url: " << req_url << std::endl;
  536. std::cout << "data: " << postdata << std::endl;
  537. const int MAX_RETRY = 3; // 初始请求 + 3次重试 = 4次请求
  538. for (int retry = 0; retry <= MAX_RETRY; retry++) {
  539. int backoff_seconds = (retry > 0) ? (1 << retry) : 0; // 指数退让: 2, 4, 8秒
  540. if (retry > 0) {
  541. std::cerr << "[创建工单] 重试第" << retry << "次(共" << MAX_RETRY << "次),等待" << backoff_seconds << "秒..." << std::endl;
  542. sleep(backoff_seconds);
  543. }
  544. CURL* curl = curl_easy_init();
  545. if (!curl) {
  546. std::cerr << "curl_easy_init failed" << std::endl;
  547. return static_cast<int>(ErrorCode::NETWORK_ERROR);
  548. }
  549. curl_easy_setopt(curl, CURLOPT_URL, req_url.c_str());
  550. struct curl_slist* headers = NULL;
  551. headers = curl_slist_append(headers, "Content-Type: application/json;charset=UTF-8");
  552. curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
  553. curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
  554. curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
  555. curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 15L);
  556. curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30L);
  557. curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postdata.c_str());
  558. curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
  559. curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
  560. CURLcode last_res = curl_easy_perform(curl);
  561. if (last_res == CURLE_OK) {
  562. int parse_res = parse_create_bill_response(response, plate_info->code, is_in, tb_num_out);
  563. if (parse_res == static_cast<int>(ErrorCode::SUCCESS)) {
  564. curl_slist_free_all(headers);
  565. curl_easy_cleanup(curl);
  566. increment_bill_count(plate_info->code, is_in);
  567. return static_cast<int>(ErrorCode::SUCCESS);
  568. }
  569. if (parse_res == static_cast<int>(ErrorCode::BUSINESS_ERROR)) {
  570. std::cerr << "[创建工单] 业务错误,不再重试" << std::endl;
  571. is_business_error = true;
  572. curl_slist_free_all(headers);
  573. curl_easy_cleanup(curl);
  574. break;
  575. }
  576. std::cerr << "[创建工单] 解析响应失败,准备重试" << std::endl;
  577. curl_slist_free_all(headers);
  578. curl_easy_cleanup(curl);
  579. response.clear();
  580. continue;
  581. }
  582. std::cout << "[创建工单] Error curl_easy_perform" << std::endl;
  583. g_metrics.record_createbill(false);
  584. g_metrics.record_error(classify_upload_error(last_res, 0));
  585. if (last_res != CURLE_OPERATION_TIMEDOUT && last_res != CURLE_COULDNT_CONNECT) {
  586. curl_slist_free_all(headers);
  587. curl_easy_cleanup(curl);
  588. return static_cast<int>(ErrorCode::NETWORK_ERROR);
  589. }
  590. curl_slist_free_all(headers);
  591. curl_easy_cleanup(curl);
  592. response.clear();
  593. }
  594. if (!is_business_error) {
  595. std::cerr << "[创建工单] 所有重试都失败" << std::endl;
  596. g_metrics.record_createbill(false);
  597. } else {
  598. g_metrics.record_createbill(false, true);
  599. }
  600. return is_business_error ? static_cast<int>(ErrorCode::BUSINESS_ERROR) : static_cast<int>(ErrorCode::NETWORK_ERROR);
  601. }
  602. int lib_curl_online_status_request(CaptureInfo* cap_info)
  603. {
  604. std::string response;
  605. std::string timestamp = get_new_timestamp();
  606. std::string sign = calculate_sign(app_key, app_secret, timestamp);
  607. std::string req_url = "";
  608. if (TestFlag == 1)
  609. {
  610. req_url = app_api + "onlineStatusTest?app_key=" +
  611. app_key + "&sign=" + sign + "&timestamp=" + timestamp;
  612. }
  613. else
  614. {
  615. req_url = app_api + "onlineStatus?app_key=" +
  616. app_key + "&sign=" + sign + "&timestamp=" + timestamp;
  617. }
  618. // v41.2优化:指数退让重试 (2秒、4秒、8秒),最多3次
  619. // v41.2: MAX_RETRY=4 表示 初始请求 + 3次重试 = 4次请求
  620. const int MAX_RETRY = 4;
  621. for (int retry = 0; retry < MAX_RETRY; retry++) {
  622. if (retry > 0) {
  623. // 指数退让:2^retry = 2, 4, 8 秒
  624. int backoff_seconds = (1 << retry);
  625. std::cerr << "[在线状态] 重试第" << retry << "次(共" << MAX_RETRY-1 << "次),等待" << backoff_seconds << "秒..." << std::endl;
  626. std::this_thread::sleep_for(std::chrono::seconds(backoff_seconds));
  627. }
  628. CURL* curl = curl_easy_init();
  629. if (!curl) {
  630. std::cerr << "curl_easy_init failed" << std::endl;
  631. return -1;
  632. }
  633. std::string postdata = json_online_status_encode(cap_info);
  634. std::cout << "data: " << postdata << std::endl;
  635. curl_easy_setopt(curl, CURLOPT_URL, req_url.c_str());
  636. struct curl_slist* headers = NULL;
  637. headers = curl_slist_append(headers, "Content-Type: application/json;charset=UTF-8");
  638. curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
  639. curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
  640. curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
  641. curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 15L);
  642. curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30L);
  643. curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postdata.c_str());
  644. curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
  645. curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
  646. CURLcode last_res = curl_easy_perform(curl);
  647. if (last_res == CURLE_OK) {
  648. parse_online_status_response(response);
  649. curl_slist_free_all(headers);
  650. curl_easy_cleanup(curl);
  651. return 0;
  652. }
  653. std::cout << "[在线状态] Error curl_easy_perform" << std::endl;
  654. if (last_res != CURLE_OPERATION_TIMEDOUT && last_res != CURLE_COULDNT_CONNECT) {
  655. curl_slist_free_all(headers);
  656. curl_easy_cleanup(curl);
  657. return -1;
  658. }
  659. curl_slist_free_all(headers);
  660. curl_easy_cleanup(curl);
  661. response.clear();
  662. }
  663. return -1;
  664. }