IM系统第二章 – 搜索好友和添加好友
问题:你是如何实现该功能的?
首先,我们得知道该功能的实现流程。
当客户端A 从窗体打开搜索栏时 — 》开始搜索 — 》搜索好友 — 》搜索完成 — 》处理结果 — 》用户点击添加好友 — 》添加操作 — 》执行添加 — 》双向添加好友 — 》处理结果 — 》显示
上面的流程用图概括就是(图片参照 小傅哥)
实现流程图中的主要功能涉及到的操作为:
- 自定义协议
- 客户端实现搜索好友的处理
- 服务端实现搜索好友的处理
- 客户端实现添加好友的处理
- 服务端实现添加好友的处理
自定义协议:(协议是用来交流的基本,双方都需要遵循这个协议才能进行数据传送)
需要实现四个协议:搜索好友的请求协议、添加好友的请求协议、搜索好友请求响应的协议、添加好友请求响应的协议
// 协议抽象类
public abstract class Packet{
private final static Map<Byte,Class<? extends Packet>> packetType = new ConcurrentHashMap<>();
static{
packetType.put(Command.SearchFriendRequest, SearchFriendRequest.class);
packetType.put(Command.SearchFriendResponse, SearchFriendResponse.class);
packetType.put(Command.AddFriendRequest, AddFriendRequest.class);
packetType.put(Command.AddFriendResponse, AddFriendResponse.class);
}
....
}
四个协议都作为单独的类来实现:
// 搜索好友请求 协议
public class SearchFriendRequest extends Packet{
private String userId; // 用户Id
private String searchKey; // 搜索字段
get()/set()...
}
// 搜索好友请求应答 协议
public class SearchFriendResponse extends Packet {
// 存储用户 数据传输对象 的集合
private List<UserDto> list;
}
// 添加好友请求协议
public class AddFriendRequest extends Packet {
private String userId; // 自己的Id
private String friendId; // 好友的Id
get()/set()...
}
// 添加好友请求应答的协议
public class AddFriendResponse extends Packet{
private String friendId; // 好友的Id
private String friendNickName; // 好友的昵称
private String friendHead; // 好友的头像
get()/set()...
}
再来看看 传输数据对象 (DTO):也就是说客户端和服务端之间 真正传送的数据对象是这些。
// 用户 数据传输对象
public class UserDto {
private String userId; // 用户Id
private String userNickName; // 用户昵称
private String userHead; // 用户头像
private Integer status; // 状态:0添加、1【保留】、2已添加
构造函数...
get()/set()...
}
// 用户 模糊对象
public class LuckUserInfo{
private String userId; //用户ID
private String userNickName; //用户昵称
private String userHead; //用户头像
private Integer status; // 状态;0添加、1[保留]、2已添加
构造函数...
get()/set()...
}
// 用户好友
public class UserFriend {
private Long id; // 自增ID
private String userId; // 用户ID
private String userFriendId; // 好友用户ID
private Date createTime; // 创建时间
private Date updateTime; // 更新时间
构造函数...
get()/set()...
}
自定义完协议后,开始实现 客户端和UI界面的搜索好友的 逻辑(这段简略说说就好)
搜索好友 客户端:
// 搜索好友 处理器
public class SearchFriendHandler extends SimpleChannelInboundHandler<SearchFriendResponse> {
private UIService uiService;
public SearchFriendHandler(UIService uiService) {
this.uiService = uiService;
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, SearchFriendResponse msg) throws Exception {
List<UserDto> list = msg.getList();
if(null == list || list.isEmpty()) return;
IChatMethod chat = uiService.getChat();
Platform.runLater(() -> {
for (UserDto user : list) {
// 界面出现好友 “添加好友”的字样(类似于你搜索好友后,你要点击是否添加)
chat.addLuckFriend(user.getUserId(), user.getUserNickName(), user.getUserHead(), user.getStatus());
}
});
}
}
刚开始学的时候我有个疑问?就是为什么继承这个SimpleChannelInboundHandler
泛型的类型是请求应答 的协议 — 》 应该是客户端需要和UI界面进行对接,所以需要将请求应答返回的数据 传给UI进行处理。(而请求 协议本就没有带很多对象的集合信息,只是单个对象的信息)
搜索好友 服务端:
/*
注释:
这里的MyBizHandler也是一个抽象类,该类继承了SimpleChannelInboundHandler<T>类,该类还自己创建了channelRead()抽象方法由继承MyBizHandler的子类去实现
下面中的UserService是操作数据库的一个接口类
*/
public class SearchFriendHandler extends MyBizHandler<SearchFriendRequest> {
@Override
public void channelRead(Channel channel, SearchFriendRequest msg) {
logger.info("搜索好友请求的结果:{}",JSON.toJSONString(msg));
// 创建 存储用户数据传输对象 的集合
List<UserDto> userDtoList = new ArrayList<>();
// 通过Id、Key获取到模糊用户信息的集合
List<LuckUserInfo> luckUserInfoList = userService.queryFuzzyUserInfoList(msg.getUserId(), msg.getSearchKey());
UserDto userDto = new UserDto();
userDto.setUserId(userInfo.getUserId());
userDto.setUserNickName(userInfo.getUserNickName());
userDto.setUserHead(userInfo.getUserHead());
userDto.setStatus(userInfo.getStatus());
userDtoList.add(userDto);
}
SearchFriendResponse response = new SearchFriendResponse();
response.setList(userDtoList);
channel.writeAndFlush(response);
}
}
添加好友 客户端:
将 AddFriendResponse
协议中获取到的 friendId、friendNickName、FriendHead
传送给 UI界面进行处理。
public class AddFriendHandler extends MyBizHandler<AddFriendResponse> {
public AddFriendHandler(UIService uiService) {
super(uiService);
}
@Override
public void channelRead(Channel channel, AddFriendResponse msg) {
IChatMethod chat = uiService.getChat();
Platform.runLater(() -> {
chat.addFriendUser(true, msg.getFriendId(), msg.getFriendNickName(), msg.getFriendHead());
});
}
}
添加好友 服务端:
通过 添加好友请求协议的 userId
(自己Id)和friendId
(好友Id)来创建 “彼此的好友” 然后来添加到userFriendList
;然后通过friendId
在数据库查询到好友用户信息,将查询到的用户属性包装在AddFriendResponse
协议上在自己的channel
管道传输;同理,通过 userId
在数据库中查询到自己的用户信息,将查询到的用户信息属性包装在 AddFriendResponse
协议上在属于好友的channel
管道传输。
public class AddFriendHandler extends MyBizHandler<AddFriendRequest> {
@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()));
// 从数据库中查询 好友信息
UserInfo userInfo = userService.queryUserInfo(msg.getFriendId());
channel.writeAndFlush(new AddFriendResponse(userInfo.getUserId(),userInfo.getUserNickName(), userInfo.getUserHead()));
Channel friendChannel = socketChannelUtil.getChannel(msg.getFriendId());
if(friendChannel == null) return;
// 从数据库中查询 自己信息
UserInfo friendInfo = userService.queryUserInfo(msg.getUserId());
friendChannel.writeAndFlush(new AddFriendResponse(friendInfo.getUserId(), friendInfo.getUserNickName(), friendInfo.getUserHead()));
}
}
总结:
这个 添加和搜索好友的功能步骤也是先通过自定义协议后,然后再去创建所需要的数据传输对象(DTO)和需要的model对象后才进行客户端和服务端的操作的;在客户端和服务端的操作中,主要是服务端需要写入具体的实现逻辑(比如,如何根据 搜索好友 协议下的信息去处理,如何根据 添加好友 协议下的信息去处理 等等。。)客户端的操作主要是收到 服务端将 请求应答的 协议包装好的信息后,与UI模块的信息进行处理(这部分就略微知道就好了)。