提交示例代码

This commit is contained in:
Bob.Song
2026-03-05 11:39:06 +08:00
commit 25958f58c3
2534 changed files with 209593 additions and 0 deletions

View File

@@ -0,0 +1,225 @@
using Fantasy.Async;
using Fantasy.DataStructure.Collection;
using Fantasy.Entitas.Interface;
using Fantasy.Helper;
namespace Fantasy;
public class MailBoxManageComponentDestroySystem : DestroySystem<MailBoxManageComponent>
{
protected override void Destroy(MailBoxManageComponent self)
{
if (self.TimerId != 0)
{
self.Scene.TimerComponent.Net.Remove(ref self.TimerId);
}
foreach (var (_, mailBox) in self.MailBoxes)
{
mailBox.Dispose();
}
self.MinTime = 0;
self.MailsByAccount.Clear();
self.MailsByMailBoxType.Clear();
self.Timers.Clear();
self.TimeOutQueue.Clear();
}
}
public static class MailBoxManageComponentSystem
{
public static async FTask Init(this MailBoxManageComponent self)
{
// 获取数据库中所有的没有处理完成的MailBox
var mailBoxes = await self.Scene.World.DataBase.Query<MailBox>(d => true);
foreach (var mailBox in mailBoxes)
{
self.MailBoxes.Add(mailBox.Id, mailBox);
switch (mailBox.MailBoxType)
{
case MailBoxType.Specify:
{
foreach (var accountId in mailBox.AccountId)
{
self.MailsByAccount.Add(accountId, mailBox);
}
break;
}
case MailBoxType.All:
case MailBoxType.AllToDate:
{
self.MailsByMailBoxType.Add((int)mailBox.MailBoxType, mailBox);
break;
}
}
}
self.TimerId = self.Scene.TimerComponent.Net.RepeatedTimer(MailBoxManageComponent.MailCheckTIme, self.Timeout);
}
private static void Timeout(this MailBoxManageComponent self)
{
var currentTime = TimeHelper.Now;
if (currentTime < self.MinTime)
{
return;
}
// 检查当然时候有过期的邮件箱需要处理
foreach (var (timeKey, _) in self.Timers)
{
if (timeKey > currentTime)
{
self.MinTime = timeKey;
break;
}
self.TimeOutQueue.Enqueue(timeKey);
}
while (self.TimeOutQueue.TryDequeue(out var timeKey))
{
foreach (var mailBoxId in self.Timers[timeKey])
{
Log.Info($"MailBox:{mailBoxId} 过期了!");
self.Remove(mailBoxId).Coroutine();
}
self.Timers.RemoveKey(timeKey);
}
}
public static async FTask Remove(this MailBoxManageComponent self, long mailBoxId)
{
if (!self.MailBoxes.Remove(mailBoxId, out var mailBox))
{
return;
}
// 先删除按照分类存储的邮箱
self.MailsByMailBoxType.RemoveValue((int)mailBox.MailBoxType, mailBox);
// 删除个人邮件的邮件箱
self.MailsByAccount.RemoveValue(mailBoxId, mailBox);
// 在数据库中删除这个邮件
await self.Scene.World.DataBase.Remove<MailBox>(mailBoxId);
// 销毁这个MailBox
mailBox.Dispose();
}
public static async FTask GetHaveMail(this MailBoxManageComponent self, MailUnit mailUnit, bool sync)
{
var now = TimeHelper.Now;
var worldDataBase = self.Scene.World.DataBase;
var mailComponent = mailUnit.GetComponent<MailComponent>();
// 玩家领取范围邮件的逻辑处理
foreach (var (mailBoxType, mailBoxList) in self.MailsByMailBoxType)
{
using var removeMailBox = ListPool<MailBox>.Create();
switch ((MailBoxType)mailBoxType)
{
// 发送给所有人的邮件
case MailBoxType.All:
{
foreach (var mailBox in mailBoxList)
{
if (!mailBox.Received.Contains(mailUnit.Id))
{
// 如果是没有领取过这个邮件,首先要把自己添加到领取过的名单中。
mailBox.Received.Add(mailUnit.Id);
// 发送给自己的邮件列表里添加邮件。
await mailUnit.GetComponent<MailComponent>().Add(MailFactory.Serializer.Clone(mailBox.Mail), true);
}
if (mailBox.ExpireTime <= now)
{
// 邮件已经过期了,要进行清理这个邮件的操作了
removeMailBox.Add(mailBox);
continue;
}
// 保存邮件箱状态到数据库中。
await worldDataBase.Save(mailBox);
}
foreach (var mailBox in removeMailBox)
{
await self.Remove(mailBox.Id);
}
// 这里有一个小的细节要处理,这里大家课下自己实现一下。
break;
}
case MailBoxType.AllToDate:
{
foreach (var mailBox in mailBoxList)
{
Log.Debug($"mailBox.CreateTime >= mailUnit.CreateTime {mailBox.CreateTime} >= {mailUnit.CreateTime}");
if (mailBox.CreateTime >= mailUnit.CreateTime && !mailBox.Received.Contains(mailUnit.Id))
{
// 如果是没有领取过这个邮件,首先要把自己添加到领取过的名单中。
mailBox.Received.Add(mailUnit.Id);
// 发送给自己的邮件列表里添加邮件。
await mailUnit.GetComponent<MailComponent>().Add(MailFactory.Serializer.Clone(mailBox.Mail), true);
}
if (mailBox.ExpireTime <= now)
{
// 邮件已经过期了,要进行清理这个邮件的操作了
removeMailBox.Add(mailBox);
continue;
}
// 保存邮件箱状态到数据库中。
await worldDataBase.Save(mailBox);
}
foreach (var mailBox in removeMailBox)
{
await self.Remove(mailBox.Id);
}
break;
}
}
}
if (self.MailsByAccount.TryGetValue(mailUnit.AccountId, out var mailBoxes))
{
using var removeMailBox = ListPool<MailBox>.Create();
foreach (var mailBox in mailBoxes)
{
var cloneMail = MailFactory.Serializer.Clone(mailBox.Mail);
await mailComponent.Add(cloneMail, sync);
mailBox.AccountId.Remove(mailUnit.AccountId);
Log.Debug($"领取了一个离线邮件 MailId:{cloneMail.Id}");
if (mailBox.AccountId.Count <= 0)
{
// 当邮件箱里没有要发送的玩家了,就表示这个邮件箱已经没有用处,可以删除了。
removeMailBox.Add(mailBox);
}
}
foreach (var mailBox in removeMailBox)
{
await self.Remove(mailBox.Id);
}
if (mailBoxes.Count <= 0)
{
// 如果MailsByAccount里的邮件箱已经没有邮件了就删除这个邮件箱的缓存。
self.MailsByAccount.RemoveByKey(mailUnit.AccountId);
}
}
}
}

View File

@@ -0,0 +1,226 @@
using Fantasy.Async;
using Fantasy.DataStructure.Collection;
using Fantasy.Entitas.Interface;
using Fantasy.Helper;
#pragma warning disable CS8619 // Nullability of reference types in value doesn't match target type.
namespace Fantasy;
public class MailComponentDestroySystem : DestroySystem<MailComponent>
{
protected override void Destroy(MailComponent self)
{
foreach (var (_, mail) in self.Mails)
{
mail.Dispose();
}
self.Mails.Clear();
self.Timer.Clear();
}
}
public class MailComponentDeserializeSystem : DeserializeSystem<MailComponent>
{
protected override void Deserialize(MailComponent self)
{
self.Timer.Clear();
foreach (var (_, mail) in self.Mails)
{
self.Timer.Add(mail.ExpireTime, mail.Id);
}
}
}
public static class MailComponentSystem
{
public static async FTask Add(this MailComponent self, Mail mail, bool sync)
{
mail.CreateTime = TimeHelper.Now;
mail.ExpireTime = mail.CreateTime + MailComponent.MailExpireTime;
// 如果身上的邮件数量,大于了设置的最大数量怎么办?
// 先不用考虑,稍后咱们再解决问题。
if (self.Mails.Count >= MailComponent.MaxMailCount)
{
// 删除最老的邮件。
var (_, value) = self.Timer.First();
var removeId = value[0];
await self.Remove(removeId, MailRemoveActionType.Overtop, sync);
}
self.Mails.Add(mail.Id, mail);
self.Timer.Add(mail.ExpireTime, mail.Id);
if (sync)
{
// 同步邮件状态给客户端。
self.Scene.NetworkMessagingComponent.SendInnerRoute(self.GetParent<MailUnit>().GateRouteId, new Mail2C_HaveMail()
{
Mail = mail.ToMailSimplifyInfo()
});
}
// 这里的保存,也可以不用,这里看情况而定。
await self.Scene.World.DataBase.Save(self);
Log.Info($"MailComponentSystem Add {mail.Id} Count:{self.Mails.Count}");
}
public static async FTask<uint> Remove(this MailComponent self, long mailId, MailRemoveActionType mailRemoveActionType ,bool sync)
{
if (!self.Mails.Remove(mailId, out var mail))
{
// 这里的1代表的没有找到对应邮件。
return 1;
}
self.Timer.RemoveValue(mail.ExpireTime, mail.Id);
mail.Dispose();
if (sync)
{
// 同步给客户端,邮件删除的消息。
self.Scene.NetworkMessagingComponent.SendInnerRoute(self.GetParent<MailUnit>().GateRouteId,
new Mail2C_MailState()
{
MailState = (int)mailRemoveActionType, MailId = mailId
});
}
// 保存下数据库。
await self.Scene.World.DataBase.Save(self);
Log.Info($"MailComponentSystem Remove {mailId} mailRemoveActionType:{mailRemoveActionType} Count:{self.Mails.Count}");
return 0;
}
public static async FTask CheckTimeOut(this MailComponent self)
{
var now = TimeHelper.Now;
using var listPool = ListPool<long>.Create();
foreach (var (_, mail) in self.Mails)
{
if (mail.ExpireTime > now)
{
continue;
}
listPool.Add(mail.Id);
}
foreach (var mailId in listPool)
{
await self.Remove(mailId, MailRemoveActionType.ExpireTimeRemove,false);
}
Log.Info($"MailComponentSystem CheckTimeOut Count:{listPool.Count}");
}
public static async FTask<(uint ErrorCode, Mail mail)> OpenMail(this MailComponent self, long mailId)
{
if (!self.Mails.TryGetValue(mailId, out var mail))
{
// 这个1代表的是没有找到对应邮件
return (1, null);
}
if (mail.ExpireTime < TimeHelper.Now)
{
// 如果邮件已经过期了,需要清楚这个邮件。
await self.Remove(mailId, MailRemoveActionType.ExpireTimeRemove, true);
// 这里2代表的是邮件已经过期。
return (2, null);
}
mail.MailState = MailState.HaveRead;
// 这个保存数据库不是必须要保存的,因为一个邮件的查看状态并不能影响游戏的逻辑。
// 这里的话,大家看自己的需求而定。
await self.Scene.World.DataBase.Save(self);
return (0, mail);
}
public static void GetMailSimplifyInfos(this MailComponent self, ICollection<MailSimplifyInfo> mailSimplifyInfos)
{
foreach (var (_, mail) in self.Mails)
{
mailSimplifyInfos.Add(mail.ToMailSimplifyInfo());
}
}
public static void GetMailSimplifyInfos(this MailComponent self, ICollection<MailSimplifyInfo> mailSimplifyInfos, int pageIndex, int pageSize)
{
foreach (var (_, mail) in self.Mails.Skip((pageIndex -1) * pageSize).Take(pageSize))
{
mailSimplifyInfos.Add(mail.ToMailSimplifyInfo());
}
}
public static async FTask<uint> Receive(this MailComponent self, long mailId, bool money, List<long> item, bool sync)
{
if (!self.Mails.TryGetValue(mailId, out var mail))
{
// 这里的1代表是没有找到该邮件。
return 1;
}
var needSave = false;
if (money && mail.Money > 0)
{
// 领取金钱一般都是在玩家身上但现在咱们所在的服务器是Mail服务器玩家并不在这个服务器.
// 所以一般金钱的操作会有一个专用的接口来操作。这个接口无论在什么服务器上,都会正确的发送到用户所在的服务器上进行金钱操作。
// 假设下面的增加金钱的一个异步接口。
// var resposne = await MoneyHelper.Add(self.Scene, mail.Money, sync);
// if (resposne.ErrorCode != 0)
// {
// // 这里的2代表的是金钱添加失败。
// return 2;
// }
// 再假设增加金钱是成功的,那么咱们就要处理邮件相关信息了。
mail.Money = 0;
needSave = true;
}
if (item != null && item.Count > 0)
{
using var listItem = ListPool<Item>.Create();
foreach (var itemId in item)
{
var rItem = mail.Items.FirstOrDefault(d => d.Id == itemId);
if (rItem == null)
{
continue;
}
listItem.Add(rItem);
}
// 假设背包在其他服务器,需要调用某一个接口来添加物品到目标服务器的背包身上。
// var response = await BagHelper.Add(self.Scene, listItem, sync);
// if (response.ErrorCode != 0)
// {
// return 2;
// }
// 还有一种情况就是背包里的空间只有一个空余位置了也就是只能放进一个物品了但是邮件领取的是2个物品.
// 会有下面2中情况,当然这个情况是要策划来决定怎么处理:
// 1、只领取一个物品到背包中另外一个不领取然后提示用户背包已满。
// 2、一个都不领取直接提示用户背包已满。
// 如果是是第一种情况下把BagHelper.Add接口就需要返回添加成功的物品ID方便邮件来处理。
// 假设全部物品都领取成功了,就可以执行下面的逻辑了。
foreach (var item1 in listItem)
{
mail.Items.Remove(item1);
item1.Dispose();
}
needSave = true;
}
if (needSave)
{
await self.Scene.World.DataBase.Save(self);
}
return 0;
}
}

View File

@@ -0,0 +1,176 @@
using Fantasy.Async;
using Fantasy.Entitas;
using Fantasy.Entitas.Interface;
using Fantasy.Helper;
// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
#pragma warning disable CS8601 // Possible null reference assignment.
namespace Fantasy;
public sealed class MailUnitManageComponentDestroySystem : DestroySystem<MailUnitManageComponent>
{
protected override void Destroy(MailUnitManageComponent self)
{
// 如果不是在.net8的环境下比如是Unity里。这个的foreach有可能会出现问题
// 问题的原因是有可能你在mailUnit组件的销毁的时候去UnitByAccountId删除。
// 这样就会出现了一个经典的错误就是无法再foreach里删除或改变元素。
foreach (var (_, mailUnit) in self.UnitByAccountId)
{
mailUnit.Dispose();
}
self.UnitByAccountId.Clear();
self.UnitByName.Clear();
self.Online.Clear();
}
}
public static class MailUnitManageComponentSystem
{
public static async FTask Init(this MailUnitManageComponent self)
{
var units = await self.Scene.World.DataBase.Query<MailUnit>(d => true, true);
foreach (var mailUnit in units)
{
self.UnitByName.Add(mailUnit.Name, mailUnit);
self.UnitByAccountId.Add(mailUnit.AccountId,mailUnit);
}
Log.Debug($"MailUnitManageComponent Init units:{units.Count}");
}
/// <summary>
/// 用户中心登录逻辑,如果有更新的数据,会自动更新。
/// </summary>
/// <param name="self"></param>
/// <param name="accountId"></param>
/// <param name="unitName"></param>
/// <param name="gateRouteId"></param>
/// <returns></returns>
public static async FTask<MailUnit> Online(this MailUnitManageComponent self, long accountId, string unitName, long gateRouteId)
{
var selfScene = self.Scene;
if (self.UnitByAccountId.TryGetValue(accountId, out var mailUnit))
{
Log.Debug($"用户已经存在行不需要重新创建 Name:{unitName} mailUnit:{mailUnit.RuntimeId}");
if (mailUnit.Name != unitName)
{
// 如果不一致的情况,要先删除之前的缓存,然后再添加到缓存。
self.UnitByName.Remove(mailUnit.Name);
self.UnitByName.Add(unitName, mailUnit);
mailUnit.Name = unitName;
}
}
else
{
// 创建新的MailUnit实体。
mailUnit = Entity.Create<MailUnit>(selfScene,accountId, true, true);
mailUnit.Name = unitName;
mailUnit.AccountId = accountId;
mailUnit.CreateTime = TimeHelper.Now;
// 把创建好的实体添加到缓存中,方便后面使用。
self.UnitByAccountId.Add(accountId, mailUnit);
self.UnitByName.Add(unitName,mailUnit);
Log.Debug($"用户不存在行需要创建 Name:{unitName} mailUnit:{mailUnit.RuntimeId}");
}
// 设置GateRouteId
mailUnit.GateRouteId = gateRouteId;
// 设置MailComponent
if (mailUnit.GetComponent<MailComponent>() == null)
{
// 数据库中查询这个组件是否存在。
var mailComponent = await selfScene.World.DataBase.Query<MailComponent>(mailUnit.Id, true);
if (mailComponent == null)
{
// MailUnit没有这个MailComponent组件就给添加一个新的组件。
mailUnit.AddComponent<MailComponent>();
}
else
{
mailUnit.AddComponent(mailComponent);
}
}
// 可选,如果你不需要保存到数据库,那么可以忽略下面的代码。
await selfScene.World.DataBase.Save(mailUnit);
// 领取下离线的邮件。如果有的情况下。
await selfScene.GetComponent<MailBoxManageComponent>().GetHaveMail(mailUnit, true);
// 把用户添加到在线的列表
self.Online.Add(mailUnit.Id, mailUnit);
// 返回MailUnit实体。
return mailUnit;
}
/// <summary>
/// 退出登录逻辑,会自动保存数据。
/// </summary>
/// <param name="self"></param>
/// <param name="mailUnit"></param>
public static async FTask Exit(this MailUnitManageComponent self, MailUnit mailUnit)
{
var mailComponent = mailUnit.GetComponent<MailComponent>();
if (mailComponent == null)
{
return;
}
// 保存mailComponent到数据库中。
await self.Scene.World.DataBase.Save(mailComponent);
// 移除这个组件。
mailUnit.RemoveComponent<MailComponent>();
// 在在线列表的容器中移除这个MailUnit实体。
self.Online.Remove(mailUnit.Id);
Log.Debug($"AccountId:{mailUnit.AccountId} Name:{mailUnit.Name} Exit!");
}
/// <summary>
/// 根据UnitName获取MailUnit实体
/// </summary>
/// <param name="self"></param>
/// <param name="unitName"></param>
/// <param name="mailUnit"></param>
/// <returns></returns>
public static bool TryGet(this MailUnitManageComponent self, string unitName, out MailUnit mailUnit)
{
return self.UnitByName.TryGetValue(unitName, out mailUnit);
}
/// <summary>
/// 根据AccountId获取MailUnit实体
/// </summary>
/// <param name="self"></param>
/// <param name="accountId"></param>
/// <param name="mailUnit"></param>
/// <returns></returns>
public static bool TryGet(this MailUnitManageComponent self, long accountId, out MailUnit mailUnit)
{
return self.UnitByAccountId.TryGetValue(accountId, out mailUnit);
}
/// <summary>
/// 删除MailUnit实体。
/// </summary>
/// <param name="self"></param>
/// <param name="accountId"></param>
/// <returns></returns>
public static async FTask<uint> Remove(this MailUnitManageComponent self, long accountId)
{
// if (!self.UnitByAccountId.TryGetValue(accountId, out var mailUnit))
// {
// // 这个1代表的是没有找到对应的MailUnit
// return 1;
// }
if (!self.UnitByAccountId.Remove(accountId, out var mailUnit))
{
// 这个1代表的是没有找到对应的MailUnit
return 1;
}
self.UnitByName.Remove(mailUnit.Name);
// 在数据库中删除MailUnit实体
await self.Scene.World.DataBase.Remove<MailUnit>(mailUnit.Id);
// 销毁这个MailUnit
mailUnit.Dispose();
return 0;
}
}

View File

@@ -0,0 +1,22 @@
using Fantasy.Entitas.Interface;
namespace Fantasy;
public class MailBoxDestroySystem : DestroySystem<MailBox>
{
protected override void Destroy(MailBox self)
{
if (self.Mail != null)
{
self.Mail.Dispose();
self.Mail = null;
}
self.MailBoxType = MailBoxType.None;
self.CreateTime = 0;
self.ExpireTime = 0;
self.SendAccountId = 0;
self.AccountId.Clear();
self.Received.Clear();
}
}

View File

@@ -0,0 +1,69 @@
using Fantasy.Entitas.Interface;
namespace Fantasy;
public sealed class MailDestroySystem : DestroySystem<Mail>
{
protected override void Destroy(Mail self)
{
self.OwnerId = 0;
self.Title = null;
self.Content = null;
self.CreateTime = 0;
self.ExpireTime = 0;
self.Money = 0;
self.MailState = MailState.None;
self.MailType = MailType.None;
foreach (var selfItem in self.Items)
{
selfItem.Dispose();
}
self.Items.Clear();
}
}
public static class MailSystem
{
public static MailSimplifyInfo ToMailSimplifyInfo(this Mail self)
{
return new MailSimplifyInfo()
{
MailId = self.Id,
OwnerId = self.OwnerId,
Title = self.Title,
Content = self.Content,
CreateTime = self.CreateTime,
ExpireTime = self.ExpireTime,
MailState = (int)self.MailState,
MailType = (int)self.MailType
};
}
public static MailInfo ToMailInfo(this Mail self)
{
var mailInfo = new MailInfo()
{
MailId = self.Id,
OwnerId = self.OwnerId,
Title = self.Title,
Content = self.Content,
CreateTime = self.CreateTime,
ExpireTime = self.ExpireTime,
Money = self.Money,
MailState = (int)self.MailState,
MailType = (int)self.MailType
};
foreach (var selfItem in self.Items)
{
mailInfo.Items.Add(new ItemInfo()
{
Name = selfItem.Name,
});
}
return mailInfo;
}
}

View File

@@ -0,0 +1,14 @@
using Fantasy.Entitas.Interface;
namespace Fantasy;
public class MailUnitDestroySystem: DestroySystem<MailUnit>
{
protected override void Destroy(MailUnit self)
{
self.Name = null;
self.AccountId = 0;
self.CreateTime = 0;
self.GateRouteId = 0;
}
}

View File

@@ -0,0 +1,12 @@
using Fantasy.Async;
using Fantasy.Network.Interface;
namespace Fantasy;
public class Other2Mail_SendRequestHandler : RouteRPC<Scene, Other2Mail_SendMailRequest, Mail2Other_SendMailResponse>
{
protected override async FTask Run(Scene scene, Other2Mail_SendMailRequest request, Mail2Other_SendMailResponse response, Action reply)
{
await MailHelper.Send(scene, request.MailBox);
}
}

View File

@@ -0,0 +1,25 @@
using Fantasy.Async;
using Fantasy.Network.Interface;
// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
namespace Fantasy;
public sealed class C2Mail_GetHaveMailRequestHandler : RouteRPC<MailUnit, C2Mail_GetHaveMailRequest, Mail2C_GetHaveMailResposne>
{
protected override async FTask Run(MailUnit mailUnit, C2Mail_GetHaveMailRequest request, Mail2C_GetHaveMailResposne response, Action reply)
{
var mailComponent = mailUnit.GetComponent<MailComponent>();
// 这个mailComponent是不是可能会为空?答案是可能的。
// 那如果是空的怎么办呢,这样情况只能是别人恶意发包了。
if (mailComponent == null)
{
return;
}
// 检查是否有超时的邮件。如果有那就清楚掉
await mailComponent.CheckTimeOut();
// 领取当前的邮件
mailComponent.GetMailSimplifyInfos(response.Mails);
}
}

View File

@@ -0,0 +1,32 @@
using Fantasy.Async;
using Fantasy.Network.Interface;
namespace Fantasy;
public class C2Mail_OpenMailRequestHandler : RouteRPC<MailUnit, C2Mail_OpenMailRequest, Mail2C_OpenMailResposne>
{
protected override async FTask Run(MailUnit mailUnit, C2Mail_OpenMailRequest request, Mail2C_OpenMailResposne response, Action reply)
{
if (request.MailId <= 0)
{
response.ErrorCode = 100;
return;
}
// 根据这个MailId来拿到邮件的详细信息
var (errorCode, mail) = await mailUnit.GetComponent<MailComponent>().OpenMail(request.MailId);
if (errorCode != 0)
{
response.ErrorCode = errorCode;
return;
}
if (!request.ReturnMailInfo)
{
return;
}
response.MailInfo = mail.ToMailInfo();
}
}

View File

@@ -0,0 +1,19 @@
using Fantasy.Async;
using Fantasy.Network.Interface;
namespace Fantasy;
public class C2Mail_ReceiveMailRequestHandler : RouteRPC<MailUnit, C2Mail_ReceiveMailRequest, Mail2C_ReceiveMailResponse>
{
protected override async FTask Run(MailUnit mailUnit, C2Mail_ReceiveMailRequest request, Mail2C_ReceiveMailResponse response, Action reply)
{
if (request.MailId <= 0)
{
response.ErrorCode = 100;
return;
}
response.ErrorCode = await mailUnit.GetComponent<MailComponent>()
.Receive(request.MailId, request.Money, request.ItemId, true);
}
}

View File

@@ -0,0 +1,21 @@
using Fantasy.Async;
using Fantasy.Network.Interface;
namespace Fantasy;
public class C2Mail_RemoveMailRequestHandler : RouteRPC<MailUnit,C2Mail_RemoveMailRequest, Mail2C_RemoveMailResponse>
{
protected override async FTask Run(MailUnit mailUnit, C2Mail_RemoveMailRequest request, Mail2C_RemoveMailResponse response, Action reply)
{
if (request.MailId <= 0)
{
// 这里的1代表MailId不正确。
response.ErrorCode = 1;
return;
}
response.ErrorCode = await mailUnit.GetComponent<MailComponent>()
.Remove(request.MailId, MailRemoveActionType.Remove, true);
}
}

View File

@@ -0,0 +1,67 @@
using Fantasy.Async;
using Fantasy.DataStructure.Collection;
using Fantasy.Network.Interface;
namespace Fantasy;
public class C2Mail_SendMailRequestHandler : RouteRPC<MailUnit, C2Mail_SendMailRequest, Mail2C_SendMailResponse>
{
protected override async FTask Run(MailUnit mailUnit, C2Mail_SendMailRequest request, Mail2C_SendMailResponse response, Action reply)
{
if (string.IsNullOrEmpty(request.UserName))
{
// 这里的1代表的是发送的接收玩家名字不正确。
response.ErrorCode = 1;
return;
}
if (string.IsNullOrEmpty(request.Title) || string.IsNullOrEmpty(request.Content))
{
// 这里的2代表的是发送的邮件标题或者内容不正确。
response.ErrorCode = 2;
return;
}
if (request.ItemId.Count > 10)
{
// 这里的3代表的是发送的邮件附件超出了最大范围。
response.ErrorCode = 3;
return;
}
if (!mailUnit.Scene.GetComponent<MailUnitManageComponent>().TryGet(request.UserName, out var receiveMailUnit))
{
// 这里的4代表的是没有该玩家。
response.ErrorCode = 4;
return;
}
if (request.Money > 0)
{
// 如果大于0就要调用某一个接口去货币所在的服务器上面去扣除玩家的钱。
// var moneyResposne = await MoneyHelper.Cost(mailUnit.Scene, request.Money);
// if (moneyResposne.ErrorCode != 0)
// {
// response.ErrorCode = moneyResposne.ErrorCode;
// return;
// }
}
using var mailItems = ListPool<Item>.Create();
if (request.ItemId.Count > 0)
{
// var itemResposne = await BagHelper.Get(mailUnit.Scene, request.ItemId);
// if (itemResposne.ErrorCode != 0)
// {
// response.ErrorCode = itemResposne.ErrorCode;
// return;
// }
// mailItems.AddRange(itemResposne.Items);
}
var accountId = ListPool<long>.Create(receiveMailUnit.AccountId);
var mail = MailFactory.Create(mailUnit.Scene, MailType.User, request.Title, request.Content, request.Money, mailItems);
var mailBox = MailBoxFactory.Create(mailUnit.Scene, MailBoxType.Specify, mailUnit.AccountId, mail, 1000 * 60 * 60, accountId);
await MailHelper.Send(mailUnit.Scene, mailBox);
}
}

View File

@@ -0,0 +1,19 @@
using Fantasy.Async;
using Fantasy.DataStructure.Collection;
using Fantasy.Network.Interface;
namespace Fantasy;
public class C2Mail_TestRequestHandler : RouteRPC<MailUnit, C2Mail_TestRequest, Mail2C_TestResponse>
{
protected override async FTask Run(MailUnit mailUnit, C2Mail_TestRequest request, Mail2C_TestResponse response, Action reply)
{
Log.Debug($"这是一个测试的自定义消息协议 Tag:{mailUnit.Name}");
response.Tag = "666";
// using var accountId = ListPool<long>.Create(65491190472245249);
var mail = MailFactory.Create(mailUnit.Scene, MailType.System, "测试所有人指定日期玩家邮件", "测试所有人指定日期玩家邮件内容", 9991);
var mailBox = MailBoxFactory.Create(mailUnit.Scene, MailBoxType.AllToDate, mailUnit.AccountId, mail,
1000 * 60 * 60);
await MailHelper.Send(mailUnit.Scene, mailBox);
}
}

View File

@@ -0,0 +1,12 @@
using Fantasy.Async;
using Fantasy.Network.Interface;
namespace Fantasy;
public class G2Mail_ExitRequestHandler : RouteRPC<MailUnit, G2Mail_ExitRequest, Mail2G_ExitResponse>
{
protected override async FTask Run(MailUnit mailUnit, G2Mail_ExitRequest request, Mail2G_ExitResponse response, Action reply)
{
await mailUnit.Scene.GetComponent<MailUnitManageComponent>().Exit(mailUnit);
}
}

View File

@@ -0,0 +1,26 @@
using Fantasy.Async;
using Fantasy.Entitas;
using Fantasy.Network.Interface;
namespace Fantasy;
public sealed class G2Mail_LoginRequestHandler : RouteRPC<Scene, G2Mail_LoginRequest, Mail2G_LoginResponse>
{
protected override async FTask Run(Scene scene, G2Mail_LoginRequest request, Mail2G_LoginResponse response, Action reply)
{
// Scene也是继承Entity
// 只要能知道Entity的RuntimeId,就可以通过这个RuntimeId发送消息了。
// 并且接收消息的Handler第一个参数的实体可以变成这个Entity的实体
// 也可以理解为这个RuntimeId其实就是RouteId
// var mailUnit = Entity.Create<MailUnit>(scene, request.AccountId, true, true);
// mailUnit.Name = request.Name;
// Log.Debug($"SceneType:{scene.SceneType} Name:{request.Name} mailUnit:{mailUnit.RuntimeId}");
// response.MailUnitRouteId = mailUnit.RuntimeId;
var mailUnit = await scene.GetComponent<MailUnitManageComponent>()
.Online(request.AccountId, request.Name, request.GateRouteId);
response.MailUnitRouteId = mailUnit.RuntimeId;
await FTask.CompletedTask;
}
}

View File

@@ -0,0 +1,13 @@
using Fantasy.Async;
using Fantasy.Network.Interface;
namespace Fantasy;
public sealed class G2Mail_TestMessageHandler : Route<MailUnit,G2Mail_TestMessage>
{
protected override async FTask Run(MailUnit mailUnit, G2Mail_TestMessage message)
{
Log.Debug($"这是一个测试的消息协议 Tag:{mailUnit.Name}");
await FTask.CompletedTask;
}
}

View File

@@ -0,0 +1,62 @@
using Fantasy.Entitas;
using Fantasy.Helper;
// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
namespace Fantasy;
public static class MailBoxFactory
{
/// <summary>
/// 创建一个邮件箱
/// </summary>
/// <param name="scene"></param>
/// <param name="mailBoxType"></param>
/// <param name="sendAccountId"></param>
/// <param name="mail"></param>
/// <param name="expireTime"></param>
/// <param name="accountId"></param>
/// <returns></returns>
public static MailBox Create(Scene scene, MailBoxType mailBoxType, long sendAccountId, Mail mail, int expireTime, List<long> accountId = null)
{
var mailBox = Entity.Create<MailBox>(scene, true, true);
mailBox.SendAccountId = sendAccountId;
mailBox.Mail = mail;
mailBox.MailBoxType = mailBoxType;
mailBox.CreateTime = TimeHelper.Now;
mailBox.ExpireTime = mailBox.CreateTime + expireTime;
if (accountId == null || accountId.Count <= 0)
{
return mailBox;
}
foreach (var raId in accountId)
{
mailBox.AccountId.Add(raId);
}
return mailBox;
}
/// <summary>
/// 创建一个邮件箱
/// </summary>
/// <param name="scene"></param>
/// <param name="mailType"></param>
/// <param name="mailBoxType"></param>
/// <param name="title"></param>
/// <param name="content"></param>
/// <param name="money"></param>
/// <param name="items"></param>
/// <param name="sendAccountId"></param>
/// <param name="expireTime"></param>
/// <param name="accountId"></param>
/// <returns></returns>
public static MailBox Create(Scene scene, MailType mailType, MailBoxType mailBoxType, string title, string content, int money, List<Item> items,
long sendAccountId, int expireTime, List<long> accountId = null)
{
var mail = MailFactory.Create(scene, mailType,title, content, money, items);
return Create(scene, mailBoxType, sendAccountId, mail, expireTime, accountId);
}
}

View File

@@ -0,0 +1,47 @@
using Fantasy.Entitas;
using Fantasy.Serialize;
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
namespace Fantasy;
public static class MailFactory
{
public static readonly ISerialize Serializer = SerializerManager.GetSerializer(FantasySerializerType.Bson);
/// <summary>
/// 创建一个基础的邮件
/// </summary>
/// <param name="scene"></param>
/// <param name="title"></param>
/// <param name="content"></param>
/// <param name="money"></param>
/// <param name="items"></param>
/// <param name="mailType"></param>
/// <returns></returns>
public static Mail Create(Scene scene, MailType mailType, string title, string content, int money = 0, List<Item> items = null)
{
var mail = Entity.Create<Mail>(scene, true, true);
mail.Title = title;
mail.Content = content;
mail.Money = money;
mail.MailType = mailType;
mail.MailState = MailState.Unread;
// if (items is not { Count: > 0 })
// {
//
// }
if (items != null && items.Count > 0)
{
foreach (var item in items)
{
// 最好的是要个这个Item给克隆出一份来。
// 这样就可以保证,无论外面怎么改变也不会影响这个邮件的东西了。
var cloneItem = Serializer.Clone(item);
mail.Items.Add(cloneItem);
}
}
return mail;
}
}

View File

@@ -0,0 +1,193 @@
using Fantasy.Async;
using Fantasy.DataStructure.Collection;
using Fantasy.Platform.Net;
// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
namespace Fantasy;
/// <summary>
/// 发送邮件的唯一接口
/// 如果不是通过这个接口发送的邮件、出现任何问题,后果自负
/// </summary>
public static class MailHelper
{
/// <summary>
/// 发送邮件
/// </summary>
/// <param name="scene"></param>
/// <param name="mailBox">发送完成后记着一定要销毁这个MailBox不然会有GC。</param>
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
});
}
/// <summary>
/// 发送邮件
/// </summary>
/// <param name="scene"></param>
/// <param name="mailBox"></param>
/// <returns></returns>
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<MailUnitManageComponent>();
var mailBoxManageComponent = scene.GetComponent<MailBoxManageComponent>();
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<long>.Create();
foreach (var accountId in mailBox.AccountId)
{
if (!mailUnitManageComponent.TryGet(accountId, out var mailUnit))
{
// 如果没有的话,代表这个用户根本不存在。
// 那这样的情况就不需要做任何处理。
continue;
}
// 如果有的话,那么就给这个用户发送邮件。
// 这个玩家是否在线?
var mailComponent = mailUnit.GetComponent<MailComponent>();
// 在线的话,就直接发送邮件给这个玩家就可以了。
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<MailComponent>();
// if (mailComponent == null)
// {
// continue;
// }
// // 能指定到这里的都是在线的玩家。
// await mailComponent.Add(MailFactory.Serializer.Clone(mailBox.Mail), true);
// }
try
{
foreach (var (_, mailUnit) in mailUnitManageComponent.Online)
{
await mailUnit.GetComponent<MailComponent>().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<MailComponent>().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<MailComponent>().Add(MailFactory.Serializer.Clone(mailBox.Mail), true);
// 在邮件盒子中记录下玩家领取的记录,避免重复领取。
mailBox.Received.Add(mailUnit.Id);
}
// 保存邮件箱到数据库。
await scene.World.DataBase.Save(mailBox);
break;
}
// 根据玩家等级、等等这样的邮件箱类型,都可以自行扩展了
// 课下作业、自己实现一个起来类型的邮箱。
}
}
}