## ADDED Requirements ### Requirement: MongoDB 数据库连接组件 服务端 MUST 在 Gate Scene 启动时初始化 `DBComponent`,封装 MongoDB.Driver 提供异步读写。连接配置从 `StartZoneConfig.DBConnection` 和 `DBName` 读取。 #### Scenario: 启动时建立连接 - **WHEN** ET Server Gate Scene 启动 - **THEN** `DBComponent` 从 StartZoneConfig 读取 mongodb 连接字符串 - **AND** 创建 `IMongoClient` 实例 - **AND** 测试连接成功(PING) - **AND** 失败时记录错误日志并按 StartConfig 决定是否阻塞启动 #### Scenario: 集合自动创建 - **WHEN** 首次写入某类型 Entity(如 Player) - **THEN** `DBComponent` 自动创建对应 collection(命名 `players`、`heroes`、`bags`...) - **AND** 创建索引(默认按 EntityId) ### Requirement: 玩家数据加载 玩家登录 Gate Scene 成功后,服务端 MUST 从 MongoDB 加载该玩家的 `Player` Entity(含 `HeroComponent` + `BagComponent`)。若玩家首次登录,MUST 创建新 Player 并发放初始资源(由 `InitialResourceConfig` 配置)。 #### Scenario: 老玩家登录 - **WHEN** 玩家 PlayerId=10001 通过 `C2G_LoginGate` 登录 - **AND** DB 中存在记录 - **THEN** 服务端从 DB 加载完整 Player Entity 到内存 - **AND** Player.GetComponent() 包含所有英雄子 Entity - **AND** Player.GetComponent() 包含所有道具 #### Scenario: 新玩家首次登录 - **WHEN** 玩家 PlayerId 在 DB 中不存在 - **THEN** 服务端创建 Player Entity - **AND** 添加 HeroComponent + BagComponent - **AND** 按 `InitialResourceConfig` 发放初始道具/英雄 - **AND** 立即写入 DB(同步保证幂等) ### Requirement: 数据持久化策略 服务端 MUST 采用 **脏标记 + 定时刷盘** 策略,避免每次业务操作直写 DB: - 业务系统通过 `EntitySystem.SetDirty(entity)` 标记脏数据 - `DBSaveComponentSystem` 每隔 N 秒(默认 30s)扫描所有脏 Entity 并批量落库 - 玩家离线(session disconnected)时 MUST 立即强制刷盘 #### Scenario: 业务操作触发脏标记 - **WHEN** `C2G_SummonHeroHandler` 修改 HeroComponent - **THEN** 通过 `Player.SetDirty()` 标记 - **AND** Handler 立即返回(不等落库) #### Scenario: 定时批量刷盘 - **WHEN** DBSaveComponent 定时器触发 - **THEN** 扫描所有标记 dirty 的 Player - **AND** 异步 `DBComponent.Save(player)` 批量写入 - **AND** 写入成功后清除 dirty 标记 - **AND** 失败时保留 dirty 等下次重试 #### Scenario: 玩家离线立即刷盘 - **WHEN** Gate Scene 收到 SessionDispose 事件 - **AND** 该 Session 对应的 Player 是 dirty - **THEN** 同步等待 `DBComponent.Save(player).Coroutine()` 完成 - **AND** 再释放 Player Entity ### Requirement: Entity → BSON 序列化 `DBComponent` MUST 使用 ET 的 MongoHelper(基于 MongoDB.Bson)序列化/反序列化 Entity。所有要持久化的 Component 字段 MUST 加 `[MongoElement]` 特性,子 Entity 通过 ET 的 Entity 关系自动遍历。 #### Scenario: 持久化 HeroComponent - **WHEN** `DBComponent.Save(player)` 触发 - **THEN** 序列化 Player 的所有 Component 包含 HeroComponent + BagComponent - **AND** 子 Hero Entity 递归序列化 - **AND** 写入 MongoDB `players` collection(按 PlayerId 为 \_id) #### Scenario: 反序列化恢复 - **WHEN** `DBComponent.Query(playerId)` 触发 - **THEN** 从 BSON 重建 Player Entity 树 - **AND** 所有子 Entity(Hero、Item 等)的父子关系正确恢复 - **AND** 通过 `Player.GetComponent().GetHero(heroId)` 能直接访问 ### Requirement: 并发与一致性 同一玩家的业务请求 MUST 在 Gate Scene 串行执行(ET 单线程纤程天然保证)。但跨玩家操作(如交易、组队)MUST 通过 ET 的消息机制传递,避免直接跨纤程访问。 #### Scenario: 同玩家串行请求 - **WHEN** 客户端在 1 帧内连续发送 SummonHero 和 HeroAddExp - **THEN** Gate Scene 单线程依次处理两个请求 - **AND** 第二个请求看到第一个的副作用(新英雄存在) #### Scenario: 跨玩家通过消息 - **WHEN** 玩家 A 给玩家 B 赠送道具(未来需求) - **THEN** 必须通过 ET 的 Actor 消息机制 - **AND** 不允许 A 的纤程直接修改 B 的 BagComponent ### Requirement: DB 索引 服务端 MUST 为常用查询字段建索引: - `players._id` (默认按 PlayerId) - `players.account`(玩家账号反查) - 业务表如 `friendships`(未来需求)需自定义索引 #### Scenario: 索引创建 - **WHEN** DBComponent 首次连接 DB - **THEN** 检查并创建 `players` collection 上的必要索引 - **AND** 通过 `IMongoCollection.Indexes.CreateOneAsync` 创建 ### Requirement: 数据迁移版本号 每个持久化 Entity MUST 包含 `dataVersion (int32)` 字段,用于未来数据结构变更时支持渐进式迁移。 #### Scenario: 版本号读取 - **WHEN** 加载 Player 时 dataVersion < 当前版本 - **THEN** `DBMigrationComponent` 运行该版本到当前版本的迁移函数 - **AND** 迁移后更新 dataVersion = 当前版本 - **AND** 立即保存到 DB #### Scenario: 新建 Player 写入版本号 - **WHEN** 创建新 Player Entity - **THEN** 自动设置 `dataVersion = 当前最新版本` ### Requirement: 日志与监控 DB 操作 MUST 记录以下日志(基于 ET 的 Log 系统): - 慢查询:超过 100ms 的读写 - 失败:连接断开、超时、序列化错误 - 容量:每隔 1 小时记录 collection 文档数和大小 #### Scenario: 慢查询日志 - **WHEN** `DBComponent.Save(player)` 耗时超过 100ms - **THEN** 输出 `Log.Warning` 包含 PlayerId、耗时 - **AND** 不影响业务流程 #### Scenario: 失败重试 - **WHEN** DB 写入抛 `MongoConnectionException` - **THEN** 框架 MUST 重试最多 3 次(指数退避) - **AND** 仍失败则保留 dirty 标记等下次刷盘 - **AND** 输出 `Log.Error` ### Requirement: 开发模式无 DB 选项 系统 MUST 支持开发期免 MongoDB 模式:当 `StartZoneConfig.DBConnection == "memory"` 时,服务端 MUST 启用 `MemoryDBComponent`,所有"持久化"只在进程内存中(Dictionary),重启清空。生产环境 MUST 禁止使用此模式(启动时 WARNING 日志提示)。 #### Scenario: 内存模式启动 - **WHEN** StartZoneConfig.DBConnection == "memory" - **THEN** DBComponent 实例化为 `MemoryDBComponent` - **AND** 所有读写在 `Dictionary` 内存中完成 - **AND** 不需要本地起 mongod #### Scenario: 内存模式重启 - **WHEN** 内存模式下 ET Server 重启 - **THEN** 所有玩家数据丢失(仅开发期使用) - **AND** 日志输出 WARNING 提示此模式不可用于生产