C++ libcurl 连接豆包 分析文件
以下github连接在国内可能连接不上,多尝试几次就好了。选择UTF-8带签名,然后确定。发送文件内容进行分析。
·
一、文件处理器基础类
这个类负责读取文件夹中的文件列表:
// FileProcessor.h
#ifndef FILEPROCESSOR_H
#define FILEPROCESSOR_H
#include <string>
#include <vector>
#include <fstream>
#include <iostream>
#include <algorithm>
#include <sstream>
#include <iomanip>
#include <chrono>
#include <ctime>
#include <cctype>
#ifdef _WIN32
#include <windows.h>
#include <direct.h> // for _mkdir, _rmdir
#include <io.h> // for _access
#define PATH_SEPARATOR '\\'
#define MKDIR(path) _mkdir(path)
#define ACCESS(path, mode) _access(path, mode)
#define STAT(path, buffer) _stat(path, buffer)
#define STAT_STRUCT struct _stat
#else
#include <sys/stat.h>
#include <unistd.h>
#include <dirent.h>
#include <cstring>
#define PATH_SEPARATOR '/'
#define MKDIR(path) mkdir(path, 0755)
#define ACCESS(path, mode) access(path, mode)
#define STAT(path, buffer) stat(path, buffer)
#define STAT_STRUCT struct stat
#endif
class FileProcessor {
private:
std::string folderPath;
// 辅助函数:检查路径是否存在
bool pathExists(const std::string& path) const;
// 辅助函数:判断是否为目录
bool isDirectory(const std::string& path) const;
// 辅助函数:获取文件大小
long getFileSize(const std::string& filePath) const;
public:
// 构造函数
FileProcessor(const std::string& path = ".");
// 获取文件夹路径
std::string getFolderPath() const;
// 设置文件夹路径
bool setFolderPath(const std::string& path);
// 获取文件夹中所有文件的路径
std::vector<std::string> getAllFiles() const;
// 获取特定扩展名的文件
std::vector<std::string> getFilesByExtension(const std::string& extension) const;
// 检查文件是否存在
bool fileExists(const std::string& filePath) const;
// 读取文件内容(文本文件)
std::string readTextFile(const std::string& filePath, size_t maxSize = 10 * 1024 * 1024) const;
// 读取二进制文件内容
std::vector<char> readBinaryFile(const std::string& filePath) const;
// 获取文件扩展名(小写)
std::string getFileExtension(const std::string& filePath) const;
// 判断是否为文本文件
bool isTextFile(const std::string& filePath) const;
// 获取文件大小(以KB/MB为单位)
std::string getFileSizeFormatted(const std::string& filePath) const;
// 获取文件名(不含路径)
std::string getFileName(const std::string& filePath) const;
// 打印文件列表
void printFileList() const;
// 打印文件详细信息
void printFileDetails() const;
// 创建目录
static bool createDirectory(const std::string& path);
// 删除文件
bool deleteFile(const std::string& filePath) const;
// 统计文件信息
void getFileStats(size_t& totalFiles, size_t& textFiles,
size_t& totalSize, size_t& textSize) const;
// 获取文件最后修改时间
std::string getFileModificationTime(const std::string& filePath) const;
private:
// Windows平台特定的文件列表获取函数
std::vector<std::string> getAllFilesWindows() const;
// 非Windows平台特定的文件列表获取函数
std::vector<std::string> getAllFilesUnix() const;
};
#endif // FILEPROCESSOR_H
// FileProcessor.cpp
#include "FileProcessor.h"
#include <chrono>
#include <iomanip>
#include <sstream>
#include <cstring>
#pragma warning(disable:4996)
#pragma execution_character_set("utf-8")
// 构造函数
FileProcessor::FileProcessor(const std::string& path) {
setFolderPath(path);
}
// 获取文件夹路径
std::string FileProcessor::getFolderPath() const {
return folderPath;
}
// 设置文件夹路径
bool FileProcessor::setFolderPath(const std::string& path) {
if (pathExists(path) && isDirectory(path)) {
folderPath = path;
// 确保路径以分隔符结尾
if (!folderPath.empty() && folderPath.back() != PATH_SEPARATOR) {
folderPath += PATH_SEPARATOR;
}
return true;
}
std::cerr << "错误:路径 '" << path << "' 不存在或不是目录" << std::endl;
return false;
}
// 检查路径是否存在
bool FileProcessor::pathExists(const std::string& path) const {
return ACCESS(path.c_str(), 0) == 0;
}
// 判断是否为目录
bool FileProcessor::isDirectory(const std::string& path) const {
STAT_STRUCT pathStat;
if (STAT(path.c_str(), &pathStat) != 0) {
return false;
}
#ifdef _WIN32
return (pathStat.st_mode & _S_IFDIR) != 0;
#else
return S_ISDIR(pathStat.st_mode);
#endif
}
// 获取文件大小
long FileProcessor::getFileSize(const std::string& filePath) const {
STAT_STRUCT stat_buf;
int rc = STAT(filePath.c_str(), &stat_buf);
return rc == 0 ? stat_buf.st_size : -1;
}
// Windows平台获取文件列表
std::vector<std::string> FileProcessor::getAllFilesWindows() const {
std::vector<std::string> files;
if (folderPath.empty()) {
std::cerr << "错误:文件夹路径未设置" << std::endl;
return files;
}
// 使用ANSI版本的Windows API,避免宽字符问题
std::string searchPath = folderPath + "*.*";
WIN32_FIND_DATAA findFileData;
HANDLE hFind = FindFirstFileA(searchPath.c_str(), &findFileData);
if (hFind == INVALID_HANDLE_VALUE) {
std::cerr << "错误:无法打开文件夹 '" << folderPath << "'" << std::endl;
return files;
}
do {
// 跳过 "." 和 ".."
if (strcmp(findFileData.cFileName, ".") == 0 || strcmp(findFileData.cFileName, "..") == 0) {
continue;
}
std::string filePath = folderPath + findFileData.cFileName;
// 只添加普通文件,跳过目录
if (!(findFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
files.push_back(filePath);
}
} while (FindNextFileA(hFind, &findFileData) != 0);
FindClose(hFind);
return files;
}
// 非Windows平台获取文件列表
std::vector<std::string> FileProcessor::getAllFilesUnix() const {
std::vector<std::string> files;
if (folderPath.empty()) {
std::cerr << "错误:文件夹路径未设置" << std::endl;
return files;
}
#ifdef _WIN32
// 如果编译到Windows但调用了Unix版本,使用Windows版本
return getAllFilesWindows();
#else
// Linux/Mac平台实现
DIR* dir = opendir(folderPath.c_str());
if (dir == nullptr) {
std::cerr << "错误:无法打开文件夹 '" << folderPath << "'" << std::endl;
return files;
}
struct dirent* entry;
while ((entry = readdir(dir)) != nullptr) {
// 跳过 "." 和 ".."
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
continue;
}
std::string filePath = folderPath + entry->d_name;
// 只添加普通文件,跳过目录
STAT_STRUCT fileStat;
if (STAT(filePath.c_str(), &fileStat) == 0) {
bool isRegularFile = S_ISREG(fileStat.st_mode);
if (isRegularFile) {
files.push_back(filePath);
}
}
}
closedir(dir);
return files;
#endif
}
// 获取文件夹中所有文件的路径
std::vector<std::string> FileProcessor::getAllFiles() const {
#ifdef _WIN32
return getAllFilesWindows();
#else
return getAllFilesUnix();
#endif
}
// 获取特定扩展名的文件
std::vector<std::string> FileProcessor::getFilesByExtension(const std::string& extension) const {
std::vector<std::string> allFiles = getAllFiles();
std::vector<std::string> filteredFiles;
std::string extLower = extension;
std::transform(extLower.begin(), extLower.end(), extLower.begin(), ::tolower);
for (const auto& file : allFiles) {
std::string fileExt = getFileExtension(file);
if (fileExt == extLower) {
filteredFiles.push_back(file);
}
}
return filteredFiles;
}
// 检查文件是否存在
bool FileProcessor::fileExists(const std::string& filePath) const {
STAT_STRUCT pathStat;
if (STAT(filePath.c_str(), &pathStat) != 0) {
return false;
}
#ifdef _WIN32
return (pathStat.st_mode & _S_IFREG) != 0; // 是普通文件
#else
return S_ISREG(pathStat.st_mode); // 是普通文件
#endif
}
// 读取文件内容(文本文件)
std::string FileProcessor::readTextFile(const std::string& filePath, size_t maxSize) const {
// 检查文件大小
long fileSize = getFileSize(filePath);
if (fileSize == -1) {
std::cerr << "错误:无法获取文件大小 '" << filePath << "'" << std::endl;
return "";
}
if (fileSize > maxSize) {
std::cerr << "警告:文件 '" << filePath << "' 大小(" << fileSize
<< "字节)超过限制(" << maxSize << "字节),将被截断读取" << std::endl;
}
std::ifstream file(filePath, std::ios::binary);
if (!file.is_open()) {
std::cerr << "错误:无法打开文件 '" << filePath << "'" << std::endl;
return "";
}
std::string content;
char buffer[4096];
size_t bytesRead = 0;
while (file.read(buffer, sizeof(buffer)) || file.gcount() > 0) {
size_t count = file.gcount();
if (bytesRead + count > maxSize) {
count = maxSize - bytesRead;
if (count <= 0) break;
}
content.append(buffer, count);
bytesRead += count;
if (bytesRead >= maxSize) break;
}
file.close();
return content;
}
// 读取二进制文件内容
std::vector<char> FileProcessor::readBinaryFile(const std::string& filePath) const {
std::vector<char> data;
std::ifstream file(filePath, std::ios::binary | std::ios::ate);
if (!file.is_open()) {
std::cerr << "错误:无法打开文件 '" << filePath << "'" << std::endl;
return data;
}
std::streamsize size = file.tellg();
file.seekg(0, std::ios::beg);
data.resize(size);
if (!file.read(data.data(), size)) {
std::cerr << "错误:读取文件 '" << filePath << "' 失败" << std::endl;
data.clear();
}
file.close();
return data;
}
// 获取文件扩展名(小写)
std::string FileProcessor::getFileExtension(const std::string& filePath) const {
size_t dotPos = filePath.find_last_of('.');
if (dotPos == std::string::npos) {
return "";
}
std::string ext = filePath.substr(dotPos);
std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
return ext;
}
// 判断是否为文本文件
bool FileProcessor::isTextFile(const std::string& filePath) const {
std::string ext = getFileExtension(filePath);
// 常见的文本文件扩展名
const std::vector<std::string> textExtensions = {
".txt", ".cpp", ".h", ".hpp", ".c", ".cc", ".py",
".java", ".js", ".html", ".css", ".xml", ".json",
".md", ".csv", ".log", ".ini", ".cfg", ".conf",
".yaml", ".yml", ".toml", ".sql", ".sh", ".bat",
".php", ".rb", ".go", ".rs", ".swift", ".kt",
".asm", ".s", ".pas", ".vb", ".cs", ".fs"
};
return std::find(textExtensions.begin(), textExtensions.end(), ext) != textExtensions.end();
}
// 获取文件大小(格式化显示)
std::string FileProcessor::getFileSizeFormatted(const std::string& filePath) const {
long size = getFileSize(filePath);
if (size == -1) {
return "未知大小";
}
std::stringstream ss;
if (size < 1024) {
ss << size << " B";
}
else if (size < 1024 * 1024) {
ss << std::fixed << std::setprecision(2) << (size / 1024.0) << " KB";
}
else {
ss << std::fixed << std::setprecision(2) << (size / (1024.0 * 1024.0)) << " MB";
}
return ss.str();
}
// 获取文件名(不含路径)
std::string FileProcessor::getFileName(const std::string& filePath) const {
size_t separatorPos = filePath.find_last_of("/\\");
if (separatorPos == std::string::npos) {
return filePath;
}
return filePath.substr(separatorPos + 1);
}
// 打印文件列表
void FileProcessor::printFileList() const {
std::vector<std::string> files = getAllFiles();
if (files.empty()) {
std::cout << "文件夹 '" << folderPath << "' 中没有文件。" << std::endl;
return;
}
std::cout << "文件夹 '" << folderPath << "' 中的文件列表:" << std::endl;
std::cout << "==================================================" << std::endl;
for (size_t i = 0; i < files.size(); ++i) {
std::string fileName = getFileName(files[i]);
std::string fileSize = getFileSizeFormatted(files[i]);
std::string fileType = isTextFile(files[i]) ? "文本" : "二进制";
std::cout << std::setw(3) << std::right << i + 1 << ". "
<< std::setw(30) << std::left << fileName
<< " 大小: " << std::setw(10) << fileSize
<< " 类型: " << fileType
<< " 扩展名: " << getFileExtension(files[i])
<< std::endl;
}
std::cout << "==================================================" << std::endl;
std::cout << "总计: " << files.size() << " 个文件" << std::endl;
}
// 打印文件详细信息
void FileProcessor::printFileDetails() const {
std::vector<std::string> files = getAllFiles();
if (files.empty()) {
std::cout << "文件夹 '" << folderPath << "' 中没有文件。" << std::endl;
return;
}
size_t totalFiles = 0, textFiles = 0, totalSize = 0, textSize = 0;
getFileStats(totalFiles, textFiles, totalSize, textSize);
std::cout << "文件夹: " << folderPath << std::endl;
std::cout << "文件统计:" << std::endl;
std::cout << " 总文件数: " << totalFiles << std::endl;
std::cout << " 文本文件数: " << textFiles << std::endl;
std::cout << " 总大小: " << (totalSize / 1024.0) << " KB" << std::endl;
std::cout << " 文本文件总大小: " << (textSize / 1024.0) << " KB" << std::endl;
std::cout << std::endl;
std::cout << "详细文件信息:" << std::endl;
std::cout << "======================================================================" << std::endl;
for (size_t i = 0; i < files.size(); ++i) {
std::string fileName = getFileName(files[i]);
std::string fileSize = getFileSizeFormatted(files[i]);
std::string modTime = getFileModificationTime(files[i]);
std::cout << "文件 " << i + 1 << ":" << std::endl;
std::cout << " 名称: " << fileName << std::endl;
std::cout << " 路径: " << files[i] << std::endl;
std::cout << " 大小: " << fileSize << std::endl;
std::cout << " 修改时间: " << modTime << std::endl;
std::cout << " 扩展名: " << getFileExtension(files[i]) << std::endl;
std::cout << " 类型: " << (isTextFile(files[i]) ? "文本" : "二进制") << std::endl;
// 如果是文本文件,显示前几行内容
if (isTextFile(files[i])) {
std::string content = readTextFile(files[i], 500); // 只读取前500字符
if (!content.empty()) {
std::cout << " 内容预览:" << std::endl;
std::cout << " ------------------------------------" << std::endl;
// 只显示前5行或前200字符
std::stringstream ss(content);
std::string line;
int lineCount = 0;
while (std::getline(ss, line) && lineCount < 5) {
if (line.length() > 80) {
line = line.substr(0, 77) + "...";
}
std::cout << " " << line << std::endl;
lineCount++;
}
std::cout << " ------------------------------------" << std::endl;
}
}
if (i < files.size() - 1) {
std::cout << std::endl;
}
}
std::cout << "======================================================================" << std::endl;
}
// 创建目录
bool FileProcessor::createDirectory(const std::string& path) {
return MKDIR(path.c_str()) == 0;
}
// 删除文件
bool FileProcessor::deleteFile(const std::string& filePath) const {
return std::remove(filePath.c_str()) == 0;
}
// 统计文件信息
void FileProcessor::getFileStats(size_t& totalFiles, size_t& textFiles,
size_t& totalSize, size_t& textSize) const {
totalFiles = 0;
textFiles = 0;
totalSize = 0;
textSize = 0;
std::vector<std::string> files = getAllFiles();
totalFiles = files.size();
for (const auto& file : files) {
long size = getFileSize(file);
if (size != -1) {
totalSize += size;
if (isTextFile(file)) {
textFiles++;
textSize += size;
}
}
}
}
// 获取文件最后修改时间
std::string FileProcessor::getFileModificationTime(const std::string& filePath) const {
STAT_STRUCT fileStat;
if (STAT(filePath.c_str(), &fileStat) != 0) {
return "未知时间";
}
time_t modTime = fileStat.st_mtime;
struct tm* timeinfo = localtime(&modTime);
std::stringstream ss;
ss << std::put_time(timeinfo, "%Y-%m-%d %H:%M:%S");
return ss.str();
}
测试
// main.cpp - 简单的测试FileProcessor类
#include "FileProcessor.h"
#include <iostream>
int main() {
std::cout << "=== 文件处理器测试程序 ===" << std::endl;
std::cout << std::endl;
// 创建文件处理器对象
FileProcessor processor("."); // 当前目录
std::cout << "当前工作目录: " << processor.getFolderPath() << std::endl;
std::cout << std::endl;
// 测试1: 打印文件列表
std::cout << "测试1: 打印文件列表" << std::endl;
std::cout << "=====================" << std::endl;
processor.printFileList();
std::cout << std::endl;
// 测试2: 获取所有文件
std::cout << "测试2: 获取所有文件" << std::endl;
std::cout << "====================" << std::endl;
std::vector<std::string> allFiles = processor.getAllFiles();
std::cout << "找到 " << allFiles.size() << " 个文件" << std::endl;
std::cout << std::endl;
// 测试3: 读取文本文件内容
std::cout << "测试3: 读取文本文件内容" << std::endl;
std::cout << "=======================" << std::endl;
int textFileCount = 0;
for (const auto& file : allFiles) {
if (processor.isTextFile(file)) {
textFileCount++;
std::string fileName = processor.getFileName(file);
std::cout << "文本文件 #" << textFileCount << ": " << fileName << std::endl;
// 只读取前300字符
std::string content = processor.readTextFile(file, 300);
if (!content.empty()) {
std::cout << "大小: " << processor.getFileSizeFormatted(file) << std::endl;
std::cout << "预览:" << std::endl;
std::cout << "----------------------------------------" << std::endl;
std::cout << content;
if (content.length() >= 300) {
std::cout << "...";
}
std::cout << std::endl << "----------------------------------------" << std::endl << std::endl;
}
// 只显示前2个文本文件
if (textFileCount >= 2) break;
}
}
if (textFileCount == 0) {
std::cout << "没有找到文本文件。" << std::endl;
}
std::cout << std::endl << "测试完成!" << std::endl;
return 0;
}
二、安装libcurl
以下github连接在国内可能连接不上,多尝试几次就好了。
1.安装git
https://blog.csdn.net/weixin_44485316/article/details/143610150?spm=1001.2014.3001.5506
2.安装cmake
https://blog.csdn.net/weixin_52677672/article/details/135815928?spm=1001.2014.3001.5506
3.安装vcpkg
F:\lib>git clone https://github.com/microsoft/vcpkg
F:\lib>cd vcpkg
F:\lib>bootstrap-vcpkg.bat
验证安装
vcpkg version
4.安装libcurl
# 先查看是否已经安装了vcpkg
vcpkg list | findstr curl
# 如果未安装执行
vcpkg install curl[ssl]:x64-windows
# 然后使用vcpkg集成到vs中
vcpkg integrate install
# 重启vs后应该就可以在vs中使用libcurl了
测试
// main.cpp - 测试libcurl是否可用
#include <iostream>
#include <curl/curl.h>
int main() {
// 初始化libcurl
curl_global_init(CURL_GLOBAL_DEFAULT);
// 创建curl句柄
CURL* curl = curl_easy_init();
if (curl) {
std::cout << "libcurl配置成功!" << std::endl;
std::cout << "版本信息:" << curl_version() << std::endl;
// 清理
curl_easy_cleanup(curl);
} else {
std::cout << "libcurl初始化失败" << std::endl;
}
curl_global_cleanup();
return 0;
}
三、豆包ai内容接口API Key申请及接入点ID获取
https://www.eyoucms.com/help/system/31645.html
四、DoubaoClient 类
这个类负责:
连接到豆包API
发送文件内容进行分析
处理API响应
解析返回的结果
// DoubaoClient.h
#ifndef DOUBAO_CLIENT_H
#define DOUBAO_CLIENT_H
#include <string>
#include <map>
#include <vector>
#include <curl/curl.h>
#include "FileProcessor.h"
class DoubaoClient {
public:
/**
* 构造函数
* @param apiKey API 密钥
* @param modelId 模型 ID
* @param apiEndpoint API 服务的基础请求地址 默认为"https://ark.cn-beijing.volces.com/api/v3/responses"
*/
DoubaoClient(const std::string& apiKey, const std::string& modelId, const std::string& apiEndpoint = "https://ark.cn-beijing.volces.com/api/v3/responses");
/**
* 析构函数,清理 CURL 资源
*/
~DoubaoClient();
// 禁止拷贝和赋值
DoubaoClient(const DoubaoClient&) = delete;
DoubaoClient& operator=(const DoubaoClient&) = delete;
/**
* 设置 API 端点地址
* @param endpoint API 地址
*/
void setApiEndpoint(const std::string& endpoint);
/**
* 设置请求超时时间(秒)
* @param timeout 超时时间,默认为 60 秒
*/
void setRequestTimeout(long timeout);
/**
* 分析文本
* @param text 用户输入的文本
* @param systemPrompt 系统提示词(可选)
* @return AI 返回的响应内容
*/
std::string analyzeText(const std::string& text, const std::string& systemPrompt = "");
/**
* 分析单个文件
* @param filePath 文件的完整路径
* @param fileProcessor FileProcessor 对象引用,用于读取文件
* @return 模型对该文件的分析结果
*/
std::string analyzeSingleFile(const std::string& filePath, const FileProcessor& fileProcessor);
/**
* 分析文件夹中的所有文件
* @param folderPath 文件夹路径
* @param fileProcessor 文件处理器
* @param extensionFilter 文件扩展名过滤(如 ".txt")。为空字符串时分析所有文件。
* @return 文件名->分析结果的映射
*/
std::map<std::string, std::string> analyzeFolderFiles(
const std::string& folderPath,
FileProcessor& fileProcessor,
const std::string& extensionFilter = "");
/**
* 获取最后一次 HTTP 状态码
* @return HTTP 状态码
*/
long getHttpStatus() const { return lastHttpStatus_; }
/**
* 获取最后一次错误信息
* @return 错误信息字符串
*/
std::string getLastError() const { return lastError_; }
/**
* 启用/禁用详细日志输出
* @param enable true 启用,false 禁用 默认不启用
*/
void setVerbose(bool enable);
private:
std::string apiKey_; // API 密钥
std::string modelId_; // 模型 ID
std::string apiEndpoint_; // API 端点 URL
CURL* curlHandle_; // libcurl 句柄
int lastHttpStatus_; // 最后一次 HTTP 状态码
std::string lastError_; // 最后一次错误信息
long requestTimeout_; // 请求超时时间(秒)
bool verbose_; // 是否启用详细日志输出模式
// 辅助函数
// 对字符串进行 JSON 转义
static std::string escapeJsonString(const std::string& str);
// 构建符合火山方舟 API 要求的 JSON 请求体
std::string buildRequestJson(const std::string& userMessage)const;
// 执行 HTTP POST 请求
std::string sendHttpRequest(const std::string& jsonData);
// 从 API 的 JSON 响应中解析出 content 字段
std::string parseResponse(const std::string& responseJson);
// libcurl 的回调函数,用于写入响应数据
static size_t writeCallback(void* contents, size_t size, size_t nmemb, std::string* output);
};
#endif // DOUBAO_CLIENT_H
// DoubaoClient.cpp
#include "DoubaoClient.h"
#include <sstream>
#include <iostream>
#include <iomanip>
#include <algorithm>
#include <thread>
#include <chrono>
#include <stdexcept>
#include <vector>
// 静态辅助函数:对字符串进行 JSON 转义
std::string DoubaoClient::escapeJsonString(const std::string& str) {
std::ostringstream oss;
for (size_t i = 0; i < str.length(); ++i) {
unsigned char c = static_cast<unsigned char>(str[i]);
switch (c) {
case '"': oss << "\\\""; break;
case '\\': oss << "\\\\"; break;
case '\b': oss << "\\b"; break;
case '\f': oss << "\\f"; break;
case '\n': oss << "\\n"; break;
case '\r': oss << "\\r"; break;
case '\t': oss << "\\t"; break;
default:
// 控制字符转义为 \uXXXX
if (c < 0x20 || c == 0x7F) {
oss << "\\u" << std::hex << std::setw(4) << std::setfill('0')
<< static_cast<int>(c);
}
else {
// 否则直接输出原始字节,保持 UTF-8 多字节序列完整
oss << c;
}
break;
}
}
return oss.str();
}
// libcurl 写回调函数 output:用户提供的输出缓冲区
size_t DoubaoClient::writeCallback(void* contents, size_t size, size_t nmemb, std::string* output) {
size_t totalSize = size * nmemb;
output->append(static_cast<char*>(contents), totalSize);
return totalSize;
}
DoubaoClient::DoubaoClient(const std::string& apiKey, const std::string& modelId, const std::string& apiEndpoint)
: apiKey_(apiKey)
, modelId_(modelId)
, apiEndpoint_(apiEndpoint)
, curlHandle_(nullptr)
, lastHttpStatus_(0)
, requestTimeout_(60L)
, verbose_(false) {
// 全局初始化 libcurl(线程安全)
curl_global_init(CURL_GLOBAL_DEFAULT);
// 初始化 easy handle
curlHandle_ = curl_easy_init();
if (!curlHandle_) {
throw std::runtime_error("Failed to initialize CURL handle.");
}
}
DoubaoClient::~DoubaoClient() {
if (curlHandle_) {
curl_easy_cleanup(curlHandle_);
curlHandle_ = nullptr;
}
curl_global_cleanup();
}
void DoubaoClient::setApiEndpoint(const std::string& endpoint) {
apiEndpoint_ = endpoint;
}
void DoubaoClient::setRequestTimeout(long timeout) {
requestTimeout_ = timeout;
}
void DoubaoClient::setVerbose(bool enable) {
verbose_ = enable;
}
// ============================================================================
// 构建请求 JSON
// ============================================================================
std::string DoubaoClient::buildRequestJson(const std::string& userMessage) const {
std::ostringstream oss;
oss << "{";
oss << "\"model\":\"" << escapeJsonString(modelId_) << "\",";
oss << "\"input\":[";
oss << "{";
oss << "\"role\":\"user\",";
oss << "\"content\":[";
oss << "{";
oss << "\"type\":\"input_text\",";
oss << "\"text\":\"" << escapeJsonString(userMessage) << "\"";
oss << "}";
oss << "]";
oss << "}";
oss << "]";
oss << "}";
return oss.str();
}
// ============================================================================
// 发送 HTTP 请求
// 即使 respose 状态码 错误也返回完整响应内容
// ============================================================================
std::string DoubaoClient::sendHttpRequest(const std::string& jsonData) {
if (!curlHandle_) {
lastError_ = "CURL handle is not initialized.";
return "";
}
// 重置 lastError_ 和 lastHttpStatus_
lastError_.clear();
lastHttpStatus_ = 0;
std::string responseString;
// 设置 HTTP 头部
struct curl_slist* headers = nullptr;
headers = curl_slist_append(headers, ("Authorization: Bearer " + apiKey_).c_str());
headers = curl_slist_append(headers, "Content-Type: application/json");
headers = curl_slist_append(headers, "User-Agent: DoubaoCppClient/1.0");
headers = curl_slist_append(headers, "Accept: application/json");
// 重置 curl handle
curl_easy_reset(curlHandle_);
// 配置 CURL 选项
curl_easy_setopt(curlHandle_, CURLOPT_URL, apiEndpoint_.c_str());
curl_easy_setopt(curlHandle_, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curlHandle_, CURLOPT_POST, 1L);
curl_easy_setopt(curlHandle_, CURLOPT_POSTFIELDS, jsonData.c_str());
curl_easy_setopt(curlHandle_, CURLOPT_POSTFIELDSIZE, static_cast<long>(jsonData.length()));
curl_easy_setopt(curlHandle_, CURLOPT_WRITEFUNCTION, writeCallback);
curl_easy_setopt(curlHandle_, CURLOPT_WRITEDATA, &responseString);
curl_easy_setopt(curlHandle_, CURLOPT_TIMEOUT, requestTimeout_);
curl_easy_setopt(curlHandle_, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);
// 调试时开启详细日志
if (verbose_) {
curl_easy_setopt(curlHandle_, CURLOPT_VERBOSE, 1L);
}
// 执行请求
CURLcode res = curl_easy_perform(curlHandle_);
// 获取 HTTP 状态码
if (res == CURLE_OK) {
curl_easy_getinfo(curlHandle_, CURLINFO_RESPONSE_CODE, &lastHttpStatus_);
}
else {
lastError_ = "CURL Error: " + std::string(curl_easy_strerror(res));
}
// 清理头部列表
curl_slist_free_all(headers);
if (res != CURLE_OK) {//执行curl_easy_perform失败
return "";
}
// 记录错误但保留响应内容
if (lastHttpStatus_ < 200 || lastHttpStatus_ >= 300) {
lastError_ = "HTTP Error " + std::to_string(lastHttpStatus_);
return responseString;
}
return responseString;
}
// ============================================================================
// 解析响应 JSON
// ============================================================================
std::string DoubaoClient::parseResponse(const std::string& responseJson) {
// 这是一个简化的 JSON 解析器,仅用于提取 text 字段。
const std::string textKey = "\"text\":";
size_t textPos = responseJson.find(textKey);
if (textPos != std::string::npos) {
// 找到字段起始位置后,定位到其值的开始(冒号后的第一个引号)
size_t valueStart = responseJson.find('\"', textPos + textKey.length());
if (valueStart != std::string::npos) {
size_t valueEnd = valueStart + 1;
while (valueEnd < responseJson.length()) {
if (responseJson[valueEnd] == '\"' &&
(valueEnd == 0 || responseJson[valueEnd - 1] != '\\')) {
break;
}
valueEnd++;
}
if (valueEnd > valueStart) {
std::string extracted = responseJson.substr(valueStart + 1, valueEnd - valueStart - 1);
// 反转义
std::string result;
for (size_t i = 0; i < extracted.length(); ++i) {
if (extracted[i] == '\\' && i + 1 < extracted.length()) {
switch (extracted[i + 1]) {
case 'n': result += '\n'; i++; break;
case 'r': result += '\r'; i++; break;
case 't': result += '\t'; i++; break;
case '\\': result += '\\'; i++; break;
case '"': result += '"'; i++; break;
default: result += extracted[i]; break;
}
}
else {
result += extracted[i];
}
}
return result;
}
}
}
// 如果解析失败,返回原始响应以便调试
return "[解析响应失败] 原始响应:\n" + responseJson;
}
// ============================================================================
// 分析文本
// ============================================================================
std::string DoubaoClient::analyzeText(const std::string& text, const std::string& systemPrompt) {
// 组合系统提示和用户文本
std::string userMessage = systemPrompt.empty() ? text : systemPrompt + "\n\n" + text;
std::string requestJson = buildRequestJson(userMessage);
if (verbose_) {
std::cout << "[DEBUG] 发送请求 JSON: " << requestJson << std::endl;
std::cout << "[DEBUG] JSON 长度:" << requestJson.length() << " 字节" << std::endl;
// 打印前 200 字节的十六进制,检查编码
std::cout << "[DEBUG] 前 100 字节 HEX: ";
int min = requestJson.length() > size_t(100) ? 100 : requestJson.length();
for (size_t i = 0; i < min; ++i) {
printf("%02X ", static_cast<unsigned char>(requestJson[i]));
}
std::cout << std::endl;
}
std::string responseJson = sendHttpRequest(requestJson);
if (responseJson.empty() && !lastError_.empty()) {
return "[网络请求失败] " + lastError_;
}
if (lastHttpStatus_ != 200) {
return "[API 错误 " + std::to_string(lastHttpStatus_) + "] " + responseJson;
}
return parseResponse(responseJson);
}
// ============================================================================
// 分析单个文件
// ============================================================================
std::string DoubaoClient::analyzeSingleFile(const std::string& filePath, const FileProcessor& fileProcessor) {
// 使用 FileProcessor 检查并读取文件
if (!fileProcessor.fileExists(filePath)) {
lastError_ = "File does not exist: " + filePath;
return "[错误] " + lastError_;
}
// 读取文件并发送
std::string fileContent = fileProcessor.readTextFile(filePath);
if (fileContent.empty()) {
return "[提示] 文件内容为空或读取失败:" + fileProcessor.getFileName(filePath);
}
std::string fileName = fileProcessor.getFileName(filePath);
std::string userMessage = "请分析以下文件,文件名为 `" + fileName + "`:\n```\n" + fileContent + "\n```";
return analyzeText(userMessage);
}
// ============================================================================
// 分析文件夹中的所有文件
// ============================================================================
std::map<std::string, std::string> DoubaoClient::analyzeFolderFiles(
const std::string& folderPath,
FileProcessor& fileProcessor,
const std::string& extensionFilter) {
std::map<std::string, std::string> results;
// 1. 设置文件夹路径
if (!fileProcessor.setFolderPath(folderPath)) {
lastError_ = "无效的文件夹路径:" + folderPath;
std::cerr << "错误:" << lastError_ << std::endl;
return results;
}
// 2. 获取文件列表
std::vector<std::string> filePaths;
if (extensionFilter.empty()) {
filePaths = fileProcessor.getAllFiles();
}
else {
filePaths = fileProcessor.getFilesByExtension(extensionFilter);
}
if (filePaths.empty()) {
std::cout << "在文件夹 \"" << folderPath << "\" 中未找到任何文件。" << std::endl;
return results;
}
std::cout << "开始在文件夹 \"" << folderPath << "\" 中分析 " << filePaths.size() << " 个文件..." << std::endl;
std::cout << "==================================================" << std::endl;
// 3. 遍历并分析每个文件
for (size_t i = 0; i < filePaths.size(); ++i) {
const std::string& filePath = filePaths[i];
std::string fileName = fileProcessor.getFileName(filePath);
std::cout << "[" << (i + 1) << "/" << filePaths.size() << "] 正在分析:" << fileName << std::endl;
std::string analysisResult = analyzeSingleFile(filePath, fileProcessor);
results[fileName] = analysisResult;
// 在控制台输出简要预览
std::string preview = analysisResult.substr(0, 120);
if (analysisResult.length() > 120) {
preview += "...";
}
// 移除预览中的换行符,使单行显示更整洁
std::replace(preview.begin(), preview.end(), '\n', ' ');
std::cout << " 结果预览:" << preview << std::endl;
// 添加延迟,避免触发 API 速率限制(RPM)
if (i < filePaths.size() - 1) {
std::cout << " 等待 1 秒..." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
}
std::cout << "--------------------------------------------------" << std::endl;
}
std::cout << "文件夹分析完成!" << std::endl;
return results;
}
测试API Key/Model ID是否有效
#include "DoubaoClient.h"
#include "FileProcessor.h"
#include <iostream>
#pragma execution_character_set("utf-8")
int main() {
// Windows 下设置控制台输出 UTF-8
#ifdef _WIN32
system("chcp 65001 > nul");
#endif
// 替换为你的有效参数
const std::string API_KEY = "你的真实API Key";
const std::string MODEL_ID = "你的真实Model ID";
try {
DoubaoClient client(API_KEY, MODEL_ID);
// 发送简单文本请求
std::string result = client.analyzeText("Hello World", "请回复测试");
std::cout << "测试结果:" << result << std::endl;
} catch (const std::exception& e) {
std::cerr << "错误:" << e.what() << std::endl;
}
return 0;
}
需要注意的是,所有文件必须保存为utf-8格式,否则响应500错误
"error":{"code":"InternalServiceError","message":"The service encountered an unexpected internal error. "}
在vs中保存文件为utf8格式步骤如下:




选择UTF-8带签名,然后确定

代码调整,因为文件上传字节数比较大,增大了请求超时时间
// DoubaoClient.h
#ifndef DOUBAO_CLIENT_H
#define DOUBAO_CLIENT_H
#include <string>
#include <map>
#include <vector>
#include <curl/curl.h>
#include "FileProcessor.h"
class DoubaoClient {
public:
/**
* 预览输出模式枚举
*/
enum class PreviewMode {
BRIEF, // 简要输出 (默认,截断至120字符)
FULL, // 完整输出
NONE // 不输出
};
/**
* 构造函数
* @param apiKey API 密钥
* @param modelId 模型 ID
* @param apiEndpoint API 服务的基础请求地址 默认为"https://ark.cn-beijing.volces.com/api/v3/responses"
*/
DoubaoClient(const std::string& apiKey, const std::string& modelId, const std::string& apiEndpoint = "https://ark.cn-beijing.volces.com/api/v3/responses");
/**
* 析构函数,清理 CURL 资源
*/
~DoubaoClient();
// 禁止拷贝和赋值
DoubaoClient(const DoubaoClient&) = delete;
DoubaoClient& operator=(const DoubaoClient&) = delete;
/**
* 设置 API 端点地址
* @param endpoint API 地址
*/
void setApiEndpoint(const std::string& endpoint);
/**
* 设置请求超时时间(秒)
* @param timeout 超时时间,默认为 120 秒
*/
void setRequestTimeout(long timeout);
/**
* 设置分析文件夹时,在控制台输出结果的模式
* @param mode 预览模式 (BRIEF, FULL, NONE) 默认模式为完整输出
*/
void setPreviewMode(PreviewMode mode);
/**
* 设置API请求间隔(毫秒),用于避免触发API速率限制
* @param delayMs 请求间隔延迟,单位毫秒 默认1000毫秒
*/
void setRateLimitDelayMs(unsigned int delayMs);
/**
* 分析文本
* @param text 用户输入的文本
* @param systemPrompt 系统提示词(可选)
* @return AI 返回的响应内容
*/
std::string analyzeText(const std::string& text, const std::string& systemPrompt = "");
/**
* 分析单个文件
* @param filePath 文件的完整路径
* @param fileProcessor FileProcessor 对象引用,用于读取文件
* @return 模型对该文件的分析结果
*/
std::string analyzeSingleFile(const std::string& filePath, const FileProcessor& fileProcessor);
/**
* 分析文件夹中的所有文件
* @param folderPath 文件夹路径
* @param fileProcessor 文件处理器
* @param extensionFilter 文件扩展名过滤(如 ".txt")。为空字符串时分析所有文件。
* @return 文件名->分析结果的映射
*/
std::map<std::string, std::string> analyzeFolderFiles(
const std::string& folderPath,
FileProcessor& fileProcessor,
const std::string& extensionFilter = "");
/**
* 获取最后一次 HTTP 状态码
* @return HTTP 状态码
*/
long getHttpStatus() const { return lastHttpStatus_; }
/**
* 获取最后一次错误信息
* @return 错误信息字符串
*/
std::string getLastError() const { return lastError_; }
/**
* 启用/禁用详细日志输出
* @param enable true 启用,false 禁用 默认不启用
*/
void setVerbose(bool enable);
/**
* 设置是否在分析文件夹时输出每个文件的简要预览到控制台
* @param enable true 输出,false 不输出 默认为 true
*/
void setEnablePreviewOutput(bool enable) { enablePreviewOutput_ = enable; }
private:
std::string apiKey_; // API 密钥
std::string modelId_; // 模型 ID
std::string apiEndpoint_; // API 端点 URL
CURL* curlHandle_; // libcurl 句柄
int lastHttpStatus_; // 最后一次 HTTP 状态码
std::string lastError_; // 最后一次错误信息
long requestTimeout_; // 请求超时时间(秒)
bool verbose_; // 是否启用详细日志输出模式
bool enablePreviewOutput_; // 是否在分析文件夹时输出结果预览到控制台
PreviewMode previewMode_; // 控制台结果预览模式
unsigned int rateLimitDelayMs_; // API请求间隔延迟(毫秒)
// 辅助函数
// 对字符串进行 JSON 转义
static std::string escapeJsonString(const std::string& str);
// 构建符合火山方舟 API 要求的 JSON 请求体
std::string buildRequestJson(const std::string& userMessage)const;
// 执行 HTTP POST 请求
std::string sendHttpRequest(const std::string& jsonData);
// 从 API 的 JSON 响应中解析出 content 字段
std::string parseResponse(const std::string& responseJson);
// libcurl 的回调函数,用于写入响应数据
static size_t writeCallback(void* contents, size_t size, size_t nmemb, std::string* output);
};
#endif // DOUBAO_CLIENT_H
// DoubaoClient.cpp
#include "DoubaoClient.h"
#include <sstream>
#include <iostream>
#include <iomanip>
#include <algorithm>
#include <thread>
#include <chrono>
#include <stdexcept>
#include <vector>
#pragma execution_character_set("utf-8")
// 静态辅助函数:对字符串进行 JSON 转义
std::string DoubaoClient::escapeJsonString(const std::string& str) {
std::ostringstream oss;
for (char c : str) {
switch (c) {
case '"': oss << "\\\""; break;
case '\\': oss << "\\\\"; break;
case '\n': oss << "\\n"; break;
case '\r': oss << "\\r"; break;
case '\t': oss << "\\t"; break;
default: oss << c; break; // 保留其他字符原生格式
}
}
return oss.str();
}
// libcurl 写回调函数 output:用户提供的输出缓冲区
size_t DoubaoClient::writeCallback(void* contents, size_t size, size_t nmemb, std::string* output) {
size_t totalSize = size * nmemb;
output->append(static_cast<char*>(contents), totalSize);
return totalSize;
}
DoubaoClient::DoubaoClient(const std::string& apiKey, const std::string& modelId, const std::string& apiEndpoint)
: apiKey_(apiKey)
, modelId_(modelId)
, apiEndpoint_(apiEndpoint)
, curlHandle_(nullptr)
, lastHttpStatus_(0)
, requestTimeout_(120L)
, verbose_(false)
, previewMode_(PreviewMode::FULL) {
// 全局初始化 libcurl(线程安全)
curl_global_init(CURL_GLOBAL_DEFAULT);
// 初始化 easy handle
curlHandle_ = curl_easy_init();
if (!curlHandle_) {
throw std::runtime_error("Failed to initialize CURL handle.");
}
}
DoubaoClient::~DoubaoClient() {
if (curlHandle_) {
curl_easy_cleanup(curlHandle_);
curlHandle_ = nullptr;
}
curl_global_cleanup();
}
void DoubaoClient::setApiEndpoint(const std::string& endpoint) {
apiEndpoint_ = endpoint;
}
void DoubaoClient::setRequestTimeout(long timeout) {
requestTimeout_ = timeout;
}
void DoubaoClient::setVerbose(bool enable) {
verbose_ = enable;
}
void DoubaoClient::setPreviewMode(PreviewMode mode) {
previewMode_ = mode;
}
void DoubaoClient::setRateLimitDelayMs(unsigned int delayMs) {
rateLimitDelayMs_ = delayMs;
}
// ============================================================================
// 构建请求 JSON
// ============================================================================
std::string DoubaoClient::buildRequestJson(const std::string& userMessage) const {
std::ostringstream oss;
oss << "{";
oss << "\"model\":\"" << escapeJsonString(modelId_) << "\",";
oss << "\"input\":[";
oss << "{";
oss << "\"role\":\"user\",";
oss << "\"content\":[";
oss << "{";
oss << "\"type\":\"input_text\",";
oss << "\"text\":\"" << escapeJsonString(userMessage) << "\"";
oss << "}";
oss << "]";
oss << "}";
oss << "]";
oss << "}";
return oss.str();
}
// ============================================================================
// 发送 HTTP 请求
// 即使 respose 状态码 错误也返回完整响应内容
// ============================================================================
std::string DoubaoClient::sendHttpRequest(const std::string& jsonData) {
if (!curlHandle_) {
lastError_ = "CURL handle is not initialized.";
return "";
}
// 重置 lastError_ 和 lastHttpStatus_
lastError_.clear();
lastHttpStatus_ = 0;
std::string responseString;
// 设置 HTTP 头部
struct curl_slist* headers = nullptr;
headers = curl_slist_append(headers, ("Authorization: Bearer " + apiKey_).c_str());
headers = curl_slist_append(headers, "Content-Type: application/json");
headers = curl_slist_append(headers, "User-Agent: DoubaoCppClient/1.0");
headers = curl_slist_append(headers, "Accept: application/json");
// 重置 curl handle
curl_easy_reset(curlHandle_);
// 配置 CURL 选项
curl_easy_setopt(curlHandle_, CURLOPT_URL, apiEndpoint_.c_str());
curl_easy_setopt(curlHandle_, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curlHandle_, CURLOPT_POST, 1L);
curl_easy_setopt(curlHandle_, CURLOPT_POSTFIELDS, jsonData.c_str());
curl_easy_setopt(curlHandle_, CURLOPT_POSTFIELDSIZE, static_cast<long>(jsonData.length()));
curl_easy_setopt(curlHandle_, CURLOPT_WRITEFUNCTION, writeCallback);
curl_easy_setopt(curlHandle_, CURLOPT_WRITEDATA, &responseString);
curl_easy_setopt(curlHandle_, CURLOPT_TIMEOUT, requestTimeout_);
curl_easy_setopt(curlHandle_, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);
// 调试时开启详细日志
if (verbose_) {
curl_easy_setopt(curlHandle_, CURLOPT_VERBOSE, 1L);
}
// 执行请求
CURLcode res = curl_easy_perform(curlHandle_);
// 获取 HTTP 状态码
if (res == CURLE_OK) {
curl_easy_getinfo(curlHandle_, CURLINFO_RESPONSE_CODE, &lastHttpStatus_);
}
else {
lastError_ = "CURL Error: " + std::string(curl_easy_strerror(res));
}
// 清理头部列表
curl_slist_free_all(headers);
if (res != CURLE_OK) {//执行curl_easy_perform失败
return "";
}
// 记录错误但保留响应内容
if (lastHttpStatus_ < 200 || lastHttpStatus_ >= 300) {
lastError_ = "HTTP Error " + std::to_string(lastHttpStatus_);
return responseString;
}
return responseString;
}
// ============================================================================
// 解析响应 JSON
// ============================================================================
std::string DoubaoClient::parseResponse(const std::string& responseJson) {
// 这是一个简化的 JSON 解析器,仅用于提取 text 字段。
const std::string textKey = "\"text\":";
size_t textPos = responseJson.find(textKey);
if (textPos != std::string::npos) {
// 找到字段起始位置后,定位到其值的开始(冒号后的第一个引号)
size_t valueStart = responseJson.find('\"', textPos + textKey.length());
if (valueStart != std::string::npos) {
size_t valueEnd = valueStart + 1;
while (valueEnd < responseJson.length()) {
if (responseJson[valueEnd] == '\"' &&
(valueEnd == 0 || responseJson[valueEnd - 1] != '\\')) {
break;
}
valueEnd++;
}
if (valueEnd > valueStart) {
std::string extracted = responseJson.substr(valueStart + 1, valueEnd - valueStart - 1);
// 反转义
std::string result;
for (size_t i = 0; i < extracted.length(); ++i) {
if (extracted[i] == '\\' && i + 1 < extracted.length()) {
switch (extracted[i + 1]) {
case 'n': result += '\n'; i++; break;
case 'r': result += '\r'; i++; break;
case 't': result += '\t'; i++; break;
case '\\': result += '\\'; i++; break;
case '"': result += '"'; i++; break;
default: result += extracted[i]; break;
}
}
else {
result += extracted[i];
}
}
return result;
}
}
}
// 如果解析失败,返回原始响应以便调试
return "[解析响应失败] 原始响应:\n" + responseJson;
}
// ============================================================================
// 分析文本
// ============================================================================
std::string DoubaoClient::analyzeText(const std::string& text, const std::string& systemPrompt) {
// 组合系统提示和用户文本
std::string userMessage = systemPrompt.empty() ? text : systemPrompt + "\n\n" + text;
std::string requestJson = buildRequestJson(userMessage);
if (verbose_) {
std::cout << "[DEBUG] 发送请求 JSON: " << requestJson << std::endl;
std::cout << "[DEBUG] JSON 长度:" << requestJson.length() << " 字节" << std::endl;
// 打印前 200 字节的十六进制,检查编码
std::cout << "[DEBUG] 前 100 字节 HEX: ";
int min = requestJson.length() > size_t(100) ? 100 : requestJson.length();
for (size_t i = 0; i < min; ++i) {
printf("%02X ", static_cast<unsigned char>(requestJson[i]));
}
std::cout << std::endl;
}
std::string responseJson = sendHttpRequest(requestJson);
if (responseJson.empty() && !lastError_.empty()) {
return "[网络请求失败] " + lastError_;
}
if (lastHttpStatus_ != 200) {
return "[API 错误 " + std::to_string(lastHttpStatus_) + "] " + responseJson;
}
return parseResponse(responseJson);
}
// ============================================================================
// 分析单个文件
// ============================================================================
std::string DoubaoClient::analyzeSingleFile(const std::string& filePath, const FileProcessor& fileProcessor) {
// 使用 FileProcessor 检查并读取文件
if (!fileProcessor.fileExists(filePath)) {
lastError_ = "File does not exist: " + filePath;
return "[错误] " + lastError_;
}
// 读取文件并发送
std::string fileContent = fileProcessor.readTextFile(filePath, 8 * 1024);
if (fileContent.empty()) {
return "[提示] 文件内容为空或读取失败:" + fileProcessor.getFileName(filePath);
}
std::string fileName = fileProcessor.getFileName(filePath);
std::string userMessage = "请分析以下文件,文件名为 `" + fileName + "`:\n```\n" + fileContent + "\n```";
return analyzeText(userMessage);
}
// ============================================================================
// 分析文件夹中的所有文件
// ============================================================================
std::map<std::string, std::string> DoubaoClient::analyzeFolderFiles(
const std::string& folderPath,
FileProcessor& fileProcessor,
const std::string& extensionFilter) {
std::map<std::string, std::string> results;
// 1. 设置文件夹路径
if (!fileProcessor.setFolderPath(folderPath)) {
lastError_ = "无效的文件夹路径:" + folderPath;
std::cerr << "错误:" << lastError_ << std::endl;
return results;
}
// 2. 获取文件列表
std::vector<std::string> filePaths;
if (extensionFilter.empty()) {
filePaths = fileProcessor.getAllFiles();
}
else {
filePaths = fileProcessor.getFilesByExtension(extensionFilter);
}
if (filePaths.empty()) {
std::cout << "在文件夹 \"" << folderPath << "\" 中未找到任何文件。" << std::endl;
return results;
}
std::cout << "开始在文件夹 \"" << folderPath << "\" 中分析 " << filePaths.size() << " 个文件..." << std::endl;
std::cout << "==================================================" << std::endl;
// 3. 遍历并分析每个文件
for (size_t i = 0; i < filePaths.size(); ++i) {
const std::string& filePath = filePaths[i];
std::string fileName = fileProcessor.getFileName(filePath);
std::cout << "[" << (i + 1) << "/" << filePaths.size() << "] 正在分析:" << fileName << std::endl;
std::string analysisResult = analyzeSingleFile(filePath, fileProcessor);
results[fileName] = analysisResult;
// 根据预览模式输出结果
if (previewMode_ != PreviewMode::NONE) {
if (previewMode_ == PreviewMode::BRIEF) {
// 简要输出
std::string preview = analysisResult.substr(0, 120);
if (analysisResult.length() > 120) {
preview += "...";
}
// 移除预览中的换行符,使单行显示更整洁
std::replace(preview.begin(), preview.end(), '\n', ' ');
std::cout << " 结果预览:" << preview << std::endl;
}
else if (previewMode_ == PreviewMode::FULL) {
// 完整输出 (默认逻辑)
std::cout << " 分析结果:" << analysisResult << std::endl;
}
}
// 添加延迟,避免触发 API 速率限制(RPM)
if (i < filePaths.size() - 1 && rateLimitDelayMs_ > 0) {
std::cout << " 等待 " << rateLimitDelayMs_ << " 毫秒..." << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(rateLimitDelayMs_));
}
std::cout << "--------------------------------------------------" << std::endl;
}
std::cout << "文件夹分析完成!" << std::endl;
return results;
}
测试文件
// main.cpp
#include "FileProcessor.h"
#include "DoubaoClient.h"
#include <iostream>
#include <fstream>
#include <vector>
#pragma execution_character_set("utf-8")
int main(int argc, char* argv[]) {
// Windows 下设置控制台输出 UTF-8
#ifdef _WIN32
system("chcp 65001 > nul");
#endif
// ==================== 第一部分:硬编码的参数配置 ====================
const std::string API_KEY = "在此处填写您真实的ARK_API_KEY";
const std::string MODEL_ID = "在此处填写您的推理接入点ID,如 ep-xxxxx";
const std::string API_ENDPOINT = "https://ark.cn-beijing.volces.com/api/v3/responses";
const long REQUEST_TIMEOUT_SECONDS = 120;
const int RATE_LIMIT_DELAY_MS = 1200; // 请求间隔,避免触发API限流
const std::string DEFAULT_FOLDER = "."; // 默认分析当前文件夹
const bool ANALYZE_ONLY_TEXT_FILES = true;
const std::string DEFAULT_EXTENSION_FILTER = ".cpp"; // 默认只分析.cpp文件
const std::string SYSTEM_PROMPT =
"你是一个专业的代码分析助手。请分析用户提供的文件内容,并按照以下要求给出分析结果:\n"
"1. 简要总结文件的主要功能或内容。\n"
"2. 如果是代码文件,指出其主要函数/类的用途。\n"
"3. 识别任何明显的潜在问题或可改进之处。\n"
"4. 用中文回复,保持回答简洁明了。";
const std::string RESULT_OUTPUT_FILE = "analysis_results.txt"; // 结果输出文件
// ==================== 第二部分:核心业务逻辑 ====================
// 1. 初始化豆包客户端(使用硬编码参数)
std::cout << "正在初始化豆包客户端..." << std::endl;
std::cout << "模型ID: " << MODEL_ID << std::endl;
DoubaoClient client(API_KEY, MODEL_ID);
client.setApiEndpoint(API_ENDPOINT);
client.setRequestTimeout(REQUEST_TIMEOUT_SECONDS);
client.setRateLimitDelayMs(RATE_LIMIT_DELAY_MS);
client.setVerbose(true);
// 2. 初始化文件处理器
FileProcessor fp;
// 3. 确定要分析的文件夹路径(支持命令行参数)
std::string targetFolder = DEFAULT_FOLDER;
if (argc > 1) {
targetFolder = argv[1];
std::cout << "使用命令行指定的文件夹: " << targetFolder << std::endl;
}
else {
std::cout << "使用默认文件夹: " << targetFolder << std::endl;
}
// 4. 确定文件扩展名过滤器
std::string extensionFilter = "";
if (ANALYZE_ONLY_TEXT_FILES) {
extensionFilter = DEFAULT_EXTENSION_FILTER;
std::cout << "扩展名过滤: " << extensionFilter << std::endl;
}
// 5. 执行文件夹文件分析
std::cout << "\n开始分析文件..." << std::endl;
std::cout << "注意:请求间隔设置为 " << RATE_LIMIT_DELAY_MS << " 毫秒,以避免触发API限流。" << std::endl;
std::cout << "----------------------------------------" << std::endl;
auto results = client.analyzeFolderFiles(targetFolder, fp, extensionFilter);
// 6. 输出分析结果摘要
std::cout << "\n\n=== 分析完成 ===" << std::endl;
std::cout << "成功分析 " << results.size() << " 个文件。" << std::endl;
// 7. 将详细结果保存到文件
if (!results.empty()) {
std::ofstream outFile(RESULT_OUTPUT_FILE);
if (outFile.is_open()) {
outFile << "=== 文件分析报告 ===\n";
outFile << "生成时间: " << __DATE__ << " " << __TIME__ << "\n";
outFile << "目标文件夹: " << targetFolder << "\n";
outFile << "分析文件类型: " << (extensionFilter.empty() ? "所有文件" : extensionFilter) << "\n";
outFile << "==========================================\n\n";
// 遍历结果映射(C++11兼容写法)
for (const auto& item : results) {
const std::string& fileName = item.first;
const std::string& analysis = item.second;
outFile << "文件: " << fileName << "\n";
outFile << "----------------------------------------\n";
outFile << analysis << "\n\n";
outFile << "==========================================\n\n";
}
outFile.close();
std::cout << "详细分析结果已保存至: " << RESULT_OUTPUT_FILE << std::endl;
}
else {
std::cerr << "警告:无法打开结果文件 " << RESULT_OUTPUT_FILE << " 进行写入。" << std::endl;
}
// 同时在控制台显示前3个文件的简要结果
std::cout << "\n=== 简要结果预览(前3个文件)===" << std::endl;
int previewCount = 0;
for (const auto& item : results) {
if (previewCount >= 3) break;
const std::string& fileName = item.first;
const std::string& analysis = item.second;
// 截取分析结果的前100个字符作为预览
std::string preview = analysis.substr(0, 100);
if (analysis.length() > 100) preview += "...";
std::cout << previewCount + 1 << ". " << fileName << ":\n ";
std::cout << preview << "\n" << std::endl;
previewCount++;
}
}
else {
std::cout << "没有生成任何分析结果。" << std::endl;
}
return 0;
}
五、ConfigManager类
这个类负责应用程序配置的加载、验证和访问。
// ConfigManager.h
#ifndef CONFIG_MANAGER_H
#define CONFIG_MANAGER_H
#include <string>
#include <map>
#include <vector>
#include <iostream>
#include <fstream>
#include <sstream>
#include <cctype>
class ConfigManager {
public:
ConfigManager() = default;
/**
* 从文件加载配置
* @param filePath 配置文件路径,例如 "config.json"
* @return 成功加载返回 true,失败返回 false
*/
bool loadFromFile(const std::string& filePath);
/**
* 从 JSON 字符串加载配置
* @param jsonString 包含配置的 JSON 字符串
* @return 成功解析返回 true,失败返回 false
*/
bool loadFromJsonString(const std::string& jsonString);
// --- 获取配置值的方法 ---
// 获取字符串类型配置
std::string getString(const std::string& key, const std::string& defaultValue = "") const;
// 获取整数类型配置
int getInt(const std::string& key, int defaultValue = 0) const;
// 获取长整数类型配置
long getLong(const std::string& key, long defaultValue = 0L) const;
// 获取双精度浮点数配置
double getDouble(const std::string& key, double defaultValue = 0.0) const;
// 获取布尔类型配置
bool getBool(const std::string& key, bool defaultValue = false) const;
// 获取字符串列表配置
std::vector<std::string> getStringArray(const std::string& key) const;
// --- 配置管理方法 ---
// 检查配置项是否存在
bool hasKey(const std::string& key) const;
// 设置配置值(可用于编程覆盖或测试)
void setString(const std::string& key, const std::string& value);
// 打印所有配置项(用于调试)
void printAll() const;
// 验证必需的配置项是否都已设置
bool validateRequired(const std::vector<std::string>& requiredKeys) const;
private:
// 存储配置项的映射表,key 支持点号表示法,如 "doubao_api.api_key"
std::map<std::string, std::string> configMap_;
// --- 私有辅助方法 ---
// 清理和规范化 JSON 字符串(移除空格、换行、注释)
std::string cleanJsonString(const std::string& jsonStr);
// 解析 JSON 对象(递归)
bool parseJsonObject(const std::string& jsonStr, size_t& pos, const std::string& parentKey = "");
// 解析 JSON 数组
bool parseJsonArray(const std::string& jsonStr, size_t& pos, const std::string& parentKey);
// 解析 JSON 字符串值
std::string parseJsonString(const std::string& jsonStr, size_t& pos);
// 解析 JSON 值(数字、布尔、null)
std::string parseJsonValue(const std::string& jsonStr, size_t& pos);
// 读取文件内容到字符串
std::string readFileToString(const std::string& filePath);
// 转义字符串中的特殊字符
std::string escapeString(const std::string& str) const;
// 反转义字符串(用于从配置读取时)
std::string unescapeString(const std::string& str) const;
};
#endif // CONFIG_MANAGER_H
// ConfigManager.cpp
#include "ConfigManager.h"
#include <algorithm>
#include <cstdlib> // 用于 getenv
#pragma warning(disable:4996)
#pragma execution_character_set("utf-8")
// 工具函数:修剪字符串两端的空白字符
static inline std::string trim(const std::string& str) {
size_t first = str.find_first_not_of(" \t\n\r");
if (first == std::string::npos) return "";
size_t last = str.find_last_not_of(" \t\n\r");
return str.substr(first, last - first + 1);
}
bool ConfigManager::loadFromFile(const std::string& filePath) {
std::string content = readFileToString(filePath);
if (content.empty()) {
std::cerr << "[ConfigManager] 错误: 无法读取配置文件 '" << filePath << "' 或文件为空。" << std::endl;
return false;
}
return loadFromJsonString(content);
}
bool ConfigManager::loadFromJsonString(const std::string& jsonString) {
if (jsonString.empty()) {
std::cerr << "[ConfigManager] 错误: 提供的 JSON 字符串为空。" << std::endl;
return false;
}
// 清理 JSON 字符串(移除注释和多余空白)
std::string cleanJson = cleanJsonString(jsonString);
size_t pos = 0;
// 检查是否以 { 开头,表示是一个 JSON 对象
while (pos < cleanJson.length() && cleanJson[pos] > 0 && std::isspace(cleanJson[pos])) pos++;
if (pos >= cleanJson.length() || cleanJson[pos] != '{') {
std::cerr << "[ConfigManager] 错误: 无效的 JSON 格式,应以 '{' 开头。" << std::endl;
return false;
}
// 解析根对象
return parseJsonObject(cleanJson, pos);
}
std::string ConfigManager::getString(const std::string& key, const std::string& defaultValue) const {
auto it = configMap_.find(key);
if (it != configMap_.end()) {
return unescapeString(it->second);
}
// 如果配置文件中没有,尝试从环境变量获取(优先级更高)
const char* envVal = std::getenv(key.c_str());
if (envVal != nullptr) {
return std::string(envVal);
}
return defaultValue;
}
int ConfigManager::getInt(const std::string& key, int defaultValue) const {
std::string val = getString(key, "");
if (val.empty()) return defaultValue;
try {
return std::stoi(val);
}
catch (const std::exception& e) {
std::cerr << "[ConfigManager] 警告: 无法将配置项 '" << key << "' 的值 '" << val << "' 转换为整数。使用默认值 " << defaultValue << "。" << std::endl;
return defaultValue;
}
}
long ConfigManager::getLong(const std::string& key, long defaultValue) const {
std::string val = getString(key, "");
if (val.empty()) return defaultValue;
try {
return std::stol(val);
}
catch (const std::exception& e) {
std::cerr << "[ConfigManager] 警告: 无法将配置项 '" << key << "' 的值 '" << val << "' 转换为长整数。使用默认值 " << defaultValue << "。" << std::endl;
return defaultValue;
}
}
double ConfigManager::getDouble(const std::string& key, double defaultValue) const {
std::string val = getString(key, "");
if (val.empty()) return defaultValue;
try {
return std::stod(val);
}
catch (const std::exception& e) {
std::cerr << "[ConfigManager] 警告: 无法将配置项 '" << key << "' 的值 '" << val << "' 转换为浮点数。使用默认值 " << defaultValue << "。" << std::endl;
return defaultValue;
}
}
bool ConfigManager::getBool(const std::string& key, bool defaultValue) const {
std::string val = getString(key, "");
if (val.empty()) return defaultValue;
// 转换为小写进行比较
std::string lowerVal = val;
std::transform(lowerVal.begin(), lowerVal.end(), lowerVal.begin(), ::tolower);
if (lowerVal == "true" || lowerVal == "1" || lowerVal == "yes" || lowerVal == "on") {
return true;
}
else if (lowerVal == "false" || lowerVal == "0" || lowerVal == "no" || lowerVal == "off") {
return false;
}
else {
std::cerr << "[ConfigManager] 警告: 配置项 '" << key << "' 的值 '" << val << "' 无法识别为布尔值。使用默认值 " << (defaultValue ? "true" : "false") << "。" << std::endl;
return defaultValue;
}
}
std::vector<std::string> ConfigManager::getStringArray(const std::string& key) const {
std::vector<std::string> result;
// 数组在内部存储为 key.0, key.1, key.2, ...
int index = 0;
while (true) {
std::string arrayKey = key + "." + std::to_string(index);
auto it = configMap_.find(arrayKey);
if (it == configMap_.end()) {
// 也检查环境变量
const char* envVal = std::getenv(arrayKey.c_str());
if (envVal == nullptr) {
break;
}
result.push_back(unescapeString(std::string(envVal)));
}
else {
result.push_back(unescapeString(it->second));
}
index++;
}
return result;
}
bool ConfigManager::hasKey(const std::string& key) const {
if (configMap_.find(key) != configMap_.end()) return true;
// 也检查环境变量
return std::getenv(key.c_str()) != nullptr;
}
void ConfigManager::setString(const std::string& key, const std::string& value) {
configMap_[key] = escapeString(value);
}
void ConfigManager::printAll() const {
std::cout << "========== 当前配置项 ==========" << std::endl;
for (const auto& item : configMap_) {
const std::string& key = item.first;
const std::string& value = item.second;
std::cout << key << " = " << unescapeString(value) << std::endl;
}
std::cout << "=================================" << std::endl;
}
bool ConfigManager::validateRequired(const std::vector<std::string>& requiredKeys) const {
bool allValid = true;
for (const auto& key : requiredKeys) {
if (!hasKey(key)) {
std::cerr << "[ConfigManager] 错误: 缺少必需的配置项 '" << key << "'。" << std::endl;
allValid = false;
}
}
return allValid;
}
// --- 私有方法实现 ---
std::string ConfigManager::readFileToString(const std::string& filePath) {
std::ifstream file(filePath, std::ios::binary); // 以二进制模式打开文件
if (!file.is_open()) {
return "";
}
// 获取文件大小
file.seekg(0, std::ios::end);
size_t fileSize = file.tellg();
file.seekg(0, std::ios::beg);
if (fileSize == 0) {
return "";
}
// 读取整个文件
std::string content(fileSize, '\0');
file.read(&content[0], fileSize);
// 检查并移除UTF-8 BOM标记
if (fileSize >= 3 &&
static_cast<unsigned char>(content[0]) == 0xEF &&
static_cast<unsigned char>(content[1]) == 0xBB &&
static_cast<unsigned char>(content[2]) == 0xBF) {
// 移除开头的3个字节(BOM标记)
content = content.substr(3);
}
return content;
}
std::string ConfigManager::cleanJsonString(const std::string& jsonStr) {
std::string result;
bool inString = false;
bool inComment = false;
char stringDelimiter = '\0';
for (size_t i = 0; i < jsonStr.length(); i++) {
char c = jsonStr[i];
// 处理字符串字面量
if (!inComment && (c == '"' || c == '\'')) {
if (!inString) {
inString = true;
stringDelimiter = c;
}
else if (c == stringDelimiter) {
// 检查转义字符
if (i > 0 && jsonStr[i - 1] == '\\') {
// 是转义的引号,继续在字符串中
}
else {
inString = false;
}
}
result += c;
continue;
}
// 不在字符串中时,处理注释
if (!inString) {
// 处理单行注释
if (c == '/' && i + 1 < jsonStr.length() && jsonStr[i + 1] == '/') {
inComment = true;
i++; // 跳过下一个字符
continue;
}
// 处理多行注释
if (c == '/' && i + 1 < jsonStr.length() && jsonStr[i + 1] == '*') {
inComment = true;
i++; // 跳过下一个字符
continue;
}
// 结束多行注释
if (inComment && c == '*' && i + 1 < jsonStr.length() && jsonStr[i + 1] == '/') {
inComment = false;
i++; // 跳过下一个字符
continue;
}
// 结束单行注释(换行)
if (inComment && c == '\n') {
inComment = false;
}
if (inComment) {
continue;
}
// 跳过不必要的空白字符(不在字符串中且不在注释中)
if (c > 0 && std::isspace(c) && c != ' ' && c != '\t') {
continue;
}
}
// 添加字符到结果
if (!inComment) {
result += c;
}
}
return result;
}
bool ConfigManager::parseJsonObject(const std::string& jsonStr, size_t& pos, const std::string& parentKey) {
// 跳过 '{'
pos++;
while (pos < jsonStr.length()) {
// 跳过空白字符
while (pos < jsonStr.length() && std::isspace(jsonStr[pos])) pos++;
if (pos >= jsonStr.length()) return false;
// 检查是否结束
if (jsonStr[pos] == '}') {
pos++;
return true;
}
// 解析键名(字符串)
if (jsonStr[pos] != '"' && jsonStr[pos] != '\'') {
std::cerr << "[ConfigManager] 解析错误: 期望键名(字符串)在位置 " << pos << std::endl;
return false;
}
std::string key = parseJsonString(jsonStr, pos);
// 跳过空白字符和 ':'
while (pos < jsonStr.length() && std::isspace(jsonStr[pos])) pos++;
if (pos >= jsonStr.length() || jsonStr[pos] != ':') {
std::cerr << "[ConfigManager] 解析错误: 期望 ':' 在键名后" << std::endl;
return false;
}
pos++; // 跳过 ':'
// 跳过空白字符
while (pos < jsonStr.length() && std::isspace(jsonStr[pos])) pos++;
if (pos >= jsonStr.length()) return false;
// 构建完整键名(带父级前缀)
std::string fullKey = parentKey.empty() ? key : parentKey + "." + key;
// 根据值的类型解析
char currentChar = jsonStr[pos];
if (currentChar == '{') {
// 嵌套对象
if (!parseJsonObject(jsonStr, pos, fullKey)) {
return false;
}
}
else if (currentChar == '[') {
// 数组
if (!parseJsonArray(jsonStr, pos, fullKey)) {
return false;
}
}
else if (currentChar == '"' || currentChar == '\'') {
// 字符串值
std::string value = parseJsonString(jsonStr, pos);
configMap_[fullKey] = escapeString(value);
}
else {
// 其他值(数字、布尔、null)
std::string value = parseJsonValue(jsonStr, pos);
configMap_[fullKey] = escapeString(value);
}
// 跳过空白字符
while (pos < jsonStr.length() && std::isspace(jsonStr[pos])) pos++;
if (pos >= jsonStr.length()) break;
// 检查是否有下一个键值对
if (jsonStr[pos] == ',') {
pos++;
continue;
}
else if (jsonStr[pos] == '}') {
// 对象结束
pos++;
return true;
}
else {
std::cerr << "[ConfigManager] 解析错误: 期望 ',' 或 '}' 在位置 " << pos << std::endl;
return false;
}
}
return true;
}
bool ConfigManager::parseJsonArray(const std::string& jsonStr, size_t& pos, const std::string& parentKey) {
// 跳过 '['
pos++;
int index = 0;
while (pos < jsonStr.length()) {
// 跳过空白字符
while (pos < jsonStr.length() && std::isspace(jsonStr[pos])) pos++;
if (pos >= jsonStr.length()) return false;
// 检查是否结束
if (jsonStr[pos] == ']') {
pos++;
return true;
}
// 构建数组元素的键名
std::string elementKey = parentKey + "." + std::to_string(index);
// 根据值的类型解析
char currentChar = jsonStr[pos];
if (currentChar == '{') {
// 嵌套对象
if (!parseJsonObject(jsonStr, pos, elementKey)) {
return false;
}
}
else if (currentChar == '[') {
// 嵌套数组
if (!parseJsonArray(jsonStr, pos, elementKey)) {
return false;
}
}
else if (currentChar == '"' || currentChar == '\'') {
// 字符串值
std::string value = parseJsonString(jsonStr, pos);
configMap_[elementKey] = escapeString(value);
}
else {
// 其他值
std::string value = parseJsonValue(jsonStr, pos);
configMap_[elementKey] = escapeString(value);
}
index++;
// 跳过空白字符
while (pos < jsonStr.length() && std::isspace(jsonStr[pos])) pos++;
if (pos >= jsonStr.length()) break;
// 检查是否有下一个元素
if (jsonStr[pos] == ',') {
pos++;
continue;
}
else if (jsonStr[pos] == ']') {
// 数组结束
pos++;
return true;
}
else {
std::cerr << "[ConfigManager] 解析错误: 期望 ',' 或 ']' 在位置 " << pos << std::endl;
return false;
}
}
return true;
}
std::string ConfigManager::parseJsonString(const std::string& jsonStr, size_t& pos) {
char delimiter = jsonStr[pos]; // 可能是 " 或 '
pos++; // 跳过开始的分隔符
std::string result;
while (pos < jsonStr.length() && jsonStr[pos] != delimiter) {
// 处理转义字符
if (jsonStr[pos] == '\\' && pos + 1 < jsonStr.length()) {
pos++; // 跳过反斜杠
switch (jsonStr[pos]) {
case '"': result += '"'; break;
case '\'': result += '\''; break;
case '\\': result += '\\'; break;
case '/': result += '/'; break;
case 'b': result += '\b'; break;
case 'f': result += '\f'; break;
case 'n': result += '\n'; break;
case 'r': result += '\r'; break;
case 't': result += '\t'; break;
case 'u':
// 简单处理Unicode转义(这里简化为跳过)
if (pos + 4 < jsonStr.length()) {
pos += 4;
}
break;
default: result += jsonStr[pos]; break;
}
}
else {
result += jsonStr[pos];
}
pos++;
}
if (pos < jsonStr.length() && jsonStr[pos] == delimiter) {
pos++; // 跳过结束的分隔符
}
return result;
}
std::string ConfigManager::parseJsonValue(const std::string& jsonStr, size_t& pos) {
size_t start = pos;
while (pos < jsonStr.length()) {
char c = jsonStr[pos];
if (c == ',' || c == '}' || c == ']' || std::isspace(c)) {
break;
}
pos++;
}
std::string value = jsonStr.substr(start, pos - start);
return trim(value);
}
std::string ConfigManager::escapeString(const std::string& str) const {
// 简单转义,将换行符等转换为可存储的形式
std::string result;
for (char c : str) {
switch (c) {
case '\n': result += "\\n"; break;
case '\r': result += "\\r"; break;
case '\t': result += "\\t"; break;
case '\\': result += "\\\\"; break;
default: result += c; break;
}
}
return result;
}
std::string ConfigManager::unescapeString(const std::string& str) const {
std::string result;
for (size_t i = 0; i < str.length(); i++) {
if (str[i] == '\\' && i + 1 < str.length()) {
switch (str[i + 1]) {
case 'n': result += '\n'; i++; break;
case 'r': result += '\r'; i++; break;
case 't': result += '\t'; i++; break;
case '\\': result += '\\'; i++; break;
default: result += str[i]; break;
}
}
else {
result += str[i];
}
}
return result;
}
// config.json
{
// ==================== 豆包API配置 ====================
"doubao_api": {
// 【重要】您的API密钥。也可以设置环境变量 ARK_API_KEY 来覆盖此值
"api_key": "your_actual_ark_api_key_here",
// 您的推理接入点ID(从火山方舟控制台获取)
"model_id": "your_actual_ark_model_id_here",
// API端点URL(通常不需要修改)
"api_endpoint": "https://ark.cn-beijing.volces.com/api/v3/responses",
// 请求超时时间(秒)
"request_timeout_seconds": 180,
// 启用详细日志输出(调试用)
"enable_debug_log": false
},
// ==================== 应用程序配置 ====================
"application": {
// 默认分析的文件夹路径
"default_folder": ".",
// 单次请求的最大文件大小(MB),避免发送过大的文件
"max_file_size_mb": 2,
// 仅分析文本文件(true/false)
"analyze_only_text_files": true,
// 允许分析的文件扩展名列表(空数组表示允许所有文件)
"allowed_extensions": [
".cpp",
".h",
".hpp",
".c",
".cc",
".py",
".java",
".js",
".txt",
".md",
".json",
".xml",
".yml",
".yaml"
],
// 文件分析之间的延迟(毫秒),用于避免触发API速率限制
"rate_limit_delay_ms": 1200,
// 输出格式:simple(仅结果)或 detailed(带文件信息)
"output_format": "detailed"
},
// ==================== 分析提示词配置 ====================
"analysis": {
// 系统提示词,用于设定AI的角色和任务
"system_prompt": "你是一个专业的代码和文档分析助手。请分析用户提供的文件内容,并按照以下要求给出分析结果:\n1. 简要总结文件的主要功能或内容。\n2. 如果是代码文件,指出其主要函数/类的用途。\n3. 识别任何明显的潜在问题或可改进之处。\n4. 用中文回复,保持回答简洁明了。",
// 用户提示词模板,{filename} 和 {content} 会被实际值替换
"user_prompt_template": "请分析以下文件:\n文件名:{filename}\n文件内容:\n```\n{content}\n```",
// 是否在提示词中包含文件路径
"include_filepath_in_prompt": false,
// 最大内容截断长度(字符数),0表示不截断
"max_content_length": 8000
},
// ==================== 输出配置 ====================
"output": {
// 结果保存的文件名(空字符串表示不保存到文件)
"result_file": "analysis_results.txt",
// 控制台输出颜色(true/false)
"enable_colors": true,
// 是否在控制台显示进度条
"show_progress": true,
// 日志级别:debug, info, warning, error
"log_level": "info"
},
// ==================== 高级配置 ====================
"advanced": {
// 最大重试次数(当API请求失败时)
"max_retries": 3,
// 重试之间的等待时间(秒)
"retry_delay_seconds": 2,
// 是否使用HTTP代理(空字符串表示不使用)
"http_proxy": "",
// 是否验证SSL证书
"verify_ssl": true,
// 并发分析的文件数(1表示顺序处理)
"concurrent_files": 1
}
}
测试
// main.cpp
#include "ConfigManager.h"
#include "FileProcessor.h"
#include "DoubaoClient.h"
#include <iostream>
#include <vector>
#pragma execution_character_set("utf-8")
int main(int argc, char* argv[]) {
// 1. 加载配置管理器
ConfigManager config;
// 1.1 尝试从文件加载配置
std::string configFile = "config.json";
if (argc > 2 && std::string(argv[1]) == "--config") {
configFile = argv[2];
}
if (!config.loadFromFile(configFile)) {
std::cerr << "警告: 无法从 '" << configFile << "' 加载配置文件,使用默认值或环境变量。" << std::endl;
}
// 1.2 验证必需配置
std::vector<std::string> requiredKeys = { "doubao_api.api_key", "doubao_api.model_id" };
if (!config.validateRequired(requiredKeys)) {
std::cerr << "错误: 缺少必需的API配置。请设置环境变量或编辑配置文件。" << std::endl;
std::cerr << "必需的环境变量: ARK_API_KEY (对应 doubao_api.api_key)" << std::endl;
return 1;
}
// 调试:打印所有配置
if (config.getBool("doubao_api.enable_debug_log", false)) {
config.printAll();
}
// 2. 初始化豆包客户端
DoubaoClient client(
config.getString("doubao_api.api_key"),
config.getString("doubao_api.model_id")
);
// 2.1 设置可选参数
std::string endpoint = config.getString("doubao_api.api_endpoint", "");
if (!endpoint.empty()) {
client.setApiEndpoint(endpoint);
}
client.setRequestTimeout(config.getLong("doubao_api.request_timeout_seconds", 30));
// 3. 初始化文件处理器
FileProcessor fp;
// 4. 确定要分析的文件夹
std::string targetFolder = config.getString("application.default_folder", ".");
if (argc > 1 && std::string(argv[1]) != "--config") {
targetFolder = argv[1]; // 命令行参数优先
}
// 5. 确定文件扩展名过滤器
std::string extensionFilter = "";
auto allowedExtensions = config.getStringArray("application.allowed_extensions");
if (!allowedExtensions.empty() && config.getBool("application.analyze_only_text_files", true)) {
// 这里简化处理:如果配置了允许的扩展名,只处理第一个
// 实际可以根据需要扩展为支持多个扩展名
if (!allowedExtensions.empty()) {
// 可以根据需要扩展为支持多个扩展名
extensionFilter = allowedExtensions[0];
}
}
// 6. 执行分析
std::cout << "=== 文件分析器启动 ===" << std::endl;
std::cout << "目标文件夹: " << targetFolder << std::endl;
std::cout << "模型ID: " << config.getString("doubao_api.model_id") << std::endl;
std::cout << "扩展名过滤: " << (extensionFilter.empty() ? "无" : extensionFilter) << std::endl;
std::cout << "==========================" << std::endl;
auto results = client.analyzeFolderFiles(targetFolder, fp, extensionFilter);
// 7. 输出结果
std::cout << "\n\n=== 分析完成 ===" << std::endl;
std::cout << "共分析 " << results.size() << " 个文件。" << std::endl;
// 8. 保存结果到文件(如果配置了)
std::string resultFile = config.getString("output.result_file", "");
if (!resultFile.empty()) {
std::ofstream outFile(resultFile);
if (outFile.is_open()) {
outFile << "=== 文件分析报告 ===\n";
outFile << "生成时间: " << __DATE__ << " " << __TIME__ << "\n";
outFile << "目标文件夹: " << targetFolder << "\n";
outFile << "=================================\n\n";
for (const auto& item : results) {
const std::string& fileName = item.first;
const std::string& analysis = item.second;
outFile << "文件: " << fileName << "\n";
outFile << "---------------------------------\n";
outFile << analysis << "\n\n";
outFile << "=================================\n\n";
}
outFile.close();
std::cout << "结果已保存到: " << resultFile << std::endl;
}
else {
std::cerr << "警告:无法打开结果文件 " << resultFile << " 进行写入。" << std::endl;
}
// 同时在控制台显示前3个文件的简要结果
std::cout << "\n=== 简要结果预览(前3个文件)===" << std::endl;
int previewCount = 0;
for (const auto& item : results) {
if (previewCount >= 3) break;
const std::string& fileName = item.first;
const std::string& analysis = item.second;
// 截取分析结果的前100个字符作为预览
std::string preview = analysis.substr(0, 100);
if (analysis.length() > 100) preview += "...";
std::cout << previewCount + 1 << ". " << fileName << ":\n ";
std::cout << preview << "\n" << std::endl;
previewCount++;
}
}
else {
std::cout << "没有生成任何分析结果。" << std::endl;
}
return 0;
}


更多推荐



所有评论(0)