4.邮件系统Mail服设计

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():初始化网络与组件注册,挂载 MailUnitManageComponentMailBoxManageComponent
  • OnInnerRoute():处理来自 Gate 的所有内部路由请求,如登录、退出、投递、客户端 RPC 等。

MailUnitManageComponent

  • Init():从数据库载入所有历史 MailUnit,构建在线和所有MailUnit集合。
  • Online(accountId, name, gateRouteId):邮件账户登录时调用。创建或更新 MailUnit,添加到在线列表。挂载 MailComponent ,检查可能存在的离线邮件并领取。
  • Exit(mailUnit):保存并移除用户 MailComponent,从在线列表中注销。
  • TryGet / Remove:按名称或账户 ID 查询、删除单元。

MailUnit

  • 字段:Id(RuntimeId)、NameAccountIdGateRouteIdMailComponent
  • Dispose():清理组件引用,释放内存(因为使用对象池的话要清理)。

MailBoxManageComponent

  • Init():加载所有未过期 MailBox,构建MailBox集合,包括全部,按类型分,按玩家账户分的集合。并启动定时器定时移除过期邮件。
  • GetHaveMail(mailUnit, sync):某个MailUnit上线时,遍历全服/限时/指定目标邮箱,得到要发送的离线邮件,通过得到这个MailUnitMailComponent为该用户批量发放离线邮件。
  • 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),协同 MailBoxManageComponentMailComponent 完成投递与持久化。

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 核心流程图说明

  1. 登录并批量领取离线邮件

    1. 客户端发送 C2G_LoginRequest 至 GateSession。

    2. GateSession 将请求路由为 G2Mail_LoginRequest{accountId, name, gateRouteId} 到 MailScene。

    3. MailScene 调用 MailUnitManageComponent.Online

      • 创建或更新对应的 MailUnit,挂载/载入其 MailComponent
      • 随即调用 MailBoxManageComponent.GetHaveMail(mailUnit, sync: true) 来批量处理该玩家所有类型的离线邮件——包括全服、限时和指定目标邮件——并通过多次 MailComponent.Add 向客户端推送 Mail2C_HaveMail(简化邮件信息)。
    4. MailScene 回应 Mail2G_LoginResponse{mailUnitRouteId} 给 GateSession,GateSession 再回传 G2C_LoginResponse 给客户端。

  2. 获取简略邮件列表

    1. 客户端发送 C2Mail_GetHaveMailRequest(无需再次触发离线领取逻辑,只为获取当前 MailComponent 中的简略列表)。

    2. GateSession 根据先前保存的路由,将请求转发到对应的 MailUnit

    3. C2Mail_GetHaveMailRequestHandler 内部:

      • 首先调用 MailComponent.CheckTimeOut() 清理自身已过期邮件;
      • 然后调用 MailComponent.GetMailSimplifyInfos(response.Mails),将所有在有效期内的邮件转换为 MailSimplifyInfo 并填充到响应中。
    4. Mail2C_GetHaveMailResponse{Mails} 直接返回给客户端,客户端据此展示邮件列表。

  3. 打开邮件、领取附件与删除邮件

    • 打开邮件

      • 客户端 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}
  4. 发送新邮件

    1. 客户端构造 C2Mail_SendMailRequest{UserName, Title, Content, Money, ItemIds} → RouteRPC 到 MailUnit

    2. C2Mail_SendMailRequestHandler 校验参数并定位接收方 MailUnit,使用 MailFactory + MailBoxFactory 构建一个新的 MailBox

    3. 调用 MailHelper.Send(scene, mailBox)

      • 若当前在 MailScene,直接执行 InnerSend;否则跨服路由到 MailScene 后再执行。

      • InnerSend 根据 MailBoxType(Specify/Online/All/AllToDate)分发:

        • Specify:在线即时推送并记录离线;
        • Online:仅在线玩家接收;
        • AllAllToDate:持久化到 MailBoxManageComponent,分批推送并在玩家下次登录时领取。
    4. 发送操作无需专门回复,一旦 MailComponent.Add 同步了 Mail2C_HaveMail,客户端即收到新邮件通知。

  5. 定时与主动过期清理

    • MailBox 管理组件

      • Init 时用 RepeatedTimer 定时调用 Timeout()
      • Timeout 扫描所有 MailBox 的过期时间键,将过期项加入队列并异步调用 Remove(mailBoxId) 清理缓存、实体与数据库。
    • MailComponent

      • 在每次客户端请求时或主动触发 CheckTimeOut(),扫描自身邮件集合并移除过期邮件,确保客户端列表中不包含已过期项。
  6. 其他服调用

    • 统一调用 MailHelper.Send(scene, mailBox):这个接口即可。

4.5 总结

  • 职责清晰:MailUnit 管理“用户状态”,MailBoxManage 管理“离线箱”,MailComponent 管理“个人邮件”。
  • 高效索引:多维索引与定时器结合,支持全服、限时、指定目标多种分发,同时自动清理过期数据。
  • 统一投递MailHelper.Send 屏蔽跨服逻辑,调用方只需构造 MailBox 即可完成分发。
  • 可扩展性:数据模型与分发策略可横向扩展,如添加“等级段”“公会”投递类型。
  • 健壮性:异步非阻塞、组件化生命周期与数据库持久化保障高并发环境下稳定运行。


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏