150 lines
6.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## 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<HeroComponent>() 包含所有英雄子 Entity
- **AND** Player.GetComponent<BagComponent>() 包含所有道具
#### 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<Player>(playerId)` 触发
- **THEN** 从 BSON 重建 Player Entity 树
- **AND** 所有子 EntityHero、Item 等)的父子关系正确恢复
- **AND** 通过 `Player.GetComponent<HeroComponent>().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<Player>.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<long, Player>` 内存中完成
- **AND** 不需要本地起 mongod
#### Scenario: 内存模式重启
- **WHEN** 内存模式下 ET Server 重启
- **THEN** 所有玩家数据丢失仅开发期使用
- **AND** 日志输出 WARNING 提示此模式不可用于生产