Netty分布式聊天室 - 群聊功能

        群聊功能如下:创建群聊、加入群聊、退出群聊、获取群聊成员、在群聊中发送消息

        基于我们之前对于私聊的完善,实际上群聊功能对于我们目前的系统而言只是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的使用才只是刚刚开始

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Netty WebSocket聊天室是一个基于Netty框架实现的网络聊天室,它使用WebSocket协议实现实时双向通信。Netty是一个高性能的网络编程框架,通过使用Netty,我们可以轻松地构建可扩展的分布式系统。 在Netty WebSocket聊天室中,主要涉及到两个关键组件:WebSocketFrameHandler和ConcurrentHashMap。WebSocketFrameHandler是处理WebSocket消息的处理器,它必须被标记为Sharable,并且全局共享一个对象,这样才能实现群聊功能。而ConcurrentHashMap则是作为维护在线用户的容器,它能够确保线程安全性。 如果想体验Netty WebSocket聊天室,你可以访问作者部署在服务器上的地址http://xindoo.xyz:8083/。另外,如果你对实现细节感兴趣,可以查看作者的博客文章https://blog.csdn.net/weixin_43333483/article/details/127716359#comments_25224363,其中包含了完整的代码和启动方式。一旦启动成功,你就可以通过访问http://localhost:8088/开始聊天了。<span class="em">1</span><span class="em">2</span> #### 引用[.reference_title] - *1* [用Netty实现WebSocket网络聊天室](https://blog.csdn.net/xindoo/article/details/126572886)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [Netty实现Netty+http+websocket聊天室案例](https://download.csdn.net/download/weixin_43333483/87502543)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱飞的男孩

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值