主题
OperationMail 运营邮件 Skill
何时使用
任务涉及以下内容时使用本 Skill:
OperationMailApi、OperationMailRecord、OperationMailQueryResult或OperationMailResult- 查询运营后台配置的全服邮件或个人邮件
- 自建邮件 UI、红点、已读、领取状态,或选择 GameSystemUI 现成收件箱
- 判断“运营邮件”和“玩家互发邮件”的边界
- 排查
ServiceNotInitialized、FailedToSend、ResultParseFailed、查询为空等问题
硬规则
- OperationMail 是服务端能力;客户端不能直接查询后台邮件。
- OperationMail 是“运营后台/系统投递给玩家”的邮件查询,不是玩家互发邮件。
- 不要开放 SQL 字符串,也不要在游戏代码里拼 SQL。底层 Host 已经固定查询
email.admin_email和email.user_email。 - 不要用 OperationMail 实现排行榜奖励发信或脚本发信;当前只做查询。
- OperationMail 本身不保存已读、已领取、隐藏、红点等玩家状态;自建收件箱时由项目用 CloudData 保存,使用 GameSystemUI 时由其邮件系统管理。
- 附件领取必须服务端幂等,不能只依赖客户端 UI 状态。
现成收件箱
需要运营邮件入口、红点、收件箱和领取时,优先阅读 docs/systems/GameSystemUIMail.md:GameSystemUI 已提供 MailWindow.Create() / MailEntrance.Create(window),服务端默认按 Game.RuntimeEnvironment 查询 debug / test / formal 邮件,并维护 CloudData 状态;只有特殊后台配置才显式覆盖 MailSystemServer.EnvironmentName。它不支持玩家互发邮件或脚本主动发信。下方示例只适用于自行实现收件箱的项目。
推荐调用模式
csharp
#if SERVER
using GameCore.OperationMail;
private static async Task<IReadOnlyList<OperationMailRecord>> QueryInboxAsync(long userId)
{
await OperationMailApi.WaitUntilReadyAsync();
var mapName = "my_map";
var env = Game.RuntimeEnvironment;
var now = DateTime.UtcNow;
var global = await OperationMailApi.QueryGlobalOperationMailsAsync(mapName, env, now);
if (!global.IsSuccess)
{
Game.Logger.LogWarning("全服运营邮件查询失败: {Result}", global.Result);
return [];
}
var personal = await OperationMailApi.QueryUserOperationMailsAsync(userId, mapName, env, now);
if (!personal.IsSuccess)
{
Game.Logger.LogWarning("个人运营邮件查询失败: {Result}", personal.Result);
return global.Records;
}
return global.Records.Concat(personal.Records).ToArray();
}
#endifCloudData 状态建议
自建收件箱时,只把玩家相关状态放 CloudData。运营邮件正文、标题、附件原始数据仍来自 OperationMail 查询。
推荐状态 key:
operation_mail_read_{mailId}operation_mail_claimed_{mailId}operation_mail_hidden_{mailId}
如果同一项目同时处理全服邮件和个人邮件,优先用 Uuid 或带来源前缀的 key,避免 id 冲突。
排障清单
MailSystem: load mail player data failed: TableNotFound:这是 CloudData 中的玩家邮件状态读取失败。修改项目 ID 或跨部署环境调试、且目标环境未发布过该项目时,缺表是预期状态;正常新建项目会建配套邮件表,目标环境首次发布也可自动建表。先核对项目/环境及发布记录,按授权发布后复测,详见 CloudData 缺表与首次发布。不要把缺表当成成功的空收件箱或无领取记录,也不要仅切换邮件查询env。ServiceNotInitialized:先确认是否await OperationMailApi.WaitUntilReadyAsync(),以及服务端 Host provider 是否初始化。FailedToSend:Host 拒绝或发送失败,可有限重试,不要无限循环。QueryMapNameMissing/QueryEnvironmentMissing:确认传入的是发布地图名和后台环境名。QueryUserIdMissing:确认使用平台User.UserId,不是本局Player.Id。ResultParseFailed:通常是后台返回结构和解析字段不一致,需要记录原始错误并对齐后台字段。- 查询为空:先确认
mapName、env、state = 0、时间窗口和个人userId。 - 环境:优先传
Game.RuntimeEnvironment的枚举重载,映射Debug/Test/Formal为后台debug/test/formal;Unknown会抛出参数异常。原字符串重载保留兼容,不能把部署环境production当作邮件env。 - 时区:后台邮件窗口按北京时间配置;传
DateTime.UtcNow或服务端DateTime.Now,查询层自动转北京墙钟。旧项目若手动DateTime.Now.AddHours(8),升级后必须去掉。Unspecified必须已经是北京墙钟;返回的StartTime/EndTime是 UTC,展示前需转换。
相关文档
docs/systems/OperationMailSystem.mddocs/systems/GameSystemUIMail.mddocs/systems/CloudDataSystem.mddocs/ai/skills/cloud-data/SKILL.md