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 { -DictOnline -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