邮件系统
概述
邮件是游戏里"离线可达"的通信与奖励投放通道:玩家不在线也能收到补偿、活动奖励、好友留言,登录后一键领取附件。邮件的核心问题有三:附件领取的幂等与安全(防重复领取)、邮箱容量与过期清理(防无限堆积)、群发的性能(全服邮件不能逐封硬写)。本文设计完整的邮件系统:类型区分、收发存储、附件领取、群发设计。
一、邮件模型与类型
1.1 邮件类型
| 类型 | 发送方 | 典型场景 | 特点 |
|---|---|---|---|
| 系统邮件 | 服务器 | 补偿、公告、维护通知 | 可群发、不可回复 |
| 好友邮件 | 玩家 | 留言、赠送道具 | 一对一、可回复 |
| 活动邮件 | 活动系统 | 活动奖励、排行奖励 | 按条件批量生成 |
| 公会邮件 | 公会 | 公会公告、活动动员 | 面向成员群发 |
不同类型影响是否可回复、是否可群发、保存时限。系统邮件往往设置较长有效期,活动邮件跟随活动周期失效。
1.2 邮件实体
Mail:
mailId、type(SYSTEM/FRIEND/ACTIVITY/GUILD)
senderId、senderName、receiverId
title、content
attachments(附件列表,Json)
createTime、expireTime、readFlag、claimedFlag
PRIMARY KEY(mailId)
索引:receiverId + createTime、receiverId + readFlag附件结构单独定义,与邮件解耦,便于领取时做幂等与校验:
Attachment:
itemId、count、bindType(绑定/不绑定)二、邮件收发存储
2.1 发信流程
发信流程:
1. 构建邮件(类型/标题/正文/附件)
2. 校验(接收者存在、邮箱未满、内容合规)
3. 写入收件人邮箱
4. 在线玩家推送红点/新邮件通知
5. 离线玩家登录后拉取好友邮件额外校验:互为好友(或至少不是黑名单)、对方未开启拒收。
2.2 收件箱存储
收件箱采用单表 + 接收者索引设计,配合 Redis 缓存热点数据:
player_mail 表:
mailId、receiverId、type、senderId、title、content
attachments、createTime、expireTime、readFlag、claimedFlag
索引 (receiverId, createTime)
Redis:
mail:unread:{playerId}(未读数量计数)
mail:list:{playerId}(最近 N 封摘要,热数据)分页拉取列表时只取摘要字段,正文与附件按需加载,避免大字段拖慢列表接口。
2.3 邮件上限
每个玩家邮箱有容量上限(如 100 封,可付费扩容)。超出上限时优先丢弃最早且已读无附件的邮件,有附件未领取的邮件不自动删除,避免玩家损失。
邮箱满处理:
新邮件到达 → 检查容量
已满 → 尝试清理(已读 + 无附件 + 已过期)
仍满 → 新邮件直接返回失败(好友邮件)或丢弃最早(系统/活动)三、附件领取
3.1 领取流程
附件领取是资金/道具发放入口,必须保证幂等与原子性:
领取流程:
1. 锁定邮件(Redis 分布式锁,防并发重复领取)
2. 校验:邮件存在、未领取、未过期
3. 校验背包容量(不足则拒绝)
4. 发放道具/货币(走统一发放接口,记流水)
5. 标记 claimedFlag = 1
6. 解锁核心要求:一份附件只能领取一次。用 claimedFlag 作为数据库层面的幂等保证,领取操作在事务内完成;若发放成功但标记失败,通过对账任务修正。
@Transactional
public ClaimResult claim(long playerId, long mailId) {
Mail mail = mailDao.selectByPk(mailId);
// 校验:接收者是本人、未领取、未过期
// 发放附件
// 更新 claimedFlag
// 写发放流水
}3.2 一键领取
一键领取 = 批量领取,逻辑不变但需要逐封处理、部分失败可重试:成功发放的标记已领取,失败的(如背包满)留在邮箱并提示原因,避免整体回滚造成重复发放。
3.3 过期清理
过期清理策略:
邮件创建时设定 expireTime
系统/活动邮件:活动结束 + N 天
好友邮件:30 天
已读且无附件:提前清理
定时任务(每日执行):
DELETE 过期且已领取 或 过期超过宽限期 的邮件
有附件未领取的过期邮件:先作废附件再删除
回收邮件容量清理任务按 expireTime 分批删除,避免大事务锁表。作废的附件写对账日志,防止漏发/错发争议。
四、群发邮件设计
4.1 发送方式
| 方式 | 适用场景 | 实现 |
|---|---|---|
| 全服群发 | 维护补偿、版本公告 | 逻辑层广播,玩家登录时拉取 |
| 条件群发 | 等级达标、指定服、指定公会 | 筛选条件 + 批量生成 |
| 定向单发 | 补偿单玩家 | 直接写入 |
4.2 全服邮件优化
全服玩家可能百万级,逐封写库不可行。采用"定义式邮件 + 领取时物化":
群发方案:
1. 创建邮件模板(type=SYSTEM,target=ALL)
2. 不批量写库,只记录"已推送定义"
3. 玩家登录/打开邮箱时,按需生成自己的邮件实例
4. 用 Redis Set 记录"已领取玩家",保证每玩家一份这样把 O(N) 的写放大转成 O(1) 的定义 + O(登录次数) 的按需生成,代价是玩家未登录前看不到邮件——可配合登录拉取与红点通知弥补。
4.3 邮件防刷
群发安全:
接收条件校验(等级/活跃/防沉迷状态)
发送频率限制(运营接口限流)
邮件 ID 防重(同一条定义不重复生成)
附件价值校验(运营配置白名单)五、实现要点
核心接口:
sendMail(sender, receiver, content, attachments)
claimAttachment(playerId, mailId)
claimAll(playerId)
deleteMail(playerId, mailId)
pullMailList(playerId, page)
sendMassMail(config) // 运营群发表结构:
player_mail(mailId 主键、receiver 索引、附件 Json 列)
工程注意:
附件发放复用经济系统统一接口(见经济系统章节)
邮件内容做敏感词过滤(复用聊天过滤组件)
红点计数用 Redis INCR/DECR,定期对账
群发邮件与活动、补偿系统对接,配置化下发常见坑:
附件领取并发重复 → 分布式锁 + 状态字段双保险
邮件堆积拖慢列表 → 分页 + 摘要字段 + 定期清理
群发写放大 → 定义式邮件 + 按需物化
过期附件凭空消失 → 作废先于删除,留对账日志