深入学习 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