IM系统第三章 – 对话通知和应答
问题:你是如何实现该功能的?
功能的流程图:
如图:(参照 小傅哥)
实现功能所需的处理:
- 自定义协议;
- UI事件实现;
- 客户端添加对话功能处理;
- 服务端添加对话功能处理;
- 服务端删除对话功能处理;
整体的流程来看,在用户发起好友、群组通信的时候,触发一个事件行为,接下来就是客户端向服务端发送与 好友和群组 的对话请求了。
注意:如果是好友的对话,那么需要保存 当前用户与好友的通信信息到我的对话框中,同时通知好友,要开始通信(即双方建立起连接);然后 你在自己的对话框中,将我加入进去。
如果是群组通信,是不需要这样通知的,因为不可能把还没有上线的所有群组用户全部通知(有的还没登录),所以这部分只需要在用户上线后收到信息后,创建出对话框到列表即可。
自定义协议:(两台机器想要进行通信,就需要协议,也就是说两台机器是通过以下的自定义通信协议来达到相应的结果)
实现该功能需要实现三个协议:对话框通知请求协议、对话框通知请求应答 协议、删除对话框协议
// 抽象协议类
public abstract class Packet {
private final static Map<Byte, Class<? extends Packet>> packetType = new ConcurrentHashMap<>();
static{
packetType.put(Command.TalkNoticeRequest, TalkNoticeRequest.class);
packetType.put(Command.TalkNoticeResponse, TalkNoticeResponse.class);
packetType.put(Command.DelTalkRequest,DelTalkRequest.class)
}
}
接下来就是三个协议类:
// 对话框通知请求协议
public class TalkNoticeRequest extends Packet{
private String userId; // 用户Id(自己的)
private String friendUserId; // 好友Id
private Integer talkType; // 对话框类型[0好友 1群组]
构造函数...
get()/set()...
}
// 对话框通知请求应答 协议
public class TalkNoticeResponse extends Packet{
private String talkId; // 对话框Id[0好友 1群组]
private String talkName; // 对话框名称[好友名称、群名称]
private String talkHead; // 对话框头像[好友头像、群头像]
private String talkSketch; // 消息简讯
private Date talkDate; // 消息时间
get()/set() ...
}
// 删除对话框协议
public class DelTalkRequest extends Packet{
private String userId; // 用户Id
private String talkId; // 对话框Id
构造函数...
get()/set()
}
因为用户发起 好友或者群组聊天 时,会触发”发送消息“事件,所以需要在客户端的 event
包下定义事件了。
// ChatEvent类中
public class ChatEvent implements IChatEvent {
/**
* 添加与用户的对话框
* @param userId 用户ID
* @param userFriendId 好友ID
*/
@Override
public void doEventAddTalkUser(String userId, String userFriendId) {
Channel channel = BeanUtil.getBean("channel",Channel.class);
channel.writeAndFlush(new TalkNoticeRequest(userId,userFriendId,0));
}
/**
* 添加与群组的对话框
* @param userId 用户ID
* @param groupId 群组ID
*/
@Override
public void doEventAddTalkGroup(String userId, String groupId) {
Channel channel = BeanUtil.getBean("channel", Channel.class);
channel.writeAndFlush(new TalkNoticeRequest(userId, groupId, 1));
}
/**
* 删除与用户的对话框
* @param userId 用户ID
* @param talkId 对话框ID
*/
@Override
public void doEventDelTalkUser(String userId, String talkId) {
Channel channel = BeanUtil.getBean("channel", Channel.class);
channel.writeAndFlush(new DelTalkRequest(userId, talkId));
}
}
接下来就是客户端 对话请求的处理 了:
public class TalkNoticeHandler extends SimpleChannelInboundHandler<TalkNoticeResponse> {
private UIService uiService;
public TalkNoticeHandler(UIService uiService) {
this.uiService = uiService;
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, TalkNoticeResponse msg) throws Exception {
IChatMethod chat = uiService.getChat();
Platform.runLater(() -> {
/**
* UI界面 其中
* talkIdx 默认为-1 首位为0
* talkType好友为0 群组为1
* msg.getTalkId() 1v1聊天Id、1vn聊天Id
* 对话框名称
* 对话框头像
* 对话框通信简介(聊天内容最后一组消息)
* 对话框时间
* 是否选中当前对话框
*/
chat.addTalkBox(-1, 0, msg.getTalkId(),
msg.getTalkName(), msg.getTalkHead(), msg.getTalkSketch(), msg.getTalkDate(), false);
});
}
}
服务端对 对话请求 处理:
由这个流程图我们也得知,服务端需要处理 好友之间的对话连接,对话框消息的落库、和好友之间的对话通知以及群聊的对话。
既然有好友和群组的对话区别,而且种类不多(msg.getTalkType()
就两个),我们完全可以用switch
分支来进行分别的处理。其中,单人的对话框建立是重点,是需要双方都需要建立对话通知的,而群组只需要单方面建立通知即可(因为不可能所有人都在线)
/*
注释:
这里的MyBizHandler也是一个抽象类,该类继承了SimpleChannelInboundHandler<T>类,该类还自己创建了channelRead()抽象方法由继承MyBizHandler的子类去实现
下面中的UserService是操作数据库的一个接口类
*/
public class TalkNoticeHandler extends MyBizHandler<TalkNoticeRequest>{
@Override
public void channelRead(Channel channel, TalkNoticeRequest msg) {
logger.info("对话通知应答处理:{}", JSON.toJSONString(msg));
switch(msg.getTalkType()){
case 0: // 好友
// 1.对话框的数据落库(创建对话框到消息列表)
userService.addTalkBoxInfo(msg.getUserId(),msg.getFriendUserId(),0);
userService.addTalkBoxInfo(msg.getFriendUserId(),msg.getUserId(),0);
// 2.查询对话框信息[自己 发送给好友的对话框]
UserInfo userInfo = userService.queryUserInfo(msg.getUserId());
// 3.发送对话框消息给好友
TalkNoticeResponse response = new TalkNoticeResponse();
response.setTalkId(userInfo.getUserId());
response.setTalkName(userInfo.getUserNickName());
response.setTalkHead(userInfo.getUserHead());
response.setTalkSketch(null);
response.setTalkDate(new Date());
// 获取好友通道
Channel friendChannel = SocketChannelUtil.getChannel(msg.getFriendId());
if (null == friendChannel) {
logger.info("用户id:{}未登录!", msg.getFriendUserId());
return;
}
friendChannel.writeAndFlush(response);
break;
case 1: // 群组
userService.addTalkBoxInfo(msg.getUserId(), msg.getFriendUserId(), 1);
break;
default:
break;
}
}
}
删除对话框通知 服务端:
当用户点击 删除对话框时 就会触发一个删除对话框事件,当然上面已经写了,就不复述了。服务端需要对这个删除操作进行处理:根据userId
和TalkId
直接操作数据库删除对话框即可。
public class DelTalkHandler extends MyBizHandler<DelTalkRequest> {
@Override
public void channelRead(Channel channel, DelTalkRequest msg) {
userService.deleteUserTalk(msg.getUserId(), msg.getTalkId());
}
}
总结
对话通知和应答 这个功能也不是很复杂,实现的步骤也是从自定义协议开始,这里比上一章多了事件的行为。因为需要点击 ”发送消息“ 才会显示对话框出来的;铺垫完这些后,客户端就 利用 TalkNoticeResponse
协议的属性 与UI模块的接口进行处理(这块简略即可),客户端的工作到此为止;服务端的工作就是 也是进行对话的处理(利用 TalkNoticeRequest
协议),但是除了这些还需要和好友之间进行对话框的落库,好友对话框的通知建立、以及群组的对话框通知建立(但是这里只需要自己通知就好,不需要与群的每个人都建立对话框通知)和最后的删除对话框的处理。