图片来自趵突网其实学习也分上、中、下:底线:随便看,坐着,窝着,躺着都可以。水的微信群。中攻略:看完材料做笔记总结,长期积累素材。Bestpolicy:实践、入门、应用、调试、总结、整理资料、总结经验输出文档。综上所述,低策略学得快,感觉自己懂了很多,中策略有点懒得动,最好的策略费时费力。每个知识点我都得自己动手。这样,你在学习的时候,不自觉地选择了错误的策略,所以你实际上并没有学到任何东西。学习可以获取知识,重要的是实践。我从小就喜欢做。以一个即时通讯项目为例,基于不同的技术方案实现了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开发①总体结构定义,侧边栏聊天窗体,相对于登录窗体,聊天窗体的内容会比较多,也相对复杂一些。因此,我们将在章节中逐步实现这些窗体、事件和界面函数。在这篇文章中,我们将主要讲解聊天框的搭建和侧边栏UI的开发:首先,我们整个聊天主窗口的定义,它是一个空白面板,去掉了默认的边框按钮(最小化,退出等).)。之后就是我们的左侧边栏,我们称之为Bar,功能区的实现。最后添加表单事件,在点击按钮时转换内容面板中的填充信息。②对话聊天框对话框选中后显示的内容区域,即用户之间的信息发送和显示。总的来说,这是一个联动的过程。点击左侧的对话框用户,右侧会填充相应的内容。那么右侧要填充的对话列表ListView需要关联到每个对话用户。当点击聊天用户时,是经过反复切换和填充的过程:点击左侧的各个对话框体,右侧聊天框的内容会随之变化。.同时,相应的对话名称也会发生变化。好友发送的信息显示在对话框左侧,个人发送的信息显示在右侧。同时,消息内容会随着内容的增加而增加高度和宽度。底部是文本输入框。在后面的实现中,我们的文本输入框是采用公共方式设计的。当然,您也可以将其设计为个人使用。③好友栏目大家经常在PC端使用微信。可以知道好友栏里有几个版块,包括;新朋友,公众号,群和底层朋友。如上图:顶部搜索框内容不变,和上一个一样。我们目前使用的方式是fxml设计。比如这部分是一个通用的功能,可以把它抽出??来放到代码中,设计成一个组件元素类。经过我们的分析,基于JavaFx组件的开发,这部分是一个嵌套的ListView,也就是最下面的面板是一个ListView,好友和群组都是一个ListView,这样我们处理起来会很方便。数据填充。另外,这种结构主要是有利于我们程序的运行过程。如果添加了好友,那么我们需要将好友信息刷新到好友栏中。在填充数据的时候,为了更加方便高效,我们设计了嵌套ListView。不明白的可以从后面的代码中得到答案。④桌面UI开发中定义事件。为了将UI和业务逻辑隔离开来,我们需要在对UI进行封装后,提供一个操作界面显示效果的接口,以及一个界面操作事件的抽象类。那么你可以根据下图来理解:以上接口都是我们当前UI对外提供的行为接口。这些界面的链接描述为:打开窗口、搜索好友、添加好友、打开对话框、发送消息。通信设计①系统架构如下图所示:前面我们提到,更合适的架构就是满足你当前需求的最好的架构。那么如何设计这样的架构,基本上就是找到一个符合点的目标。我们之所以这样设计,是因为这个系统有以下几点:我们的系统必须在服务器端有一个网页,用来管理通讯用户,控制和监控服务器端。数据库的对象类不能被外界污染,必须隔离。例如,如果你的数据库类暴露给外部显示类,你现在需要添加一个字段,而这个字段不是你的数据库的属性。那么这个时候,数据库类已经被污染了。因为我们目前是用Java语言实现Netty通信,在通信过程中无论是服务端还是客户端都需要用到协议的定义和解析。那么我们就需要将这一层抽取出来,对外提供Jar包。接口、业务处理、底层服务、通信交互必须明确区分和实现,避免混乱和维护困难。结合我们上面的四个目标,你心目中的模型结构是怎样的呢?以及在选择相应的技术栈方面有没有规划?接下来,我们将介绍两种架构设计模型,一种是大家非常熟悉的MVC,另一种是大家可能听说过的DDD领域驱动设计。②通信协议如下图所示:从图上看,我们需要在传输对象的时候在传输包中加入一个帧标识,来判断当前业务对象是哪个对象,这样也可以让我们的业务更加清晰和避免使用大量的if语句来判断。协议框架:协议└──src├──main│├──java││└──org.itstack.naive.chat││├──codec││├──ObjDecoder.java│││└──ObjEncoder.java││├──协议│││├──demo││├──Command.java│││└──Packet.java││└──util││└──SerializationUtil.java│├──资源││└──application.yml│└──webapp│└──chat│└──res│└──index.html└──test└──java└──org.itstack.demo。test└──ApiTest.java协议包:publicabstractclassPacket{privatefinalstaticMap>packetType=newConcurrentHashMap<>();static{packetType.put(Command.LoginRequest,LoginRequest.class);packetType.put(Command.LoginResponse,LoginResponse.class);packetType.put(Command.MsgRequest,MsgRequest.class);packetType.put(Command.MsgResponse,MsgResponse.class);packetType.put(Command.TalkNoticeRequest,TalkNoticeRequest.class);packetType.put(Command.TalkNoticeResponse,TalkNoticeResponse.class);packetType.put(Command.SearchFriendRequest,SearchFriendRequest.class);packetType.put(Command.SearchFriendResponse,SearchFriendResponse.class);packetType.put(Command.AddFriendRequest,AddFriendRequest.class);packetType.put(Command.AddFriendResponse,AddFriendResponse.class);packetType.put(Command.DelTalkRequest,DelTalkRequest.class);packetType.put(命令。MsgGroupRequest,MsgGroupRequest.class);packetType.put(Command.MsgGroupResponse,MsgGroupResponse.class);packetType.put(Command.ReconnectRequest,ReconnectRequest.class);}publicstaticClassget(Bytecommand){returnpacketType.get(命令);}/***获取协议命令**@return返回命令值*/publicabstractBytegetCommand();}③添加好友如上图所示:从上面的流程可以看出,这里分为两部分;(1)搜索好友,(2)当天添加好友后,好友会出现在我们的好友栏中。而这里我们使用的是单方同意的方式来添加好友,即在你添加好友的同时,对方也有你的好友信息。如果您需要添加好友并同意您的业务,您可以在发起添加好友时添加状态消息并请求添加好友。对方同意后,两个用户就可以成为好友,进行交流。添加好友,案例代码:publicclassAddFriendHandlerextendsMyBizHandler{publicAddFriendHandler(UserServiceuserService){super(userService);}@OverridepublicvoidchannelRead(Channelchannel,AddFriendRequestmsg){//1.添加好友到数据库[A->BB->A]ListuserFriendList=newArrayList<>();userFriendList.add(newUserFriend(msg.getUserId(),msg.getFriendId()));userFriendList.add(newUserFriend(msg.getFriendId(),msg.getUserId()));userService.addUserFriend(userFriendList);//2.推送好友添加完成AUserInfouserInfo=userService.queryUserInfo(msg.getFriendId());channel.writeAndFlush(newAddFriendResponse(userInfo.getUserId(),userInfo.getUserNickName(),userInfo.getUserHead()));//3.推送好友添加完毕,friendInfo.getUserNickName(),friendInfo.getUserHead()));}}④消息响应如上图所示:从整体流程可以看出,当用户发起好友或群组通讯时,会触发一个事件行为,然后客户端发送与朋友的消息到服务器。对话请求服务器收到对话请求,如果是与好友的对话,则需要将与好友的通讯信息保存到对话框中。同时通知你的朋友我要和你交流。你在你的对话列表中,加我。那么如果是群通讯的话,就没有必要这样通知了,因为不可能通知所有不在线的群用户(还没有登录),所以这部分只需要在创建之后用户在线接收信息。退出对话框到列表。大家可以仔细体会一下,也可以想想其他的实现方式。消息响应,案例代码:publicclassMsgHandlerextendsMyBizHandler{publicMsgHandler(UserServiceuserService){super(userService);}@OverridepublicvoidchannelRead(Channelchannel,MsgRequestmsg){logger.info("消息信息处理:{}",JSON.toJSONString(msg));//异步写库userService.asyncAppendChatRecord(newChatRecordInfo(msg.getUserId(),msg.getFriendId(),msg.getMsgText(),msg.getMsgType(),msg.getMsgDate()));//添加对话框[If对方没有你的对话框,添加]userService.addTalkBoxInfo(msg.getFriendId(),msg.getUserId(),Constants.TalkType.Friend.getCode());//获取好友通讯通道ChannelfriendChannel=SocketChannelUtil.getChannel(msg.getFriendId());if(null==friendChannel){logger.info("用户id:{}未登录!",msg.getFriendId());return;}//发送消息给friendChannel.writeAndFlush(newMsgResponse(msg.getUserId(),msg.getMsgText(),msg.getMsgType(),msg.getMsgDate()));}}⑤断开重连如上图:来自上面的过程我们可以看到,当网络连接断开时,它会向服务器发送relink请求。然后,发起链接的过程与系统的初始链接不同。断线后要重新连接,需要将用户的ID信息一起发送给服务器,以便服务器更新用户与通信通道Channel的绑定关系。同时,还需要更新群内的重连信息,将用户的重连添加到群地图中。至此,用户与好友、群组的交流能力就可以恢复了。消息响应,案例代码://通道状态定期检查;3秒后,执行scheduledExecutorService.scheduleAtFixedRate(()->{while(!nettyClient.isActive()){System.out.println("通信管道检查:通信通道状态"+nettyClient.isActive());try{System.out.println("通信通道检查:断线重连[开始]");ChannelfreshChannel=executorService.submit(nettyClient).get();if(null==CacheUtil.userId)continue;freshChannel.writeAndFlush(newReconnectRequest(CacheUtil.userId));}catch(InterruptedException|ExecutionExceptione){System.out.println("通信管道检查:断开重连[错误]");}}},3,5,TimeUnit.SECONDS);⑥集群通信如上图所示:跨服务之间的case使用redis发布订阅传递消息。如果你是大型服务,你可以使用zookeeper。用户A向用户B发送消息时,需要传递B的channelId,服务端通过该channelId判断该channelId是否属于自己的服务。单机也可以启动多个Netty服务,程序会自动寻找可用端口。源码下载本项目是本人利用JavaFx、Netty4等技术栈搭建的仿桌面版微信。这套IM代码分为三组模块;用户界面、客户端和服务器。之所以这样拆分,是为了将UI展示和业务逻辑隔离开来,使用事件和接口来驱动,让代码层面更干净,更容易扩展和维护。总结如下:本IM系统涉及到大量的技术栈内容,使用了Netty4.x、SpringBoot、Mybatis、MySQL、JavaFx、layui等技术栈,整个系统框架结构采用DDD四层架构+插座模块。建造。所有的UI都是以事件驱动的方式设计的,前后端分离。在这个过程中,只要你能坚持学习,你一定会收获很多的内容。吹够了!任何一个新技术栈的学习过程都会包含这样一条路线;运行HelloWorld,熟练使用API??,项目实践,最后深挖源码。那么听到这样的需求,Java程序员肯定会想到一系列的技术知识点来填充我们项目中的各个模块。比如接口用JavaFx、Swing等,通信用Socket,或者知道Netty框架,服务端控制用MVC模型加SpringBoot等。但是如何合理的搭建我们的系统这些各种技术栈就是这确实是学习、实践和成长过程中最重要的部分。作者:小富哥编辑:陶家龙来源:转载自公众号bugstack(ID:bugstack)