From f6d867dae426d0073d5c6e858a519380f9d828e6 Mon Sep 17 00:00:00 2001 From: sladro Date: Sun, 1 Mar 2026 18:43:23 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E9=85=8D=E7=BD=AE=E6=96=87?= =?UTF-8?q?=E4=BB=B6=EF=BC=8C=E6=B7=BB=E5=8A=A0=E6=89=B9=E5=A4=84=E7=90=86?= =?UTF-8?q?=E5=9B=9E=E8=B0=83=E6=9C=8D=E5=8A=A1=E5=99=A8=E5=9C=B0=E5=9D=80?= =?UTF-8?q?=E5=92=8C=E4=BB=A4=E7=89=8C=E7=9A=84=E9=BB=98=E8=AE=A4=E5=80=BC?= =?UTF-8?q?=EF=BC=9B=E4=BC=98=E5=8C=96=E6=89=B9=E5=A4=84=E7=90=86=E6=93=8D?= =?UTF-8?q?=E4=BD=9C=E5=A4=84=E7=90=86=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Config.h | 6 +- MFCCreoDll.cpp | 325 ++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 310 insertions(+), 21 deletions(-) diff --git a/Config.h b/Config.h index c99a205..d19a3b1 100644 --- a/Config.h +++ b/Config.h @@ -7,8 +7,10 @@ public: static const int HTTP_PORT = 12345; static const int WEBSOCKET_PORT = 12346; - // API配置 - static constexpr const char* API_PREFIX = "/api"; + // API配置 + static constexpr const char* API_PREFIX = "/api"; + static constexpr const char* BATCH_CALLBACK_SERVER_ADDRESS = ""; + static constexpr const char* BATCH_CALLBACK_TOKEN = ""; // 超时配置 static const int HTTP_TIMEOUT_MS = 30000; // 30秒 diff --git a/MFCCreoDll.cpp b/MFCCreoDll.cpp index 2fd45db..4ed330b 100644 --- a/MFCCreoDll.cpp +++ b/MFCCreoDll.cpp @@ -12,14 +12,23 @@ #include "ModelSearchHandler.h" #include "HierarchyStatisticsAnalyzer.h" #include "ComponentChildrenManager.h" -#include "BatchOperationManager.h" -#include -#include -#include -#include -#include -#include -#include +#include "BatchOperationManager.h" +#include "Config.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#pragma comment(lib, "winhttp.lib") #ifdef _DEBUG #define new DEBUG_NEW @@ -351,7 +360,7 @@ bool ExtractJsonBool(const std::string& json, const std::string& key) { } // JSON字符串转义函数 -std::string EscapeJsonString(const std::string& str) { +std::string EscapeJsonString(const std::string& str) { std::string escaped = str; // Replace backslashes @@ -382,8 +391,258 @@ std::string EscapeJsonString(const std::string& str) { pos += 2; } - return escaped; -} + return escaped; +} + +std::wstring Utf8ToWide(const std::string& input) { + if (input.empty()) { + return L""; + } + + int required = MultiByteToWideChar(CP_UTF8, 0, input.c_str(), -1, nullptr, 0); + if (required <= 0) { + return L""; + } + + std::wstring output(required, L'\0'); + MultiByteToWideChar(CP_UTF8, 0, input.c_str(), -1, &output[0], required); + if (!output.empty() && output.back() == L'\0') { + output.pop_back(); + } + return output; +} + +std::string GetCurrentIsoUtcTimeString() { + std::time_t now = std::time(nullptr); + std::tm utc_tm{}; + gmtime_s(&utc_tm, &now); + + char buffer[32] = {0}; + std::strftime(buffer, sizeof(buffer), "%Y-%m-%dT%H:%M:%SZ", &utc_tm); + return std::string(buffer); +} + +std::string BuildBatchCallbackUrl(const std::string& callback_server_address) { + if (callback_server_address.empty()) { + return ""; + } + + std::string base = callback_server_address; + while (!base.empty() && base.back() == '/') { + base.pop_back(); + } + return base + "/api/v1/plugin-callbacks/task-result"; +} + +std::string BuildBatchCallbackResultJson(const BatchOperationsResponse& batch_result) { + std::ostringstream json; + json << "{" + << "\"total_operations\": " << batch_result.total_operations << "," + << "\"successful_operations\": " << batch_result.successful_operations << "," + << "\"failed_operations\": " << batch_result.failed_operations << "," + << "\"execution_time\": \"" << EscapeJsonString(batch_result.execution_time) << "\"," + << "\"results\": ["; + + for (size_t i = 0; i < batch_result.results.size(); ++i) { + if (i > 0) json << ","; + const BatchOperationResult& op_result = batch_result.results[i]; + json << "{" + << "\"operation_index\": " << op_result.operation_index << "," + << "\"operation_type\": \"" << EscapeJsonString(op_result.operation_type) << "\"," + << "\"success\": " << (op_result.success ? "true" : "false") << "," + << "\"execution_time\": \"" << EscapeJsonString(op_result.execution_time) << "\""; + + if (op_result.success) { + json << ",\"result\": " << op_result.result_json << ",\"error\": null"; + } else { + json << ",\"result\": null," + << "\"error\": \"" << EscapeJsonString(op_result.error_message) << "\""; + } + json << "}"; + } + + json << "]" + << "}"; + return json.str(); +} + +std::string BuildTaskResultCallbackPayload(const std::string& execution_id, + const std::string& software_id, + bool is_success, + const std::string& error_message, + const std::string& result_json, + const std::string& token) { + std::ostringstream json; + json << "{" + << "\"execution_id\": \"" << EscapeJsonString(execution_id) << "\"," + << "\"software_id\": \"" << EscapeJsonString(software_id) << "\"," + << "\"status\": \"" << (is_success ? "success" : "failed") << "\","; + + if (is_success) { + json << "\"error_message\": null,"; + } else { + json << "\"error_message\": \"" << EscapeJsonString(error_message) << "\","; + } + + if (is_success) { + json << "\"result\": " << result_json << ","; + } else { + json << "\"result\": {},"; + } + + json << "\"finished_at\": \"" << GetCurrentIsoUtcTimeString() << "\"," + << "\"token\": \"" << EscapeJsonString(token) << "\"" + << "}"; + return json.str(); +} + +bool PostJsonToUrl(const std::string& url, + const std::string& json_body, + DWORD& http_status_code, + std::string& error_message) { + http_status_code = 0; + error_message.clear(); + + std::wstring wurl = Utf8ToWide(url); + if (wurl.empty()) { + error_message = "Invalid callback URL"; + return false; + } + + URL_COMPONENTS components; + ZeroMemory(&components, sizeof(components)); + components.dwStructSize = sizeof(components); + components.dwSchemeLength = (DWORD)-1; + components.dwHostNameLength = (DWORD)-1; + components.dwUrlPathLength = (DWORD)-1; + components.dwExtraInfoLength = (DWORD)-1; + + if (!WinHttpCrackUrl(wurl.c_str(), 0, 0, &components)) { + error_message = "WinHttpCrackUrl failed"; + return false; + } + + std::wstring host(components.lpszHostName, components.dwHostNameLength); + std::wstring path(components.lpszUrlPath ? std::wstring(components.lpszUrlPath, components.dwUrlPathLength) : L"/"); + if (components.dwExtraInfoLength > 0 && components.lpszExtraInfo) { + path += std::wstring(components.lpszExtraInfo, components.dwExtraInfoLength); + } + + bool secure = (components.nScheme == INTERNET_SCHEME_HTTPS); + INTERNET_PORT port = components.nPort; + DWORD flags = secure ? WINHTTP_FLAG_SECURE : 0; + + HINTERNET session = WinHttpOpen(L"MFCCreoDll/1.0", + WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, + WINHTTP_NO_PROXY_NAME, + WINHTTP_NO_PROXY_BYPASS, + 0); + if (!session) { + error_message = "WinHttpOpen failed"; + return false; + } + + DWORD timeout = 10000; + WinHttpSetTimeouts(session, timeout, timeout, timeout, timeout); + + HINTERNET connection = WinHttpConnect(session, host.c_str(), port, 0); + if (!connection) { + WinHttpCloseHandle(session); + error_message = "WinHttpConnect failed"; + return false; + } + + HINTERNET request = WinHttpOpenRequest(connection, + L"POST", + path.c_str(), + nullptr, + WINHTTP_NO_REFERER, + WINHTTP_DEFAULT_ACCEPT_TYPES, + flags); + if (!request) { + WinHttpCloseHandle(connection); + WinHttpCloseHandle(session); + error_message = "WinHttpOpenRequest failed"; + return false; + } + + std::wstring headers = L"Content-Type: application/json\r\n"; + bool send_ok = WinHttpSendRequest(request, + headers.c_str(), + (DWORD)-1, + (LPVOID)json_body.data(), + static_cast(json_body.size()), + static_cast(json_body.size()), + 0) == TRUE; + if (!send_ok || WinHttpReceiveResponse(request, nullptr) != TRUE) { + WinHttpCloseHandle(request); + WinHttpCloseHandle(connection); + WinHttpCloseHandle(session); + error_message = "WinHTTP send/receive failed"; + return false; + } + + DWORD size = sizeof(http_status_code); + WinHttpQueryHeaders(request, + WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, + WINHTTP_HEADER_NAME_BY_INDEX, + &http_status_code, + &size, + WINHTTP_NO_HEADER_INDEX); + + WinHttpCloseHandle(request); + WinHttpCloseHandle(connection); + WinHttpCloseHandle(session); + + return (http_status_code >= 200 && http_status_code < 300); +} + +void SendTaskResultCallbackWithRetry(const std::string& callback_server_address, + const std::string& execution_id, + const std::string& software_id, + const BatchOperationsResponse& batch_result, + const std::string& callback_token) { + if (callback_server_address.empty() || execution_id.empty() || callback_token.empty()) { + return; + } + + bool is_success = batch_result.success; + std::string callback_error = batch_result.error_message; + if (!is_success && callback_error.empty()) { + callback_error = "Batch operations failed"; + } + + std::string result_json = BuildBatchCallbackResultJson(batch_result); + std::string payload = BuildTaskResultCallbackPayload( + execution_id, + software_id.empty() ? "creo" : software_id, + is_success, + callback_error, + result_json, + callback_token + ); + + std::string callback_url = BuildBatchCallbackUrl(callback_server_address); + if (callback_url.empty()) { + return; + } + + const std::vector retry_delays_seconds = {1, 3, 5}; + const int max_attempts = 1 + static_cast(retry_delays_seconds.size()); + + for (int attempt = 0; attempt < max_attempts; ++attempt) { + DWORD status_code = 0; + std::string post_error; + bool ok = PostJsonToUrl(callback_url, payload, status_code, post_error); + if (ok) { + return; + } + + if (attempt < static_cast(retry_delays_seconds.size())) { + std::this_thread::sleep_for(std::chrono::seconds(retry_delays_seconds[attempt])); + } + } +} // 模型导出路由处理器 HttpResponse ExportModelHandler(const HttpRequest& request) { @@ -2056,7 +2315,7 @@ std::vector ExtractBatchOperations(const std::string& json) { } // Batch Operations Handler -HttpResponse BatchOperationsHandler(const HttpRequest& request) { +HttpResponse BatchOperationsHandler(const HttpRequest& request) { HttpResponse response; if (request.method != "POST") { @@ -2066,10 +2325,31 @@ HttpResponse BatchOperationsHandler(const HttpRequest& request) { } try { - // Parse batch operations request - BatchOperationsRequest batch_request; - - batch_request.software_type = ExtractJsonValue(request.body, "software_type"); + // Parse batch operations request + BatchOperationsRequest batch_request; + std::string execution_id = ExtractJsonValue(request.body, "execution_id"); + std::string callback_server_address = Config::BATCH_CALLBACK_SERVER_ADDRESS; + if (callback_server_address.empty()) { + callback_server_address = ExtractJsonValue(request.body, "callback_server_address"); + } + if (callback_server_address.empty()) { + callback_server_address = ExtractJsonValue(request.body, "callback_server"); + } + if (callback_server_address.empty()) { + callback_server_address = ExtractJsonValue(request.body, "server_address"); + } + if (callback_server_address.empty()) { + callback_server_address = ExtractJsonValue(request.body, "callback_base_url"); + } + std::string callback_token = Config::BATCH_CALLBACK_TOKEN; + if (callback_token.empty()) { + callback_token = ExtractJsonValue(request.body, "callback_token"); + } + if (callback_token.empty()) { + callback_token = ExtractJsonValue(request.body, "token"); + } + + batch_request.software_type = ExtractJsonValue(request.body, "software_type"); // Validate software_type if (batch_request.software_type.empty()) { @@ -2091,8 +2371,15 @@ HttpResponse BatchOperationsHandler(const HttpRequest& request) { return response; } - // Execute batch operations - BatchOperationsResponse batch_result = BatchOperationManager::Instance().ExecuteBatchOperations(batch_request); + // Execute batch operations + BatchOperationsResponse batch_result = BatchOperationManager::Instance().ExecuteBatchOperations(batch_request); + SendTaskResultCallbackWithRetry( + callback_server_address, + execution_id, + batch_request.software_type, + batch_result, + callback_token + ); // Build response JSON std::ostringstream json; @@ -2214,4 +2501,4 @@ extern "C" void user_terminate() KillTimer(NULL, timer_id); timer_id = 0; } -} \ No newline at end of file +}