【十七】springboot整合WebSocket实现聊天室(超详细)

 springboot篇章整体栏目: 


【一】springboot整合swagger(超详细

【二】springboot整合swagger(自定义)(超详细)

【三】springboot整合token(超详细)

【四】springboot整合mybatis-plus(超详细)(上)

【五】springboot整合mybatis-plus(超详细)(下)

【六】springboot整合自定义全局异常处理

【七】springboot整合redis(超详细)

【八】springboot整合AOP实现日志操作(超详细)

【九】springboot整合定时任务(超详细)

【十】springboot整合redis实现启动服务即将热点数据保存在全局以及redis(超详细)

【十一】springboot整合quartz实现定时任务优化(超详细)

【十二】springboot整合线程池解决高并发(超详细,保你理解)

【十三】springboot整合异步调用并获取返回值(超详细)

【十四】springboot整合WebService(超详细)

【十五】springboot整合WebService(关于传参数)(超详细)

【十六】springboot整合WebSocket(超详细)

【十七】springboot整合WebSocket实现聊天室(超详细)

【十八】springboot实现自定义全局异常处理

【十九】springboot整合ElasticSearch实战(万字篇)

【二十】springboot整合过滤器实战

【二十一】springboot整合拦截器实战并对比过滤器

【二十二】springboot整合activiti7(1) 实战演示篇

【二十三】springboot整合spring事务详解以及实战

【二十四】springboot使用EasyExcel和线程池实现多线程导入Excel数据


介绍:接下来我会把学习阶段学到的框架等知识点进行整合,每一次整合是在前一章的基础上进行的,所以后面的整合不会重复放前面的代码。每次的demo我放在结尾,本次是接着上一章的内容延续的,只增加新增的或者修改的代码。

上一章是初步整合websocket之后实现了一个进度条的小demo,这次使用websocket实现聊天室功能,实现多个用户在线聊天以及私聊。

 首先我先展示一下效果图(随便弄的,界面比较丑陋):

【十七】springboot整合WebSocket实现聊天室(超详细)_第1张图片 我再展示一下我的目录结构(前端我用的HbuilderX):

【十七】springboot整合WebSocket实现聊天室(超详细)_第2张图片

【十七】springboot整合WebSocket实现聊天室(超详细)_第3张图片

前端就一个html文件,引用的线上的jquery和bootstrap,所以我没有写样式文件。

后端相比上一章没有新增文件。只是进行了WebSocket文件的改造,websocket的依赖不需要导了,上一章已经导入了。

第一步:改造WebSocket

package com.swagger.demo.config;

import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.CopyOnWriteArraySet;

/**
 * @ClassName WebSocketConfig
 * @Description TODO
 * @Author zrc
 * @Date 11:01
 * @Version 1.0
 **/
//注册成组件
@Component
//定义websocket服务器端,它的功能主要是将目前的类定义成一个websocket服务器端。注解的值将被用于监听用户连接的终端访问URL地址
@ServerEndpoint("/websocket/{username}")
//如果不想每次都写private  final Logger logger = LoggerFactory.getLogger(当前类名.class); 可以用注解@Slf4j;可以直接调用log.info
@Slf4j
public class WebSocket {

    //实例一个session,这个session是websocket的session
    private Session session;

    //存放当前用户名
    private String userName;

    //存放需要接受消息的用户名
    private String toUserName;

    //存放在线的用户数量
    private static Integer userNumber = 0;

    //存放websocket的集合(本次demo不会用到,聊天室的demo会用到)
    private static CopyOnWriteArraySet webSocketSet = new CopyOnWriteArraySet<>();

    //前端请求时一个websocket时
    @OnOpen
    public void onOpen(Session session,@PathParam("username") String username) throws IOException {
        this.session = session;
        //将当前对象放入webSocketSet
        webSocketSet.add(this);
        //增加在线人数
        userNumber++;
        //保存当前用户名
        this.userName = username;
        //获得所有的用户
        Set userLists = new TreeSet<>();
        for (WebSocket webSocket : webSocketSet) {
            userLists.add(webSocket.userName);
        }

        //将所有信息包装好传到客户端(给所有用户)
        Map map1 = new HashMap();
        //  把所有用户列表
        map1.put("onlineUsers", userLists);
        //messageType 1代表上线 2代表下线 3代表在线名单 4代表普通消息
        map1.put("messageType", 1);
        //  返回用户名
        map1.put("username", username);
        //  返回在线人数
        map1.put("number", this.userNumber);
        //发送给所有用户谁上线了,并让他们更新自己的用户菜单
        sendMessageAll(JSON.toJSONString(map1),this.userName);
        log.info("【websocket消息】有新的连接, 总数:{}", this.userNumber);

        // 更新在线人数(给所有人)
        Map map2 = new HashMap();
        //messageType 1代表上线 2代表下线 3代表在线名单 4代表普通消息
        map2.put("messageType", 3);
        //把所有用户放入map2
        map2.put("onlineUsers", userLists);
        //返回在线人数
        map2.put("number", this.userNumber);
        // 消息发送指定人(所有的在线用户信息)
        sendMessageAll(JSON.toJSONString(map2),this.userName);
    }

    //前端关闭时一个websocket时
    @OnClose
    public void onClose() throws IOException {
        //从集合中移除当前对象
        webSocketSet.remove(this);
        //在线用户数减少
        userNumber--;
        Map map1 = new HashMap();
        //messageType 1代表上线 2代表下线 3代表在线名单 4代表普通消息
        map1.put("messageType", 2);
        //所有在线用户
        map1.put("onlineUsers", this.webSocketSet);
        //下线用户的用户名
        map1.put("username", this.userName);
        //返回在线人数
        map1.put("number", userNumber);
        //发送信息,所有人,通知谁下线了
        sendMessageAll(JSON.toJSONString(map1),this.userName);
        log.info("【websocket消息】连接断开, 总数:{}", webSocketSet.size());
    }

    //前端向后端发送消息
    @OnMessage
    public void onMessage(String message) throws IOException {
        log.info("【websocket消息】收到客户端发来的消息:{}", message);
        //将前端传来的数据进行转型
        com.alibaba.fastjson.JSONObject jsonObject = JSON.parseObject(message);
        //获取所有数据
        String textMessage = jsonObject.getString("message");
        String username = jsonObject.getString("username");
        String type = jsonObject.getString("type");
        String tousername = jsonObject.getString("tousername");
        //群发
        if(type.equals("群发")){
            Map map3 = new HashMap();
            map3.put("messageType", 4);
            //所有在线用户
            map3.put("onlineUsers", this.webSocketSet);
            //发送消息的用户名
            map3.put("username", username);
            //返回在线人数
            map3.put("number", userNumber);
            //发送的消息
            map3.put("textMessage", textMessage);
            //发送信息,所有人,通知谁下线了
            sendMessageAll(JSON.toJSONString(map3),this.userName);
        }
        //私发
        else{
            //发送给对应的私聊用户
            Map map3 = new HashMap();
            map3.put("messageType", 4);
            //所有在线用户
            map3.put("onlineUsers", this.webSocketSet);
            //发送消息的用户名
            map3.put("username", username);
            //返回在线人数
            map3.put("number", userNumber);
            //发送的消息
            map3.put("textMessage", textMessage);
            //发送信息,所有人,通知谁下线了
            sendMessageTo(JSON.toJSONString(map3),tousername);

            //发送给自己
            Map map4 = new HashMap();
            map4.put("messageType", 4);
            //所有在线用户
            map4.put("onlineUsers", this.webSocketSet);
            //发送消息的用户名
            map4.put("username", username);
            //返回在线人数
            map4.put("number", userNumber);
            //发送的消息
            map4.put("textMessage", textMessage);
            //发送信息,所有人,通知谁下线了
            sendMessageTo(JSON.toJSONString(map3),username);
        }
    }

    /**
     *  消息发送所有人
     */
    public void sendMessageAll(String message, String FromUserName) throws IOException {
        for (WebSocket webSocket: webSocketSet) {
            //消息发送所有人(同步)getAsyncRemote
            webSocket.session.getBasicRemote().sendText(message);
        }
    }

    /**
     *  消息发送指定人
     */
    public void sendMessageTo(String message, String ToUserName) throws IOException {
        //遍历所有用户
        for (WebSocket webSocket : webSocketSet) {
            if (webSocket.userName.equals(ToUserName)) {
                //消息发送指定人
                webSocket.session.getBasicRemote().sendText(message);
                log.info("【发送消息】:", this.userName+"向"+ToUserName+"发送消息:"+message);
                break;
            }
        }
    }

}

下面,我逐步介绍:

1、定义全局变量

【十七】springboot整合WebSocket实现聊天室(超详细)_第4张图片

 首先定义几个全局变量:

username用于保存当前的websocket连接传过来的用户名,作为websocket的唯一标识,后面私聊时通过这个唯一标识发送消息给对应的websocket连接。

userNumber用于保存更新在线用户数,每次新增或断开一个websocket连接之后,更新该在线用户数。

webSocketSet用于保存在线的所有websocket对象,是个websocket对象的集合,使用Set集合,保证不会出现重复的对象,后面私发或群发时通过遍历该对象,将消息发送给对应的对象。

2、新增两个发送消息的方法

【十七】springboot整合WebSocket实现聊天室(超详细)_第5张图片

sendMessageAll是将消息发送给全部websocket对象,从上面可以看到,遍历websocket集合的所有对象,调用websocket的session里面的getBasicRemote的sendText方法发送传入的message消息。

sendMessageTo是将消息发送给指定的websocket对象,从上面可以看到,遍历websocket集合的所有对象,当用户名满足传入的接受用户时,调用websocket的session里面的getBasicRemote的sendText方法发送传入的message消息。

3、改造onOpen

【十七】springboot整合WebSocket实现聊天室(超详细)_第6张图片

【十七】springboot整合WebSocket实现聊天室(超详细)_第7张图片

【十七】springboot整合WebSocket实现聊天室(超详细)_第8张图片

@PathParam("username") String username用来接受前端传入的用户名,注意在ServerEndpoint注解参数改一下,加上用户名的传输。

在onopen里面增加全局的在线人数以及websocket对象集合,保存当前用户名。

遍历webSocketSet集合,获取出来所有在线用户。

将所有前端需要的信息包装到map,调用sendMessageAll方法,通知所有在线用户,某某用户上线了。

此处为了前端方便处理,调用两次sendMessageAll方法,type如上图所示,分别两次sendMessageAll方法处理上线通知和用户列表更新通知。

4、改造onClose

【十七】springboot整合WebSocket实现聊天室(超详细)_第9张图片

该方法是连接关闭时触发,所以将当前websocket对象从websocket的集合从移除并减少在线用户数,将前端需要的数据包装好后调用sendMessageAll方法,通知前端某某用户下线了。

5、改造onMessage

【十七】springboot整合WebSocket实现聊天室(超详细)_第10张图片

【十七】springboot整合WebSocket实现聊天室(超详细)_第11张图片

onMessage方法是接受前端传来数据时触发。

通过JSON.parseObject方法解析前端传过来的数据。

获取message里面的键值对数据

判断是群发还是私发然后调用不同的方法。

此处私发时需要发送给对应的websocket对象还需要发送给自己。

后端的改造到此结束,下面介绍前端的代码。

第二步:创建前端html文件




    聊天室
	
	
	
	
	

 

	
    

下面介绍各部分代码:

1、html代码

【十七】springboot整合WebSocket实现聊天室(超详细)_第12张图片

这部分代码没什么说的,我为了方便直接使用的bootstrap框架 。

2、js代码

【十七】springboot整合WebSocket实现聊天室(超详细)_第13张图片为了简便,又为了每个websocket的用户名唯一,我直接使用的随机数,在新建websocket实例里面,新增一个username参数的传递。

【十七】springboot整合WebSocket实现聊天室(超详细)_第14张图片

接收到消息的回调方法,先使用JSON.parse()方法解析一下数据格式,再通过判断传来的数据类型type,分别进行处理。

【十七】springboot整合WebSocket实现聊天室(超详细)_第15张图片

新增一个用户列表点击功能,点击时,将输入框显示如下,便于私聊:【十七】springboot整合WebSocket实现聊天室(超详细)_第16张图片

【十七】springboot整合WebSocket实现聊天室(超详细)_第17张图片

发送按钮,获取要发送的数据,将数据包装好,调用websocket的send方法发送到后端,后端通过OnMessage注解的方法进行处理。

完毕!

第三步:演示

此处我跑了四个websocket实例,如下:

【十七】springboot整合WebSocket实现聊天室(超详细)_第18张图片

【十七】springboot整合WebSocket实现聊天室(超详细)_第19张图片【十七】springboot整合WebSocket实现聊天室(超详细)_第20张图片

【十七】springboot整合WebSocket实现聊天室(超详细)_第21张图片

 可见,通知上线实现正常,接下来关闭实例4,如下:

【十七】springboot整合WebSocket实现聊天室(超详细)_第22张图片

下线通知正常,下面展示发送消息

【十七】springboot整合WebSocket实现聊天室(超详细)_第23张图片 【十七】springboot整合WebSocket实现聊天室(超详细)_第24张图片

 【十七】springboot整合WebSocket实现聊天室(超详细)_第25张图片

可以看到群发成功,点击用户列表的某一用户,展示私发。

【十七】springboot整合WebSocket实现聊天室(超详细)_第26张图片

【十七】springboot整合WebSocket实现聊天室(超详细)_第27张图片

【十七】springboot整合WebSocket实现聊天室(超详细)_第28张图片

可以看到,私发只有选择的那个人可以收到消息。

到此,整合完毕。

本期整合到此完毕,接下来会继续更新加强整合,尽情期待。

客户端访问地址:http://localhost:8090/swagger-ui.html或者http://localhost:8090/doc.html

demo地址:studydemo/整合swagger at main · zrc11/studydemo · GitHub

码字不易,若帮到各位,帮忙三连,感谢

修改(2021/10/9)

上面的代码忘记实现用户下线时更新用户列表

修改一下onClose注解注解的onClose方法,如下:

 //前端关闭时一个websocket时
    @OnClose
    public void onClose() throws IOException {
        //从集合中移除当前对象
        webSocketSet.remove(this);
        //在线用户数减少
        userNumber--;

        //通知下线
        Map map1 = new HashMap();
        //messageType 1代表上线 2代表下线 3代表在线名单 4代表普通消息
        map1.put("messageType", 2);
        //所有在线用户
        map1.put("onlineUsers", this.webSocketSet);
        //下线用户的用户名
        map1.put("username", this.userName);
        //返回在线人数
        map1.put("number", userNumber);
        //发送信息,所有人,通知谁下线了
        sendMessageAll(JSON.toJSONString(map1),this.userName);

        //通知修改用户列表
        // 更新在线人数(给所有人)
        Map map2 = new HashMap();
        //获得所有的用户
        Set userLists = new TreeSet<>();
        for (WebSocket webSocket : webSocketSet) {
            userLists.add(webSocket.userName);
        }
        //messageType 1代表上线 2代表下线 3代表在线名单 4代表普通消息
        map2.put("messageType", 3);
        //把所有用户放入map2
        map2.put("onlineUsers", userLists);
        //返回在线人数
        map2.put("number", this.userNumber);
        // 消息发送指定人(所有的在线用户信息)
        sendMessageAll(JSON.toJSONString(map2),this.userName);
        log.info("【websocket消息】连接断开, 总数:{}", webSocketSet.size());
    }

新增部分代码如下:

【十七】springboot整合WebSocket实现聊天室(超详细)_第29张图片

你可能感兴趣的:(springboot整合篇,websocket,java,spring)