C++后端项目:统一大模型接入 SDK(五)

目录

一、开篇

[二、ChatSDK --- 外观模式](#二、ChatSDK — 外观模式)

[2.1 为什么需要外观模式?](#2.1 为什么需要外观模式?)

[2.2 initModels --- 注册 + 初始化](#2.2 initModels — 注册 + 初始化)

[2.3 sendMessage --- 完整消息发送链路](#2.3 sendMessage — 完整消息发送链路)

[三、SessionManager + DataManager --- 会话与持久化](#三、SessionManager + DataManager — 会话与持久化)

[3.1 SessionManager 构造函数](#3.1 SessionManager 构造函数)

[3.2 会话 ID 生成](#3.2 会话 ID 生成)

[3.3 内存 + 数据库双存储](#3.3 内存 + 数据库双存储)

[3.4 DataManager --- SQLite 表结构](#3.4 DataManager — SQLite 表结构)

[四、ChatServer --- HTTP 服务](#四、ChatServer — HTTP 服务)

[4.1 构造函数 --- 初始化 ChatSDK](#4.1 构造函数 — 初始化 ChatSDK)

[4.2 setHttpRoutes --- 7 个 API 路由](#4.2 setHttpRoutes — 7 个 API 路由)

[4.3 处理请求的标准模式](#4.3 处理请求的标准模式)

[4.4 流式响应的 SSE 转发 (*)](#4.4 流式响应的 SSE 转发 (*))

[4.5 start / stop / isRunning](#4.5 start / stop / isRunning)

[五、main.cpp --- 程序入口](#五、main.cpp — 程序入口)

六、系列总结

[6.1 五篇博客覆盖了哪些内容?](#6.1 五篇博客覆盖了哪些内容?)

[6.2 项目中用到的 C++ 特性](#6.2 项目中用到的 C++ 特性)

[6.3 项目在简历怎么写](#6.3 项目在简历怎么写)

一、开篇

前四篇我们把底层的 Provider 一个个实现了,现在它们就像一个个零件散落一地。最后一篇要做的就是把这些零件组装成一个完整的、可运行的 HTTP 服务

这一篇你会看到:

模块 作用
ChatSDK 外观模式------对外提供极简接口,内部协调所有子系统
SessionManager + DataManager 会话管理 + SQLite 持久化
ChatServer 7 个 RESTful API,把 SDK 包装成 HTTP 服务
main.cpp 程序入口,gflags 参数解析 + 信号处理
完整调用链路 从客户端到 AI 厂商再返回的全过程

二、ChatSDK --- 外观模式

2.1 为什么需要外观模式?

后面有 LLMManager(模型管理)、SessionManager(会话管理)、DataManager(数据持久化)。如果每个调用者都要知道这三个类怎么配合,使用成本太高了。

外观模式的做法:提供一个高层接口(ChatSDK),把子系统的复杂协调逻辑封装在内部。

cpp 复制代码
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;   // 会话管理
};

调用者只需要

cpp 复制代码
// 三步搞定
ChatSDK chatSDK;
chatSDK.initModels(configs);                         // 初始化
std::string sessionId = chatSDK.createSession("deepseek-chat");  // 创建会话
std::string reply = chatSDK.sendMessage(sessionId, "你好");     // 发消息

不需要知道:LLMManager 的存在、SessionManager 的存在、DataManager 的存在。

2.2 initModels --- 注册 + 初始化

cpp 复制代码
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 --- 完整消息发送链路

cpp 复制代码
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 构造函数

cpp 复制代码
SessionManager::SessionManager(const std::string& dbName) 
    : _dataManager(dbName) {
    // 从 SQLite 中加载所有已有会话到内存
    auto sessions = _dataManager.getAllSessions();
    for (auto& session : sessions) {
        _sessions[session->_sessionId] = session;
    }
}

3.2 会话 ID 生成

cpp 复制代码
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 内存 + 数据库双存储

cpp 复制代码
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 表结构

cpp 复制代码
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 为例):

cpp 复制代码
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

cpp 复制代码
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 路由

cpp 复制代码
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 为例:

cpp 复制代码
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 客户端

cpp 复制代码
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

cpp 复制代码
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 --- 程序入口

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 多多!

相关推荐
cui_ruicheng4 小时前
Linux网络编程(六):UDP聊天室与线程池
linux·服务器·网络·udp
SimonKing4 小时前
IP定位库的完美替代品:ip2region,开源、免费!
java·后端·程序员
运维自动化&云计算4 小时前
修复chrome把mp4视频识别为音频、firefox不能加载mp4问题
服务器·前端·iis·mp4播放
XiYang-DING4 小时前
【Spring】Lombok
java·后端·spring
ZC跨境爬虫4 小时前
模块化烹饪小程序开发日记 Day5:(后端Flask接口开发与AI智能解析菜谱的实现)
前端·人工智能·后端·python·ui·flask
Yeats_Liao4 小时前
物联网接入层技术剖析(一):从select到epoll
java·linux·后端·物联网·struts
文青小兵4 小时前
云计算Linux——数据库MySQL读写分离、数据库备份、恢复(十八)
linux·运维·服务器·数据库·mysql·云计算
小钻风33664 小时前
Spring Boot WebSocket 两种集成方式深度解析
spring boot·后端·websocket
土星云SaturnCloud4 小时前
土星云AI边缘计算-算法运行环境搭建:Docker部署全流程实操
服务器·人工智能·docker·ai·边缘计算