群聊功能如下:创建群聊、加入群聊、退出群聊、获取群聊成员、在群聊中发送消息
基于我们之前对于私聊的完善,实际上群聊功能对于我们目前的系统而言只是crud的重复操作
创建群聊
创建一个新的群聊,然后把群主拉入群中,实际上对于UserGroup表,我们可以再加一个标识列,表面用户在群聊中的身份,但这里就按简单的来,后面对于用户的退出和重复进入也不用逻辑删除了:
/**
* @author 14501
*/
@Slf4j
@Component
@ChannelHandler.Sharable
public class GroupCreateRequestMessageHandler extends SimpleChannelInboundHandler<GroupCreateRequestMessage> {
@Resource
private ChatGroupService chatGroupService;
@Resource
private UserGroupService userGroupService;
@Override
@Transactional
protected void channelRead0(ChannelHandlerContext ctx, GroupCreateRequestMessage msg) {
Long userId = msg.getUserId();
//创建群组
ChatGroup chatGroup = new ChatGroup();
chatGroup.setGroupName(msg.getGroupName());
chatGroupService.save(chatGroup);
//将创建人拉入群组
UserGroup userGroup = new UserGroup();
userGroup.setGroupId(chatGroup.getId());
userGroup.setUser(userId);
userGroupService.save(userGroup);
JSONObject object = new JSONObject();
object.put("groupId",chatGroup.getId());
object.put("groupName",chatGroup.getGroupName());
ArrayList<Long> longs = new ArrayList<>();
longs.add(userId);
object.put("users",longs);
ctx.writeAndFlush(new TextWebSocketFrame(new ResponseResult<>(Code.SUCCESS.code,object).toString()));
}
}
加入群聊
当用户加入群聊时,可以先行拉取群聊近段时间的历史记录,这里就不添加了
对于用户重复加入群聊,或以往加入过群聊的情况,可以使用逻辑删除来对业务代码进行优化,有兴趣的同学可以改进一下:
/**
* @author 14501
*/
@Slf4j
@Component
@ChannelHandler.Sharable
public class GroupJoinRequestMessageHandler extends SimpleChannelInboundHandler<GroupJoinRequestMessage> {
@Resource
private UserGroupService userGroupService;
@Resource
private ChatGroupService chatGroupService;
@Override
protected void channelRead0(ChannelHandlerContext ctx, GroupJoinRequestMessage msg) throws Exception {
Long userId = msg.getUserId();
Long groupId = msg.getGroupId();
ChatGroup group = chatGroupService.getById(groupId);
String result;
if(group == null){
result = new ResponseResult<>(Code.BAD_REQUEST.code,"不存在的群组").toString();
}else{
LambdaQueryWrapper<UserGroup> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(UserGroup::getGroupId,groupId)
.eq(UserGroup::getUser,userId);
UserGroup one = userGroupService.getOne(wrapper);
if(one != null){
result = new ResponseResult<>(Code.BAD_REQUEST.code, "用户已在群组中").toString();
}else{
UserGroup userGroup = new UserGroup();
userGroup.setUser(userId);
userGroup.setGroupId(groupId);
userGroupService.save(userGroup);
result = new ResponseResult<>(Code.SUCCESS).toString();
}
}
ctx.writeAndFlush(new TextWebSocketFrame(result));
}
}
退出群聊
handler就不多赘述了:
/**
* @author 14501
*/
@Slf4j
@Component
@ChannelHandler.Sharable
public class GroupQuitRequestMessageHandler extends SimpleChannelInboundHandler<GroupQuitRequestMessage> {
@Resource
private UserGroupMapper userGroupMapper;
@Override
protected void channelRead0(ChannelHandlerContext ctx, GroupQuitRequestMessage msg) {
int del = userGroupMapper.outGroup(msg.getUserId(),msg.getGroupId());
String res;
if(del > 0){
res = new ResponseResult<>(Code.SUCCESS).toString();
}else{
res = new ResponseResult<>(Code.BAD_REQUEST.code,"用户不在该群组中").toString();
}
ctx.writeAndFlush(new TextWebSocketFrame(res));
}
}
mapper可以用注解写一下,比用Mybatis-Plus的方便:
/**
* @author 14501
* @description 针对表【user_group】的数据库操作Mapper
* @createDate 2023-11-28 18:18:29
* @Entity generator.domain.UserGroup
*/
@Mapper
public interface UserGroupMapper extends BaseMapper<UserGroup> {
/**
* 返回群组所有成员的id列表
* @param groupId 群组id
* @return 成员id列表
*/
@Select("SELECT user FROM user_group WHERE group_id = #{groupId}")
List<Long> getGroupMembers(Long groupId);
/**
* 退出群组
* @param userId 用户id
* @param groupId 群组id
* @return 受影响的行数
*/
@Delete("DELETE FROM user_group WHERE group_id = #{groupId} AND user = #{userId}")
int outGroup(Long userId, Long groupId);
}
群聊成员
这里只返回了成员的id,实际业务时可以自行增强一下:
/**
* @author 14501
*/
@Slf4j
@Component
@ChannelHandler.Sharable
public class GroupMembersRequestMessageHandler extends SimpleChannelInboundHandler<GroupMembersRequestMessage> {
@Resource
private UserGroupMapper userGroupMapper;
@Override
protected void channelRead0(ChannelHandlerContext ctx, GroupMembersRequestMessage msg) {
List<Long> ids = userGroupMapper.getGroupMembers(msg.getGroupId());
ctx.writeAndFlush(new TextWebSocketFrame(new ResponseResult<>(Code.SUCCESS,ids).toString()));
}
}
群聊消息
这里使用之前优化过的消息推送的通用方法:
/**
* @author 14501
*/
@Slf4j
@Component
@ChannelHandler.Sharable
public class GroupChatRequestMessageHandler extends SimpleChannelInboundHandler<GroupChatRequestMessage> {
@Resource
private UserGroupMapper userGroupMapper;
@Resource
private RabbitMqPublisher publish;
@Resource
private ChatGroupMsgService chatGroupMsgService;
@Override
protected void channelRead0(ChannelHandlerContext ctx, GroupChatRequestMessage msg) throws Exception {
msg.setFrom(msg.getUserId());
List<Long> ids = userGroupMapper.getGroupMembers(msg.getGroupId());
ChatGroupMsg chatGroupMsg = new ChatGroupMsg(msg);
chatGroupMsgService.save(chatGroupMsg);
publish.sendMsg(ids,chatGroupMsg);
ctx.writeAndFlush(new TextWebSocketFrame(new ResponseResult<>(Code.SUCCESS,chatGroupMsg).toString()));
log.info("群聊msg:{},已提交发送",chatGroupMsg);
}
}
到此,基础的分布式聊天室已经搭建完成
调试
先登录三个账号:ccx、ls、zs
创建群组
群组创建成功,目前只有一个成员在群里
加入群组
zs、ls都加入成功,服务器只依靠token来判断userId(实际上目前的系统鉴权有个Bug,就是token对应的userId不是目前channel对应的userId,这里要在WebSocketHandler那边加个判断,望周知):
查看群组成员
可以看到三个成员都已经在里面
发送群聊
三人都接收到了群聊消息,这里可以把发送者的id从id列表摘除,或者前端对同id消息做去重:
群聊离线消息
当用户不在线时,群聊中的消息会和私聊消息保存在一个Redis缓存list里,用户上线时可以就可以看到,下面是zs离线时ls发送的消息:
退出群聊
zs觉得群聊太吵了,决定退出群聊:
再去查看群聊的成员列表时,已经没有zs了(UserId = 2):
再次发送群聊
ls发现zs退群了,询问众人原因:
但群里只有ccx了,他也不知道为什么,zs也无法接收到群聊消息了:
调试结束
其他
基本的聊天室已经搭建完成,但对于Netty的使用才只是刚刚开始