Moment · C++ 后端社区项目
基于 Drogon 框架,从零搭建用户系统、帖子 CRUD、评论点赞、分页、Redis 缓存、wrk 压测,直到集成本地 AI 聊天助手的完整学习路线。13 篇文档,覆盖从环境搭建到高并发优化的全部关键节点。
WSL2 环境基础
先搭好开发环境,再开始写代码。覆盖从安装 WSL2 到配通完整 C++ 后端开发工具链的全部细节。
零基础教程:在 Windows 10/11 上通过 WSL2 安装 Ubuntu 22.04,配置用户名、密码和基础开发工具链。不需要双系统或传统虚拟机,5 分钟理解 WSL2 的核心优势。
wsl -l -v 验证安装并转换版本wsl --shutdown 等常用管理命令/mnt/c 与 /home 路径差异# 一键安装(管理员 PowerShell)
wsl --install
wsl --set-default-version 2
# 安装指定发行版
wsl --install -d Ubuntu-22.04
# 验证安装
wsl -l -v
# 安装开发工具链
sudo apt update && sudo apt install gcc g++ cmake make git -y
wsl -l -v显示 Ubuntu-22.04 且 VERSION 为 2- 能正常执行
sudo apt update无报错 gcc --version和cmake --version输出版本号- 家目录在
/home/用户名,不在/mnt/c
安装 WSL 扩展,用 3 种方式连接 Ubuntu 环境,为 WSL 安装 C/C++、CMake 等开发扩展,并完成第一个 Hello WSL2 程序的编译运行。覆盖 4 类常见问题的排查方案。
# 方式 1:命令面板(推荐)
Ctrl+Shift+P → WSL: Connect to WSL → 选择发行版
# 方式 2:从终端启动
cd ~/projects && code .
# 方式 3:远程资源管理器
安装 Remote Explorer 扩展 → 右键 Connect to WSL
- VSCode 左下角显示「WSL: Ubuntu-22.04」
- 终端提示符为 Linux 风格
user@DESKTOP:~$ - 编译运行 Hello WSL2 程序成功输出
科普型长文:先解释每个命令和软件包的含义,再一步步从 C++ 小程序走到 CMake 工程骨架,最后安装 Redis、MySQL、Protobuf 等后端依赖。适合作为知识库反复查阅。
# 修复 apt 404(换阿里云镜像)
sudo sed -i 's|http://archive.ubuntu.com/ubuntu|http://mirrors.aliyun.com/ubuntu|g' /etc/apt/sources.list
sudo apt clean && sudo apt update
# CMake 项目骨架
cmake_minimum_required(VERSION 3.16)
project(echo-server)
set(CMAKE_CXX_STANDARD 20)
add_executable(echo-server src/main.cpp)
# 安装后端依赖
sudo apt install -y protobuf-compiler libprotobuf-dev \
mysql-server redis-server libmysqlclient-dev
# 验证
redis-cli ping # → PONG
protoc --version
- 项目在
~/workspace下,不在/mnt/c - CMake 能成功生成 echo-server 可执行文件
redis-cli ping返回 PONG- 理解后续开发路线(先 HTTP → 再数据库 → 再业务 → 最后 IM)
Echo Server 核心路线
从最小 HTTP 服务到完整社区后端,按学习依赖顺序逐步推进。每一步都是上一步的自然延伸。
串联整个 Echo Server 系列的入口页。把 7 个阶段按学习依赖排序,说明每个阶段的定位和前后依赖关系,帮助你建立全局视角。
第一阶段:先把 HTTP 服务跑起来。安装编译依赖,用 Drogon 搭建最小服务,完成 /ping、注册、登录 3 个接口。用 unordered_map 临时保存用户,mutex 保护并发读写。
GET /ping # 连通性测试
POST /api/auth/register # 内存版注册
POST /api/auth/login # 内存版登录
# 核心思路:
# 1. unordered_map<string, User> 保存用户
# 2. mutex 保护注册与登录的并发读写
# 3. makeJsonResponse 统一返回 JSON
curl http://127.0.0.1:8080/ping返回 200- 注册接口返回用户信息,重复注册返回错误
- 登录接口正确验证账号密码并返回 token
第二阶段:把内存临时账号升级为 MySQL 持久化。创建 echo_app 数据库和 echo_user 业务用户,设计 users 表(utf8mb4),让注册和登录接口直接操作 MySQL。
CREATE DATABASE IF NOT EXISTS echo_app
CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'echo_user'@'localhost' IDENTIFIED BY 'Echo123456';
GRANT ALL ON echo_app.* TO 'echo_user'@'localhost';
CREATE TABLE users (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL UNIQUE,
password_hash VARCHAR(255) NOT NULL,
nickname VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_users_username (username)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
- 注册后
SELECT * FROM users能看到新账号 - 重启 MySQL 服务后登录仍然成功
- 密码以 hash 形式存储,不是明文
第三阶段:补齐社区项目核心 CRUD。设计 posts 表,实现 token 鉴权辅助函数,完成发帖、查最新、用户主页、帖子详情和删除自己的帖子 5 个接口。
POST /api/posts/create # 发帖(需 token)
GET /api/posts/latest # 最新帖子列表
GET /api/posts/user # 用户主页帖子
GET /api/posts/detail # 帖子详情
POST /api/posts/delete # 删除自己的帖子(需 token)
# Token 鉴权核心:
std::string auth = req->getHeader("Authorization");
// 解析 "Bearer demo_token_用户ID"
- 无 token 发帖返回 401
- 用户只能删除自己的帖子
- 删除用户后其帖子被级联删除
第四阶段:从「能发帖」升级到「能互动」。建立 comments 和 post_likes 两张表,实现评论、点赞、取消点赞,并用 LEFT JOIN + 子查询让列表直接返回统计数据。
SELECT p.id, u.username, p.content,
COALESCE(lc.like_count, 0) AS like_count,
COALESCE(cc.comment_count, 0) AS comment_count
FROM posts p
JOIN users u ON u.id = p.user_id
LEFT JOIN (
SELECT post_id, COUNT(*) AS like_count
FROM post_likes GROUP BY post_id
) lc ON lc.post_id = p.id
LEFT JOIN (
SELECT post_id, COUNT(*) AS comment_count
FROM comments GROUP BY post_id
) cc ON cc.post_id = p.id
ORDER BY p.id DESC LIMIT ? OFFSET ?;
- 同一用户重复点赞同一帖子不会报错(UNIQUE KEY)
- 取消点赞后 like_count 正确减少
- 帖子列表每条都包含 like_count 和 comment_count
第五阶段:把固定 LIMIT 改造为 page/page_size 参数。实现参数解析兜底函数,统一所有列表接口的分页行为,为后续压测提供稳定的测试对象。
int parsePositiveInt(const std::string& value,
int defaultVal, int maxVal) {
try {
int n = std::stoi(value);
if (n <= 0) return defaultVal;
if (n > maxVal) return maxVal;
return n;
} catch (...) { return defaultVal; }
}
int page = parsePositiveInt(req->getParameter("page"), 1, 100000);
int pageSize = parsePositiveInt(req->getParameter("page_size"), 20, 100);
int offset = (page - 1) * pageSize;
- page=-1 或 page_size=9999 不崩溃,回退默认值
- 第 1 页和第 2 页数据不重叠
- 所有列表接口分页行为一致
进阶优化与 AI 扩展
从普通 CRUD 升级为有高并发意识的后端项目,最后接入本地 AI 大模型,为项目增加差异化亮点。
第六阶段:不是盲目加 Redis,而是先压测、再分析 SQL、再消除 N+1、最后对热点接口做缓存和限流。用 wrk 用数据说话,体现高并发意识。
# wrk 压测(4线程 100并发 30秒)
wrk -t4 -c100 -d30s -L "http://127.0.0.1:8080/api/posts/latest?page=1&page_size=20"
# Redis 缓存 key 设计
user:profile:{user_id} → 用户资料
post:detail:{post_id} → 帖子详情
rate:ip:{ip}:{minute} → IP 限流计数
rate:user:{user_id}:{minute} → 用户限流计数
# 读帖子详情流程:
# 1. GET Redis → 命中直接返回
# 2. 未命中 → 查 MySQL → SETEX 60s
- EXPLAIN 显示索引命中,type 不为 ALL
- 加缓存后帖子详情 QPS 显著提升
- 超出限流阈值返回 429 状态码
深入剖析 N+1 级联查库的致命成因,将循环同步查库重构为集合级 SQL 聚合操作。用户主页 4 合 1 多重聚合查询把 4 次网络往返压缩为 1 次。
SELECT
(SELECT COUNT(*) FROM posts WHERE user_id = ?) AS post_count,
(SELECT COUNT(*) FROM posts WHERE user_id = ?
AND image_url IS NOT NULL) AS media_count,
(SELECT COUNT(*) FROM user_follows
WHERE following_id = ?) AS follower_count,
(SELECT COUNT(*) FROM user_follows
WHERE follower_id = ?) AS following_count;
-- 4 次网络往返 → 1 次,QPS 直接翻倍
- 用户主页接口 P99 延迟降到 500ms 以下
- 帖子列表每条数据的 like_count/comment_count 正确
- SQL EXPLAIN 无 filesort 和全表扫描
第七阶段:在本地 WSL 部署 llama.cpp 推理服务,下载 Qwen2.5-1.5B 量化模型,让 Drogon 后端通过 HTTP 调用本地大模型,并实现多轮会话和 SSE 流式输出。
# 1. 编译 llama.cpp
cd ~/workspace/echo-ai
cmake -B build
cmake --build build -j2 --target llama-server llama-cli
# 2. 启动本地推理服务(端口 18080)
./build/bin/llama-server \
-m ./models/qwen2.5-1.5b/qwen2.5-1.5b-instruct-q4_k_m.gguf \
--host 127.0.0.1 --port 18080 -c 2048
# 3. Drogon 连接本地 AI
g_aiClient = drogon::HttpClient::newHttpClient(
"http://127.0.0.1:18080");
- llama-server 监听 18080 并正常响应
- 多轮对话能记住上下文
- 前端逐字显示 AI 回复(打字机效果)
- 会话历史保存到 MySQL 并可回看
深度补充文档:详细记录 llama.cpp 源码编译过程、Qwen 模型量化部署、MySQL 多轮对话表结构设计,以及 Drogon 异步流转发(AsyncStreamResponse)的实现细节。
auto streamResp = HttpResponse::newAsyncStreamResponse(
[](ResponseStreamPtr stream) {
// 逐行转发 llama.cpp 的文本增量
stream->send("data: {\"content\":\"片段\"}\n\n");
// 收到 [DONE] 后断开
stream->close();
}
);
callback(streamResp);
- 前端收到 SSE 流并逐字渲染
- 长文本生成过程不阻塞主线程
- 会话和消息正确持久化到 MySQL