基本通信测试通过。

This commit is contained in:
2026-03-10 11:51:02 +08:00
commit b361eb3f88
15 changed files with 690 additions and 0 deletions

20
.clang-format Normal file
View File

@@ -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

12
.clangd Normal file
View File

@@ -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

6
.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
build/
.cache/
.qtcreator/
.vs
out/
CMakeUserPresets.json

150
.vscode/settings.json vendored Normal file
View File

@@ -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"
}
}

48
CMakeLists.txt Normal file
View File

@@ -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
)

62
bf.config.cpp Normal file
View File

@@ -0,0 +1,62 @@
#include "bf.config.h"
#include <fstream>
#include <nlohmann/json.hpp>
#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;
}
}

32
bf.config.h Normal file
View File

@@ -0,0 +1,32 @@
#ifndef BF_CONFIG_H
#define BF_CONFIG_H
#include <string>
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

10
bf.interface.cpp Normal file
View File

@@ -0,0 +1,10 @@
#include "bf.interface.h"
BF_Interface::~BF_Interface()
{
}
void BF_Interface::setConfig(std::shared_ptr<BF_Config> config)
{
config_ = config;
}

24
bf.interface.h Normal file
View File

@@ -0,0 +1,24 @@
#ifndef BF_INTERFACE_H
#define BF_INTERFACE_H
#include <string>
#include "bf.config.h"
#include "bf.util.h"
class BF_Interface
{
public:
BF_Interface() = default;
virtual ~BF_Interface();
public:
void setConfig(std::shared_ptr<BF_Config> 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<BF_Config> config_{};
};
#endif // BF_INTERFACE_H

61
bf.request.cpp Normal file
View File

@@ -0,0 +1,61 @@
#include "bf.request.h"
#include <fmt/format.h>
#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<int>(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<char*>(buffer), totalSize);
return totalSize;
}

27
bf.request.h Normal file
View File

@@ -0,0 +1,27 @@
#ifndef BF_REQUEST_H
#define BF_REQUEST_H
#include <curl/curl.h>
#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

82
bf.util.cpp Normal file
View File

@@ -0,0 +1,82 @@
#include "bf.util.h"
#include <iomanip>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <random>
#include <sstream>
std::shared_ptr<spdlog::logger> gLogger = nullptr;
void BF_Util::initLogger()
{
if (!gLogger) {
auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
console_sink->set_pattern("%^[%Y-%m-%d %H:%M:%S.%e][%l]: %v%$");
std::vector<spdlog::sink_ptr> sinks{console_sink};
gLogger = std::make_shared<spdlog::logger>("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<void> 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();
}

23
bf.util.h Normal file
View File

@@ -0,0 +1,23 @@
#ifndef BF_UTIL_H
#define BF_UTIL_H
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/spdlog.h>
#include <string>
extern std::shared_ptr<spdlog::logger> 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

20
conanfile.py Normal file
View File

@@ -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))

113
main.cpp Normal file
View File

@@ -0,0 +1,113 @@
#include <CLI/CLI.hpp>
#include <iostream>
#include <string>
#include <vector>
#include <xlnt/xlnt.hpp>
#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<std::string> 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<void> del(nullptr, [request](void*) { delete request; });
std::shared_ptr<BF_Config> bc = std::make_shared<BF_Config>();
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<XlsxTask> 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<std::string>();
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;
}