From b361eb3f88a31d2303b5369e039d56e47e700d14 Mon Sep 17 00:00:00 2001 From: taynpg Date: Tue, 10 Mar 2026 11:51:02 +0800 Subject: [PATCH] =?UTF-8?q?=E5=9F=BA=E6=9C=AC=E9=80=9A=E4=BF=A1=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E9=80=9A=E8=BF=87=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .clang-format | 20 ++++++ .clangd | 12 ++++ .gitignore | 6 ++ .vscode/settings.json | 150 ++++++++++++++++++++++++++++++++++++++++++ CMakeLists.txt | 48 ++++++++++++++ bf.config.cpp | 62 +++++++++++++++++ bf.config.h | 32 +++++++++ bf.interface.cpp | 10 +++ bf.interface.h | 24 +++++++ bf.request.cpp | 61 +++++++++++++++++ bf.request.h | 27 ++++++++ bf.util.cpp | 82 +++++++++++++++++++++++ bf.util.h | 23 +++++++ conanfile.py | 20 ++++++ main.cpp | 113 +++++++++++++++++++++++++++++++ 15 files changed, 690 insertions(+) create mode 100644 .clang-format create mode 100644 .clangd create mode 100644 .gitignore create mode 100644 .vscode/settings.json create mode 100644 CMakeLists.txt create mode 100644 bf.config.cpp create mode 100644 bf.config.h create mode 100644 bf.interface.cpp create mode 100644 bf.interface.h create mode 100644 bf.request.cpp create mode 100644 bf.request.h create mode 100644 bf.util.cpp create mode 100644 bf.util.h create mode 100644 conanfile.py create mode 100644 main.cpp diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..8d63d3f --- /dev/null +++ b/.clang-format @@ -0,0 +1,20 @@ +BasedOnStyle: LLVM +IndentWidth: 4 +PointerAlignment: Left +AccessModifierOffset: -4 +ReflowComments: true +SpacesBeforeTrailingComments: 3 +AllowShortFunctionsOnASingleLine: None +AllowShortEnumsOnASingleLine: false +BreakBeforeBraces: Custom +BraceWrapping: + AfterFunction: true + AfterClass: true +TabWidth: 4 +ColumnLimit: 130 +IncludeBlocks: Regroup +IncludeCategories: + - Regex: '^<.*>' + Priority: 1 + - Regex: '^".*"' + Priority: 2 diff --git a/.clangd b/.clangd new file mode 100644 index 0000000..40557fb --- /dev/null +++ b/.clangd @@ -0,0 +1,12 @@ +Hover: + ShowAKA: Yes +Diagnostics: + UnusedIncludes: None # 禁用未使用头文件提示 + Suppress: [ + anon_type_definition, # 禁用匿名的typedef提示 + unused-variable, # 禁用未使用变量提示 + unused-function, # 禁用未使用函数提示 + unused-includes, + ] + ClangTidy: + Remove: misc-unused-alias-decls diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..395ffb9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +build/ +.cache/ +.qtcreator/ +.vs +out/ +CMakeUserPresets.json \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..5c5f875 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,150 @@ +{ + "files.autoSave": "onFocusChange", + "editor.fontSize": 14, + "editor.fontFamily": "'Source Code Pro', 'Source Code Pro', 'Source Code Pro'", + "editor.wordWrap": "on", + "terminal.integrated.fontFamily": "'Source Code Pro'", + "cmake.configureOnOpen": true, + //"C_Cpp.intelliSenseEngine": "disabled", + "cmake.debugConfig": { + "console": "externalTerminal", + "setupCommands": [ + { + "description": "-gdb-set charset utf-8", + "text": "-gdb-set charset UTF-8" + }, + { + "description": "Enable gdb pretty-printing", + "text": "-enable-pretty-printing", + "ignoreFailures": true + } + ], + //"visualizerFile": "${workspaceRoot}/.vscode/qt6.natvis", + "args": [ + "--test-request", + "-c", + "baidu_fanyi.json" + ] + }, + "cmake.configureArgs": [ + "-Wno-dev" + ], + // "cmake.configureSettings": { + // "CMAKE_TOOLCHAIN_FILE": "${env:VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" + // }, + + "cmake.options.statusBarVisibility": "visible", + "cmake.generator": "Ninja", + "C_Cpp.default.compileCommands": "${workspaceRoot}/build/compile_commands.json", + "C_Cpp.default.cppStandard": "c++17", + "C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools", + "editor.inlayHints.enabled": "off", + "editor.unicodeHighlight.allowedLocales": { + "ja": true, + "zh-hant": true, + "zh-hans": true + }, + "files.associations": { + "*.cfg": "json", + "xstring": "cpp", + "algorithm": "cpp", + "array": "cpp", + "atomic": "cpp", + "cctype": "cpp", + "charconv": "cpp", + "chrono": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "condition_variable": "cpp", + "cstddef": "cpp", + "cstdint": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "ctime": "cpp", + "cwchar": "cpp", + "exception": "cpp", + "filesystem": "cpp", + "forward_list": "cpp", + "fstream": "cpp", + "functional": "cpp", + "initializer_list": "cpp", + "iomanip": "cpp", + "ios": "cpp", + "iosfwd": "cpp", + "iostream": "cpp", + "istream": "cpp", + "iterator": "cpp", + "limits": "cpp", + "list": "cpp", + "locale": "cpp", + "map": "cpp", + "memory": "cpp", + "mutex": "cpp", + "new": "cpp", + "optional": "cpp", + "ostream": "cpp", + "ratio": "cpp", + "sstream": "cpp", + "stdexcept": "cpp", + "streambuf": "cpp", + "string": "cpp", + "string_view": "cpp", + "system_error": "cpp", + "thread": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "typeinfo": "cpp", + "unordered_map": "cpp", + "utility": "cpp", + "vector": "cpp", + "xfacet": "cpp", + "xhash": "cpp", + "xiosbase": "cpp", + "xlocale": "cpp", + "xlocbuf": "cpp", + "xlocinfo": "cpp", + "xlocmes": "cpp", + "xlocmon": "cpp", + "xlocnum": "cpp", + "xloctime": "cpp", + "xmemory": "cpp", + "xmemory0": "cpp", + "xstddef": "cpp", + "xtr1common": "cpp", + "xtree": "cpp", + "xutility": "cpp", + "bit": "cpp", + "compare": "cpp", + "concepts": "cpp", + "coroutine": "cpp", + "format": "cpp", + "stop_token": "cpp", + "bitset": "cpp", + "complex": "cpp", + "cstdarg": "cpp", + "set": "cpp", + "variant": "cpp", + "expected": "cpp", + "source_location": "cpp", + "regex": "cpp", + "*.in": "cpp", + "deque": "cpp", + "future": "cpp", + "queue": "cpp", + "resumable": "cpp", + "any": "cpp", + "codecvt": "cpp", + "csignal": "cpp", + "cwctype": "cpp", + "memory_resource": "cpp", + "numeric": "cpp", + "random": "cpp", + "shared_mutex": "cpp", + "cinttypes": "cpp", + "cfenv": "cpp", + "unordered_set": "cpp", + "ranges": "cpp", + "typeindex": "cpp" + } +} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..d3e370c --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,48 @@ +cmake_minimum_required(VERSION 3.16) + +project(baidu_fanyi LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +if (MSVC) + add_compile_options(/utf-8) + add_definitions(-DWIN32_LEAN_AND_MEAN) + add_definitions(-D_WIN32_WINNT=0x0601) + add_definitions(-D_CRT_SECURE_NO_WARNINGS) +endif() + +set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib/${CMAKE_BUILD_TYPE}) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin/${CMAKE_BUILD_TYPE}/) + +find_package(CURL CONFIG REQUIRED) +find_package(OpenSSL CONFIG REQUIRED) +find_package(Xlnt CONFIG REQUIRED) +find_package(nlohmann_json CONFIG REQUIRED) +find_package(CLI11 CONFIG REQUIRED) +find_package(spdlog CONFIG REQUIRED) +find_package(fmt CONFIG REQUIRED) + +set(SOURCES +bf.config.cpp bf.config.h +bf.request.cpp bf.request.h +bf.interface.cpp bf.interface.h +bf.util.cpp bf.util.h +) + +add_executable(baidu_fanyi main.cpp ${SOURCES}) +target_link_libraries(baidu_fanyi PRIVATE + xlnt::xlnt OpenSSL::SSL OpenSSL::Crypto + CURL::libcurl nlohmann_json::nlohmann_json + CLI11::CLI11 spdlog::spdlog fmt::fmt +) + +execute_process( + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${CMAKE_BINARY_DIR}/compile_commands.json + ${CMAKE_SOURCE_DIR}/build/compile_commands.json + RESULT_VARIABLE copy_result + ERROR_QUIET + OUTPUT_QUIET +) \ No newline at end of file diff --git a/bf.config.cpp b/bf.config.cpp new file mode 100644 index 0000000..bc80248 --- /dev/null +++ b/bf.config.cpp @@ -0,0 +1,62 @@ +#include "bf.config.h" + +#include +#include + +#include "bf.util.h" + +bool BF_Config::parseConfig(const std::string& file) +{ + try { + std::ifstream ifs(file); + if (!ifs.is_open()) { + gLogger->error("Failed to open config file: {}", file); + return false; + } + nlohmann::json configJson; + ifs >> configJson; + + if (!configJson.contains("baseUrl")) { + gLogger->error("Missing 'baseUrl' field in config file"); + return false; + } + baseUrl = configJson["baseUrl"]; + + if (!configJson.contains("appId")) { + gLogger->error("Missing 'appId' field in config file"); + return false; + } + appId = configJson["appId"]; + + if (!configJson.contains("appKey")) { + gLogger->error("Missing 'appKey' field in config file"); + return false; + } + appKey = configJson["appKey"]; + + if (!configJson.contains("appSecret")) { + gLogger->error("Missing 'appSecret' field in config file"); + return false; + } + appSecret = configJson["appSecret"]; + + if (configJson.contains("type")) { + int typeValue = configJson["type"]; + if (typeValue == BFT_AI) { + type = BFT_AI; + } else { + type = BFT_COMMON; + } + } + + gLogger->info("Config loaded successfully from: {}", file); + return true; + + } catch (const nlohmann::json::exception& e) { + gLogger->error("JSON parsing error: {}", e.what()); + return false; + } catch (const std::exception& e) { + gLogger->error("Failed to parse config: {}", e.what()); + return false; + } +} \ No newline at end of file diff --git a/bf.config.h b/bf.config.h new file mode 100644 index 0000000..918d0de --- /dev/null +++ b/bf.config.h @@ -0,0 +1,32 @@ +#ifndef BF_CONFIG_H +#define BF_CONFIG_H + +#include + +enum BF_Type { + BFT_COMMON = 0, + BFT_AI +}; + +struct XlsxTask { + int startRow; + int startCol; + int endRow; + int endCol; +}; + +class BF_Config +{ +public: + BF_Config() = default; + bool parseConfig(const std::string& file); + +public: + std::string baseUrl; + std::string appId; + std::string appKey; + std::string appSecret; + BF_Type type = BFT_COMMON; +}; + +#endif // BF_CONFIG_H \ No newline at end of file diff --git a/bf.interface.cpp b/bf.interface.cpp new file mode 100644 index 0000000..c072315 --- /dev/null +++ b/bf.interface.cpp @@ -0,0 +1,10 @@ +#include "bf.interface.h" + +BF_Interface::~BF_Interface() +{ +} + +void BF_Interface::setConfig(std::shared_ptr config) +{ + config_ = config; +} \ No newline at end of file diff --git a/bf.interface.h b/bf.interface.h new file mode 100644 index 0000000..5101a4a --- /dev/null +++ b/bf.interface.h @@ -0,0 +1,24 @@ +#ifndef BF_INTERFACE_H +#define BF_INTERFACE_H + +#include + +#include "bf.config.h" +#include "bf.util.h" + +class BF_Interface +{ +public: + BF_Interface() = default; + virtual ~BF_Interface(); + +public: + void setConfig(std::shared_ptr config); + virtual std::string doReady(const std::string& words, const std::string& from, const std::string& to) = 0; + virtual std::string getResult(const std::string& data) = 0; + +protected: + std::shared_ptr config_{}; +}; + +#endif // BF_INTERFACE_H \ No newline at end of file diff --git a/bf.request.cpp b/bf.request.cpp new file mode 100644 index 0000000..1f76cba --- /dev/null +++ b/bf.request.cpp @@ -0,0 +1,61 @@ +#include "bf.request.h" + +#include + +#include "bf.util.h" + +BF_Request::BF_Request() +{ + curl_global_init(CURL_GLOBAL_DEFAULT); + curl_ = curl_easy_init(); +} + +BF_Request::~BF_Request() +{ + curl_easy_cleanup(curl_); + curl_global_cleanup(); +} + +std::string BF_Request::getResult(const std::string& url) +{ + std::string reponse; + if (!curl_) { + gLogger->error("curl init failed."); + return reponse; + } + + curl_easy_setopt(curl_, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl_, CURLOPT_POST, 0L); + curl_easy_setopt(curl_, CURLOPT_WRITEFUNCTION, writeData); + curl_easy_setopt(curl_, CURLOPT_WRITEDATA, &reponse); + curl_easy_setopt(curl_, CURLOPT_SSL_VERIFYHOST, 0L); + curl_easy_setopt(curl_, CURLOPT_SSL_VERIFYPEER, 0L); + + CURLcode res = curl_easy_perform(curl_); + if (res != CURLE_OK) { + gLogger->error("curl request failed. error code: {}", static_cast(res)); + return reponse; + } + return reponse; +} + +std::string BF_Request::doReady(const std::string& words, const std::string& from, const std::string& to) +{ + char* buffer = new char[2048]{}; + auto rs = BF_Util::RandomStrNum(); + auto s = fmt::format("{}{}{}{}", config_->appId, words, rs, config_->appSecret); + auto md5 = BF_Util::MD5(s); + auto reqWords = BF_Util::urlEncode(words); + std::snprintf(buffer, 2048, "q=%s&from=%s&to=%s&appid=%s&salt=%s&sign=%s", reqWords.c_str(), from.c_str(), to.c_str(), + config_->appId.c_str(), rs.c_str(), md5.c_str()); + std::string data(buffer); + delete[] buffer; + return data; +} + +size_t BF_Request::writeData(void* buffer, size_t size, size_t nmemb, std::string* str) +{ + auto totalSize = size * nmemb; + str->append(static_cast(buffer), totalSize); + return totalSize; +} \ No newline at end of file diff --git a/bf.request.h b/bf.request.h new file mode 100644 index 0000000..a620c5d --- /dev/null +++ b/bf.request.h @@ -0,0 +1,27 @@ +#ifndef BF_REQUEST_H +#define BF_REQUEST_H + +#include + +#include "bf.interface.h" +#include "bf.util.h" +#include "bf.config.h" + +class BF_Request : public BF_Interface +{ +public: + BF_Request(); + ~BF_Request() override; + +public: + std::string getResult(const std::string& url) override; + std::string doReady(const std::string& words, const std::string& from, const std::string& to) override; + +private: + static size_t writeData(void* buffer, size_t size, size_t nmemb, std::string* str); + +private: + CURL* curl_{}; +}; + +#endif \ No newline at end of file diff --git a/bf.util.cpp b/bf.util.cpp new file mode 100644 index 0000000..ee596af --- /dev/null +++ b/bf.util.cpp @@ -0,0 +1,82 @@ +#include "bf.util.h" + +#include +#include +#include +#include +#include + +std::shared_ptr gLogger = nullptr; + +void BF_Util::initLogger() +{ + if (!gLogger) { + auto console_sink = std::make_shared(); + console_sink->set_pattern("%^[%Y-%m-%d %H:%M:%S.%e][%l]: %v%$"); + std::vector sinks{console_sink}; + gLogger = std::make_shared("baidu_fanyi", sinks.begin(), sinks.end()); + gLogger->set_level(spdlog::level::debug); + spdlog::register_logger(gLogger); + } +} + +void BF_Util::setLogLevel(spdlog::level::level_enum level) +{ + if (gLogger) { + gLogger->set_level(level); + } +} + +std::string BF_Util::MD5(const std::string& data) +{ + EVP_MD_CTX* mdctx = EVP_MD_CTX_new(); + if (!mdctx) { + gLogger->error("EVP_MD_CTX_new failed"); + return std::string(); + } + std::shared_ptr d(nullptr, [mdctx](void*) { EVP_MD_CTX_free(mdctx); }); + auto ir = EVP_DigestInit_ex(mdctx, EVP_md5(), nullptr); + if (!ir) { + gLogger->error("EVP_DigestInit_ex failed"); + return std::string(); + } + auto ur = EVP_DigestUpdate(mdctx, data.c_str(), data.length()); + if (!ur) { + gLogger->error("EVP_DigestUpdate failed"); + return std::string(); + } + unsigned char md_value[EVP_MAX_MD_SIZE]; + unsigned int md_len; + auto fr = EVP_DigestFinal_ex(mdctx, md_value, &md_len); + if (!fr) { + gLogger->error("EVP_DigestFinal_ex failed"); + return std::string(); + } + std::ostringstream os; + for (unsigned int i = 0; i < md_len; i++) { + os << std::setw(2) << std::setfill('0') << std::hex << (int)md_value[i]; + } + return os.str(); +} + +std::string BF_Util::RandomStrNum() +{ + static std::random_device rd; + static std::mt19937 gen(rd()); + static std::uniform_int_distribution<> dis(100000000, 999999999); + long long num = dis(gen); + return std::to_string(num); +} + +std::string BF_Util::urlEncode(const std::string& data) +{ + std::ostringstream os; + for (unsigned char c : data) { + if (std::isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') { + os << c; + } else { + os << '%' << std::uppercase << std::setw(2) << std::setfill('0') << std::hex << (int)c; + } + } + return os.str(); +} diff --git a/bf.util.h b/bf.util.h new file mode 100644 index 0000000..12864cb --- /dev/null +++ b/bf.util.h @@ -0,0 +1,23 @@ +#ifndef BF_UTIL_H +#define BF_UTIL_H + +#include +#include +#include + +extern std::shared_ptr gLogger; + +class BF_Util +{ +public: + BF_Util() = default; + +public: + static void initLogger(); + static void setLogLevel(spdlog::level::level_enum level); + static std::string MD5(const std::string& data); + static std::string RandomStrNum(); + static std::string urlEncode(const std::string& data); +}; + +#endif \ No newline at end of file diff --git a/conanfile.py b/conanfile.py new file mode 100644 index 0000000..dffdff3 --- /dev/null +++ b/conanfile.py @@ -0,0 +1,20 @@ +from conan import ConanFile +import os + +class CompressorRecipe(ConanFile): + + settings = "os", "compiler", "build_type", "arch" + generators = "CMakeToolchain", "CMakeDeps" + + def requirements(self): + self.requires("libcurl/8.18.0") + self.requires("openssl/3.6.1") + self.requires("xlnt/1.6.1", options={"shared": True}) + self.requires("nlohmann_json/3.12.0") + self.requires("cli11/2.6.0") + self.requires("spdlog/1.17.0") + self.requires("fmt/12.1.0") + + def layout(self): + self.folders.generators = os.path.join("build", str(self.settings.build_type), "generators") + self.folders.build = os.path.join("build", str(self.settings.build_type)) \ No newline at end of file diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..d358910 --- /dev/null +++ b/main.cpp @@ -0,0 +1,113 @@ +#include +#include +#include +#include +#include + +#include "bf.request.h" +#include "bf.util.h" + +#ifdef _WIN32 +#include "Windows.h" +#endif + +int main(int argc, char** argv) +{ +#ifdef _WIN32 + SetConsoleOutputCP(CP_UTF8); +#endif + CLI::App app{"百度翻译xlsx批量工具。"}; + + std::string workbook; + std::string sheetName; + std::string from = "auto"; + std::string to = "en"; + bool testRequest = false; + std::vector tasks; // 存储多个字符串 + + app.add_option("-t,--task", tasks, "要处理的行列范围(起始行,起始列,结束行,结束列),如:1,1,10,10)")->multi_option_policy(); + app.add_option("-s,--sheet", sheetName, "工作表名称"); + app.add_option("-w,--workbook", workbook, "工作簿"); + app.add_option("-f,--from", from, "源语言(默认auto)"); + app.add_option("--to-language,--to", to, "目标语言(默认en)"); + app.add_flag("--test-request", testRequest, "测试请求"); + + // 添加其他可选参数 + std::string config = "baidu_fanyi.json"; + app.add_option("-c,--config", config, "配置文件(默认baidu_fanyi.json)"); + + bool verbose = false; + app.add_flag("-v,--verbose", verbose, "详细输出"); + + // 解析命令行参数 + CLI11_PARSE(app, argc, argv); + + BF_Util::initLogger(); + BF_Util::setLogLevel(spdlog::level::debug); + + // 测试参数,这里直接返回。 + // for (const auto& task : tasks) { + // gLogger->info("任务:{}", task); + // } + // return 0; + + BF_Interface* request = new BF_Request(); + std::shared_ptr del(nullptr, [request](void*) { delete request; }); + + std::shared_ptr bc = std::make_shared(); + if (!bc->parseConfig(config)) { + gLogger->error("解析配置文件失败!"); + return -1; + } + request->setConfig(bc); + + if (testRequest) { + gLogger->info("测试请求开始..."); + gLogger->info("测试内容:[你好,世界。] from auto to en."); + std::string readyData = request->doReady("你好,世界。", from, to); + std::string data = fmt::format("{}{}", bc->baseUrl, readyData); + std::string result = request->getResult(data); + gLogger->info("测试请求结束!"); + gLogger->info("结果:[{}]", result); + return 0; + } + + xlnt::workbook wb; + xlnt::worksheet ws; + try { + wb.load(""); + ws = wb.sheet_by_title(sheetName); + std::vector tasks; + for (auto& task : tasks) { + for (int i = task.startRow; i <= task.endRow; i++) { + for (int j = task.startCol; j <= task.endCol; j++) { + xlnt::cell cell = ws.cell(i, j); + std::string text = cell.value(); + if (text.empty()) { + gLogger->error("数据为空,跳过:[{}][{}]", i, j); + continue; + } + auto readyData = request->doReady(text, from, to); + if (readyData.empty()) { + gLogger->error("doReady数据为空,跳过:[{}][{}][{}]", i, j, text); + continue; + } + auto data = fmt::format("{}{}", bc->baseUrl, readyData); + std::string result = request->getResult(data); + if (result.empty()) { + gLogger->error("getResult数据为空,跳过:[{}][{}][{}]", i, j, text); + continue; + } + gLogger->info("翻译成功:[{}][{}][{}][{}]", i, j, text, result); + cell.value(result); + } + } + } + } catch (const std::exception& e) { + gLogger->error(e.what()); + } + + wb.save(fmt::format("{}_out.xlsx", workbook)); + gLogger->info("结束!"); + return 0; +} \ No newline at end of file