Springboot+WebSocket+Netty实现在线聊天/群聊系统

 更新时间:2023年08月11日 09:19:10   作者:wml_JavaKill  
这篇文章主要实现在好友添加、建群、聊天对话、群聊功能,使用Java作为后端语言进行支持,界面友好,开发简单,文章中有详细的代码示例供大家参考,需要的朋友可以参考下

一、文章前言

此文主要实现在好友添加、建群、聊天对话、群聊功能,使用Java作为后端语言进行支持,界面友好,开发简单。

二、开发流程及工具准备

2.1、下载安装IntelliJ IDEA(后端语言开发工具),Mysql数据库,微信Web开发者工具。

三、开发步骤

1.创建maven project

先创建一个名为SpringBootDemo的项目,选择【New Project】

在这里插入图片描述

然后在弹出的下图窗口中,选择左侧菜单的【New Project】(注:和2022之前的idea版本不同,这里左侧没有【Maven】选项,不要选【Maven Archetype】!!!),输入Name(项目名):SpringBootDemo,language选择【java】,build system选择【maven】,然后选择jdk,我这里选的是jdk18.

在这里插入图片描述

然后点击【Create】

在这里插入图片描述

2.在project下创建module

点击右键选择【new】—【Module…】

在这里插入图片描述

左侧选择【Spring initializr】,通过idea中集成的Spring initializr工具进行spring boot项目的快速创建。窗口右侧:name可根据自己喜好设置,group和artifact和上面一样的规则,其他选项保持默认值即可,【next】

在这里插入图片描述

Developer Tools模块勾选【Spring Boot DevTools】,web模块勾选【Spring Web】

在这里插入图片描述

此时,一个Springboot项目已经搭建完成,可开发后续功能

3.编写一个消息实体类、Mapper、service(三层架构)

@Data
public class Chat {
    @TableId(type = IdType.AUTO)
    private Long id;
    private Long userId;
    private Long targetUserId;
    private LocalDateTime createTime;
    private String userName;
    private String targetUserName;
    private String content;
}

由于我们使用mybatis-plus,所以简单的增删改查不用自己写,框架自带了,只需要实现或者继承他的Mapper、Service

在这里插入图片描述

在这里插入图片描述

4.编写WebSocket服务类

@ServerEndpoint("/imserver/{userId}")
@Component
public class WebSocketService {
    /**
     * concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
     */
    private static ConcurrentHashMap<String, WebSocketService> webSocketMap = new ConcurrentHashMap<>();
    /**
     * 与某个客户端的连接会话,需要通过它来给客户端发送数据
     */
    private Session session;
    /**
     * 接收userId
     */
    private String userId = "";
    public static ChatMapper chatMapper = null;
    /**
     * 连接建立成功调用的方法
     * <p>
     * 1.用map存 每个客户端对应的MyWebSocket对象
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("userId") String userId) {
        this.session = session;
        this.userId = userId;
        if (webSocketMap.containsKey(userId)) {
            webSocketMap.remove(userId);
            webSocketMap.put(userId, this);
            //加入set中
        } else {
            webSocketMap.put(userId, this);
            //加入set中
        }
    }
    /**
     * 报错
     *
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error) {
        error.printStackTrace();
    }
    /**
     * 实现服务器推送到对应的客户端
     */
    public void sendMessage(String message) {
        try {
            this.session.getBasicRemote().sendText(message);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    /**
     * 自定义 指定的userId服务端向客户端发送消息
     */
    public static void sendInfo(Chat chat) {
        QueryWrapper<Chat> queryWrapper = new QueryWrapper();
        List<Chat> chats=chatMapper.selectList(queryWrapper.lambda()
                .eq(Chat::getTargetUserId, chat.getTargetUserId()).or().eq(Chat::getUserId, chat.getTargetUserId()).or().eq(Chat::getTargetUserId, chat.getUserId()).or().eq(Chat::getUserId, chat.getUserId()));
        //log.info("发送消息到:"+userId+",报文:"+message);
        if (!StringUtils.isEmpty(chat.getTargetUserId().toString()) && webSocketMap.containsKey(chat.getTargetUserId().toString())) {
            webSocketMap.get(chat.getUserId().toString()).sendMessage(JSONObject.toJSONString(chats));
            webSocketMap.get(chat.getTargetUserId().toString()).sendMessage(JSONObject.toJSONString(chats));
        } else {
            webSocketMap.get(chat.getUserId().toString()).sendMessage(JSONObject.toJSONString(chats));
        }
    }
    /**
     * 自定义关闭
     *
     * @param userId
     */
    public static void close(String userId) {
        if (webSocketMap.containsKey(userId)) {
            webSocketMap.remove(userId);
        }
    }
    /**
     * 获取在线用户信息
     *
     * @return
     */
    public static Map getOnlineUser() {
        return webSocketMap;
    }

5.创建控制器Controller

先创建Controller Package

在这里插入图片描述

创建一个Controller

在这里插入图片描述

输入类名,选在【Class】

在这里插入图片描述

因为要编写Rest风格的Api,要在Controller上标注@RestController注解

6.创建具体的Api接口

@RestController
public class DemoController {
    @Autowired
    private ChatService chatService;
    @PostMapping("/push")
    public ResponseEntity<String> pushToWeb(@RequestBody Chat chat) throws IOException {
        chat.setCreateTime(LocalDateTime.now());
        chatService.save(chat);
        WebSocketService.sendInfo(chat);
        return ResponseEntity.ok("MSG SEND SUCCESS");
    }
    @GetMapping("/close")
    public String close(String userId) {
        WebSocketService.close(userId);
        return "ok";
    }
    @GetMapping("/getOnlineUser")
    public Map getOnlineUser() {
        return WebSocketService.getOnlineUser();
    }
    @GetMapping("/getMessage")
    public ResponseEntity<List<Chat>> getMessage(String userId) {
        QueryWrapper<Chat> queryWrapper = new QueryWrapper();
        List<Chat> list = chatService.
                list(queryWrapper.lambda().eq(Chat::getTargetUserId, userId).or().eq(Chat::getUserId, userId));
        return ResponseEntity.ok(list);
    }
}

7.编写netty配置

package com.example.demo.config;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
import org.springframework.stereotype.Component;
public class NettyServer {
    public void start(){
        //创建两个线程组boosGroup和workerGroup,含有的子线程NioEventLoop的个数默认为cpu核数的两倍
        //boosGroup只是处理链接请求,真正的和客户端业务处理,会交给workerGroup完成
        EventLoopGroup boosGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            //创建服务器的启动对象
            ServerBootstrap bootstrap = new ServerBootstrap();
            //使用链式编程来配置参数
            //设置两个线程组
            bootstrap.group(boosGroup,workerGroup)
                    //使用NioSctpServerChannel作为服务器的通道实现
                    .channel(NioServerSocketChannel.class)
                    //初始化服务器链接队列大小,服务端处理客户端链接请求是顺序处理的,所以同一时间只能处理一个客户端链接
                    //多个客户端同时来的时候,服务端将不能处理的客户端链接请求放在队列中等待处理
                    .option(ChannelOption.SO_BACKLOG,1024)
                    //创建通道初始化对象,设置初始化参数
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            System.out.println("收到到新的链接");
                            //websocket协议本身是基于http协议的,所以这边也要使用http解编码器
                            ch.pipeline().addLast(new HttpServerCodec());
                            //以块的方式来写的处理器
                            ch.pipeline().addLast(new ChunkedWriteHandler());
                            ch.pipeline().addLast(new HttpObjectAggregator(8192));
                            ch.pipeline().addLast(new MessageHandler());//添加测试的聊天消息处理类
                            ch.pipeline().addLast(new WebSocketServerProtocolHandler("/ws", null, true, 65536 * 10));
                        }
                    });
            System.out.println("netty server start..");
            //绑定一个端口并且同步,生成一个ChannelFuture异步对象,通过isDone()等方法判断异步事件的执行情况
            //启动服务器(并绑定端口),bind是异步操作,sync方法是等待异步操作执行完毕
            ChannelFuture cf = bootstrap.bind(1245).sync();
            //给cf注册监听器,监听我们关心的事件
            cf.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture channelFuture) throws Exception {
                    if (cf.isSuccess()){
                        System.out.println("监听端口成功");
                    }else {
                        System.out.println("监听端口失败");
                    }
                }
            });
            //对通道关闭进行监听,closeFuture是异步操作,监听通道关闭
            //通过sync方法同步等待通道关闭处理完毕,这里会阻塞等待通道关闭
            cf.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            boosGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

8.前端代码Websocket聊天功能

if (!window.WebSocket) {
							window.WebSocket = window.MozWebSocket;
						}
						if (window.WebSocket) {
							me.websocket = new WebSocket(me.ws + me.info.id);
							me.websocket.onmessage = function(event) {
								var json = JSON.parse(event.data);
								me.msgListMethod();
								console.log(json);
							};
							console.log(me.websocket)
							me.websocket.onopen = function(event) {
								console.log("Netty-WebSocket服务器。。。。。。连接");
							};
							me.websocket.onerror = function(evt) {
								console.log('发生错误..., evt');
							};
							me.websocket.CONNECTING = function(evt) {
								console.log('正在链接中');
							};
						} else {
							alert("您的浏览器不支持WebSocket协议!");
						}
						//监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
						window.onbeforeunload = function() {
							if (me.websocket != null) {
								me.websocket.close();
							}
						};

这里用到了很多消息发送功能,比如文件、图片。群聊还可查看群成员功能

以上就是Springboot+WebSocket+Netty实现在线聊天/群聊系统的详细内容,更多关于Springboot实现在线聊天的资料请关注脚本之家其它相关文章!

相关文章

  • Java查看和修改线程优先级操作详解

    Java查看和修改线程优先级操作详解

    JAVA中每个线程都有优化级属性,默认情况下,新建的线程和创建该线程的线程优先级是一样的。本文将为大家详解Java查看和修改线程优先级操作的方法,需要的可以参考一下
    2022-08-08
  • 浅谈Java线程Thread之interrupt中断解析

    浅谈Java线程Thread之interrupt中断解析

    这篇文章主要介绍了Java线程Thread之interrupt中断解析,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-01-01
  • Springboot框架实现自动装配详解

    Springboot框架实现自动装配详解

    在使用springboot时,很多配置我们都没有做,都是springboot在帮我们完成,这很大一部分归功于springboot自动装配。本文将详细为大家讲解SpringBoot的自动装配原理,需要的可以参考一下
    2022-08-08
  • Java经典面试题汇总--多线程

    Java经典面试题汇总--多线程

    本篇总结的是Java多线程相关的面试题,后续会持续更新,希望我的分享可以帮助到正在备战面试的实习生或者已经工作的同行,如果发现错误还望大家多多包涵,不吝赐教,谢谢
    2021-06-06
  • 关于在IDEA中SpringBoot项目中activiti工作流的使用详解

    关于在IDEA中SpringBoot项目中activiti工作流的使用详解

    这篇文章主要介绍了关于在IDEA中SpringBoot项目中activiti工作流的使用详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-08-08
  • 微服务链路追踪Spring Cloud Sleuth整合Zipkin解析

    微服务链路追踪Spring Cloud Sleuth整合Zipkin解析

    这篇文章主要为大家介绍了微服务链路追踪Spring Cloud Sleuth整合Zipkin解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-02-02
  • Java通过动态代理实现一个简单的拦截器操作

    Java通过动态代理实现一个简单的拦截器操作

    这篇文章主要介绍了Java通过动态代理实现一个简单的拦截器操作,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-07-07
  • MyBatis实现插入大量数据方法详解

    MyBatis实现插入大量数据方法详解

    最近在公司项目开发中遇到批量数据插入或者更新,下面这篇文章主要给大家介绍了关于MyBatis实现批量插入的相关资料,需要的朋友可以参考下
    2022-11-11
  • 分布式事务CAP两阶段提交及三阶段提交详解

    分布式事务CAP两阶段提交及三阶段提交详解

    这篇文章主要为大家介绍了分布式事务CAP、两阶段提交及三阶段提交的内容详解有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-01-01
  • 基于SpringBoot使用MyBatis插件的问题

    基于SpringBoot使用MyBatis插件的问题

    MyBatis-Plus并不能为我们解决所有问题,例如一些复杂的SQL,多表联查,我们就需要自己去编写代码和SQL语句,我们该如何快速的解决这个问题呢,这个时候可以使用MyBatisX插件,今天小编给大家带来了SpringBoot使用MyBatis插件问题,感兴趣的朋友一起看看吧
    2022-03-03

最新评论