Unity Metaverse(七)、基于环信IM SDK实现的好友系统、私聊、群聊

news/2024/10/23 9:25:32/

文章目录

  • 🎈 简介
  • 🎈 用户管理
  • 🎈 好友管理
  • 🎈 聊天管理
    • 🔸 发送与接收消息
    • 🔸 消息处理
      • 消息项的对象池管理


🎈 简介

在之前的文章中已经介绍了如何接入环信IM Unity SDK,及基于该SDK实现用户的登录注册功能,该篇文章介绍的是如何通过它来实现用户管理、好友系统(联系人管理)及聊天消息管理功能。

环信IM SDK
核心功能

🎈 用户管理

SDK为我们提供的用户属性管理包括用户昵称头像邮箱电话性别签名生日等,除此之外,我们可以使用扩展字段Ext来管理业务层所需的其它用户属性。例如在我们的Metaverse项目中,就将用户的Avatar人物信息存储在了Ext扩展字段中,在拿到Ext字段的值后通过反序列化即可得到用户的Avatar人数信息,反之,在用户的Avatar信息发生变更时,只需序列化再存储到Ext扩展字段中并更新用户属性即可。

用户属性管理相关的接口调用封装:

/// <summary>
/// 获取用户信息
/// </summary>
/// <param name="userId">UserID</param>
/// <param name="onSuccess">获取成功回调事件</param>
/// <param name="onError">获取失败回调事件</param>
public void GetUserInfo(string userId, Action<AgoraChat.UserInfo> onSuccess, Action<int, string> onError = null)
{SDKClient.Instance.UserInfoManager.FetchUserInfoByUserId(new List<string>(1) { userId },new ValueCallBack<Dictionary<string, AgoraChat.UserInfo>>(onSuccess: dic =>{AgoraChat.UserInfo userInfo = dic[userId];Main.Log.Info("【环信IM SDK】获取用户{0}信息:{1}", userId, userInfo);onSuccess.Invoke(userInfo);},onError: (code, desc) =>{Main.Log.Info("【环信IM SDK】获取用户{0}信息失败:Code -{1}  Desc -{2}", userId, code, desc);onError?.Invoke(code, desc);}));
}
/// <summary>
/// 更新自身用户信息
/// </summary>
/// <param name="userInfo">用户信息</param>
/// <param name="onSuccess">更新成功回调事件</param>
/// <param name="onError">更新失败回调事件</param>
public void UpdateOwnUserInfo(AgoraChat.UserInfo userInfo, Action onSuccess, Action<int, string> onError = null)
{SDKClient.Instance.UserInfoManager.UpdateOwnInfo(userInfo, new CallBack(onSuccess: () =>{LocalUserInfo.Update(userInfo);Main.Log.Info("【环信IM SDK】更新自身用户信息成功:{0}", LocalUserInfo);onSuccess?.Invoke();},onError: (code, desc) =>{Main.Log.Info("【环信IM SDK】更新自身用户信息失败:Code -{0}  Desc -{1}", code, desc);onError?.Invoke(code, desc);}));
}

🎈 好友管理

添加好友

好友功能是通过环信IM SDK提供的Contact Manager联系人管理来实现的,例如发起添加联系人请求:

/// <summary>
/// 发送添加好友请求
/// </summary>
/// <param name="userId">用户ID</param>
/// <param name="reason">原因/验证信息</param>
/// <param name="onSuccess">请求成功回调事件</param>
/// <param name="onError">请求失败回调事件</param>
public void AddContact(string userId, string reason, Action onSuccess = null, Action<int, string> onError = null)
{SDKClient.Instance.ContactManager.AddContact(userId, reason, new CallBack(onSuccess: () =>{Main.Log.Info("【Contact Manager】添加好友请求成功:UserId -{0}  Reason -{1}", userId, reason);onSuccess?.Invoke();},onError: (code, desc) =>{Main.Log.Info("【Contact Manager】添加好友请求失败:UserId -{0}  Reason -{1}  Code -{2}  Desc -{3}", userId, reason, code, desc);onError?.Invoke(code, desc);}));
}

请求人发起请求后,被请求人如果监听了与联系人管理相关的回调,会执行On Contact Invited回调事件,在回到事件中我们可以通过弹窗形式来让用户决定是否同意添加联系人。如何监听与联系人管理相关的回调?继承IContactManagerDelegate接口并实现,通过Add Contact Manager DelegateRemove Contact Manager Delegate来开启监听和停止监听。

namespace Metaverse
{public class ContactManagerDelegate : IContactManagerDelegate{/// <summary>/// 好友新增事件/// 用户B向用户A发送好友请求,用户A同意该请求,用户A收到该事件。/// </summary>/// <param name="userId">B用户ID</param>public void OnContactAdded(string userId){Main.Log.Info("【环信IM SDK】新增好友{0}", userId);}/// <summary>/// 被删除好友事件/// 用户B将用户A从联系人列表上删除,用户A收到该事件。/// </summary>/// <param name="userId">B用户ID</param>public void OnContactDeleted(string userId){Main.Log.Info("【环信IM SDK】被用户{0}删除好友", userId);}/// <summary>/// 被请求添加好友事件/// 用户B向用户A发送好友请求,用户A收到该事件。/// </summary>/// <param name="userId">B用户ID</param>/// <param name="reason">原因/验证信息</param>public void OnContactInvited(string userId, string reason){Main.Log.Info("【环信IM SDK】收到用户{0}添加好友的请求:{1}", userId, reason);/************************************************************** 收到添加好友的请求,弹出弹窗,让用户点击同意或拒绝* 同意就调用Main.Custom.ContactManager.AcceptAddContact* 拒绝就调用Main.Custom.ContactManager.DeclineAddContact*************************************************************/Main.UI.ShowOrLoadView<PopupView>(ViewLevel.POP, new PopupData("添加好友请求", string.Format("用户{0}请求添加您为好友,是否同意?", userId)){confirm = "同意",cancle = "拒绝",onConfirm = () => Main.Custom.ContactManager.AcceptAddContact(userId),onCancle = () => Main.Custom.ContactManager.DeclineAddContact(userId),});}/// <summary>/// 请求添加好友被对方同意事件/// 用户A向用户B发送好友请求,用户B收到好友请求后,同意加好友,则用户A收到该事件。/// </summary>/// <param name="userId">B用户ID</param>public void OnFriendRequestAccepted(string userId){Main.Log.Info("【环信IM SDK】添加用户{0}为好友的请求被对方同意", userId);}/// <summary>/// 请求添加好友被对方拒绝事件/// 用户A向用户B发送好友请求,用户B收到好友请求后,拒绝加好友,则用户A收到该事件。/// </summary>/// <param name="userId">B用户ID</param>public void OnFriendRequestDeclined(string userId){Main.Log.Info("【环信IM SDK】添加用户{0}为好友的请求被对方拒绝", userId);}}
}
  • 用户A向用户B发送好友请求,用户B同意则调用AcceptAddContact,拒绝则调用DeclineAddContact
  • 用户A向用户B发送好友请求,用户B收到好友请求后,同意加好友,则用户A收到OnFriendRequestAccepted事件;
  • 用户A向用户B发送好友请求,用户B收到好友请求后,拒绝加好友,则用户A收到OnFriendRequestDeclined事件。

🎈 聊天管理

房间 IM消息

私聊消息

聊天是通过环信IM SDK提供的Chat Manager实现的,会话(Conversation)分为三种,单聊群聊聊天室会话

  • 单聊是指两个用户建立的会话,双方可以在会话中收发消息。私聊基于此实现。
  • 群聊会话是由群成员发送消息所组成的,群成员可以在群会话中收发消息。我们的房间IM就是通过群里会话实现的。
  • 聊天室会话并未应用于项目中。

🔸 发送与接收消息

SDK将消息划分为多种类型,包括文本消息语音消息图片消息视频消息文件消息透传消息等,以基本的文本消息为例,消息发送的接口封装如下:

/// <summary>
/// 发送文本消息
/// </summary>
/// <param name="messageType">消息类型 Chat:单聊  Group:群聊  Room:聊天室消息</param>
/// <param name="userOrGroupId">用户或者群聊的ID</param>
/// <param name="content">文本内容</param>
/// <param name="onSuccess">发送成功回调事件</param>
/// <param name="onError">发送失败回调事件</param>
public void SendTextMessage(MessageType messageType, string userOrGroupId,string content, Action<Message> onSuccess = null, Action<int, string> onError = null)
{Message msg = Message.CreateTextSendMessage(userOrGroupId, content);msg.MessageType = messageType;SDKClient.Instance.ChatManager.SendMessage(ref msg, new CallBack(onSuccess: () =>{Main.Log.Info("【Chat Manager】发送文本消息成功:MessageType -{0}  UserOrGroupId -{1}  Content -{2}",messageType, userOrGroupId, content);onSuccess?.Invoke(msg);},onError: (code, desc) =>{Main.Log.Info("【Chat Manager】发送文本消息失败:MessageType -{0}  UserOrGroupId -{1}  Content -{2}  Code -{3}  Desc -{4}",messageType, userOrGroupId, content, code, desc);onError?.Invoke(code, desc);}));
}

当用户监听了与聊天管理相关的回调后,收到消息时会执行On Messages Received回调事件,在事件中处理我们的业务逻辑。如何监听与聊天管理相关的回调?继承IChatManagerDelegate接口并实现,通过Add Chat Manager DelegateRemove Chat Manager Delegate来开启监听和停止监听。

/// <summary>
/// 新消息接收事件
/// </summary>
/// <param name="messages">新消息列表</param>
public void OnMessagesReceived(List<Message> messages)
{for (int i = 0; i < messages.Count; i++){Message msg = messages[i];//抛出事件Main.Events.Publish(MessageReceivedEventArgs.Allocate(msg));}
}

🔸 消息处理

接收到消息后,通过开发框架中Event事件系统将其抛出,好友视图中会订阅该事件来接收来自好友的消息,房间视图中会订阅该事件来接收来自房间内其他用户发送的消息。例如:

namespace Metaverse
{public class RoomPlaceView : UIView{#region >> NonPublic Variables[Tooltip("聊天输入框"), SerializeField] private InputField chatInputField;[Tooltip("聊天项预制件"), SerializeField] private Text chatItemPrefab;[Tooltip("聊天项列表"), SerializeField] private RectTransform chatContent;#endregion#region >> Viewprotected override void OnInit(IViewData data){base.OnInit(data);//订阅消息接收事件Main.Events.Subscribe(MessageReceivedEventArgs.EventID, OnMessageReceivedEvent);}protected override void OnUnload(){base.OnUnload();//取消订阅消息接收事件Main.Events.Unsubscribe(MessageReceivedEventArgs.EventID, OnMessageReceivedEvent);}#endregion#region >> UI Event/// <summary>/// 聊天发送按钮点击事件/// </summary>public void OnSendButtonClick(){//未输入任何内容 返回if (string.IsNullOrEmpty(chatInputField.text)) return;//将当前聊天框中输入的文字内容发送Main.Custom.ChatManager.SendTextMessage(MessageType.Group, (Main.FSM.GetMachine<GamePlace>().CurrentState as PlaceRoom).PlaceID,chatInputField.text, message =>{//添加聊天项AddChatItem(message);//消息发送成功,将聊天框输入的内容清空chatInputField.text = string.Empty;});}#endregion#region >> Subscribed Event//消息接收事件private void OnMessageReceivedEvent(EventArgs e){if (e is MessageReceivedEventArgs mre){//新增聊天项AddChatItem(mre.message, false);}}#endregion#region >> NonPublic Methods/*********************************************************************************** 添加聊天项:*  isFromSelf - 消息发送方是否是自己*  如果不是自己发送的消息 需要根据用户ID获取用户信息**********************************************************************************/private void AddChatItem(Message message, bool isFromSelf = true){if (isFromSelf)Add(Main.Custom.UserManager.LocalUserInfo.NickName, (message.Body as TextBody).Text);elseMain.Custom.UserManager.GetOrQuery(message.From, userInfo => Add(userInfo.NickName, (message.Body as TextBody).Text));void Add(string userName, string content){/********************************************************************************* 此处判断如果PlaceID不一致,不执行代码块中逻辑* 因为此处逻辑的执行是在异步回调中 假如在异步期间已经退出之前的房间 * 则此处会实例化会造成异常********************************************************************************/if ((Main.FSM.GetMachine<GamePlace>().CurrentState as PlaceRoom).PlaceID == message.To){//实例化var instance = Instantiate(chatItemPrefab);//设置父级instance.transform.SetParent(chatContent.transform, false);//消息内容instance.text = string.Format("<color=cyan>{0}:</color>{1}", userName, content);//预制件是隐藏的 实例化后显示instance.gameObject.SetActive(true);//一帧之后更新LayoutGroup自动布局Main.Actions.Sequence(this).Frame(1).Event(() => LayoutRebuilder.ForceRebuildLayoutImmediate(chatContent)).Begin();}}}#endregion}
}

关于会话ID:
Conversation Id,即会话ID,在单聊中它其实就是对方用户的User Id(用户ID),在群聊中它其实就是群组的Group Id(群组ID)。

消息项的对象池管理

每一条消息处理时都需要实例化一个消息项,尤其是在与不同的好友聊天时,消息项会被大量使用,因此在项目中考虑使用对象池来管理:

protected override void OnInit(IViewData data)
{base.OnInit(data);//订阅消息接收事件Main.Events.Subscribe(MessageReceivedEventArgs.EventID, OnMessageReceivedEvent);/********************************************* 初始化对象池* 设置创建方法* 设置最大缓存数量********************************************/Main.ObjectPool.Mono.CreateBy(() =>{//实例化var instance = Instantiate(chatItemPrefabLocal);//设置父级instance.transform.SetParent(chatItemPrefabLocal.transform.parent, false);//获取组件并返回return instance.GetComponent<ChatItemLocal>();});Main.ObjectPool.Mono.SetMaxCacheCount<ChatItemLocal>(99);Main.ObjectPool.Mono.CreateBy(() =>{//实例化var instance = Instantiate(chatItemPrefabRemote);//设置父级instance.transform.SetParent(chatItemPrefabRemote.transform.parent, false);//获取组件并返回return instance.GetComponent<ChatItemRemote>();});Main.ObjectPool.Mono.SetMaxCacheCount<ChatItemRemote>(99);
}
  • 从对象池中获取实例
/*********************************************************************************** 添加聊天项:*  userId - 发送者用户ID 传null表示发送方是自己*      如果发送者是自己 会新增一项ChatItemLocal*      如果发送者是对方 会新增一项ChatItemRemote**********************************************************************************/
private void AddChatItem(Message message, string userId = null)
{bool flag = !string.IsNullOrEmpty(userId);//从对象池中获取实例ChatItemBase instance;if (flag) instance = Main.ObjectPool.Mono.Allocate<ChatItemRemote>();else instance = Main.ObjectPool.Mono.Allocate<ChatItemLocal>();//预制件是隐藏的 实例化后调用显示接口instance.gameObject.SetActive(true);//设置数据instance.Set((message.Body as AgoraChat.MessageBody.TextBody).Text,DateTime2MessageTimeString(new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds(message.ServerTime != 0 ? message.ServerTime : message.LocalTime).ToLocalTime()));//开始适配大小instance.GetComponentInChildren<ChatItemTextBgAdaptor>().Adapt();//缓存到字典chatItemDic.Add(message, instance);
}
  • 回收实例到对象池中
string CurrentChatUserID
{get{return currentChatUserID;}set{/****************************************************************************** 判断当前聊天对象是否发生变更*  发生变更后不仅更新值 还要执行聊天对象发生变更事件*  即回收当前实例化出的聊天项 并清空字典缓存*****************************************************************************/if (currentChatUserID != value){currentChatUserID = value;foreach (var kv in chatItemDic){switch (kv.Value.Type){case ChatItemType.LOCAL: Main.ObjectPool.Mono.Recycle(kv.Value as ChatItemLocal); break;case ChatItemType.REMOTE: Main.ObjectPool.Mono.Recycle(kv.Value as ChatItemRemote); break;}}chatItemDic.Clear();}}
}

http://www.ppmy.cn/news/75235.html

相关文章

# IO模型

IO模型 非阻塞IO 当程序读取硬件数据时&#xff0c;不管硬件数据是否准备好&#xff0c;read()函数不会阻塞&#xff0c;会继续向下执行 程序会不停监测IO事件是否产生&#xff0c;CPU消耗率高 防止进程阻塞在IO函数上&#xff0c;如果要获得有效数据&#xff0c;需要轮循 …

Mybatis 缓存

JPA 原理 事务 事务是计算机应用中不可或缺的组件模型&#xff0c;它保证了用户操作的原子性 ( Atomicity )、一致性 ( Consistency )、隔离性 ( Isolation ) 和持久性 ( Durabilily )。 本地事务 紧密依赖于底层资源管理器&#xff08;例如数据库连接 )&#xff0c;…

【BBQ: A Hand-Built Bias Benchmark for Question Answering 论文精读】

BBQ: A Hand-Built Bias Benchmark for Question Answering 论文精读 InformationAbstract1 Introduction2 Related Work3 The Dataset3.1 Coverage3.2 Template Construction3.3 Vocabulary4 Validation5 Evaluation6 Results7 Discussion8 Conclusion9 Ethical Consideration…

shell sed命令

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 sed 命令sed 编辑器sed 的工作流程的三个过程命定格式常用选项常用操作 实验操作打印内容使用地址删除行替换插入 sed 命令 sed 编辑器 sed是一种流编辑器&#x…

【JavaScript】线程和进程,JavaScript线程,事件队列,事件循环 ,微任务、宏任务

❤️ Author&#xff1a; 老九 ☕️ 个人博客&#xff1a;老九的CSDN博客 &#x1f64f; 个人名言&#xff1a;不可控之事 乐观面对 &#x1f60d; 系列专栏&#xff1a; 文章目录 进程和线程JavaScript线程事件队列、事件循环微任务、宏任务面试题1面试题2 进程和线程 进程&a…

Ceres简介及示例(8)On Derivatives(Analytic Derivatives)

考虑以下曲线(Rat43) 的拟合问题: y b 1 ( 1 e b 2 − b 3 x ) 1 / b 4 y \frac{b_1}{(1e^{b_2-b_3x})^{1/b_4}} y(1eb2​−b3​x)1/b4​b1​​ 也就是说&#xff0c;给定一些数据 { x i , y i } , ∀ i 1 , . . . , n \{x_i, y_i\},\ \forall i1,... ,n {xi​,yi​}, ∀…

小红书数据分析:如何用ChatGPT输出爆文笔记

ChatGPT的热度依旧不减&#xff0c;随着技术升级&#xff0c;越来越多更高级的玩法被发掘。今天我们就来聊聊&#xff0c;如何用ChatGPT写出小红书风格的文章。 首先&#xff0c;小红书笔记制作分为两个步骤&#xff1a; 1、找选题 2、写小红书风格的笔记 我们用例子说话&a…

SQL执行过程

1. select 语句执行过程 一条 select 语句的执行过程如上图所示 1、建立连接 连接器会校验你输入的用户名和密码是否正确&#xff0c;如果错误会返回提示&#xff0c;如果正确&#xff0c;连接器会查询当前用户对于的权限。连接器的作用就是校验用户权限 2、查询缓存 MySQL…