commit b361eb3f88a31d2303b5369e039d56e47e700d14 Author: taynpg Date: Tue Mar 10 11:51:02 2026 +0800 基本通信测试通过。 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