新增私聊相关

This commit is contained in:
2025-08-18 23:24:33 +08:00
parent 34b25273a7
commit 8122c577f6
68 changed files with 1442 additions and 467 deletions

View File

@@ -0,0 +1,44 @@
using Fantasy;
using Fantasy.Async;
using Fantasy.Network.Interface;
namespace NB.Chat;
public class C2S_DeleteMailRequestHandler : RouteRPC<SocialUnit, C2S_DeleteMailRequest, S2C_DeleteMailResponse>
{
protected override async FTask Run(SocialUnit entity, C2S_DeleteMailRequest request,
S2C_DeleteMailResponse response, Action reply)
{
if (request.Id < 1)
{
response.ErrorCode = ErrorCode.ErrArgs;
return;
}
var mailManageComponent = entity.Scene.GetComponent<MailManageComponent>();
if (mailManageComponent == null)
{
Log.Error("组件不存在 MailManageComponent");
response.ErrorCode = ErrorCode.ErrServer;
return;
}
var conversation = await mailManageComponent.GetConversation(entity.Id, request.Id);
if (conversation == null)
{
response.ErrorCode = ErrorCode.ErrArgs;
return;
}
conversation.RemoveId.Add(entity.Id);
if (conversation.RemoveId.Count > 1)
{
//都删除了,则吧会话直接移除掉
await mailManageComponent.DeleteConversation(entity.Id, request.Id);
}
else
{
await conversation.Save();
}
}
}

View File

@@ -0,0 +1,22 @@
using System.Diagnostics;
using Fantasy;
using Fantasy.Async;
using Fantasy.Network.Interface;
namespace NB.Chat;
public class
C2S_GetConversationsRequestHandler : RouteRPC<SocialUnit, C2S_GetConversationsRequest, S2C_GetConversationsResponse>
{
protected override async FTask Run(SocialUnit entity, C2S_GetConversationsRequest request,
S2C_GetConversationsResponse response, Action reply)
{
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
var mailManageComponent = entity.Scene.GetComponent<MailManageComponent>();
var list = await mailManageComponent.GetConversations(entity.Id);
response.List = await list.ToInfo(entity.Scene, entity.Id);
stopwatch.Stop();
Log.Info($"查询会话列表耗时={stopwatch.ElapsedMilliseconds}");
}
}

View File

@@ -0,0 +1,68 @@
using Fantasy;
using Fantasy.Async;
using Fantasy.Network.Interface;
namespace NB.Chat;
public class C2S_SendMailRequestHandler : RouteRPC<SocialUnit, C2S_SendMailRequest, S2C_SendMailResponse>
{
protected override async FTask Run(SocialUnit entity, C2S_SendMailRequest request, S2C_SendMailResponse response,
Action reply)
{
if (request.Target < 1)
{
response.ErrorCode = ErrorCode.ErrArgs;
return;
}
var mailManageComponent = entity.Scene.GetComponent<MailManageComponent>();
if (mailManageComponent == null)
{
Log.Error("组件不存在 MailManageComponent");
response.ErrorCode = ErrorCode.ErrServer;
return;
}
var chatUnitManage = entity.Scene.GetComponent<SocialUnitManageComponent>();
if (chatUnitManage == null)
{
Log.Error("组件不存在 SocialUnitManageComponent");
response.ErrorCode = ErrorCode.ErrServer;
return;
}
var conversation = await mailManageComponent.GetConversation(entity.Id, request.Target);
if (conversation == null)
{
response.ErrorCode = ErrorCode.ErrArgs;
return;
}
//检查是否可以发消息,如果会话只有一句,则不允许再发
if (!conversation.CanSend(entity.Id))
{
response.ErrorCode = ErrorCode.MailNotReply;
return;
}
var mail = MailFactory.Create(entity.Scene, request.Content);
mail.Sender = entity.Id;
mail.OwnerId = request.Target;
await conversation.Add(mail);
var res = new S2C_HaveMail()
{
Mail = mail.ToMailInfo(),
Key = conversation.Key
};
//同步客户端
entity.Scene.NetworkMessagingComponent.SendInnerRoute(entity.GateRouteId, res);
var chatUnit = chatUnitManage.Get(request.Target);
if (chatUnit != null)
{
//对方在线
entity.Scene.NetworkMessagingComponent.SendInnerRoute(chatUnit.GateRouteId, res);
}
}
}

View File

@@ -0,0 +1,55 @@
using Fantasy;
using Fantasy.Entitas;
using Fantasy.Helper;
using NB.Game;
namespace NB.Chat;
public static class MailBoxFactory
{
/// <summary>
/// 创建一个邮件箱
/// </summary>
/// <param name="scene"></param>
/// <param name="sendAccountId"></param>
/// <param name="mail"></param>
/// <param name="expireTime"></param>
/// <param name="accountIds"></param>
/// <returns></returns>
public static MailBox Create(Scene scene, long sendAccountId, Mail mail, int expireTime, List<long> accountIds)
{
var mailBox = Entity.Create<MailBox>(scene, true, true);
mailBox.SendAccountId = sendAccountId;
mailBox.Mail = mail;
mailBox.ExpireTime = TimeHelper.Now + expireTime;
mailBox.CreateTime = TimeHelper.Now;
if (accountIds == null || accountIds.Count <= 0)
{
return mailBox;
}
foreach (var accountId in accountIds)
{
mailBox.AccountId.Add(accountId);
}
return mailBox;
}
/// <summary>
/// 创建一个邮件箱
/// </summary>
/// <param name="scene"></param>
/// <param name="sendAccountId"></param>
/// <param name="expireTime"></param>
/// <param name="accountIds"></param>
/// <param name="content"></param>
/// <param name="items"></param>
/// <returns></returns>
public static MailBox Create(Scene scene, long sendAccountId, int expireTime, List<long> accountIds,
string content, List<AwardItem> items = null)
{
var mail = MailFactory.Create(scene, content, items);
return Create(scene, sendAccountId, mail, expireTime, accountIds);
}
}

View File

@@ -0,0 +1,38 @@
using Fantasy;
using Fantasy.Async;
using NB.Game;
namespace NB.Chat;
public static class MailConversationHelper
{
/// <summary>
/// 从数据库中读取GameAccount
/// </summary>
/// <param name="scene"></param>
/// <param name="firstId"></param>
/// <param name="secondId"></param>
/// <returns></returns>
public static async FTask<MailConversation> LoadDataBase(Scene scene, long firstId, long secondId)
{
var conversation =
await scene.World.DataBase.First<MailConversation>(d => d.FirstId == firstId && d.SecondId == secondId);
if (conversation == null)
{
return null;
}
conversation.Deserialize(scene);
return conversation;
}
/// <summary>
/// 从数据库中移除
/// </summary>
/// <param name="scene"></param>
/// <param name="id"></param>
public static async FTask DeleteDataBase(Scene scene,long id)
{
await scene.World.DataBase.Remove<MailConversation>(id);
}
}

View File

@@ -0,0 +1,30 @@
using Fantasy;
using Fantasy.Entitas;
using Fantasy.Helper;
using Fantasy.Serialize;
using NB.Game;
namespace NB.Chat;
public static class MailFactory
{
private static readonly ISerialize _serializer = SerializerManager.GetSerializer(FantasySerializerType.Bson);
public static Mail Create(Scene scene, string content, List<AwardItem> items = null)
{
var mail = Entity.Create<Mail>(scene, true, true);
mail.Content = content;
mail.State = MailState.Unread;
mail.CreateTime = TimeHelper.Now;
if (items != null && items.Count > 0)
{
foreach (var item in items)
{
mail.Items.Add(_serializer.Clone(item));
}
}
return mail;
}
}

View File

@@ -0,0 +1,32 @@
using Fantasy;
using Fantasy.Async;
namespace NB.Chat;
/// <summary>
/// 发送邮件接口
/// </summary>
public static class MailHelper
{
public static async FTask Send(Scene scene, MailBox mailBox)
{
if (mailBox.BoxType == MailBoxType.None)
{
Log.Error("不支持的邮件类型");
return;
}
switch (mailBox.BoxType)
{
case MailBoxType.All:
{
break;
}
case MailBoxType.Specify:
{
break;
}
}
}
}

View File

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

View File

@@ -0,0 +1,79 @@
using Fantasy;
using Fantasy.Async;
using Fantasy.Entitas.Interface;
using Fantasy.Helper;
namespace NB.Chat;
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 static class MailComponentSystem
{
public static async FTask Add(this MailComponent self, Mail mail, bool sync)
{
mail.CreateTime = TimeHelper.Now;
mail.ExpireTime = TimeHelper.Now + MailComponent.MaxExpireTime;
if (self.Mails.Count >= MailComponent.MaxMailCount)
{
//删除最老的邮件
var (_, value) = self.Timer.First();
foreach (var removeId in value)
{
await self.Remove(removeId, sync);
}
}
self.Mails.Add(mail.Id, mail);
self.Timer.Add(mail.ExpireTime, mail.Id);
if (sync)
{
//同步客户端
self.Scene.NetworkMessagingComponent.SendInnerRoute(0,new S2C_HaveMail()
{
Mail = mail.ToMailInfo(),
});
}
await self.Scene.World.DataBase.Save(self);
Log.Info($"MailComponent Add id:{self.Id} mailId:{mail.Id} count:{self.Mails.Count}");
}
public static async FTask<uint> Remove(this MailComponent self, long mailId, bool sync)
{
if (!self.Mails.Remove(mailId, out var mail))
{
return 1;
}
self.Timer.RemoveValue(mail.ExpireTime, mail.Id);
mail.Dispose();
if (sync)
{
//同步客户端
self.Scene.NetworkMessagingComponent.SendInnerRoute(0,new S2C_MailState()
{
MailState = (int)MailState.Deleted,
MailId = mailId,
});
}
await self.Scene.World.DataBase.Save(self);
Log.Info($"MailComponent Remove id:{self.Id} mailId:{mail.Id} count:{self.Mails.Count}");
return 0;
}
}

View File

@@ -0,0 +1,126 @@
using Fantasy;
using Fantasy.Async;
using Fantasy.Entitas.Interface;
using Fantasy.Helper;
using NB.Game;
namespace NB.Chat;
public class MailConversationDeserializeSystem : DeserializeSystem<MailConversation>
{
protected override void Deserialize(MailConversation self)
{
self.Key = $"{self.FirstId}-{self.SecondId}";
}
}
public class MailConversationDestroySystem : DestroySystem<MailConversation>
{
protected override void Destroy(MailConversation self)
{
foreach (var mail in self.Mails)
{
mail.Dispose();
}
self.Mails.Clear();
self.FirstId = 0;
self.SecondId = 0;
self.Key = string.Empty;
}
}
public static class MailConversationSystem
{
public static async FTask Add(this MailConversation self, Mail mail)
{
self.Mails.Add(mail);
if (self.Mails.Count > AppConfig.MaxConversationCount)
{
self.Mails.RemoveAt(0);
}
if (mail.Items != null && mail.Items.Count > 0)
{
//有附件需要立马保存
await self.Save(true);
}
else
{
await self.Save(false);
}
}
public static async FTask Save(this MailConversation self, bool forceSave = true)
{
// self.NeedSave = true;
// self.NeedSaveTime = TimeHelper.Now + AppConfig.PlayerDataAutoSaveTime;
if (forceSave)
{
self.UpdateTime = TimeHelper.Now;
await self.Scene.World.DataBase.Save(self);
}
else
{
self.NeedSave = true;
self.NeedSaveTime = TimeHelper.Now + AppConfig.ChatDataAutoSaveTime;
}
}
/// <summary>
/// 是否可以发送邮件
/// </summary>
/// <param name="self"></param>
/// <param name="sendId"></param>
/// <returns></returns>
public static bool CanSend(this MailConversation self, long sendId)
{
if (self.Mails.Count < 1) return true;
foreach (var mail in self.Mails)
{
if (mail.Sender != sendId)
{
return true;
}
}
return false;
}
#region
public static async FTask<List<ConversationInfo>> ToInfo(this List<MailConversation> self, Scene scene, long selfId)
{
List<ConversationInfo> ret = new List<ConversationInfo>();
HashSet<long> ids = new HashSet<long>();
foreach (var conversation in self)
{
if (conversation.RemoveId.Contains(selfId)) continue;
ids.Add(conversation.FirstId);
ids.Add(conversation.SecondId);
}
ids.Remove(selfId);
var infos = await CacheHandler.GetPlayerBasicCacheInfos(scene, ids.ToList());
foreach (var conversation in self)
{
if (conversation.RemoveId.Contains(selfId)) continue;
var item = new ConversationInfo();
item.List = conversation.Mails.ToMailInfo();
var otherId = conversation.FirstId == selfId ? conversation.SecondId : conversation.FirstId;
var info = infos.Find(t => t.RoleId == otherId);
if (info != null)
{
item.RoleInfo = info;
}
ret.Add(item);
}
return ret;
}
#endregion
}

View File

@@ -0,0 +1,93 @@
using Fantasy;
using Fantasy.Async;
using Fantasy.Entitas;
using Fantasy.Entitas.Interface;
using Fantasy.Helper;
using NB.Game;
namespace NB.Chat;
public class MailManageComponentDestroySystem : DestroySystem<MailManageComponent>
{
protected override void Destroy(MailManageComponent self)
{
foreach (var (_, conversation) in self.Conversations)
{
conversation.Dispose();
}
self.Conversations.Clear();
}
}
public static class MailManageComponentSystem
{
public static async FTask DeleteConversation(this MailManageComponent self, long selfId, long otherId)
{
var firstId = selfId;
var secondId = otherId;
if (otherId < selfId)
{
firstId = otherId;
secondId = selfId;
}
var key = $"{firstId}-{secondId}";
if (self.Conversations.Remove(key, out var conversation))
{
await MailConversationHelper.DeleteDataBase(conversation.Scene, conversation.Id);
conversation.Dispose();
}
}
public static async FTask<MailConversation?> GetConversation(this MailManageComponent self, long selfId,
long otherId)
{
var firstId = selfId;
var secondId = otherId;
if (otherId < selfId)
{
firstId = otherId;
secondId = selfId;
}
var key = $"{firstId}-{secondId}";
if (!self.Conversations.TryGetValue(key, out var conversation))
{
//检查数据库中是否存在
conversation = await MailConversationHelper.LoadDataBase(self.Scene, firstId, secondId);
if (conversation == null)
{
//检查id是否合法
var roleInfo = await CacheHandler.GetPlayerBasicCacheInfo(self.Scene, otherId);
if (roleInfo == null)
{
return null;
}
conversation = Entity.Create<MailConversation>(self.Scene, true, true);
conversation.FirstId = firstId;
conversation.SecondId = secondId;
conversation.Key = key;
await conversation.Save(false);
}
self.Conversations.Add(key, conversation);
}
return conversation;
}
public static async FTask<List<MailConversation>> GetConversations(this MailManageComponent self, long id)
{
List<MailConversation> players =
await self.Scene.World.DataBase.QueryByPageOrderBy<MailConversation>(
d => d.FirstId == id || d.SecondId == id, 1, 50,
d => d.UpdateTime);
return players;
}
}

View File

@@ -0,0 +1,52 @@
using Fantasy;
using Fantasy.Entitas.Interface;
using NB.Game;
namespace NB.Chat;
public class MailDestroySystem : DestroySystem<Mail>
{
protected override void Destroy(Mail self)
{
self.OwnerId = 0;
self.Sender = 0;
self.Content = string.Empty;
self.ExpireTime = 0;
self.CreateTime = 0;
self.State = MailState.None;
self.MailType = MailType.None;
foreach (var item in self.Items)
{
item.Dispose();
}
self.Items.Clear();
}
}
public static class MailSystem
{
public static MailInfo ToMailInfo(this Mail mail)
{
return new MailInfo()
{
Id = mail.Id,
Content = mail.Content,
CreateTime = mail.CreateTime,
MailState = (int)mail.State,
MailType = (int)mail.MailType,
Items = mail.Items.ToAwardInfo()
};
}
public static List<MailInfo> ToMailInfo(this List<Mail> mails)
{
var list = new List<MailInfo>();
foreach (var mail in mails)
{
list.Add(mail.ToMailInfo());
}
return list;
}
}