Spring Boot 整合 socket 实现简单聊天

来看一下实现的界面效果
在这里插入图片描述
pom.xml的maven依赖

 <!-- 引入 socket -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>
        <!-- 引入 Fastjson ,实现序列化使用  -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.62</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

配置类

@Configuration
public class WebSocketConfiguration {
    /**
     * 给 spring 容器注入这个 ServerEndpointExporter对象
     * <p>
     * 这个bean会检测所有带有 @ServerEndpoint 注解的 bean 并注册他们。
     * ps:
     * 如果使用的是外置的 Tomcat 容器,则不需要自己提供 ServerEndpointExporter,因为它将由 Tomcat 容器自己提供和管理。
     *
     * @return ServerEndpointExporter
     */
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

消息接口

public interface Message {
}

WebSocket 会话上下文工具

@Slf4j
public class WebSocketContext {

    /**
     * Session 与用户的映射
     */
    private static final Map<Session, String> SESSION_USER_MAP = new ConcurrentHashMap<>();
    /**
     * 用户与 Session 的映射
     */
    private static final Map<String, Session> USER_SESSION_MAP = new ConcurrentHashMap<>();

    /**
     * 添加 Session 在这个方法中,会绑定用户和 Session 之间的映射
     *
     * @param session Session
     * @param user    用户
     */
    public static void add(Session session, String user) {
        // 更新 USER_SESSION_MAP , 这里的 user 正常来讲应该是具体的用户(id),而不是单纯的 session.getId()
        USER_SESSION_MAP.put(user, session);
        // 更新 SESSION_USER_MAP
        SESSION_USER_MAP.put(session, user);
    }

    /**
     * 移除 Session
     *
     * @param session Session
     */
    public static void remove(Session session) {
        // 从 SESSION_USER_MAP 中移除
        String user = SESSION_USER_MAP.remove(session);
        // 从 USER_SESSION_MAP 中移除
        if (user != null && user.length() > 0) {
            USER_SESSION_MAP.remove(user);
        }
    }


    /**
     * 广播发送消息给所有在线用户
     *
     * @param type    消息类型
     * @param message 消息体
     * @param <T>     消息类型
     * @param me      当前消息的发送者,不会将消息发送给自己
     */
    public static <T extends Message> void broadcast(String type, T message, Session me) {
        // 创建消息
        String messageText = buildTextMessage(type, message);
        // 遍历 SESSION_USER_MAP ,进行逐个发送
        for (Session session : SESSION_USER_MAP.keySet()) {
            if (!session.equals(me)) {
                sendTextMessage(session, messageText);
            }
        }
    }

    /**
     * 发送消息给单个用户的 Session
     *
     * @param session Session
     * @param type    消息类型
     * @param message 消息体
     * @param <T>     消息类型
     */
    public static <T extends Message> void send(Session session, String type, T message) {
        // 创建消息
        String messageText = buildTextMessage(type, message);
        // 遍历给单个 Session ,进行逐个发送
        sendTextMessage(session, messageText);
    }

    /**
     * 发送消息给指定用户
     *
     * @param user    指定用户
     * @param type    消息类型
     * @param message 消息体
     * @param <T>     消息类型
     * @return 发送是否成功
     */
    public static <T extends Message> boolean send(String user, String type, T message) {
        // 获得用户对应的 Session
        Session session = USER_SESSION_MAP.get(user);
        if (session == null) {
            log.error("==> user({}) 不存在对应的 session", user);
            return false;
        }
        // 发送消息
        send(session, type, message);
        return true;
    }

    /**
     * 构建完整的消息
     *
     * @param type    消息类型
     * @param message 消息体
     * @param <T>     消息类型
     * @return 消息
     */
    private static <T extends Message> String buildTextMessage(String type, T message) {
        JSONObject messageObject = new JSONObject();
        messageObject.put("type", type);
        messageObject.put("body", message);
        return messageObject.toString();
    }

    /**
     * 真正发送消息
     *
     * @param session     Session
     * @param messageText 消息
     */
    private static void sendTextMessage(Session session, String messageText) {
        if (session == null) {
            log.error("===> session 为 null");
            return;
        }
        RemoteEndpoint.Basic basic = session.getBasicRemote();
        if (basic == null) {
            log.error("===> session.basic 为 null");
            return;
        }
        try {
            basic.sendText(messageText);
        } catch (IOException e) {
            log.error("===> session: {} 发送消息: {} 发生异常", session, messageText, e);
        }
    }

    /**
     * 在线人数通知
     */
    public static void countNotice() {
        Integer count = SESSION_USER_MAP.size();
        ChatCountMessage message = new ChatCountMessage();
        message.setCount(count);
        broadcast(MsgTypeEnum.CHAT_COUNT.getCode(), message, null);
    }
}

消息类型枚举

@Getter
@AllArgsConstructor
public enum MsgTypeEnum {
    /**
     * 同于标识 当前消息是 聊天消息
     */
    CHAT_MSG("1", "聊天消息"),
    /**
     * 用于标识 当前消息是 人数消息
     */
    CHAT_COUNT("2", "聊天室人数");

    private final String code;
    private final String desc;
}

配置接入点
ServerEndpoint 注解是一个类层次的注解,它的功能主要是将目前的类定义成一个websocket服务器端,
注解的值将被用于监听用户连接的终端访问URL地址,客户端可以通过这个URL来连接到WebSocket服务器端

@Component
@ServerEndpoint("/chat")
@Slf4j
public class WebSocketServer {

    /**
     * 连接建立成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session) {
        log.info("===> onOpen:{}", session.getId());
        // 上线,并且通知到其他人
        WebSocketContext.add(session, session.getId());
        WebSocketContext.countNotice();
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose(Session session) {
        log.info("===> onClose:{}", session.getId());
        // 下线,并且通知到其他人
        WebSocketContext.remove(session);
        WebSocketContext.countNotice();
    }

    /**
     * 收到客户端消息后调用的方法
     *
     * @param message 客户端发送过来的消息
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        log.info("===> onMessage:{},message:{}", session.getId(), message);
        // 进行消息的转发,同步到其他的客户端上
        ChatMsgMessage msg = JSON.parseObject(message, ChatMsgMessage.class);
        WebSocketContext.broadcast(MsgTypeEnum.CHAT_MSG.getCode(), msg, session);
    }

    /**
     * 监听错误
     *
     * @param session session
     * @param error   错误
     */
    @OnError
    public void onError(Session session, Throwable error) {
        log.error("SessionId:{},出现异常:{}", session.getId(), error.getMessage());
        error.printStackTrace();
    }

}

在线人数消息实体

@Data
@Accessors(chain = true)
public class ChatCountMessage implements Message {

    public static final String TYPE = MsgTypeEnum.CHAT_COUNT.getCode();

    /**
     * 消息编号
     */
    private String msgId;
    /**
     * 内容
     */
    private Integer count;

}

消息发送实体

@Data
@Accessors(chain = true)
public class ChatMsgMessage implements Message {

    public static final String TYPE = MsgTypeEnum.CHAT_MSG.getCode();

    /**
     * 消息编号
     */
    private String msgId;
    /**
     * 内容
     */
    private String msg;

}

在resources下新建static静态文件夹
在这里插入图片描述
index.css文件

@font-face {
    font-family: "pix";
    src: url("../DottedSongtiSquareRegular.otf");
}

html, body, pre, code, kbd, samp {
    font-family: "pix", serif;
    font-weight: bold;
    font-size: 35px;
}

.all-div {
    display: flex;
    flex-direction: column;
    width: 800px;
    margin: 20px auto;
    overflow-scrolling: auto;
}

/*.message {*/
/*    overflow: auto;*/
/*    width: 800px;*/
/*    height: 400px;*/
/*    margin-top: 20px;*/
/*}*/

.send-btns {
    display: flex;
}

/*自己发送聊天的样式*/
.message-me {
    color: red;
    text-align: right
}

.message-list {
    display: flex;
    flex-direction: column;
}

.message-left {
    display: flex;
    margin-top: 2rem;
    align-self: flex-start;
}
.message-right {
    display: flex;
    margin-top: 2rem;
    align-self: flex-end;
}

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>在线聊天</title>
    <link href="NES.css" rel="stylesheet"/>
    <link href="/css/index.css" rel="stylesheet">
</head>
<style>
</style>
<body>
<div class="all-div">
    <!--  头部区域  -->
    <div style="margin-left: 10px">
        Spring Boot 集成 WebSocket 示例;
        <span class="nes-text is-primary">在线人数:</span>
        <span class="nes-text is-error" id="count">0</span>
    </div>
    <!--  内容显示区域  -->
    <div class="nes-container is-rounded is-dark message-list" id="message">

    </div>
    <!--  操作区域  -->
    <div class="nes-field is-inline">
        <br/>
        <input id="text" type="text" class="nes-input" style="padding: .2rem 1rem !important;"/>
        <button onclick="send()" class="nes-btn is-success">发送</button>
        <button onclick="closeWebSocket()" class="nes-btn is-error">关闭WebSocket连接</button>
    </div>
</div>
</body>

<script type="text/javascript">
    let websocket = null;
    //判断当前浏览器是否支持WebSocket
    if ('WebSocket' in window) {
        //改成你的地址
        websocket = new WebSocket("ws://127.0.0.1:8080/chat");
    } else {
        alert('当前浏览器不支持 websocket')
        throw "当前浏览器不支持 websocket"
    }

    //连接发生错误的回调方法
    websocket.onerror = function () {
        setMessageInnerHTML("WebSocket连接发生错误" + "&#13;");
    };

    //连接成功建立的回调方法
    websocket.onopen = function () {
        setMessageInnerHTML("WebSocket连接成功" + "&#13;");
    }
    //接收到消息的回调方法
    websocket.onmessage = function (event) {
        let jsonData = event.data;
        let data = JSON.parse(jsonData);
        console.log("收到消息==", event);
        if (data.type === "1") {
            let msg = otherPersonShowMsg(data.body.msg)
            setMessageInnerHTML(msg);
        }
        if (data.type === "2") {
            setChatCountInnerHTML(data.body.count)
        }
    }

    //连接关闭的回调方法
    websocket.onclose = function () {
        setMessageInnerHTML("WebSocket连接关闭" + "&#13;");
    }

    //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
    window.onbeforeunload = function () {
        closeWebSocket();
    }

    //将消息显示在网页上
    function setMessageInnerHTML(innerHTML) {
        document.getElementById('message').innerHTML += innerHTML;
    }

    //将消息显示在网页上
    function setChatCountInnerHTML(innerHTML) {
        document.getElementById('count').innerHTML = innerHTML;
    }

    //关闭WebSocket连接
    function closeWebSocket() {
        websocket.close();
        setChatCountInnerHTML(0)
    }

    //发送消息
    function send() {
        var message = document.getElementById('text').value;
        websocket.send('{"msg":"' + message + '"}');
        document.getElementById('text').value = '';
        message = this.meShowMsg(message);
        setMessageInnerHTML(message);
    }

    // 显示别人发送的消息
    function otherPersonShowMsg(str) {
        return ` <section class="message-left">
                  <i class="nes-bcrikko"></i>
                  <div class="nes-balloon from-left is-dark" style="padding: .2rem 1rem !important;">
                    <p>${str}</p>
                  </div>
                </section>`
    }

    // 显示自己发送的消息
    function meShowMsg(str) {
        return ` <section class="message-right">
                    <div class="nes-balloon from-right is-dark" style="padding: .2rem 1rem !important;">
                      <p>${str}</p>
                    </div>
                    <i class="nes-bcrikko"></i>
              </section>`
    }
</script>
</html>

以上的是Spring Boot 整合 socket 实现简单聊天 若需完整代码 可识别二维码后 给您发代码。
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/594152.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【CV-CUDA实战】使用Python+TensorRT+CVCUDA优化YOLOv8

目录 什么是CV-CUDA环境准备准备CV-CUDA静态库解压添加至变量将PyBind静态库复制到env下算子设计前处理算子 TensorRT模型加载后处理函数 完整代码输出演示为什么重新写了&#xff1f;结语 什么是CV-CUDA NVIDIA CV-CUDA™ 是一个开源项目&#xff0c;用于构建云规模人工智能 (…

【数据结构(邓俊辉)学习笔记】列表02——无序列表

文章目录 0.概述1.插入与构造1.1 插入1.1.1 前插入1.1.2后插入1.1.3 复杂度 1.2 基于复制构造1.2.1 copyNodes()1.2.2 基于复制构造1.2.3 复杂度 2.删除与析构2.1 删除2.1.1 实现2.1.2 复杂度 2.2 析构2.2.1 释放资源及清除节点2.2.2 复杂度 3.查找3.1 实现3.2 复杂度 4.唯一化…

每天五分钟深度学习:数学中常见函数中的导数

本文重点 导数是微积分学中的一个核心概念,它描述了函数在某一点附近的变化率。在物理学、工程学、经济学等众多领域中,导数都发挥着极其重要的作用。本文旨在详细介绍数学中常见函数的导数,以期为读者提供一个全面而深入的理解。 数学中常见的导数 常数函数的导数 对于常数…

Raft共识算法笔记,MIT6.824,

处理leader和follow的一个重要思路是多数投票&#xff0c;确保系统中存在奇数个服务器&#xff08;例如3台&#xff09;。进行任何操作都需要来自多数服务器的同意&#xff0c;例如3台服务器中的2台。如果没有多数同意&#xff0c;系统会等待。为什么多数投票有助于避免脑裂问题…

springboot项目 字典/枚举翻译 终极解决方案 AOP+自定义注解+递归实体字段+实体动态三级缓存+责任链+多种转换方式

目录 前言实现思路技术确定 食用方式效果使用样例项目中使用第一步 复制包第二步 实现LoadDictDatabase并将其注入容器第三步 标识需要翻译的字段第四步 标识需要翻译的方法第五步 调用需要翻译的方法 实现细节TODO 前言 字典,即在存储介质中进行存储时,为了避免业务上对其名称…

计数排序,基数排序,桶排序

目录 计数排序: 基数排序&#xff1a; 桶排序: 计数排序: 计数排序是一种非比较型整数排序算法&#xff0c;特别适用于一定范围内的整数排序。它的核心思想是使用一个额外的数组&#xff08;称为计数数组&#xff09;来计算每个值的出现次数&#xff0c;然后根据这些计数信…

[贪心] 区间选点问题

905. 区间选点 - AcWing题库 思路&#xff1a;就是将所有区间按照右端点排序&#xff0c; 然后选取一些区间的右端点 代码&#xff1a; #include <iostream> #include <algorithm> #include <vector> using namespace std; const int N 100010;typedef p…

Flask与HTTP

一、请求响应循环 “请求-响应循环”&#xff1a;客户端发出请求&#xff0c;服务器处理请求并返回响应。 Flask Web程序的工作流程&#xff1a; 当用户访问一个URL&#xff0c;浏览器便生成对应的HTTP请求&#xff0c;经由互联网发送到对应的Web服务器。Web服务器接收请求&a…

信号,信号列表,信号产生方式,信号处理方式

什么是信号 信号在我们的生活中非常常见&#xff1b;如红绿灯&#xff0c;下课铃&#xff0c;游戏团战信号&#xff0c;这些都是信号&#xff1b;信号用来提示接收信号者行动&#xff0c;但接收信号的人接收到信号会进行一系列的行为&#xff0c;完成某个动作&#xff1b;这就…

基于Java EE平台项目管理系统的设计与实现(论文 + 源码)

【免费】基于javaEE平台的项目管理系统.zip资源-CSDN文库https://download.csdn.net/download/JW_559/89267688 基于Java EE平台项目管理系统的设计与实现 摘 要 随着社会信息化的发展&#xff0c;很多的社会管理问题也一并出现了根本性变化&#xff0c;项目公司的报表及文…

【YOLO】目标检测 YOLO框架之train.py参数含义及配置总结手册(全)

1.一直以来想写下基于YOLO开源框架的系列文章&#xff0c;该框架也是日常项目开发中常用的一款工具&#xff0c;最近刚好挤时间梳理、总结下这块儿的知识体系。 2.熟悉、梳理、总结下YOLO目标检测相关知识体系&#xff0c;之前实战配置时总是临时性检索些注释含义&#xff0c;但…

JVM组成之类加载器

类加载器&#xff08;ClassLoader&#xff09;&#xff1a;是Java虚拟机提供给应用程序去实现获取类和接口字节码数据的技术。 类加载器多数是有Java编写的&#xff0c;也有部分是c编写的&#xff0c;负责接收来自外部的二进制数据&#xff0c;然后执行JNI&#xff08;也就是本…

【Java】山外有山,类外还有类

【Java】山外有山&#xff0c;类外还有类 内部类是Java语言中的一种特性&#xff0c;它允许在另一个类中定义一个类。 内部类可以是静态的&#xff08;不依赖于外部类的实例&#xff09;&#xff0c;也可以是非静态的&#xff08;依赖于外部类的实例&#xff09;。 在本篇博…

在R的 RGui中,使用devtools 安装trajeR

创建于&#xff1a;2024.5.5 文章目录 1. 报错信息2. 尝试使用指定的清华镜像&#xff0c;没有解决3. 找到原因&#xff1a;官网把包删除了4. 尝试从网上下载&#xff0c;然后安装。没有成功5. 使用devtools安装5.1 尝试直接安装&#xff1a;install.packages("devtools&q…

【智能算法应用】混合粒子群算法求解CVRP问题

目录 1.算法原理2.数学模型3.结果展示4.参考文献5.代码获取 1.算法原理 【智能算法】粒子群算法&#xff08;PSO&#xff09;原理及实现 经典PSO算法用于连续空间优化问题&#xff0c;VRP问题为离散组合优化问题&#xff0c;涉及如何有效地分配一组车辆去访问多个客户点&…

OSEK的设计哲学与架构

1 前言 OSEK是为单核分布式嵌入式控制单元量身定制的实时系统&#xff0c;对事件驱动&#xff08;event driven&#xff09;的硬实时控制系统具有良好的适配性。OSEK没有强求不同软件模块间的完全兼容性&#xff0c;而是将重心放到了软件的可移植性上来。简单来说&#xff0c;与…

[报错解决]Communications link failure

报错 主机IDEA项目连接虚拟机的数据库报错。 主要报错信息有&#xff1a; com.mysql.cj.jdbc.exceptions.CommunicationsException: Communications link failure The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received a…

智慧旅游引领未来风尚,科技助力旅行更精彩:科技的力量推动旅游业创新发展,为旅行者带来更加便捷、高效和智能的旅行服务

目录 一、引言 二、智慧旅游的概念与特点 &#xff08;一&#xff09;智慧旅游的概念 &#xff08;二&#xff09;智慧旅游的特点 三、科技推动旅游业创新发展 &#xff08;一&#xff09;大数据技术的应用 &#xff08;二&#xff09;人工智能技术的应用 &#xff08;…

Linux Ubuntu 开机自启动浏览器

终端输入命令&#xff1a;gnome-session-properties 打开启动设置 如果提示&#xff1a;Command ‘gnome-session-properties’ not found, but can be installed with: apt install gnome-startup-applications 则执行&#xff1a;apt install gnome-startup-applications安装…

一、写给Android开发者之harmony入门

一、创建新项目 对比 android-studio&#xff1a;ability类似安卓activity ability分为两种类型(Stage模型) UIAbility和Extensionability&#xff08;提供系统服务和后台任务&#xff09; 启动模式 1、 singleton启动模式&#xff1a;单例 2、 multiton启动模式&#xff1…