目录一、前言
这知识学的,根本没有忘的快呀?!
你是否觉得,将大量资料一键收藏带来一时之快,观看视频时感到兴奋,阅读文章时有所感悟,可一旦热情消退,便对其中内容一无所知。如此一来,宝贵的时间被虚度,知识却未能真正掌握,这究竟是什么原因呢?
其实因为学习也分为上策、中策和下策:
综合来看,采取下策学习起来迅速,仿佛掌握了许多知识;中策则需要动手实践,但往往因为懒惰而不愿付诸行动;至于上策,则需要投入大量时间和精力,对每个知识点都要亲自实践。因此,在学习过程中,你不知不觉地选择了下策,实际上并未真正学到什么。
掌握知识的关键在于实践,小傅哥所撰写的文章中,主要围绕通过实践代码来验证结论展开论述。我自幼便热爱动手操作,以即时通讯项目为例,我已经运用多种技术方案完成了五到六次的实践,纯粹是为了加深对技术的理解,具体截图如下:
这次IM实践的机会摆在你面前,愿你能够充分利用!随后,我将为你详细讲解IM的系统架构、通信协议、一对一及群组聊天功能、表情发送机制、以及UI事件驱动等关键要素,并会提供完整的源码,让你能够轻松开始学习。
二、演示
在学习过程启动之前,我将首先向各位展示这款模仿PC端微信界面设计的即时通讯系统的实际运行表现。
聊天页面
添加好友
三、系统设计
在这套IM系统中,服务端基于DDD(领域驱动设计)模式构建。我们将相关功能委托给特定模块进行启停管理,并在服务端构建了控制台,这使得操作通信系统以及进行用户和通信管理变得极为便捷。至于客户端的构建,我们采用了UI分离的策略,确保业务逻辑代码与用户界面展示相分离,从而实现了高度可扩展的控制机制。
此外,在功能实现方面,涵盖了诸如精准模仿微信桌面客户端的各项核心操作,例如登录过程、搜索与添加好友、用户间的通讯、群组通讯以及表情的发送等功能。若需针对具体应用场景进行功能拓展,这套系统框架提供了相应的扩展可能。
四、UI开发1. 整体结构定义、侧边栏
聊天界面相较于登录界面,其内容更为丰富,结构也更为繁杂。鉴于此,我们将分阶段逐步完善这些界面以及相关的事件和接口功能。具体到本文,我们将重点介绍聊天框的构建过程以及侧边栏用户界面的开发。
2. 对话聊天框
对话框内容被选中后,其显示区域便呈现出来,这包括用户间信息的发送与展示。总体而言,这是一个相互关联的动态过程。当用户点击左侧的对话框时,右侧便会自动填充相应的信息。因此,右侧填充的对话列表必须与每位对话用户相对应。用户点击聊天对象时,实际上是在经历一个不断切换信息填充的循环过程。
3. 好友栏
我们普遍在电脑上使用微信,注意到好友列表中划分了若干部分,这些部分包括新添加的朋友、关注的公众号、所属的群聊以及位于最底部的个人好友。
4. 事件定义
在桌面版用户界面开发过程中,为确保用户界面与业务逻辑相分离,我们需在UI打包完成后,提供展现操作界面效果的接口,并定义界面操作事件的抽象类。具体理解可参照下方的示意图。
图片
五、通信设计1. 系统架构
先前我们谈论了更适宜的体系结构,这恰是满足你目前需求的最佳选择。那么,如何构建这样的体系结构呢?关键在于锁定那些符合我们目标的要素。至于我们之所以采取这种设计,原因在于系统内存在以下几点:
基于我们之前提到的四个目标,在你的思维中是否已经形成了某种模型结构?在技术选型方面,你是否已经有了相应的规划?随后,我们将向大家介绍两种架构设计的模型,其一是大家耳熟能详的 MVC 模式,而另一种则是你可能有所耳闻的 DDD 领域驱动设计。
2. 通信协议
观察图稿可知,在传输对象时,我们需在传输包中加入一个帧标识,这样便可识别出当前的业务对象是哪一个,从而使得业务流程更加明确,减少对众多if语句的依赖。
协议框架
agreement └── src ├── main │ ├── java 此指令明确指出,不得对特定代码库进行任何形式的修改,具体为org.itstack.naive.chat组织下的chat模块。 │ │ ├── codec
协议包
公共抽象类,名为Packet。 private final static Map extends Packet>创建一个新的并发HashMap对象,用于存储数据包类型。<>(); static { 将Command对象的LoginRequest键值对与LoginRequest类进行映射,存入packetType映射表中。 将Command类中的LoginResponse指令与LoginResponse类进行映射,存储在packetType中。 将Command类中的MsgRequest指令与MsgRequest类进行关联,存入packetType映射中。 将Command类中的MsgResponse消息响应类型与MsgResponse类进行映射,存储于packetType映射表中。 将Command类中的TalkNoticeRequest指令与TalkNoticeRequest类进行关联,存入packetType映射中。 将Command类中的TalkNoticeResponse指令与TalkNoticeResponse类进行关联,并存入packetType映射中。 将命令类型“SearchFriendRequest”与搜索好友请求类“SearchFriendRequest.class”进行映射,并存储在packetType映射表中。 将Command类中的SearchFriendResponse指令与SearchFriendResponse类进行映射,存储于packetType中。 将Command类中的AddFriendRequest指令与AddFriendRequest类进行关联,并存储在packetType映射中。 将Command类中的AddFriendResponse命令与AddFriendResponse类进行关联,并存储在packetType映射表中。 将Command类中的DelTalkRequest指令与DelTalkRequest类进行映射,存储于packetType映射表中。 将Command类中的MsgGroupRequest消息组请求类型与MsgGroupRequest类进行映射,存入packetType映射表中。 将Command中的MsgGroupResponse消息组响应类型与MsgGroupResponse类进行映射,存入packetType映射表中。 packetType 中将 Command 类的 ReconnectRequest 方法与 ReconnectRequest 类进行了关联;具体来说,将 ReconnectRequest 类映射到了 Command 类的 ReconnectRequest 方法上。 } public static Class extends Packet> get(Byte command) { 返回值是packetType根据command所获取的类型。 } /** * 获取协议指令 * * @return 返回指令值 */ 公开地提供一个抽象的获取指令的方法,返回类型为字节。 }
3. 添加好友
添加好友,案例代码
该类名为AddFriendHandler,继承自MyBizHandler。 { 公开构造函数AddFriendHandler,接收UserService类型的参数userService。 super(userService); } @Override public方法,用于接收通道读取操作,参数包括通道对象channel和添加好友请求消息msg。 // 1. 添加好友到数据库中[A->B B->A] List创建了一个新的ArrayList对象,名为userFriendList。<>(); userFriendList中新增了一个UserFriend对象,其userId属性设置为msg对象的userId,friendId属性设置为msg对象的friendId。 userFriendList中新增了一条记录,该记录由一个UserFriend对象构成,其friendId属性值为msg.getFriendId(),userId属性值为msg.getUserId()。 用户服务模块成功添加了用户好友列表。 // 2. 推送好友添加完成 A userInfo变量被赋予了一个值,这个值是通过调用userService中的queryUserInfo方法得到的,该方法接收msg对象中的friendId属性作为参数。 channel输出并立即发送一个添加好友的响应对象,其中包含用户ID、用户昵称以及用户头像信息。 // 3. 推送好友添加完成 B 通过msg中的好友ID,利用SocketChannelUtil工具方法获取了对应的Channel实例,命名为Channel friendChannel。 若friendChannel为空,则直接返回; 通过调用userService的queryUserInfo方法,获取了与msg.getUserId()相关的用户信息,并将其赋值给UserInfo类型的变量friendInfo。 friendChannel执行写入并刷新操作,发送一个包含好友信息的新响应对象,该对象包含好友的ID、昵称和头像。 } }
4. 消息应答
图片
消息应答,案例代码
MsgHandler类继承自MyBizHandler。 { 创建MsgHandler实例时,需传入一个UserService对象。 super(userService); } @Override public方法执行时,接收Channel类型的channel参数和MsgRequest类型的msg参数。 日志记录器输出:“消息信息处理”,并附带通过JSON格式化后的消息内容:,msg.toString()。 // 异步写库
5. 断线重连
消息应答,案例代码
对Channel状态进行定时巡检,经过3秒的延迟,随后每隔5秒进行一次操作。 调度执行器以固定速率安排任务,任务内容为:当Netty客户端未处于活跃状态时,持续输出提示信息:“进行通信管道检查:当前通信管道是否活跃的状态为”加nettyClient.isActive()的返回值。 尝试执行以下操作:输出“通信管道巡检:断线重连,巡检启动”; executorService提交了nettyClient,并通过get()方法获取了返回的Channel freshChannel。 若CacheUtil.userId为空,则继续执行。 freshChannel执行写入并刷新操作,发送一个包含CacheUtil中userId的重新连接请求。 捕获到InterruptedException或ExecutionException异常时,系统将输出提示信息:“通信管道巡检:检测到断线,正在尝试重新连接 [错误提示]”。 } }, 3, 5, TimeUnit.SECONDS);
6. 集群通信
六、源码下载
本工程由作者小傅哥主导,采用诸如.x等技术和工具,并遵循DDD(领域驱动设计)的理念,成功构建了一个模拟桌面微信的通信功能核心系统。
该IM代码被划分为三个模块,分别是用户界面(UI)、客户端以及服务端;这种划分的目的是为了实现用户界面展示与业务逻辑的分离,通过事件和接口来驱动,从而使代码结构更加清晰、层次分明,便于后续的扩展与维护。
序号
工程
介绍
--chat-ui
通过开发的UI端,我们实现了登录框体和聊天框体的功能;在聊天框体内部,用户可以享受到丰富的行为交互界面、接口以及事件处理。最终,我的UI端通过打包成Jar包的形式对外输出,确保了UI界面与业务流程的独立运作。
--chat-
客户端构成了我们通信系统的核心部分,主要依赖.x框架来实现数据交互。同时,它还承担着导入UI所需的Jar包任务,负责实现包括登录验证、添加好友搜索、对话提醒、信息发送等一系列UI事件。此外,它还需遵循我们在服务端定义的通信协议,以实现信息的有效交互。
--chat-
服务端同样采用了.x作为其通信框架,并且配备了一个用于管理后台的页面。此外,我们的服务端在架构设计上倾向于采用DDD(领域驱动设计)的理念,与集合相结合,确保了框架的整洁性、干净度以及易于扩展性。
.sql
系统工程中涉及数据库的表格架构及其初始数据资料,总共有6个关键表格;这些表格包括用户信息表、群组信息表、用户与群组关联表、好友信息表、对话信息表以及聊天记录信息表。在具体业务开发过程中,用户可根据需求自行进行扩展和优化。目前,数据库的表格结构设计仅以实现核心功能为出发点。
七、总结
关注一下,与学长一起学习成长、共同进步,做一个码场最贵!