C++后端项目:统一大模型接入 SDK(五)
目录
三、SessionManager + DataManager — 会话与持久化
4.2 setHttpRoutes — 7 个 API 路由
一、开篇
前四篇我们把底层的 Provider 一个个实现了,现在它们就像一个个零件散落一地。最后一篇要做的就是把这些零件组装成一个完整的、可运行的 HTTP 服务。
这一篇你会看到:
| 模块 | 作用 |
|---|---|
| ChatSDK | 外观模式——对外提供极简接口,内部协调所有子系统 |
| SessionManager + DataManager | 会话管理 + SQLite 持久化 |
| ChatServer | 7 个 RESTful API,把 SDK 包装成 HTTP 服务 |
| main.cpp | 程序入口,gflags 参数解析 + 信号处理 |
| 完整调用链路 | 从客户端到 AI 厂商再返回的全过程 |
二、ChatSDK — 外观模式
2.1 为什么需要外观模式?
后面有 LLMManager(模型管理)、SessionManager(会话管理)、DataManager(数据持久化)。如果每个调用者都要知道这三个类怎么配合,使用成本太高了。
外观模式的做法:提供一个高层接口(ChatSDK),把子系统的复杂协调逻辑封装在内部。
class ChatSDK {
public:
// 对外接口(一共 7 个)
bool initModels(const std::vector<std::shared_ptr<Config>>& configs);
std::string createSession(const std::string& modelName);
std::shared_ptr<Session> getSession(const std::string& sessionId);
std::vector<std::string> getSessionLists() const;
bool deleteSession(const std::string& sessionId);
std::vector<ModelInfo> getAvailableModels() const;
std::string sendMessage(const std::string& sessionId, const std::string& message);
std::string sendMessageStream(const std::string& sessionId,
const std::string& message,
std::function<void(const std::string&, bool)> callback);
private:
bool _initialized = false;
std::unordered_map<std::string, std::shared_ptr<Config>> _modelConfigs;
LLMManager _llmManager; // 模型管理
public: // 注意:为了 ChatServer 能访问,故意放在 public
SessionManager _sessionManager; // 会话管理
};
调用者只需要:
// 三步搞定
ChatSDK chatSDK;
chatSDK.initModels(configs); // 初始化
std::string sessionId = chatSDK.createSession("deepseek-chat"); // 创建会话
std::string reply = chatSDK.sendMessage(sessionId, "你好"); // 发消息
不需要知道:LLMManager 的存在、SessionManager 的存在、DataManager 的存在。
2.2 initModels — 注册 + 初始化
bool ChatSDK::initModels(const std::vector<std::shared_ptr<Config>>& configs) {
registerAllProvider(configs); // 第1步:创建 Provider 对象
initProviders(configs); // 第2步:注入 API Key 等配置
_initialized = true;
return true;
}
dynamic_pointer_cast 是 C++ 运行时类型识别(RTTI)的实现手段。因为 Config 有虚析构函数(第二篇讲过),编译器会为它生成虚函数表,RTTI 才能工作。
2.3 sendMessage — 完整消息发送链路
std::string ChatSDK::sendMessage(const std::string& sessionId,
const std::string& message) {
// 1. 检查初始化状态
if (!_initialized) return "";
// 2. 获取会话对象
auto session = _sessionManager.getSession(sessionId);
if (!session) return "";
// 3. 保存用户消息(到内存 + SQLite)
Message userMessage("user", message);
_sessionManager.addMessage(sessionId, userMessage);
// 4. 获取完整历史消息(多轮对话上下文)
auto historyMessages = _sessionManager.getHistroyMessages(sessionId);
// 5. 构造请求参数(从模型配置中拿 temperature 和 max_tokens)
auto configIt = _modelConfigs.find(session->_modelName);
std::map<std::string, std::string> requestParam;
requestParam["temperature"] = std::to_string(configIt->second->_temperature);
requestParam["max_tokens"] = std::to_string(configIt->second->_maxTokens);
// 6. 调用 LLMManager → 多态到具体的 Provider
auto response = _llmManager.sendMessage(session->_modelName,
historyMessages, requestParam);
if (response.empty()) return "";
// 7. 保存 AI 回复
Message assistantMessage("assistant", response);
_sessionManager.addMessage(sessionId, assistantMessage);
_sessionManager.updateSessionTimestamp(sessionId);
return response;
}
第 6 步发生了多态调用:_llmManager.sendMessage("deepseek-chat", ...) 内部找到 DeepSeekProvider 对象,调用它的 sendMessage。调用者不需要知道细节。
三、SessionManager + DataManager — 会话与持久化
3.1 SessionManager 构造函数
SessionManager::SessionManager(const std::string& dbName)
: _dataManager(dbName) {
// 从 SQLite 中加载所有已有会话到内存
auto sessions = _dataManager.getAllSessions();
for (auto& session : sessions) {
_sessions[session->_sessionId] = session;
}
}
3.2 会话 ID 生成
std::string SessionManager::generateSessionId() {
_sessionCounter.fetch_add(1); // 原子自增
std::time_t time = std::time(nullptr);
std::ostringstream os;
os << "session_" << time << "_"
<< std::setw(8) << std::setfill('0') << _sessionCounter;
return os.str(); // 如 "session_1758016244_00000001"
}
格式:session_时间戳_8位序号
为什么这么设计?
- 时间戳保证全局唯一(同一毫秒也不重复)
- 序号方便人类阅读
- 前缀一眼识别是会话 ID
3.3 内存 + 数据库双存储
bool SessionManager::addMessage(const std::string& sessionId, const Message& message) {
// 1. 写入内存(即时生效,速度快)
_mutex.lock();
auto it = _sessions.find(sessionId);
if (it == _sessions.end()) { _mutex.unlock(); return false; }
Message msg(message._role, message._content);
msg._messageId = generateMessageId(it->second->_messages.size());
msg._timestamp = std::time(nullptr);
it->second->_messages.push_back(msg);
it->second->_updatedAt = std::time(nullptr);
_mutex.unlock();
// 2. 写入数据库(持久化,重启不丢)
_dataManager.insertMessage(sessionId, msg);
return true;
}
双存储的好处:
| 存储 | 优点 | 缺点 |
|---|---|---|
内存(unordered_map) |
读写快,微秒级 | 进程退出就没了 |
| SQLite | 持久化,重启后数据还在 | 写磁盘慢一点 |
策略:每条数据同时写内存和数据库,兼顾速度和持久性。
3.4 DataManager — SQLite 表结构
bool DataManager::initDataBase() {
// 会话表
std::string createSessionTable = R"(
CREATE TABLE IF NOT EXISTS sessions (
session_id TEXT PRIMARY KEY,
model_name TEXT NOT NULL,
create_time INTEGER NOT NULL,
update_time INTEGER NOT NULL
);
)";
executeSQL(createSessionTable);
// 消息表(外键关联会话)
std::string createMessageTable = R"(
CREATE TABLE IF NOT EXISTS messages (
message_id TEXT PRIMARY KEY,
session_id TEXT NOT NULL,
role TEXT NOT NULL,
content TEXT NOT NULL,
timestamp INTEGER NOT NULL,
FOREIGN KEY (session_id) REFERENCES sessions(session_id)
ON DELETE CASCADE
);
)";
executeSQL(createMessageTable);
}
SQLite 操作的标准流程(以 insertSession 为例):
bool DataManager::insertSession(const Session& session) {
std::lock_guard<std::mutex> lock(_mutex);
// 1. 准备 SQL 语句(用 ? 做参数占位符,防 SQL 注入)
sqlite3_stmt* stmt;
sqlite3_prepare_v2(_db,
"INSERT INTO sessions VALUES (?, ?, ?, ?);",
-1, &stmt, nullptr);
// 2. 绑定参数
sqlite3_bind_text(stmt, 1, session._sessionId.c_str(), -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 2, session._modelName.c_str(), -1, SQLITE_TRANSIENT);
sqlite3_bind_int64(stmt, 3, static_cast<int64_t>(session._createdAt));
sqlite3_bind_int64(stmt, 4, static_cast<int64_t>(session._updatedAt));
// 3. 执行
sqlite3_step(stmt);
// 4. 释放
sqlite3_finalize(stmt);
}
参数绑定 prevent SQL 注入:用 sqlite3_bind_text 而不是手拼 SQL 字符串。
如果手拼 "INSERT INTO sessions VALUES ('" + userInput + "')",用户输入里带个单引号就能搞破坏。
四、ChatServer — HTTP 服务
4.1 构造函数 — 初始化 ChatSDK
ChatServer::ChatServer(const ServerConfig& config) {
_chatSDK = std::make_shared<ai_chat_sdk::ChatSDK>();
// 配置4个模型
auto deepseekConfig = std::make_shared<ai_chat_sdk::APIConfig>();
deepseekConfig->_modelName = "deepseek-chat";
deepseekConfig->_apiKey = config.deepseekAPIKey;
deepseekConfig->_temperature = config.temperature;
deepseekConfig->_maxTokens = config.maxTokens;
// ... gpt-4o-mini、gemini-2.0-flash、ollama 同理 ...
std::vector<std::shared_ptr<ai_chat_sdk::Config>> modelConfigs = {
deepseekConfig, chatGPTConfig, geminiConfig, ollamaConfig
};
// 初始化所有模型
if (!_chatSDK->initModels(modelConfigs)) {
ERR("ChatSDK init Failed!!!");
return;
}
// 创建 HTTP 服务器
_chatServer = std::make_unique<httplib::Server>();
}
4.2 setHttpRoutes — 7 个 API 路由
void ChatServer::setHttpRoutes() {
// 创建会话
_chatServer->Post("/api/session", [this](const Request& req, Response& res) {
handleCreateSessionRequest(req, res);
});
// 获取会话列表
_chatServer->Get("/api/sessions", [this](const Request& req, Response& res) {
handleGetSessionListsRequest(req, res);
});
// 获取可用模型列表
_chatServer->Get("/api/models", [this](const Request& req, Response& res) {
handleGetModelListsRequest(req, res);
});
// 删除会话(路径参数:(.*) 匹配 session_id)
_chatServer->Delete("/api/session/(.*)", [this](const Request& req, Response& res) {
handleDeleteSessionRequest(req, res);
});
// 获取会话历史消息
_chatServer->Get("/api/session/(.*)/history", [this](const Request& req, Response& res) {
handleGetHistoryMessagesRequest(req, res);
});
// 发送消息(全量返回)
_chatServer->Post("/api/message", [this](const Request& req, Response& res) {
handleSendMessageRequest(req, res);
});
// 发送消息(流式返回)
_chatServer->Post("/api/message/async", [this](const Request& req, Response& res) {
handleSendMessageStreamRequest(req, res);
});
}
接口对照表:
| 方法 | 路径 | 功能 | 响应格式 |
|---|---|---|---|
| GET | /api/models | 获取可用模型列表 | JSON |
| POST | /api/session | 创建新会话 | JSON |
| GET | /api/sessions | 获取所有会话 | JSON |
| DELETE | /api/session/{id} | 删除指定会话 | JSON |
| GET | /api/session/{id}/history | 获取会话历史消息 | JSON |
| POST | /api/message | 发送消息(全量) | JSON |
| POST | /api/message/async | 发送消息(流式) | SSE |
4.3 处理请求的标准模式
以 handleCreateSessionRequest 为例:
void ChatServer::handleCreateSessionRequest(const Request& req, Response& res) {
// 1. 反序列化请求体 JSON
Json::Value requestJson;
Json::Reader reader;
if (!reader.parse(req.body, requestJson)) {
res.status = 400;
res.set_content(buildResponse("invalid json"), "application/json");
return;
}
// 2. 提取参数
std::string modelName = requestJson.get("model", "deepseek-chat").asString();
// 3. 调用 ChatSDK
std::string sessionId = _chatSDK->createSession(modelName);
if (sessionId.empty()) {
res.status = 500;
res.set_content(buildResponse("create session failed"), "application/json");
return;
}
// 4. 构造响应 JSON
Json::Value dataJson;
dataJson["session_id"] = sessionId;
dataJson["model"] = modelName;
Json::Value responseJson;
responseJson["success"] = true;
responseJson["message"] = "create session success";
responseJson["data"] = dataJson;
Json::StreamWriterBuilder writer;
res.status = 200;
res.set_content(Json::writeString(writer, responseJson), "application/json");
}
每个 handler 都遵循这个模式:JSON反序列化 → 参数校验 → 调用 ChatSDK → 构造 JSON 响应。
4.4 流式响应的 SSE 转发 (*)
这是把"AI 厂商到 DeepSeekProvider 的流"进一步转发给 HTTP 客户端。
void ChatServer::handleSendMessageStreamRequest(const Request& req, Response& res) {
// ... 解析 sessionId 和 message(同上) ...
// 1. 设置 SSE 响应头
res.status = 200;
res.set_header("Cache-Control", "no-cache"); // 不要缓存
res.set_header("Connection", "keep-alive"); // 保持连接
res.set_header("Access-Control-Allow-Origin", "*"); // 允许跨域
// 2. 使用 chunked content provider 做流式推送
res.set_chunked_content_provider("text/event-stream",
[this, sessionId, message](size_t offset, httplib::DataSink& sink) -> bool {
// 定义写回调:将 AI 回复的一段文本包装成 SSE 格式
auto writeChunk = [&](const std::string& chunk, bool last) {
// SSE 格式:data: "内容"\n\n
// Json::valueToQuotedString 转义特殊字符(如换行)
std::string sseData = "data: " +
Json::valueToQuotedString(chunk.c_str()) + "\n\n";
sink.write(sseData.data(), sseData.size());
if (last) {
std::string doneData = "data: [DONE]\n\n";
sink.write(doneData.data(), doneData.size());
sink.done(); // 通知 httplib 流结束
return false;
}
return true;
};
// 先发一个空块,防止客户端挂起
writeChunk("", false);
// 调用 ChatSDK 的流式接口
// 内部会调 DeepSeekProvider::sendMessageStream
// DeepSeekProvider 每收到一块数据 → 回调 writeChunk → 转发给客户端
_chatSDK->sendMessageStream(sessionId, message, writeChunk);
return false; // 告诉 httplib 没有更多数据了
});
}
两层嵌套的流式回调:
AI 服务器(DeepSeek)
↓ SSE 数据块
DeepSeekProvider::content_receiver ← 解析 SSE,提取 delta.content
↓ callback(content, false)
ChatSDK::sendMessageStream
↓ writeChunk lambda
ChatServer::handleSendMessageStream ← 包装成 SSE 格式
↓ sink.write(sseData)
HTTP 客户端(浏览器/App)
↓ 解析 SSE
用户看到"字一个一个蹦出来"
4.5 start / stop / isRunning
bool ChatServer::start() {
if (_isRunning.load()) return false;
setHttpRoutes(); // 注册路由
_chatServer->set_mount_point("/", "./www"); // 静态页面
// HTTP 服务在独立线程运行,不阻塞主线程
std::thread serverThread([this]() {
_chatServer->listen(_config.host, _config.port);
});
serverThread.detach();
_isRunning.store(true);
return true;
}
void ChatServer::stop() {
if (!_isRunning.load()) return;
if (_chatServer) _chatServer->stop();
_isRunning.store(false);
}
五、main.cpp — 程序入口
// gflags 命令行参数
DEFINE_string(host, "0.0.0.0", "服务器绑定的地址");
DEFINE_int32(port, 8080, "服务器绑定的端口号");
DEFINE_string(log_level, "INFO", "日志级别");
DEFINE_double(temperature, 0.7, "温度值");
DEFINE_int32(max_tokens, 2048, "最大token数");
DEFINE_string(config_file, "./ChatServer.conf", "配置文件路径");
int main(int argc, char** argv) {
// 1. 解析命令行参数
gflags::ParseCommandLineFlags(&argc, &argv, true);
// 2. 从环境变量读取 API Key(安全!不硬编码在代码里)
config.deepseekAPIKey = getEnvVar("deepseek_apikey");
config.chatGPTAPIKey = getEnvVar("chatgpt_apikey");
config.geminiAPIKey = getEnvVar("gemini_apikey_new");
// 3. 初始化日志
bite::Logger::initLogger("ChatServer", "stdout", logLevel);
// 4. 创建并启动 ChatServer
ChatServer server(config);
server.start();
// 5. 主线程等待(后台线程跑 HTTP 服务)
while (server.isRunning()) {
std::this_thread::sleep_for(std::chrono::seconds(100));
}
}
关键设计:API Key 从环境变量读取,不硬编码在代码里。这样代码可以公开(放 GitHub/Gitee),不会泄露密钥。
六、系列总结
6.1 五篇博客覆盖了哪些内容?
| 篇 | 标题 | 核心知识点 |
|---|---|---|
| 1 | 项目概览 + AI科普 | LLM/Token/Prompt、三大模型、项目架构 |
| 2 | 数据结构 + 日志 | 虚析构、RTTI、双重检查锁定、spdlog异步 |
| 3 | 策略模式 | 开闭原则、纯虚函数、多态、unique_ptr所有权 |
| 4 | Provider + SSE | HTTP请求8步骤、SSE协议、content_receiver缓冲区 |
| 5 | HTTP Server + 数据流 | 外观模式、SQLite持久化、RESTful API、完整链路 |
6.2 项目中用到的 C++ 特性
| 特性 | 使用场景 |
|---|---|
| C++17 | 整个项目 |
| 虚函数/纯虚函数 | LLMProvider 抽象基类 |
| 智能指针 | unique_ptr 管理 Provider,shared_ptr 管理 Config/Session |
| 移动语义 | std::move 转移 Provider 所有权 |
| lambda | content_receiver 回调、writeChunk 回调 |
| std::function | sendMessageStream 的回调参数 |
| dynamic_cast | dynamic_pointer_cast 运行时类型判断 |
| 原子操作 | atomic 控制运行状态,atomic<int64_t> 计数器 |
| 互斥锁 | mutex + lock_guard 保护共享数据 |
| 正则匹配 | 路由路径参数 /(.*) |
6.3 项目在简历怎么写
AI 大模型接入 SDK
• 基于 C++17 设计统一大模型接入 SDK,封装 DeepSeek/ChatGPT/Gemini 三家 API
• 采用策略模式 + 抽象基类 LLMProvider,符合开闭原则,新增模型无需修改现有代码
• 实现 SSE 流式传输,基于 cpp-httplib 的 content_receiver 实现实时逐字输出
• 封装 spdlog 日志系统 + gtest 单元测试 + gflags 命令行参数
• 基于 cpp-httplib 搭建 RESTful HTTP 服务,提供 7 个 API 接口
• 使用 SQLite3 持久化会话与消息,支持程序重启后数据恢复
五篇完结,感谢各位阅读! 如若这个系列对你有帮助,欢迎点赞收藏关注。有问题欢迎私信,看到就回。
祝来到这里的兄弟秋招顺利,offer 多多!
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)