4.邮件系统Mail服设计
4.1 设计思路
Mail服务器是邮件系统的核心中枢,专注于离线邮件管理、实时投递与持久化,彻底解耦网络路由与业务逻辑:
- 单元管理:
MailUnitManageComponent挂载到MailScene上,缓存并维护所有MailUnit(邮件用户实体),负责登录/退出与组件挂载生命周期。 - 邮箱管理:
MailBoxManageComponent挂载到MailScene上,作为“邮件物流中心”,管理所有 MailBox,支持多种分发策略。 - 邮件组件:
MailComponent挂载在在线的MailUnit上,管理个人邮件列表、附件领取与过期清理,保证玩家在线时实时推送。下线时 - 跨服路由:
MailUnit记录对应Gate服的GateAccount的GateRouteId,可以路由给客户端。 - 统一投递:
MailHelper.Send负责所有场景下的 MailBox 分发,让发邮件的接口统一。同时支持自动选择本地或跨服路由,并驱动InnerSend执行核心逻辑。
这样的设计让 Mail服专注“业务态”,由 Gate 负责“网络态”,既利于水平扩展,也便于单元测试与热重载。
4.2 Mail服主要组件
classDiagram
class MailScene {
+Start()
+OnInnerRoute()
}
class MailUnitManageComponent {
-Dict Online
-Dict UnitByName
-Dict UnitByAccountId
+Init()
+Online(accountId, name, gateRouteId): MailUnit
+Exit(mailUnit)
+TryGet(name/accountId, out MailUnit)
+Remove(accountId)
}
class MailUnit {
+long Id
+string Name
+long AccountId
+long GateRouteId
+MailComponent MailComponent
+Dispose()
}
class MailBoxManageComponent {
-Dict MailBoxes
-OneToManyList MailsByAccount
-OneToManyList MailsByMailBoxType
-SortedOneToManyList Timers
-Queue TimeOutQueue
+Init()
+GetHaveMail(mailUnit, sync)
+Remove(mailBoxId)
+Timeout()
}
class MailComponent {
-Dict Mails
-SortedOneToManyList Timer
+Add(mail, sync)
+Remove(mailId, actionType, sync)
+OpenMail(mailId): (ErrorCode,Mail)
+Receive(mailId, money, items, sync)
+CheckTimeOut()
}
class MailHelper {
+Send(scene, mailBox)
-InnerSend(scene, mailBox)
}
MailScene --> MailUnitManageComponent : 挂载
MailScene --> MailBoxManageComponent : 挂载
MailUnitManageComponent --> MailUnit : 管理
MailUnit --> MailComponent : 挂载
MailBoxManageComponent --> MailBox : 管理
MailComponent --> Mail : 引用
MailBox --> Mail : 引用
MailHelper --|> MailScene : 使用
MailScene
- Start():初始化网络与组件注册,挂载
MailUnitManageComponent与MailBoxManageComponent。 - OnInnerRoute():处理来自 Gate 的所有内部路由请求,如登录、退出、投递、客户端 RPC 等。
MailUnitManageComponent
- Init():从数据库载入所有历史
MailUnit,构建在线和所有MailUnit集合。 - Online(accountId, name, gateRouteId):邮件账户登录时调用。创建或更新
MailUnit,添加到在线列表。挂载MailComponent,检查可能存在的离线邮件并领取。 - Exit(mailUnit):保存并移除用户
MailComponent,从在线列表中注销。 - TryGet / Remove:按名称或账户 ID 查询、删除单元。
MailUnit
- 字段:
Id(RuntimeId)、Name、AccountId、GateRouteId、MailComponent。 - Dispose():清理组件引用,释放内存(因为使用对象池的话要清理)。
MailBoxManageComponent
- Init():加载所有未过期
MailBox,构建MailBox集合,包括全部,按类型分,按玩家账户分的集合。并启动定时器定时移除过期邮件。 - GetHaveMail(mailUnit, sync):某个
MailUnit上线时,遍历全服/限时/指定目标邮箱,得到要发送的离线邮件,通过得到这个MailUnit的MailComponent为该用户批量发放离线邮件。 - Timeout():定时扫描过期邮件并移除。
- Remove(mailBoxId):移除缓存、销毁实体并从数据库删除。
MailComponent
- Add(mail, sync):新增
Mail,同步推送给在线玩家并持久化。注意超过容量要删除最老的邮件。更新Timer集合,存储过期时间对应的邮件id。 - Remove(mailId, actionType, sync):删除邮件,发状态变更给客户端并持久化。
- OpenMail(mailId):标记已读并返回详情给客户端;过期则自动清理。
- Receive(mailId, money, items, sync):处理邮件附件领取逻辑,调用道具/货币服务。
- CheckTimeOut():扫描并清理所有过期邮件。
MailHelper
- Send(scene, mailBox):暴露给外部调用的发送邮件接口。判断当前 Scene,若本地则
InnerSend,否则跨服路由到 MailScene发送和,可以自己添加路由规则。 - InnerSend(scene, mailBox):根据
MailBoxType选择分发策略(Specify/Online/All/AllToDate),协同MailBoxManageComponent与MailComponent完成投递与持久化。
4.3 核心流程图
sequenceDiagram
autonumber
participant ClientSession
participant GateSession
participant MailScene
participant MailUnit
participant MailBoxManageComponent as BoxMgr
participant MailComponent
%% 1. 登录流程
ClientSession->>GateSession: 1. C2G_LoginRequest
GateSession->>MailScene: 2. G2Mail_LoginRequest{accountId, name, gateRouteId}
MailScene->>MailUnitManageComponent: 3. Online(accountId, name, gateRouteId)
MailUnitManageComponent-->>GateSession: 4. Mail2G_LoginResponse{mailUnitRouteId}
GateSession-->>ClientSession: 5. G2C_LoginResponse
%% 2. 获取离线邮件
ClientSession->>GateSession: 6. C2Mail_GetHaveMailRequest
GateSession->>MailUnit: 7. Route→MailUnit
MailUnit->>BoxMgr: 8. GetHaveMail(mailUnit, true)
BoxMgr->>MailComponent: 9. Add(clonedMail, sync) %% pushes Mail2C_HaveMail to client
MailComponent-->>GateSession: 10. Mail2C_HaveMail*
MailUnit-->>GateSession: 11. Mail2C_GetHaveMailResponse
GateSession-->>ClientSession: 12. Mail2C_GetHaveMailResponse
%% 3. 其他操作(打开/领取/删除/发送)同理
Note over GateSession, MailUnit: Open/Receive/Remove/Send via RouteRPC to MailUnit
4.4 核心流程图说明
登录并批量领取离线邮件
客户端发送
C2G_LoginRequest至 GateSession。GateSession 将请求路由为
G2Mail_LoginRequest{accountId, name, gateRouteId}到 MailScene。MailScene 调用
MailUnitManageComponent.Online:- 创建或更新对应的
MailUnit,挂载/载入其MailComponent; - 随即调用
MailBoxManageComponent.GetHaveMail(mailUnit, sync: true)来批量处理该玩家所有类型的离线邮件——包括全服、限时和指定目标邮件——并通过多次MailComponent.Add向客户端推送Mail2C_HaveMail(简化邮件信息)。
- 创建或更新对应的
MailScene 回应
Mail2G_LoginResponse{mailUnitRouteId}给 GateSession,GateSession 再回传G2C_LoginResponse给客户端。
获取简略邮件列表
客户端发送
C2Mail_GetHaveMailRequest(无需再次触发离线领取逻辑,只为获取当前MailComponent中的简略列表)。GateSession 根据先前保存的路由,将请求转发到对应的
MailUnit。在
C2Mail_GetHaveMailRequestHandler内部:- 首先调用
MailComponent.CheckTimeOut()清理自身已过期邮件; - 然后调用
MailComponent.GetMailSimplifyInfos(response.Mails),将所有在有效期内的邮件转换为MailSimplifyInfo并填充到响应中。
- 首先调用
Mail2C_GetHaveMailResponse{Mails}直接返回给客户端,客户端据此展示邮件列表。
打开邮件、领取附件与删除邮件
打开邮件
客户端
C2Mail_OpenMailRequest{MailId, ReturnMailInfo}→ RouteRPC 到MailUnit→ 调用MailComponent.OpenMail(mailId):- 若邮件存在且未过期,标记为已读;若过期,则自动清理并返回过期错误;
- 根据
ReturnMailInfo决定是否附带完整MailInfo返回。
最终返回
Mail2C_OpenMailResponse{ErrorCode, MailInfo?}。
领取附件
客户端
C2Mail_ReceiveMailRequest{MailId, money, itemIds}→ RouteRPC 到MailUnit→ 调用MailComponent.Receive:- 分别处理金钱与物品发放(假设通过外部货币和背包服务接口);
- 删除已领取的附件,更新邮件状态并持久化;
- 返回
Mail2C_ReceiveMailResponse{ErrorCode}。
删除邮件
客户端
C2Mail_RemoveMailRequest{MailId}→ RouteRPC → 调用MailComponent.Remove(mailId, RemoveActionType.Remove, sync: true):- 从组件中移除对应邮件,销毁实体,并通过
Mail2C_MailState推送删除状态; - 返回
Mail2C_RemoveMailResponse{ErrorCode}。
- 从组件中移除对应邮件,销毁实体,并通过
发送新邮件
客户端构造
C2Mail_SendMailRequest{UserName, Title, Content, Money, ItemIds}→ RouteRPC 到MailUnit。C2Mail_SendMailRequestHandler校验参数并定位接收方MailUnit,使用MailFactory+MailBoxFactory构建一个新的MailBox。调用
MailHelper.Send(scene, mailBox):若当前在 MailScene,直接执行
InnerSend;否则跨服路由到 MailScene 后再执行。InnerSend根据MailBoxType(Specify/Online/All/AllToDate)分发:- Specify:在线即时推送并记录离线;
- Online:仅在线玩家接收;
- All/AllToDate:持久化到
MailBoxManageComponent,分批推送并在玩家下次登录时领取。
发送操作无需专门回复,一旦
MailComponent.Add同步了Mail2C_HaveMail,客户端即收到新邮件通知。
定时与主动过期清理
MailBox 管理组件:
Init时用RepeatedTimer定时调用Timeout();Timeout扫描所有MailBox的过期时间键,将过期项加入队列并异步调用Remove(mailBoxId)清理缓存、实体与数据库。
MailComponent:
- 在每次客户端请求时或主动触发
CheckTimeOut(),扫描自身邮件集合并移除过期邮件,确保客户端列表中不包含已过期项。
- 在每次客户端请求时或主动触发
其他服调用
- 统一调用
MailHelper.Send(scene, mailBox):这个接口即可。
- 统一调用
4.5 总结
- 职责清晰:MailUnit 管理“用户状态”,MailBoxManage 管理“离线箱”,MailComponent 管理“个人邮件”。
- 高效索引:多维索引与定时器结合,支持全服、限时、指定目标多种分发,同时自动清理过期数据。
- 统一投递:
MailHelper.Send屏蔽跨服逻辑,调用方只需构造MailBox即可完成分发。 - 可扩展性:数据模型与分发策略可横向扩展,如添加“等级段”“公会”投递类型。
- 健壮性:异步非阻塞、组件化生命周期与数据库持久化保障高并发环境下稳定运行。
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com