6.6 KiB
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
playerscollection(按 PlayerId 为 _id)
Scenario: 反序列化恢复
- WHEN
DBComponent.Query<Player>(playerId)触发 - THEN 从 BSON 重建 Player Entity 树
- AND 所有子 Entity(Hero、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 检查并创建
playerscollection 上的必要索引 - 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 提示此模式不可用于生产