作者:小傅哥
博客:https://bugstack.cn
❝沉淀、分享、成长,让自己和他人都能有所收获!😜
❞
一、前言
二、演示
三、系统设计
四、UI开发
1. 整体结构定义、侧边栏
2. 对话聊天框
3. 好友栏
4. 事件定义
五、通信设计
1. 系统架构
2. 通信协议
3. 添加好友
4. 消息应答
5. 断线重连
6. 集群通信
六、源码下载 🎉
七、总结
这知识学的,根本没有忘的快呀?!
是不是感觉很多资料,点收藏起来爽
、看视频时候嗨
、读文章当时会
,只要过了那个劲,就完了,根本不记得这里面都讲了啥。时间浪费了,东西还没学到手,这是为啥?
其实因为学习也分为上策、中策和下策:
综上,下策学起来很快感觉自己好像会了不少,中策有点要动手了懒不想动,上策就很耗时耗力了要自己对每一个知识点都能事必躬亲到亲力亲为。就这样你在学习的时候不自觉的就选择了下策,因此其实并没有学到什么。
学习能把知识学到手,讲究的是实践,在小傅哥编写的文章中,基本都是以实践代码验证结果为核心,讲述文章内容。😁从小我就喜欢动手,就以一个即时通信的项目为例,已经基于不同技术方案实现了5、6次,仅为了实践技术,截图如下:
那么,这次IM实践的机会给你,希望你能用的上!接下来我会给你介绍一个IM的系统架构、通信协议、单聊群聊、表情发送、UI事件驱动等各项内容,以及提供全套的源码让你可以上手学习。
在开始学习之前,先给大家演示下这套仿照PC端微信界面的IM系统运行效果。
聊天页面
添加好友
视频演示
在这套IM
中,服务端采用DDD
领域驱动设计模式进行搭建。将 Netty 的功能交给 SpringBoot
进行启停控制,同时在服务端搭建控制台可以非常方便的操作通信系统,进行用户和通信管理。在客户端的建设上采用UI
分离的方式进行搭建,以保证业务代码与UI
展示分离,做到非常易于扩展的控制。
另外在功能实现上包括;完美仿照微信桌面版客户端、登录、搜索添加好友、用户通信、群组通信、表情发送等核心功能。如果有对于实际需要使用的功能,可以按照这套系统框架进行扩展。
JavaFx
与
Maven
搭建UI桌面工程,逐步讲解登录框体、聊天框体、对话框、好友栏等各项UI展示及操作事件。从而在这一章节中让Java 程序员学会开发桌面版应用。
聊天窗体,相对于登陆窗体来说,聊天窗体的内容会比较多,同时也会相对复杂一些。因此我们会分章节的逐步来实现这些窗体以及事件和接口功能。在本篇文章中我们会主要讲解聊天框体的搭建以及侧边栏 UI 开发。
内容面板
中的填充信息。
对话框选中后的内容区域展现,也就是用户之间信息发送和展现。从整体上看这是一个联动的过程,点击左侧的对话框用户,右侧就有相应内容的填充。那么右侧被填充对话列表 ListView 需要与每一个对话用户关联,点击聊天用户的时候,是通过反复切换填充的过程。
大家都经常使用 PC 端的微信,可以知道在好友栏里是分了几段内容的,其中包含;新的朋友、公众号、群组和最下面的好友。
在桌面版 UI 开发中,为了能使 UI 与业务逻辑隔离,需要在我们把 UI 打包后提供出操作界面的展示效果的接口以及界面操作事件抽象类。那么可以按照下图理解;
序号 | 接口名 | 描述 |
---|---|---|
1 | void doShow() | 打开窗口 |
2 | void setUserInfo(String userId, String userNickName, String userHead) | 设置登陆用户 ID、昵称、头像 |
3 | void addTalkBox(int talkIdx, Integer talkType, String talkId, String talkName, String talkHead, String talkSketch, Date talkDate, Boolean selected) | 填充对话框列表 |
4 | void addTalkMsgUserLeft(String talkId, String msg, Date msgData, Boolean idxFirst, Boolean selected, Boolean isRemind) | 填充对话框消息 - 好友 (别人的消息) |
在前面我们说到更适合的架构,才是符合你当下需要最好的架构。那么怎么设计这样架构呢,基本就是要找到符合点的目标。我们之所以这样设计是为什么,那么在这个系统里有如下几点;
结合我们上面这四点的目标,你头脑中有什么模型结构体现了呢?以及相应的技术栈选择上是否有计划了?接下来我们会介绍两种架构设计的模型,一种是你非常熟悉的 MVC
,另外一种是你可能听说过的 DDD
领域驱动设计。
从图稿上来看,我们在传输对象的时候需要在传输包中添加一个 帧标识 以此来判断当前的业务对象是哪个对象,也就可以让我们的业务更加清晰,避免使用大量的 if 语句判断。
协议框架
agreement
└── src
├── main
│ ├── java
│ │ └── org.itstack.naive.chat
│ │ ├── codec
│ │ │ ├── ObjDecoder.java
│ │ │ └── ObjEncoder.java
│ │ ├── protocol
│ │ │ ├── demo
│ │ │ ├── Command.java
│ │ │ └── Packet.java
│ │ └── util
│ │ └── SerializationUtil.java
│ ├── resources
│ │ └── application.yml
│ └── webapp
│ └── chat
│ └── res
│ └── index.html
└── test
└── java
└── org.itstack.demo.test
└── ApiTest.java
协议包
public abstract class Packet {
private final static Map<Byte, Class<? extends Packet>> packetType = new ConcurrentHashMap<>();
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(Command.MsgGroupRequest, MsgGroupRequest.class);
packetType.put(Command.MsgGroupResponse, MsgGroupResponse.class);
packetType.put(Command.ReconnectRequest, ReconnectRequest.class);
}
public static Class<? extends Packet> get(Byte command) {
return packetType.get(command);
}
/**
* 获取协议指令
*
* @return 返回指令值
*/
public abstract Byte getCommand();
}
添加好友,案例代码
public class AddFriendHandler extends MyBizHandler<AddFriendRequest> {
public AddFriendHandler(UserService userService) {
super(userService);
}
@Override
public void channelRead(Channel channel, AddFriendRequest msg) {
// 1. 添加好友到数据库中[A->B B->A]
List<UserFriend> userFriendList = new ArrayList<>();
userFriendList.add(new UserFriend(msg.getUserId(), msg.getFriendId()));
userFriendList.add(new UserFriend(msg.getFriendId(), msg.getUserId()));
userService.addUserFriend(userFriendList);
// 2. 推送好友添加完成 A
UserInfo userInfo = userService.queryUserInfo(msg.getFriendId());
channel.writeAndFlush(new AddFriendResponse(userInfo.getUserId(), userInfo.getUserNickName(), userInfo.getUserHead()));
// 3. 推送好友添加完成 B
Channel friendChannel = SocketChannelUtil.getChannel(msg.getFriendId());
if (null == friendChannel) return;
UserInfo friendInfo = userService.queryUserInfo(msg.getUserId());
friendChannel.writeAndFlush(new AddFriendResponse(friendInfo.getUserId(), friendInfo.getUserNickName(), friendInfo.getUserHead()));
}
}
消息应答,案例代码
public class MsgHandler extends MyBizHandler<MsgRequest> {
public MsgHandler(UserService userService) {
super(userService);
}
@Override
public void channelRead(Channel channel, MsgRequest msg) {
logger.info("消息信息处理:{}", JSON.toJSONString(msg));
// 异步写库
userService.asyncAppendChatRecord(new ChatRecordInfo(msg.getUserId(), msg.getFriendId(), msg.getMsgText(), msg.getMsgType(), msg.getMsgDate()));
// 添加对话框[如果对方没有你的对话框则添加]
userService.addTalkBoxInfo(msg.getFriendId(), msg.getUserId(), Constants.TalkType.Friend.getCode());
// 获取好友通信管道
Channel friendChannel = SocketChannelUtil.getChannel(msg.getFriendId());
if (null == friendChannel) {
logger.info("用户id:{}未登录!", msg.getFriendId());
return;
}
// 发送消息
friendChannel.writeAndFlush(new MsgResponse(msg.getUserId(), msg.getMsgText(), msg.getMsgType(), msg.getMsgDate()));
}
}
消息应答,案例代码
// Channel 状态定时巡检;3 秒后每 5 秒执行一次
scheduledExecutorService.scheduleAtFixedRate(() -> {while (!nettyClient.isActive()) {System.out.println("通信管道巡检:通信管道状态" + nettyClient.isActive());
try {System.out.println("通信管道巡检:断线重连 [Begin]");
Channel freshChannel = executorService.submit(nettyClient).get();
if (null == CacheUtil.userId) continue;
freshChannel.writeAndFlush(new ReconnectRequest(CacheUtil.userId));
} catch (InterruptedException | ExecutionException e) {System.out.println("通信管道巡检:断线重连 [Error]");}
}
}, 3, 5, TimeUnit.SECONDS);
本项目是作者小傅哥使用JavaFx、Netty4.x、SpringBoot、Mysql等技术栈和偏向于DDD领域驱动设计方式,搭建的仿桌面版微信实现通信核心功能。
这套 IM
代码分为了三组模块;UI、客户端、服务端。之所以这样拆分,是为了将UI展示与业务逻辑隔离,使用事件和接口进行驱动,让代码层次更加干净整洁易于扩展和维护。
序号 | 工程 | 介绍 |
---|---|---|
1 | itstack-naive-chat-ui | 使用JavaFx开发的UI端,在我们的UI端中提供了;登录框体、聊天框体,同时在聊天框体中有大量的行为交互界面以及接口和事件。最终我的UI端使用Maven打包的方式向外提供Jar包,以此来达到UI界面与业务行为流程分离。 |
2 | itstack-naive-chat-client | 客户端是我们的通信核心工程,主要使用Netty4.x作为我们的socket框架来完成通信交互。并且在此工程中负责引入UI的Jar包,完成UI定义的事件(登录验证、搜索添加好友、对话通知、发送信息等等),以及需要使用我们在服务端工程定义的通信协议来完成信息的交互操作。 |
3 | itstack-navie-chat-server | 服务端同样使用Netty4.x作为socket的通信框架,同时在服务端使用Layui作为管理后台的页面,并且我们的服务端采用偏向于DDD领域驱动设计的方式与Netty集合,以此来达到我们的框架结构整洁干净易于扩展。 |
4 | itstack.sql | 系统工程数据库表结构以及初始化数据信息,共计6张核心表;用户表、群组表、用户群组关联表、好友表、对话表以及聊天记录表。用户在实际业务开发中可以自行拓展完善,目前库表结构只以核心功能为基础。 |
IM
亲,源码给我点个Star,不要
白皮袄
!!!
- END -
下方扫码关注 bugstack虫洞栈,与小傅哥一起学习成长、共同进步,做一个码场最贵Coder!
java
工程师、架构师,开发过交易&营销、写过运营&活动、设计过中间件也倒腾过中继器、IO板卡。不只是写Java语言,也搞过C#、PHP,是一个技术活跃的折腾者。
CodeGuide
、
itstack-demo-design
,持续霸榜 Trending,成为全球热门项目。
本文分享自微信公众号 - bugstack虫洞栈(bugstack)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。
|