using Fantasy.Async; using Fantasy.DataStructure.Collection; using Fantasy.Platform.Net; // ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract namespace Fantasy; /// /// 发送邮件的唯一接口 /// 如果不是通过这个接口发送的邮件、出现任何问题,后果自负 /// public static class MailHelper { /// /// 发送邮件 /// /// /// 发送完成后,记着一定要销毁这个MailBox,不然会有GC。 public static async FTask Send(Scene scene, MailBox mailBox) { if (scene.SceneType == SceneType.Mail) { await InnerSend(scene, mailBox); return; } // 如果不在同一个Scene下,就需要发送内部的网络消息给这个Scene了 var mailSceneConfig = SceneConfigData.Instance.GetSceneBySceneType(SceneType.Mail)[0]; await scene.NetworkMessagingComponent.CallInnerRoute(mailSceneConfig.RouteId, new Other2Mail_SendMailRequest() { MailBox = mailBox }); } /// /// 发送邮件 /// /// /// /// private static async FTask InnerSend(Scene scene, MailBox mailBox) { // 现在的情况,这个接口只能是在Mail这个服务器下可以操作 // 但是真实的情况,其他同时开发的逻辑,调用这个接口一般都是在其他的服务器。 // 这个问题其实很好解决,只需要判定当前Scene不是Mail、那就做一个协议,自动发送到这个MailScene就可以了。 if (mailBox.MailBoxType == MailBoxType.None) { Log.Error("MailBoxType MailBoxType.None not support!"); return; } var mailUnitManageComponent = scene.GetComponent(); var mailBoxManageComponent = scene.GetComponent(); mailBoxManageComponent.MailBoxes.Add(mailBox.Id, mailBox); switch (mailBox.MailBoxType) { case MailBoxType.Specify: { if (mailBox.AccountId.Count <= 0) { Log.Error($"{mailBox.Id} AccountId is 0!"); return; } // 这里可能有几种情况 // 1、AccountId里面可能只有一个人。 // 2、AccountId里面有多个人。 using var sendAccountIds = ListPool.Create(); foreach (var accountId in mailBox.AccountId) { if (!mailUnitManageComponent.TryGet(accountId, out var mailUnit)) { // 如果没有的话,代表这个用户根本不存在。 // 那这样的情况就不需要做任何处理。 continue; } // 如果有的话,那么就给这个用户发送邮件。 // 这个玩家是否在线? var mailComponent = mailUnit.GetComponent(); // 在线的话,就直接发送邮件给这个玩家就可以了。 if (mailComponent != null) { await mailComponent.Add(MailFactory.Serializer.Clone(mailBox.Mail), true); sendAccountIds.Add(accountId); } else { // 不在线 // 首先 // 1、如果玩家不在线,那就把这个邮件在数据库中拿出来,然后把邮件插入到玩家的邮件列表里。然后再保存 // 这样的做法是不推荐的,因为咱们游戏所有的东西都是有状态的,但是你拿出来的邮件是没有状态的。这样可能会导致一些问题的出现。 // 正确的做做法: // 把这个邮件方法一个地方,比如是一个处理中心,当玩家上线的时候,主动去这个中心领取这个邮件。 // 快递驿站、菜鸟、 mailBoxManageComponent.MailsByAccount.Add(accountId, mailBox); Log.Debug("发送离线邮件成功,用户上线第一时间会领取这个邮件。"); } } // 移除掉发送成功的账号。 foreach (var sendAccountId in sendAccountIds) { mailBox.AccountId.Remove(sendAccountId); } // 如果没有任何收件人了、就可以把这个邮箱给删除了。 if (mailBox.AccountId.Count <= 0) { mailBox.Dispose(); } else { // 当这个邮件箱还有没有接收的玩家时候,要保存到数据库中,方便下次停服维护再重新启动的时候,加载这个邮件箱。 await scene.World.DataBase.Save(mailBox); Log.Debug("保存离线邮件成功"); } break; } case MailBoxType.Online: { // // 这里有个问题,如何知道在线的人呢? // foreach (var (_, mailUnit) in mailUnitManageComponent.UnitByAccountId) // { // var mailComponent = mailUnit.GetComponent(); // if (mailComponent == null) // { // continue; // } // // 能指定到这里的都是在线的玩家。 // await mailComponent.Add(MailFactory.Serializer.Clone(mailBox.Mail), true); // } try { foreach (var (_, mailUnit) in mailUnitManageComponent.Online) { await mailUnit.GetComponent().Add(MailFactory.Serializer.Clone(mailBox.Mail), true); } } finally { mailBox.Dispose(); } break; } case MailBoxType.All: { // 要保证这个邮件一定要有一个生命周期。并且这个周期一定要短,如果是想要实现永久的,可以比如30天发送一次。 mailBoxManageComponent.MailsByMailBoxType.Add((int)MailBoxType.All, mailBox); // 首先给所有在线的玩家发送。 foreach (var (_, mailUnit) in mailUnitManageComponent.Online) { await mailUnit.GetComponent().Add(MailFactory.Serializer.Clone(mailBox.Mail), true); // 在邮件盒子中记录下玩家领取的记录,避免重复领取。 mailBox.Received.Add(mailUnit.Id); } // 保存邮件箱到数据库。 await scene.World.DataBase.Save(mailBox); break; } case MailBoxType.AllToDate: { mailBoxManageComponent.MailsByMailBoxType.Add((int)MailBoxType.AllToDate, mailBox); foreach (var (_, mailUnit) in mailUnitManageComponent.Online) { if (mailUnit.CreateTime > mailBox.CreateTime) { // 如果执行到这里,表示这里用户是这个邮件创建之后的用户。这个就不要发送了 continue; } // 所以这个邮件类型的逻辑就是,给当前邮件创建时间之前的玩家发送。 await mailUnit.GetComponent().Add(MailFactory.Serializer.Clone(mailBox.Mail), true); // 在邮件盒子中记录下玩家领取的记录,避免重复领取。 mailBox.Received.Add(mailUnit.Id); } // 保存邮件箱到数据库。 await scene.World.DataBase.Save(mailBox); break; } // 根据玩家等级、等等这样的邮件箱类型,都可以自行扩展了 // 课下作业、自己实现一个起来类型的邮箱。 } } }