一、前言
二、演示
三、系统设计
四、UI开发
1.整体结构定义、侧边栏
2.对话聊天框
3.好友栏
4.事件定义
五、通信设计
1.系统架构
2.通信协议
3.添加好友
4.消息应答
5.断线重连
6.集群通信
六、源码下载
七、总结
一、前言这知识学的,根本没有忘的快呀?!
其实因为学习也分为上策、中策和下策:
下策:眼睛看就行,坐着、窝着、躺着,都行,反正也不累,还能一边回复下吹水的微信群
中策:看完的资料做笔记整理归纳,长期积累资料
上策:实践、上手、应用、调试、归纳、整理资料,总结经验输出文档
综上,下策学起来很快感觉自己好像会了不少,中策有点要动手了懒不想动,上策就很耗时耗力了要自己对每一个知识点都能事必躬亲到亲力亲为。就这样你在学习的时候不自觉的就选择了下策,因此其实并没有学到什么。
学习能把知识学到手,讲究的是实践,在小傅哥编写的文章中,基本都是以实践代码验证结果为核心,讲述文章内容。从小我就喜欢动手,就以一个即时通信的项目为例,已经基于不同技术方案实现了5、6次,仅为了实践技术,截图如下:
有些是刚学完Socket和Swing的时候,想动手试试这些技术能不能写个QQ出来。
也有的是因为实习培训需要完成的项目,不过在有了一些基础后,一周时间就能写完全部功能。
虽然这些项目在现在看上去还是丑丑的界面,以及代码逻辑可能也不是那么完善。但放在学习阶段的每一次实现中,都能为自己带来很多技术上的成长。
那么,这次IM实践的机会给你,希望你能用的上!接下来我会给你介绍一个IM的系统架构、通信协议、单聊群聊、表情发送、UI事件驱动等各项内容,以及提供全套的源码让你可以上手学习。
二、演示在开始学习之前,先给大家演示下这套仿照PC端微信界面的IM系统运行效果。
聊天页面
添加好友
三、系统设计在这套IM中,服务端采用DDD领域驱动设计模式进行搭建。将Netty的功能交给SpringBoot进行启停控制,同时在服务端搭建控制台可以非常方便的操作通信系统,进行用户和通信管理。在客户端的建设上采用UI分离的方式进行搭建,以保证业务代码与UI展示分离,做到非常易于扩展的控制。
另外在功能实现上包括;完美仿照微信桌面版客户端、登录、搜索添加好友、用户通信、群组通信、表情发送等核心功能。如果有对于实际需要使用的功能,可以按照这套系统框架进行扩展。
UI开发:使用JavaFx与Maven搭建UI桌面工程,逐步讲解登录框体、聊天框体、对话框、好友栏等各项UI展示及操作事件。从而在这一章节中让Java程序员学会开发桌面版应用。
架构设计:在这一章节中我们会使用DDD领域驱动设计的四层模型结构与Netty结合使用,架构出合理的分层框架。同时还有相应库表功能的设计。相信这些内容学习后,你一定也可以假设出更好的框架。
功能实现:这部分我们主要将通信中的各项功能逐步实现,包括;登录、添加好友、对话通知、消息发送、断线重连等各项功能。最终完成整个项目的开发,同时也可以让你从实践中学会技能。
四、UI开发1.整体结构定义、侧边栏聊天窗体,相对于登陆窗体来说,聊天窗体的内容会比较多,同时也会相对复杂一些。因此我们会分章节的逐步来实现这些窗体以及事件和接口功能。在本篇文章中我们会主要讲解聊天框体的搭建以及侧边栏UI开发。
首先是我们整个聊天主窗体的定义,是一块空白面板,并去掉默认的边框按钮(最小化、退出等)
之后是我们左侧边栏,我们称之为条形Bar,功能区域的实现。
2.对话聊天框对话框中左侧展示好友发送的信息,右侧展示个人发送的信息。同时消息内容会随着内容的增多而增加高度和宽度。
最下面是文本输入框,在后面的实现里我们文本输入框采用公用的方式进行设计,当然你也可以设计为单独的个人使用。
3.好友栏大家都经常使用PC端的微信,可以知道在好友栏里是分了几段内容的,其中包含;新的朋友、公众号、群组和最下面的好友。
最上面的搜索框这部分内容不变,和前面的一样。我们目前使用的方式是fxml设计,例如这部分是通用功能,可以抽取出来放到代码中,设计成一个组件元素类。
经过我们的分析,在使用JavaFx组件开发为基础下,这部分是一种嵌套ListView,也就是最底层的面板是一个ListView,好友和群组有各是一个ListView,这样处理后我们会很方便的进行数据填充。
另外这样的结构主要有利于在我们程序运行过程中,如果你添加了好友,那么我们需要将好友信息刷新到好友栏中,而在数据填充的时候,为了更加便捷高效,所以我们设计了嵌套的ListView。如果还不是特别理解,可以从后续的代码中获得答案。
4.事件定义在桌面版UI开发中,为了能使UI与业务逻辑隔离,需要在我们把UI打包后提供出操作界面的展示效果的接口以及界面操作事件抽象类。那么可以按照下图理解;
序号
接口名
描述
1
voiddoShow()
打开窗口
2
voidsetUserInfo(StringuserId,StringuserNickName,StringuserHead)
设置登陆用户ID、昵称、头像
3
voidaddTalkBox(inttalkIdx,IntegertalkType,StringtalkId,StringtalkName,StringtalkHead,StringtalkSketch,DatetalkDate,Booleanselected)
填充对话框列表
4
voidaddTalkMsgUserLeft(StringtalkId,Stringmsg,DatemsgData,BooleanidxFirst,Booleanselected,BooleanisRemind)
填充对话框消息-好友(别人的消息)
以上这些接口就是我们目前UI为外部提供的所有行为接口,这些接口的一个链路描述就是;打开窗口、搜索好友、添加好友、打开对话框、发送消息。
五、通信设计1.系统架构在前面我们说到更适合的架构,才是符合你当下需要最好的架构。那么怎么设计这样架构呢,基本就是要找到符合点的目标。我们之所以这样设计是为什么,那么在这个系统里有如下几点;
我们系统在服务端要有web页面进行管理通信用户以及服务端的控制和监控。
数据库的对象类,不要被外部污染,要有隔离性。比如说;你的数据库类暴漏给外部做展示类使用了,那么现在需要增加一个字段,而这个字段又不是你数据库存在的属性。那么这个时候就已经把数据库类污染了。
因为目前我们都是在Java语言下实现Netty通信,那么服务端与客户端都会需要使用到通信过程中的协议定义和解析。那么我们需要抽离这一层对外提供Jar包。
接口、业务处理、底层服务、通信交互,要有明确的区分和实现,避免造成混乱难以维护。
结合我们上面这四点的目标,你头脑中有什么模型结构体现了呢?以及相应的技术栈选择上是否有计划了?接下来我们会介绍两种架构设计的模型,一种是你非常熟悉的MVC,另外一种是你可能听说过的DDD领域驱动设计。
2.通信协议从图稿上来看,我们在传输对象的时候需要在传输包中添加一个帧标识以此来判断当前的业务对象是哪个对象,也就可以让我们的业务更加清晰,避免使用大量的if语句判断。
协议框架
agreement└──src├──main│├──java││└──││├──codec│││├──│││└──││├──protocol│││├──demo│││├──│││└──││└──util││└──│├──resources││└──│└──webapp│└──chat│└──res│└──└──test└──java└──└──
协议包
publicabstractclassPacket{privatefinalstaticMapByte,Class?extsPacketpacketType=newConcurrentHashMap();static{(,);(,);(,);(,);(,);(,);(,);(,);(,);(,);(,);(,);(,);(,);}publicstaticClass?extsPacketget(Bytecommand){(command);}/***获取协议指令**@return返回指令值*/publicabstractBytegetCommand();}3.添加好友从上面的流程中可以看到,这里包含了两部分内容;(1)搜索好友,(2)添加好友。当天就完成好友后,好友会出现到我们的好友栏中。
并且这里面我们采用的是单方面同意加好友,也就是你添加一个好友的时候,对方也同样有你的好友信息。
如果你的业务中是需要添加好友并同意的,那么可以在发起好友添加的时候,添加一条状态信息,请求加好友。对方同意后,两个用户才能成为好友并进行通信。
添加好友,案例代码
publicclassAddFriHandlerextsMyBizHandlerAddFriRequest{publicAddFriHandler(UserServiceuserService){super(userService);}@OverridepublicvoidchannelRead(Channelchannel,AddFriRequestmsg){//1.添加好友到数据库中[A-BB-A]ListUserFriuserFriList=newArrayList();(newUserFri((),()));(newUserFri((),()));(userFriList);//2.推送好友添加完成AUserInfouserInfo=(());(newAddFriResponse((),(),()));//3.推送好友添加完成BChannelfriChannel=(());if(null==friChannel)return;UserInfofriInfo=(());(newAddFriResponse((),(),()));}}4.消息应答从整体的流程可以看到,在用户发起好友、群组通信的时候,会触发一个事件行为,接下来客户端向服务端发送与好友的对话请求。
服务端收到对话请求后,如果是好友对话,那么需要保存与好友的通信信息到对话框中。同时通知好友,我与你要通信了。你在自己的对话框列表中,把我加进去。
那么如果是群组通信,是可以不用这样通知的,因为不可能把还没有在线的所有群组用户全部通知(人家还没登录呢),所以这部分只需要在用户上线收到信息后,创建出对话框到列表中即可。可以仔细理解下,同时也可以想想其他实现的方式。
消息应答,案例代码
publicclassMsgHandlerextsMyBizHandlerMsgRequest{publicMsgHandler(UserServiceuserService){super(userService);}@OverridepublicvoidchannelRead(Channelchannel,MsgRequestmsg){("消息信息处理:{}",(msg));//异步写库(newChatRecordInfo((),(),(),(),()));//添加对话框[如果对方没有你的对话框则添加]((),(),());//获取好友通信管道ChannelfriChannel=(());if(null==friChannel){("用户id:{}未登录!",());return;}//发送消息(newMsgResponse((),(),(),()));}}5.断线重连从上述流程中我们看到,当网络连接断开以后,会像服务端发送重新链接的请求。那么在这个发起链接的过程,和系统的最开始链接有所区别。断线重连是需要将用户的ID信息一同--发送给服务端,好让服务端可以去更新用户与通信管道Channel的绑定关系。
同时还需要更新群组内的重连信息,把用户的重连加入群组映射中。此时就可以恢复用户与好友和群组的通信功能。
消息应答,案例代码
//Channel状态定时巡检;3秒后每5秒执行一次(()-{while(!()){("通信管道巡检:通信管道状态"+());try{("通信管道巡检:断线重连[Begin]");ChannelfreshChannel=(nettyClient).get();if(null==)continue;(newReconnectRequest());}catch(InterruptedException|ExecutionExceptione){("通信管道巡检:断线重连[Error]");}}},3,5,);6.集群通信跨服务之间案例采用redis的发布和订阅进行传递消息,如果你是大型服务可以使用zookeeper
用户A在发送消息给用户B时候,需要传递B的channeId,以用于服务端进行查找channeId所属是否自己的服务内
单台机器也可以启动多个Netty服务,程序内会自动寻找可用端口
六、源码下载本项目是作者小傅哥使用JavaFx、、SpringBoot、Mysql等技术栈和偏向于DDD领域驱动设计方式,搭建的仿桌面版微信实现通信核心功能。
这套IM代码分为了三组模块;UI、客户端、服务端。之所以这样拆分,是为了将UI展示与业务逻辑隔离,使用事件和接口进行驱动,让代码层次更加干净整洁易于扩展和维护。
序号
工程
介绍
1
itstack-naive-chat-ui
使用JavaFx开发的UI端,在我们的UI端中提供了;登录框体、聊天框体,同时在聊天框体中有大量的行为交互界面以及接口和事件。最终我的UI端使用Maven打包的方式向外提供Jar包,以此来达到UI界面与业务行为流程分离。
2
itstack-naive-chat-client
客户端是我们的通信核心工程,主要使用作为我们的socket框架来完成通信交互。并且在此工程中负责引入UI的Jar包,完成UI定义的事件(登录验证、搜索添加好友、对话通知、发送信息等等),以及需要使用我们在服务端工程定义的通信协议来完成信息的交互操作。
3
itstack-navie-chat-server
服务端同样使用作为socket的通信框架,同时在服务端使用Layui作为管理后台的页面,并且我们的服务端采用偏向于DDD领域驱动设计的方式与Netty集合,以此来达到我们的框架结构整洁干净易于扩展。
4
系统工程数据库表结构以及初始化数据信息,共计6张核心表;用户表、群组表、用户群组关联表、好友表、对话表以及聊天记录表。用户在实际业务开发中可以自行拓展完善,目前库表结构只以核心功能为基础。
七、总结此IM系统涉及到的技术栈内容较多,、SpringBoot、Mybatis、Mysql、JavaFx、layui等技术栈的使用,以及整个系统框架结构采用DDD四层架构+Socket模块的方式进行搭建,所有的UI都以前后端分离事件驱动方式进行设计,在这个过程中只要你能坚持学习下来,那么一定会收获非常多的内容。足够吹牛啦!
任何一个新技术栈的学习过程都会包括这样一条路线;运行HelloWorld、熟练使用API、项目实践以及最后的深度源码挖掘。那么在听到这样一个需求时候,Java程序员肯定会想到一些列的技术知识点来填充我们项目中的各个模块,例如;界面用JavaFx、Swing等,通信用Socket或者知道Netty框架、服务端控制用MVC模型加上SpringBoot等。但是怎么将这些各个技术栈合理的架设出我们的系统确是学习、实践、成长过程中最重要的部分。
-





