Boost.Beast 学习课程:HTTP 与 WebSocket

课程导航:学习路径 | Boost.System | Boost.Asio | Boost.Beast | Boost.JSON | Boost.MySQL 前置知识 课程 1: Boost.System(error_code、system_error) 课程 2: Boost.Asio(io_context、协程、TCP socket) HTTP 协议基础(请求/响应格式、状态码、头部) 学习目标 完成本课程后,你将能够: 理解 Beast 的 HTTP message 模型和 Body 类型系统 使用 Parser 安全解析 HTTP 请求(含 body_limit 保护) 编写协程式 HTTP 服务端和客户端 实现 WebSocket 升级和消息循环 读懂 Hical 的 HttpServer、HttpRequest/Response 封装和 WebSocket 集成 目录 前置知识 学习目标 目录 1. 核心概念 1.1 Beast 的定位 1.2 HTTP message 模型 1.3 Buffer 体系 1.4 Parser 与安全限制 2. 基础用法 2.1 构建 HTTP 请求和响应 2.2 协程式 HTTP 服务端 2.3 Parser 高级用法 3. 进阶主题 3.1 WebSocket 3.2 自定义 Body 类型 3.3 超时机制 4. Hical 实战解读 4.1 handleSession:完整 HTTP 处理循环 4.2 HttpRequest/Response 封装 4.3 WebSocketSession 封装 4.4 handleWebSocket:升级与消息循环 4.5 错误处理模式 5. 练习题 练习 1:基础 HTTP 服务端 练习 2:body_limit 保护 练习 3:WebSocket Echo Server 练习 4:Keep-Alive 练习 5(挑战):静态文件服务器 参考答案 练习 1 参考答案:基础 HTTP 服务端 练习 2 参考答案:body_limit 保护 练习 3 参考答案:WebSocket Echo Server 练习 4 参考答案:Keep-Alive 练习 5 参考答案:静态文件服务器 6. 总结与拓展阅读 Beast 核心 API 速查表 HTTP 请求处理数据流 拓展阅读 下一步 1. 核心概念 1.1 Beast 的定位 Beast 是协议实现库,不是 Web 框架。它在 Asio 之上添加 HTTP/WebSocket 协议的解析和序列化,但不提供路由、中间件等应用层功能(这些由 Hical 提供)。 ...

April 15, 2026 · 19 min · 3882 words

Boost.System 学习课程:错误处理基石

课程导航:学习路径 | Boost.System | Boost.Asio | Boost.Beast | Boost.JSON | Boost.MySQL 前置知识 C++ 基础(类、模板、异常处理) 了解操作系统错误码概念(errno、GetLastError) 学习目标 完成本课程后,你将能够: 理解 error_code + error_category 的设计原理 掌握 I/O 操作中 错误码 和 异常 两种错误处理模式 编写自定义 error_category 读懂 Hical 的跨平台错误码映射层 目录 前置知识 学习目标 目录 1. 核心概念 1.1 为什么需要统一的错误码体系 1.2 error_code 三要素 1.3 error_category 体系 1.4 error_condition vs error_code 2. 基础用法 2.1 创建和检查 error_code 2.2 两种错误处理模式 2.3 常见错误码速查表 3. 进阶主题 3.1 自定义 error_category 3.2 跨平台错误码映射 4. Hical 实战解读 4.1 Error.h:框架级错误码枚举 4.2 Error.cpp:fromBoostError 跨平台映射 4.3 错误码在连接管理中的使用 4.4 设计模式总结 5. 练习题 练习 1:error_code 基础 练习 2:自定义 error_category 练习 3:阅读源码 参考答案 练习 1 参考答案 练习 2 参考答案 练习 3 参考答案 6. 总结与拓展阅读 核心要点 拓展阅读 下一步 1. 核心概念 1.1 为什么需要统一的错误码体系 C 语言 errno 的问题: ...

April 15, 2026 · 12 min · 2361 words

Hical 框架应用场景全景分析

Hical 是一个现代 C++ 高性能 Web 框架,基于 Boost.Asio/Beast,具备 PMR 内存池、协程异步 I/O、WebSocket、SSL/TLS、C++26 反射层等特性。本文覆盖游戏行业 + 通用行业的全部适用场景。 一、游戏行业场景(简要) 场景 说明 GM/运营后台 REST API + 静态页面 + Session 鉴权 支付/SDK 回调网关 高并发 HTTP 接入,协程非阻塞 WebSocket 实时服务 聊天、排行榜推送、GM 监控 内部微服务 HTTP 层 MetaJson/MetaRoutes 零样板代码 二、通用行业场景 1. IoT / 嵌入式设备管理后台 ⭐ 高匹配度 为什么适合: IoT 网关和嵌入式设备普遍用 C/C++ 开发,Hical 保持语言一致性 单二进制部署,无运行时依赖,适合资源受限环境(树莓派、工控机) PMR 内存池提供可预测的内存行为,避免 GC 抖动 WebSocket 双向通信天然适合设备状态实时推送 典型用例: 设备状态监控 Dashboard(HTTP + WebSocket 推送) 固件 OTA 更新接口(Multipart 文件上传) 传感器数据采集 HTTP 接口 边缘计算节点的本地 Web 管理界面 2. 高频交易 / 量化金融辅助服务 为什么适合: ...

April 13, 2026 · 2 min · 359 words

深入学习 C++26 静态反射(Static Reflection)

深入学习 C++26 静态反射(Static Reflection) 提案:P2996R9(Reflection for C++26) 头文件:<meta> 命名空间:std::meta 编译器支持:Clang(P2996 实验分支)、EDG(部分)/ GCC 和 MSVC 计划中 注意:C++26 标准预计 2026 年底定稿,本文语法基于当前最新提案,最终可能有微调 一、为什么需要静态反射? 1.1 C++ 元编程的历史痛点 C++ 一直以"零开销抽象"著称,但在类型自省这件事上,四十年来只能靠旁门左道: 方案 A:宏暴力展开 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 // 用宏定义可序列化结构体 #define DEFINE_FIELDS(TYPE, ...) \ static constexpr auto fields() { \ return std::make_tuple(__VA_ARGS__); \ } struct Player { uint64_t id; std::string name; int level; int64_t gold; DEFINE_FIELDS(Player, FIELD(id), FIELD(name), FIELD(level), FIELD(gold) // 手动列举每个字段 ) }; 方案 B:代码生成工具(protobuf / flatbuffers / 自研工具) ...

April 9, 2026 · 27 min · 5613 words

深入学习 C++20 协程(Coroutines)

深入学习 C++20 协程(Coroutines) 头文件:<coroutine> 命名空间:std 编译器要求:GCC 11+ / Clang 14+ / MSVC 19.28+(均需 -std=c++20 或以上) 注意:GCC 10 / Clang 8~13 可通过 -fcoroutines 和 <experimental/coroutine> 使用实验性支持 一、为什么需要协程? 1.1 异步编程的传统痛点 游戏服务器中充斥着异步操作——数据库查询、网络 I/O、定时器回调。传统方案各有各的痛: 方案 A:回调地狱(Callback Hell) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 void HandleLogin(Connection* conn, const LoginPacket& pkt) { // 第1步:查询数据库验证账号 dbManager->QueryAsync("SELECT * FROM accounts WHERE name=?", pkt.name, [conn, pkt](const DBResult& result) { if (!result.ok) { conn->SendError("DB错误"); return; } // 第2步:查询角色列表 dbManager->QueryAsync("SELECT * FROM characters WHERE account_id=?", result.accountId, [conn](const DBResult& charResult) { if (!charResult.ok) { conn->SendError("DB错误"); return; } // 第3步:加载角色数据 dbManager->QueryAsync("SELECT * FROM inventory WHERE char_id=?", charResult.charId, [conn, charResult](const DBResult& invResult) { // 第4步:终于可以发送登录成功了... conn->SendLoginSuccess(charResult, invResult); }); }); }); } 方案 B:状态机(State Machine) ...

April 8, 2026 · 22 min · 4611 words

深入学习 C++17 PMR(Polymorphic Memory Resource)

深入学习 C++17 PMR(Polymorphic Memory Resource) 头文件:<memory_resource> 命名空间:std::pmr 编译器要求:GCC 9+ / Clang 9+ / MSVC 19.13+(均需 -std=c++17 或以上) 一、为什么需要 PMR? 1.1 传统 Allocator 模型的痛点 C++98 引入的 Allocator 是模板参数,这意味着: 1 2 3 4 std::vector<int, MyAlloc<int>> vec1; std::vector<int, std::allocator<int>> vec2; // vec1 和 vec2 是不同类型!无法互相赋值、放进同一个容器 核心问题: 痛点 说明 类型传染 Allocator 是模板参数,换一个 Allocator 就变了类型,所有接口签名都要跟着改 无法运行时切换 编译期绑定,测试时想换成 debug allocator?重新编译 难以组合 想让 vector 内部的 string 也用同一个 arena?极其繁琐 状态传播困难 有状态 allocator(如持有内存池指针)在容器拷贝/移动时语义复杂 1.2 PMR 的解法:运行时多态 PMR 用一个虚基类 std::pmr::memory_resource 取代模板参数,容器统一使用 std::pmr::polymorphic_allocator<T>: ...

April 6, 2026 · 12 min · 2482 words

深入学习 io_uring(三):C++ 封装、协程集成与高性能架构

系列导航:入门篇 | 进阶篇 | 实战篇 前置知识 已阅读入门篇和进阶篇,掌握 io_uring 双环形缓冲区和 liburing API 了解 C++20 协程基础(co_await、coroutine_handle、promise_type) 建议先阅读 深入学习 Boost.Asio(三):实战篇 中的协程部分作为对照 1. RAII 封装:安全管理 io_uring 资源 1.1 为什么需要 C++ 封装 直接使用 liburing 的 C API 有三个痛点: io_uring_queue_init / io_uring_queue_exit 手动配对,容易遗漏 user_data 是 void* 或 uint64_t,类型安全全靠人肉 提交→收割的事件循环代码高度模板化,每个项目重写一遍 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 封装层次: 应用代码(协程/回调) │ ▼ ┌──────────────────────┐ │ IoUringAwaitable │ ← 协程集成层(co_await 一个 I/O 操作) └──────────────────────┘ │ ▼ ┌──────────────────────┐ │ IoUringContext │ ← 事件循环层(submit / wait / dispatch) └──────────────────────┘ │ ▼ ┌──────────────────────┐ │ IoUring (RAII) │ ← 资源管理层(init / exit) └──────────────────────┘ │ ▼ liburing C API 1.2 IoUring:RAII 包装 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 // IoUring.hpp — RAII 封装 io_uring 实例 // 编译:g++ -std=c++20 -O2 xxx.cpp -luring -o xxx #pragma once #include <liburing.h> #include <stdexcept> #include <string> #include <cstring> class IoUring { public: // 构造时初始化 io_uring,指定 SQ 大小和可选标志 explicit IoUring(unsigned entries, unsigned flags = 0) { int ret = io_uring_queue_init(entries, &ring_, flags); if (ret < 0) { throw std::runtime_error( "io_uring_queue_init 失败: " + std::string(strerror(-ret))); } } // 禁止拷贝(io_uring 资源不可共享) IoUring(const IoUring&) = delete; IoUring& operator=(const IoUring&) = delete; // 允许移动 IoUring(IoUring&& other) noexcept : ring_(other.ring_) { other.moved_ = true; } // 析构时自动清理 ~IoUring() { if (!moved_) { io_uring_queue_exit(&ring_); } } // 获取 SQE(SQ 满时自动 submit 腾出空间) io_uring_sqe* getSqe() { io_uring_sqe* sqe = io_uring_get_sqe(&ring_); if (!sqe) { // SQ 满了,先提交当前积压的请求 io_uring_submit(&ring_); sqe = io_uring_get_sqe(&ring_); if (!sqe) { throw std::runtime_error("SQ 空间不足,即使 submit 后仍无法获取 SQE"); } } return sqe; } int submit() { return io_uring_submit(&ring_); } // 阻塞等待至少一个 CQE io_uring_cqe* waitCqe() { io_uring_cqe* cqe = nullptr; int ret = io_uring_wait_cqe(&ring_, &cqe); if (ret < 0) { throw std::runtime_error( "io_uring_wait_cqe 失败: " + std::string(strerror(-ret))); } return cqe; } // 非阻塞查看 CQE io_uring_cqe* peekCqe() { io_uring_cqe* cqe = nullptr; int ret = io_uring_peek_cqe(&ring_, &cqe); if (ret == -EAGAIN) return nullptr; // 无就绪 CQE if (ret < 0) { throw std::runtime_error( "io_uring_peek_cqe 失败: " + std::string(strerror(-ret))); } return cqe; } // 标记 CQE 已消费 void seenCqe(io_uring_cqe* cqe) { io_uring_cqe_seen(&ring_, cqe); } // 访问底层 io_uring(高级用法需要) io_uring* raw() { return &ring_; } private: io_uring ring_{}; bool moved_ = false; }; 设计原则:RAII 保证 io_uring_queue_exit 一定被调用,即使异常传播也不会泄漏内核资源。getSqe() 中自动 submit 是防御性编程——避免 SQ 满导致的隐性 bug。 ...

October 3, 2025 · 14 min · 2887 words

深入学习 io_uring(二):高级特性与 TCP 网络编程

系列导航:入门篇 | 进阶篇 | 实战篇 前置知识 已阅读入门篇,理解 SQ/CQ 双环形缓冲区和 liburing 基本 API 熟悉 TCP socket 编程基础(socket、bind、listen、accept) 1. SQPOLL:零系统调用提交 1.1 常规模式的瓶颈 入门篇中每次 io_uring_submit() 底层都会调用 io_uring_enter() 系统调用: 1 2 3 4 5 6 7 8 9 10 常规模式: 用户态 内核态 │ │ │ SQE 写入共享内存 │ │ │ │ io_uring_enter(to_submit=N) │ ├────────系统调用──────────────→│ ← 仍有上下文切换 │ │ 读取 SQ,执行 I/O │ 返回 │ │←─────────────────────────────┤ 对于超高频提交场景(如高频交易、高吞吐数据库),连这一次系统调用都嫌多。 ...

October 2, 2025 · 15 min · 3155 words

深入学习 io_uring(一):从原理到第一个异步程序

系列导航:入门篇 | 进阶篇 | 实战篇 引言:epoll 之后,还能更快吗? 假设你用 epoll 写了一个高并发 TCP 服务器,性能已经不错——C10K 问题解决了。但当你把连接数推到 C1M(百万级) 时: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 epoll 的瓶颈: 用户态 内核态 │ │ │ epoll_wait() │ ├───────系统调用─────────────→│ ← 每次至少一次上下文切换 │ │ │ 返回就绪的 fd 列表 │ │←───────────────────────────┤ │ │ │ recv(fd_1, buf, ...) │ ├───────系统调用─────────────→│ ← 每个 fd 又一次系统调用! │ │ │ send(fd_1, resp, ...) │ ├───────系统调用─────────────→│ ← 再一次! │ │ │ recv(fd_2, buf, ...) │ ├───────系统调用─────────────→│ ← N 个连接 = 2N+ 次系统调用 问题:epoll 只解决了"哪些 fd 就绪"的问题,每次 I/O 操作仍然需要独立的系统调用。百万连接下,系统调用的开销成为主要瓶颈——上下文切换、数据拷贝、内核锁竞争。 ...

October 1, 2025 · 13 min · 2737 words

深入学习 ODB(八):实战——构建完整的游戏存档系统

系列导航:编译器与注解 | 连接与事务 | 对象关系 | 类型安全查询 | 继承与视图 | 迁移与多库 | 性能与实践 | 实战项目(本文) 引言:从零到完整 经过前七篇的学习,我们掌握了 ODB 的所有核心能力。本篇将综合运用它们,构建一个完整可运行的游戏存档系统——这不是简化的教学示例,而是接近生产级别的架构。 我们要实现以下游戏系统: 1 2 3 4 5 6 7 ┌─────────────────────────────────────────────────────────┐ │ 游戏存档系统 │ ├─────────────┬──────────────┬────────────┬───────────────┤ │ 玩家系统 │ 背包系统 │ 公会系统 │ 交易系统 │ │ Player │ Inventory │ Guild │ TradeLog │ │ Profile │ Equipment │ Members │ AuditLog │ └─────────────┴──────────────┴────────────┴───────────────┘ 1. 需求分析与数据建模 1.1 E-R 关系图 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 ┌──────────────┐ │ PlayerProfile│ │ (1:1) │ └──────┬───────┘ │ ┌──────────┐ ┌──────┴───────┐ ┌──────────────┐ │ Equipment│◀── 1:N ┤ Player ├ M:N ──▶│ Guild │ │ (装备栏) │ │ │ │ │ └──────────┘ └──────┬───────┘ └──────────────┘ │ ┌──────┴───────┐ │InventoryItem │ │ (1:N 背包) │ └──────────────┘ ┌──────────────┐ │ TradeLog │ ← 独立审计表 └──────────────┘ 1.2 实体清单 实体 说明 关键关系 BaseEntity 抽象基类,提供 id / createdAt / updatedAt 所有实体继承 Player 玩家核心数据 一对一 Profile,一对多背包/装备,多对多公会 PlayerProfile 玩家扩展档案 反向引用 Player InventoryItem 背包道具 多对一 Player Equipment 装备栏(穿戴中的装备) 多对一 Player Guild 公会 多对多 Player TradeLog 交易日志 独立表,通过 playerId 关联 2. ODB 对象定义 2.1 抽象基类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 // db/objects/base_entity.hxx #ifndef BASE_ENTITY_HXX #define BASE_ENTITY_HXX #include <string> #include <cstdint> #include <odb/core.hxx> // 所有实体的公共字段 #pragma db object abstract class BaseEntity { public: uint64_t id() const { return id_; } const std::string& createdAt() const { return createdAt_; } const std::string& updatedAt() const { return updatedAt_; } void setCreatedAt(const std::string& t) { createdAt_ = t; } void setUpdatedAt(const std::string& t) { updatedAt_ = t; } protected: friend class odb::access; #pragma db id auto uint64_t id_{0}; #pragma db type("DATETIME") column("created_at") not_null std::string createdAt_; #pragma db type("DATETIME") column("updated_at") not_null std::string updatedAt_; }; #endif 2.2 玩家与档案 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 // db/objects/player_profile.hxx #ifndef PLAYER_PROFILE_HXX #define PLAYER_PROFILE_HXX #include <string> #include <odb/core.hxx> #include <odb/lazy-ptr.hxx> #include "base_entity.hxx" class Player; #pragma db object table("player_profile") class PlayerProfile : public BaseEntity { public: PlayerProfile() = default; PlayerProfile(const std::string& avatar, const std::string& signature) : avatar_(avatar), signature_(signature) {} const std::string& avatar() const { return avatar_; } const std::string& signature() const { return signature_; } int totalKills() const { return totalKills_; } int totalDeaths() const { return totalDeaths_; } int totalPlayTime() const { return totalPlayTime_; } void setAvatar(const std::string& a) { avatar_ = a; } void setSignature(const std::string& s) { signature_ = s; } void addKill() { ++totalKills_; } void addDeath() { ++totalDeaths_; } void addPlayTime(int seconds) { totalPlayTime_ += seconds; } odb::lazy_weak_ptr<Player>& owner() { return owner_; } private: friend class odb::access; #pragma db inverse(profile_) odb::lazy_weak_ptr<Player> owner_; #pragma db type("VARCHAR(256)") std::string avatar_; #pragma db type("VARCHAR(128)") std::string signature_; #pragma db column("total_kills") not_null int totalKills_{0}; #pragma db column("total_deaths") not_null int totalDeaths_{0}; #pragma db column("total_play_time") not_null int totalPlayTime_{0}; // 累计在线秒数 }; #endif 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 // db/objects/player.hxx #ifndef PLAYER_HXX #define PLAYER_HXX #include <string> #include <vector> #include <map> #include <memory> #include <cstdint> #include <odb/core.hxx> #include <odb/lazy-ptr.hxx> #include <odb/section.hxx> #include "base_entity.hxx" class PlayerProfile; class InventoryItem; class Equipment; class Guild; // 玩家状态枚举 enum class PlayerStatus : int { Active = 0, // 正常 Banned = 1, // 封禁 Deleted = 2 // 已删除(软删除) }; #pragma db model version(1, 1) #pragma db object table("player") class Player : public BaseEntity { public: Player() = default; Player(const std::string& name, int level = 1) : name_(name), level_(level) {} // --- 核心字段访问 --- const std::string& name() const { return name_; } int level() const { return level_; } uint64_t exp() const { return exp_; } uint64_t gold() const { return gold_; } PlayerStatus status() const { return status_; } void setName(const std::string& n) { name_ = n; } void setLevel(int lv) { level_ = lv; } void addExp(uint64_t e) { exp_ += e; } void addGold(uint64_t g) { gold_ += g; } void deductGold(uint64_t g) { gold_ -= g; } void setStatus(PlayerStatus s) { status_ = s; } // --- 关系访问 --- odb::lazy_shared_ptr<PlayerProfile>& profile() { return profile_; } std::vector<odb::lazy_shared_ptr<InventoryItem>>& inventory() { return inventory_; } std::vector<odb::lazy_shared_ptr<Equipment>>& equipments() { return equipments_; } std::vector<odb::lazy_shared_ptr<Guild>>& guilds() { return guilds_; } // --- 扩展段访问 --- odb::section& skillSection() { return skillSection_; } const std::map<uint64_t, int>& skills() const { return skills_; } void setSkillLevel(uint64_t skillId, int level) { skills_[skillId] = level; } private: friend class odb::access; // ===== 核心字段(默认加载)===== #pragma db type("VARCHAR(32)") not_null unique std::string name_; #pragma db not_null index int level_{1}; uint64_t exp_{0}; uint64_t gold_{0}; #pragma db column("vip_level") not_null int vipLevel_{0}; #pragma db not_null PlayerStatus status_{PlayerStatus::Active}; // ===== 关系映射 ===== // 一对一:玩家档案 #pragma db not_null odb::lazy_shared_ptr<PlayerProfile> profile_; // 一对多:背包道具 #pragma db inverse(owner_) value_not_null std::vector<odb::lazy_shared_ptr<InventoryItem>> inventory_; // 一对多:装备栏 #pragma db inverse(wearer_) value_not_null std::vector<odb::lazy_shared_ptr<Equipment>> equipments_; // 多对多:加入的公会 #pragma db inverse(members_) value_not_null std::vector<odb::lazy_shared_ptr<Guild>> guilds_; // ===== 扩展段(按需加载)===== #pragma db load(lazy) update(change) section(skillSection_) \ table("player_skills") key_column("skill_id") value_column("level") std::map<uint64_t, int> skills_; #pragma db transient odb::section skillSection_; }; #endif 2.3 背包与装备 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 // db/objects/inventory_item.hxx #ifndef INVENTORY_ITEM_HXX #define INVENTORY_ITEM_HXX #include <cstdint> #include <odb/core.hxx> #include <odb/lazy-ptr.hxx> #include "base_entity.hxx" class Player; // 道具品质 enum class ItemQuality : int { White = 1, Green = 2, Blue = 3, Purple = 4, Orange = 5 }; #pragma db object table("inventory") class InventoryItem : public BaseEntity { public: InventoryItem() = default; InventoryItem(uint64_t templateId, int count, int slot, ItemQuality quality) : templateId_(templateId), count_(count), slot_(slot), quality_(quality) {} uint64_t templateId() const { return templateId_; } int count() const { return count_; } int slot() const { return slot_; } ItemQuality quality() const { return quality_; } void setCount(int c) { count_ = c; } void addCount(int n) { count_ += n; } void setSlot(int s) { slot_ = s; } odb::lazy_shared_ptr<Player>& owner() { return owner_; } private: friend class odb::access; #pragma db not_null odb::lazy_shared_ptr<Player> owner_; #pragma db column("template_id") not_null index uint64_t templateId_{0}; #pragma db not_null int count_{1}; #pragma db not_null int slot_{0}; #pragma db not_null ItemQuality quality_{ItemQuality::White}; }; #endif 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 // db/objects/equipment.hxx #ifndef EQUIPMENT_HXX #define EQUIPMENT_HXX #include <cstdint> #include <odb/core.hxx> #include <odb/lazy-ptr.hxx> #include "base_entity.hxx" class Player; // 装备部位 enum class EquipSlot : int { Head = 0, Chest = 1, Legs = 2, Feet = 3, MainHand = 4, OffHand = 5, Ring = 6, Necklace = 7 }; #pragma db object table("equipment") class Equipment : public BaseEntity { public: Equipment() = default; Equipment(uint64_t templateId, EquipSlot slot, int attack, int defense, int enhanceLevel) : templateId_(templateId), slot_(slot), attack_(attack), defense_(defense), enhanceLevel_(enhanceLevel) {} uint64_t templateId() const { return templateId_; } EquipSlot slot() const { return slot_; } int attack() const { return attack_; } int defense() const { return defense_; } int enhanceLevel() const { return enhanceLevel_; } int maxEnhance() const { return maxEnhance_; } void setEnhanceLevel(int lv) { enhanceLevel_ = lv; } void addAttack(int a) { attack_ += a; } void addDefense(int d) { defense_ += d; } odb::lazy_shared_ptr<Player>& wearer() { return wearer_; } private: friend class odb::access; #pragma db not_null odb::lazy_shared_ptr<Player> wearer_; #pragma db column("template_id") not_null uint64_t templateId_{0}; #pragma db not_null EquipSlot slot_{EquipSlot::MainHand}; int attack_{0}; int defense_{0}; #pragma db column("enhance_level") not_null int enhanceLevel_{0}; #pragma db column("max_enhance") not_null int maxEnhance_{15}; }; #endif 2.4 公会 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 // db/objects/guild.hxx #ifndef GUILD_HXX #define GUILD_HXX #include <string> #include <vector> #include <cstdint> #include <odb/core.hxx> #include <odb/lazy-ptr.hxx> #include "base_entity.hxx" class Player; #pragma db object table("guild") class Guild : public BaseEntity { public: Guild() = default; Guild(const std::string& name, uint64_t leaderId) : name_(name), leaderId_(leaderId) {} const std::string& name() const { return name_; } uint64_t leaderId() const { return leaderId_; } int level() const { return level_; } const std::string& announcement() const { return announcement_; } void setLevel(int lv) { level_ = lv; } void setLeaderId(uint64_t id) { leaderId_ = id; } void setAnnouncement(const std::string& a) { announcement_ = a; } std::vector<odb::lazy_shared_ptr<Player>>& members() { return members_; } private: friend class odb::access; #pragma db type("VARCHAR(32)") not_null unique std::string name_; #pragma db column("leader_id") not_null uint64_t leaderId_{0}; #pragma db not_null int level_{1}; #pragma db column("max_members") not_null int maxMembers_{50}; #pragma db type("VARCHAR(256)") std::string announcement_; #pragma db value_not_null unordered table("guild_members") std::vector<odb::lazy_shared_ptr<Player>> members_; }; #endif 2.5 交易日志 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 // db/objects/trade_log.hxx #ifndef TRADE_LOG_HXX #define TRADE_LOG_HXX #include <string> #include <cstdint> #include <odb/core.hxx> #include "base_entity.hxx" enum class TradeAction : int { Buy = 1, // 商店购买 Sell = 2, // 商店出售 Transfer = 3, // 玩家间转账 Enhance = 4, // 装备强化消耗 Reward = 5 // 系统奖励 }; #pragma db object table("trade_log") class TradeLog : public BaseEntity { public: TradeLog() = default; TradeLog(uint64_t playerId, TradeAction action, uint64_t itemId, int quantity, uint64_t goldChange, const std::string& detail) : playerId_(playerId), action_(action), itemId_(itemId), quantity_(quantity), goldChange_(goldChange), detail_(detail) {} uint64_t playerId() const { return playerId_; } TradeAction action() const { return action_; } uint64_t itemId() const { return itemId_; } int quantity() const { return quantity_; } uint64_t goldChange() const { return goldChange_; } const std::string& detail() const { return detail_; } private: friend class odb::access; #pragma db column("player_id") not_null index uint64_t playerId_{0}; #pragma db not_null TradeAction action_{TradeAction::Buy}; #pragma db column("item_id") uint64_t itemId_{0}; int quantity_{0}; #pragma db column("gold_change") uint64_t goldChange_{0}; #pragma db type("VARCHAR(256)") std::string detail_; }; #endif 2.6 View 定义 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 // db/views/game_views.hxx #ifndef GAME_VIEWS_HXX #define GAME_VIEWS_HXX #include <string> #include <cstdint> #include <odb/core.hxx> #include "db/objects/player.hxx" #include "db/objects/guild.hxx" #include "db/objects/trade_log.hxx" // 排行榜条目 #pragma db view object(Player) struct LeaderboardEntry { #pragma db column(Player::id_) uint64_t id; #pragma db column(Player::name_) std::string name; #pragma db column(Player::level_) int level; #pragma db column(Player::exp_) uint64_t exp; }; // 全服统计 #pragma db view object(Player) struct ServerStats { #pragma db column("count(" + Player::id_ + ")") uint64_t totalPlayers; #pragma db column("avg(" + Player::level_ + ")") double avgLevel; #pragma db column("max(" + Player::level_ + ")") int maxLevel; #pragma db column("sum(" + Player::gold_ + ")") uint64_t totalGold; }; // 公会战力排名 #pragma db view object(Guild) object(Player: Guild::members_) struct GuildPowerRank { #pragma db column(Guild::id_) uint64_t guildId; #pragma db column(Guild::name_) std::string guildName; #pragma db column("count(" + Player::id_ + ")") int memberCount; #pragma db column("sum(" + Player::level_ + ")") int totalPower; }; // 每日交易汇总 #pragma db view query( \ "SELECT DATE(created_at) AS trade_date, " \ " action AS trade_action, " \ " COUNT(*) AS trade_count, " \ " SUM(gold_change) AS total_gold " \ "FROM trade_log " \ "GROUP BY DATE(created_at), action " \ "ORDER BY trade_date DESC, action ASC") struct DailyTradeReport { #pragma db column("trade_date") type("DATE") std::string tradeDate; #pragma db column("trade_action") int tradeAction; #pragma db column("trade_count") uint64_t tradeCount; #pragma db column("total_gold") uint64_t totalGold; }; #endif 3. Repository 层实现 Repository 层封装所有数据库操作,让业务层不直接依赖 ODB 类型。 ...

July 12, 2025 · 24 min · 4920 words