新增私聊相关

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,24 @@
// using Fantasy;
// using Fantasy.Async;
// using Fantasy.Network.Interface;
//
// namespace NB.Chat;
//
// public class
// C2S_GetOfflineMessageRequestHandler : RouteRPC<SocialUnit, C2S_GetOfflineMessageRequest,
// S2C_GetOfflineMessageResponse>
// {
// protected override async FTask Run(SocialUnit entity, C2S_GetOfflineMessageRequest request,
// S2C_GetOfflineMessageResponse response,
// Action reply)
// {
// var chatUnitManage = entity.Scene.GetComponent<SocialUnitManageComponent>();
// if (chatUnitManage.NotSendMessage.TryGetValue(entity.Id, out var list))
// {
// response.Message.AddRange(list);
// chatUnitManage.NotSendMessage.RemoveByKey(entity.Id);
// }
//
// await FTask.CompletedTask;
// }
// }

View File

@@ -0,0 +1,155 @@
using System.Diagnostics;
using System.Text;
using Fantasy;
using Fantasy.Async;
using Fantasy.Helper;
using Fantasy.Network.Interface;
using NB.Game;
namespace NB.Chat;
public sealed class
C2S_SendMessageRequestHandler : RouteRPC<SocialUnit, C2S_SendMessageRequest, S2C_SendMessageResponse>
{
protected override async FTask Run(SocialUnit socialUnit, C2S_SendMessageRequest request,
S2C_SendMessageResponse response, Action reply)
{
if (request.Target < 1)
{
response.ErrorCode = ErrorCode.ErrArgs;
return;
}
SocialSceneHelper.BroadcastChannel(socialUnit.Scene, request.Target, new ChatMessageInfo()
{
SendTime = TimeHelper.Now,
Content = request.Message,
});
// if (request.Type == 0) //频道聊天
// {
// }
// else if (request.Type == 1) //私聊
// {
// //发送私聊
// SocialSceneHelper.PrivateMessage(socialUnit.Scene, socialUnit.Id, request.Target, new ChatMessageInfo()
// {
// SendTime = TimeHelper.Now,
// Content = request.Message,
// });
// }
// var list = new List<long>()
// {
// 337951357380329472,
// 337951357380329473,
// 337951357380329474,
// 337951357380329475,
// 337951357380329476,
// 337951357380329477,
// 337951357380329478,
// 337951357380329479,
// 337951357380329480,
// 337951357380329481,
// 337951357380329482,
// 337951357380329483,
// 337951357380329484,
// 337951357380329485,
// 337951357380329486,
// 337951357380329487,
// 337951357380329488,
// 337951357380329489,
// 337951357380329490,
// 337951357380329491,
// 337951357380329492,
// 337951357380329493,
// 337951357380329494,
// 337951357380329495,
// 337951357380329496,
// 337951357380329497,
// 337951357380329498,
// 337951357380329499,
// 337951357380329500,
// 337951357380329501,
// 337951357380329502,
// 337951357380329503,
// 337951357380329504,
// 337951357380329505,
// 337951357380329506,
// 337951357380329507,
// 337951357380329508,
// 337951357380329509,
// 337951357380329510,
// 337951357380329511,
// 337951357380329512,
// 337951357380329513,
// 337951357380329514,
// 337951357380329515,
// 337951357380329516,
// 337951357380329517,
// 337951357380329518,
// 337951357380329519,
// 337951357380329520,
// 337951357380329521,
// 337951357380329522,
// 337951357380329523,
// 337951357380329524,
// 337951357380329525,
// 337951357380329526,
// 337951357380329527,
// 337951357380329528,
// 337951357380329529,
// 337951357380329530,
// 337951357380329531,
// 337951357380329532,
// 337951357380329533,
// 337951357380329534,
// 337951357380329535,
// 337951357380329536,
// 337951357380329537,
// 337951357380329538,
// 337951357380329539,
// 337951357380329540,
// 337951357380329541,
// 337951357380329542,
// 337951357380329543,
// 337951357380329544,
// 337951357380329545,
// 337951357380329546,
// 337951357380329547,
// 337951357380329548,
// 337951357380329549,
// 337951357380329550,
// 337951357380329551,
// 337951357380329552,
// 337951357380329553,
// 337951357380329554,
// 337951357380329555,
// 337951357380329556,
// 337951357380329557,
// 337951357380329558,
// 337951357380329559,
// 337951357380329560,
// 337951357380329561,
// 337951357380329562,
// 337951357380329563,
// 337951357380329564,
// 337951357380329565,
// 337951357380329566,
// 337951357380329567,
// 337951357380329568,
// 337951357380329569,
// 337951357380329570,
// 337951357380329571
// };
//
// Stopwatch stopwatch = new Stopwatch();
// stopwatch.Start();
// var infoList = await CacheHandler.GetPlayerBasicCacheInfos(socialUnit.Scene,list);
// stopwatch.Stop();
// Log.Info($"查询数量={infoList.Count} 耗时={stopwatch.ElapsedMilliseconds}");
//
await FTask.CompletedTask;
}
}

View File

@@ -17,7 +17,7 @@ public static class ChatUnitManageComponentSystem
/// <param name="message"></param>
public static void SaveOfflineMessage(this SocialUnitManageComponent self, long targetId, ChatMessageInfo message)
{
self.NotSendMessage.Add(targetId, message);
// self.NotSendMessage.Add(targetId, message);
}
#endregion

View File

@@ -1,24 +0,0 @@
using Fantasy;
using Fantasy.Async;
using Fantasy.Network.Interface;
namespace NB.Chat;
public class
C2S_GetOfflineMessageRequestHandler : RouteRPC<SocialUnit, C2S_GetOfflineMessageRequest,
S2C_GetOfflineMessageResponse>
{
protected override async FTask Run(SocialUnit entity, C2S_GetOfflineMessageRequest request,
S2C_GetOfflineMessageResponse response,
Action reply)
{
var chatUnitManage = entity.Scene.GetComponent<SocialUnitManageComponent>();
if (chatUnitManage.NotSendMessage.TryGetValue(entity.Id, out var list))
{
response.Message.AddRange(list);
chatUnitManage.NotSendMessage.RemoveByKey(entity.Id);
}
await FTask.CompletedTask;
}
}

View File

@@ -1,41 +0,0 @@
using System.Text;
using Fantasy;
using Fantasy.Async;
using Fantasy.Helper;
using Fantasy.Network.Interface;
namespace NB.Chat;
public sealed class
C2S_SendMessageRequestHandler : RouteRPC<SocialUnit, C2S_SendMessageRequest, S2C_SendMessageResponse>
{
protected override async FTask Run(SocialUnit socialUnit, C2S_SendMessageRequest request,
S2C_SendMessageResponse response, Action reply)
{
if (request.Target < 1)
{
response.ErrorCode = ErrorCode.ErrArgs;
return;
}
if (request.Type == 0) //频道聊天
{
SocialSceneHelper.BroadcastChannel(socialUnit.Scene, request.Target, new ChatMessageInfo()
{
SendTime = TimeHelper.Now,
Content = request.Message,
});
}
else if (request.Type == 1) //私聊
{
//发送私聊
SocialSceneHelper.PrivateMessage(socialUnit.Scene, socialUnit.Id, request.Target, new ChatMessageInfo()
{
SendTime = TimeHelper.Now,
Content = request.Message,
});
}
await FTask.CompletedTask;
}
}

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;
}
}