SpringBoot整合WebSocket实现消息推送或聊天功能示例

news2024/11/25 4:34:45

最近在做一个功能,就是需要实时给用户推送消息,所以就需要用到 websocket

springboot 接入 websocket 非常简单,只需要下面几个配置即可
pom 文件

		<!-- spring-boot-web启动器 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- Spring WebSocket -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>

        <!-- lombok插件 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

application.yml

server:
  port: 1001

logging:
  config: classpath:logback-spring.xml

spring:
  profiles:
    active: dev

config 文件,我之前参考的别人的博客就是没有这个,导致怎么都请求不了,这个要注意

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

/**
 * @author Sakura
 * @date 2024/9/13 14:02
 * 开启websocket支持
 */
@Configuration
public class WebSocketConfig {

    // 使用boot内置tomcat时需要注入此bean
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

工具类

import lombok.extern.log4j.Log4j2;

import javax.websocket.Session;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author Sakura
 * @date 2024/9/13 11:40
 */
@Log4j2
public class WebsocketUtil {

    private static final Map<String, Session> ONLINE_SESSION = new ConcurrentHashMap<>();

    /**
     * 添加session
     */
    public static void addSession(String userId, Session session){
        // 一个用户只允许一个session链接
        ONLINE_SESSION.putIfAbsent(userId, session);
        log.info("User [{}] connected. Total online users: {}", userId, ONLINE_SESSION.size());
    }

    /**
     * 移除session
     */
    public static void removeSession(String userId){
        ONLINE_SESSION.remove(userId);
        log.info("User [{}] disconnected. Total online users: {}", userId, ONLINE_SESSION.size());
    }

    /**
     * 给单个用户推送消息
     */
    public static void sendMessage(String userId, String message){
        Session session = ONLINE_SESSION.get(userId);
        if(session == null){
            log.warn("Session for user [{}] not found", userId);
            return;
        }
        sendMessage(session, message);
    }

    public static void sendMessage(Session session, String message) {
        if (session != null) {
            session.getAsyncRemote().sendText(message);
        }
    }

    /**
     * 给所有用户发消息
     */
    public static void sendMessageForAll(String message) {
        ONLINE_SESSION.forEach((userId, session) -> {
            CompletableFuture.runAsync(() -> sendMessage(session, message))
                    .exceptionally(ex -> {
                        log.error("Failed to send message to user [{}]: {}", userId, ex.getMessage());
                        return null;
                    });
        });
    }

    /**
     * 给指定的多个用户推送消息
     */
    public static void sendMessageForUsers(Set<String> userIds, String message) {
        userIds.forEach(userId -> {
            Session session = ONLINE_SESSION.get(userId);
            if (session == null) {
                log.warn("Session for user [{}] not found", userId);
                return;
            }
            CompletableFuture.runAsync(() -> sendMessage(session, message))
                    .exceptionally(ex -> {
                        log.error("Failed to send message to user [{}]: {}", userId, ex.getMessage());
                        return null;
                    });
        });
    }

}

WebsocketController

import com.yike.websocket.util.WebsocketUtil;
import lombok.extern.java.Log;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;

/**
 * @author Sakura
 * @date 2024/9/13 11:41
 */
@Component
@ServerEndpoint(value = "/chat/{userId}")
@Log
public class WebsocketController {

    /**
     * 连接事件,加入注解
     * @param userId
     * @param session
     */
    @OnOpen
    public void onOpen(@PathParam(value = "userId") String userId, Session session) {
        log.info("WebSocket连接成功,用户ID: " + userId);
        // 添加到session的映射关系中
        WebsocketUtil.addSession(userId, session);
        // 广播通知,某用户上线了
//        WebsocketUtil.sendMessageForAll(message);
    }

    /**
     * 连接事件,加入注解
     * 用户断开链接
     *
     * @param userId
     * @param session
     */
    @OnClose
    public void onClose(@PathParam(value = "userId") String userId, Session session) {
        log.info("WebSocket连接断开,用户ID: " + userId);
        // 删除映射关系
        WebsocketUtil.removeSession(userId);
        // 广播通知,用户下线了
//        WebsocketUtil.sendMessageForAll(message);
    }

    /**
     * 当接收到用户上传的消息
     *
     * @param userId
     * @param session
     */
    @OnMessage
    public void onMessage(@PathParam(value = "userId") String userId, Session session, String message) {
        log.info("用户ID: " + userId + " 发送消息: " + message);
        // 直接广播
//        WebsocketUtil.sendMessageForAll(msg);
    }

    /**
     * 处理用户活连接异常
     *
     * @param session
     * @param throwable
     */
    @OnError
    public void onError(Session session, Throwable throwable) {
        log.info("用户异常断开链接,原因: " + throwable.getMessage());
        try {
            session.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        throwable.printStackTrace();
    }
}

然后我们加一个测试用的发消息接口

import com.yike.websocket.util.WebsocketUtil;
import lombok.extern.java.Log;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.Set;

/**
 * @author Sakura
 * @date 2024/9/13 13:38
 */
@RestController
@RequestMapping("/msg")
@Log
public class MsgController {

    @PostMapping("/send")
    public void send(@RequestParam("id") String id, @RequestParam("message") String message) {
        log.info("发送消息给:" + id + "-" + message);
        WebsocketUtil.sendMessage(id, message);
    }

    @PostMapping("/sendAll")
    public void sendAll(@RequestParam("message") String message) {
        log.info("群发消息:" + message);
        WebsocketUtil.sendMessageForAll(message);
    }

    @PostMapping("/sendUserList")
    public void sendAll(@RequestParam("userIds") Set<String> userIds, @RequestParam("message") String message) {
        log.info("发送多人消息:" + userIds.toString() + message);
        WebsocketUtil.sendMessageForAll(message);
    }
}

接下来我们开始测试,这里我用的 apipost,听说 postman 也可以连接 websocket 但是我试了下没找到在哪里

在 apipost 里面选择新建Websocket就可以了

在这里插入图片描述

刚才我们在 WebsocketController 里面写的地址是 /chat/{userId}

在这里插入图片描述

所以这里就填 ws://127.0.0.1:1000/chat/123, 后面那个123就是用户唯一标识,通常情况就是客户端登录成功后拿到用户ID了然后再通过这个ID来建立 websocket 连接

我们点击那个连接,可以看到提示连接成功了

在这里插入图片描述

看一下控制台,这里显示两个用户是因为我建立了两个连接方面后面测试,大家也是一样,换一下后面的 userId 就可以

在这里插入图片描述

下面我们通过这几个接口测试给客户端发消息

在这里插入图片描述

首先是给单个用户发消息,我们给用户 123 发消息,这里用 JSON 字符串是为了分辨不同的消息类型,让客户端知道要做什么,大家可以随便定义,反正就是个字符串类型

在这里插入图片描述

可以看到控制台提示发送成功了

在这里插入图片描述

我们去看下 123 的控制台,可以看到拿到消息了

在这里插入图片描述

然后群发消息 “大家好”

在这里插入图片描述

看下一 456,收到了

在这里插入图片描述
123 也收到了
在这里插入图片描述

给多个用户发消息的也是一样的

在这里插入图片描述

我这里因为只是服务端给客户端推送消息,如果大家要做聊天功能的话就需要自己通过这三个接口来写业务逻辑实现

客户端发消息到服务端

在这里插入图片描述

看一下控制台可以看到已经收到消息了

在这里插入图片描述

大家要是想做聊天工具的话就需要在下面这个接口里面加逻辑,比如客户端发 JSON 格式的字符串,然后里面指定好用户和消息内容这些就可以,当然大家最好做好认证这些,确保消息是用户自己发出的

在这里插入图片描述

普通的 springboot 项目上面那些就够了,但是因为我的项目是 springcloud 的,我这边想的是把这个服务单独出来,然后其它服务通过 openfeign 来调这个服务给客户端推送消息,所以这里面就整合了nacos 和 gateway,然后前端通过公共的域名来访问,比如 wss://www.sakura.com/api-websocket/chat/2(注意:因为我域名是HTTPS协议的,所以连接的时候要用 wss)

好了接下来我只说重点的配置,其它的不动的大家不懂可以直接问我即可

首先是启动文件,因为我这是个单独的服务,所以不需要连接数据库这些,所以要加上 exclude= {DataSourceAutoConfiguration.class}

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;

/**
 * @author Sakura
 * @date 2024/9/13 11:43
 */
@SpringBootApplication(exclude= {DataSourceAutoConfiguration.class})
public class WebSocketApplication {

    public static void main(String[] args) {
        SpringApplication.run(WebSocketApplication.class, args);
    }

}

然后就是gateway的路由配置

在这里插入图片描述

feign 接口大家根据自己的项目写就好

在这里插入图片描述

最后就是域名的 nginx 配置,我们在里面加上这个

location /api-websocket/ {
        proxy_pass http://localhost:1001/;

        # WebSocket 相关的头部配置
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # 避免 WebSocket 超时断开
        proxy_read_timeout 3600;
        proxy_send_timeout 3600;
    }

然后就可以连接了

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

深度学习驱动超材料设计领域发展

深度学习在超材料设计领域的应用是一个令人兴奋的研究方向。超材料&#xff08;Metamaterials&#xff09;是一类具有自然界中不存在的特殊性质的人工材料&#xff0c;它们通过精确设计微结构来获得独特的电磁、光学或声学特性。这些特性使得超材料在各个领域都有广泛的应用前景…

大模型产品经验漫谈

前文 昨天给领导汇报了最近做的一个 txt2sql 技术路线实现的智能助手的项目&#xff0c;总算是告一段落了&#xff0c;做了半年的时间&#xff0c;作为整个项目的技术负责人从头到尾主导项目&#xff0c;肯定是有不少收获和感悟的&#xff0c;趁现在还在脑袋里面热乎着&#x…

GPS/LBS/Wi-Fi定位,全安排!—合宙Air201资产定位模组LuatOS快速入门04

经历了hello world、点灯、远程控制三期基础教程&#xff0c;小伙伴们是不是收获满满&#xff0c;期待更高阶的应用呢&#xff1f; 本期&#xff0c;我们将学习合宙Air201的核心功能之一——定位功能&#xff01; Air201定位示例教程 合宙Air201资产定位模组——是一个集成超…

SAP B1 Web Client MS Teams App集成连载一:先决条件/Prerequisites

一、先决条件/Prerequisites 在设置 SAP Business One 应用之前&#xff0c;确保您已具备以下各项&#xff1a;Before you set up the SAP Business One app, make sure you have acquired the following: 1.Microsoft Teams 管理员账户/A Microsoft Teams admin account 您需…

电力电子器件:二极管,晶闸管,GTO,GTR,MOSFET,IGBT

目录 1. 二极管 2. 晶闸管&#xff08;SCR&#xff09; 3. 门极关断晶闸管&#xff08;GTO&#xff09; 4. 门极可关断晶体管&#xff08;GTR&#xff09; 5. 金属氧化物半导体场效应晶体管&#xff08;MOSFET&#xff09; 6. 绝缘栅双极型晶体管&#xff08;IGBT&#x…

HarmonyOS开发实战( Beta5.0)使用ArkUI的FrameNode扩展实现动态布局类框架详解

鸿蒙HarmonyOS开发往期必看&#xff1a; 最新版&#xff01;“非常详细的” 鸿蒙HarmonyOS Next应用开发学习路线&#xff01;&#xff08;从零基础入门到精通&#xff09; HarmonyOS NEXT应用开发性能实践总结 简介 在特定的节假日或活动节点&#xff0c;应用通常需要推送相…

【Unity新闻】Unity的产品命名变化

快速回顾一下Unity产品命名的调整。 在2023年 Unity就宣布版本命名的变化&#xff0c;将使用Unity 6作为最新版本的命名。 具体的规则&#xff0c;在论坛里进行了说明。 以后正式的LTS版本就是Unity 6&#xff0c;将在2024年末发布。 而不管是之前的Runtime费还是今天的费用…

短视频剪辑从简单到复杂,这四款很OK!

作为一个刚刚踏入视频剪辑世界的新手&#xff0c;我最近可是忙得不亦乐乎。我尝试了四款流行的视频剪辑软件&#xff0c;今天&#xff0c;就让我来和大家分享一下我的使用感受&#xff0c;看看哪款软件更适合我们这些初学者。这里先说一句&#xff0c;选择视频剪辑软件就像挑衣…

Python Module 模块详解:模块导入与项目管理的最佳实践

Python Module 模块详解&#xff1a;模块导入与项目管理的最佳实践 文章目录 Python Module 模块详解&#xff1a;模块导入与项目管理的最佳实践一 准备示例代码二 引用 module三 大型项目的模块管理四 完整文件示例五 源码地址 本文详细介绍了 Python 中模块&#xff08;Modul…

yolo自动化项目实例解析(二)ui页面整理

我们在上一章整理main.py 的if __name__ __main__: 内容还留下面这一段&#xff0c; app QApplication(sys.argv) # 初始化Qt应用ratio screen_width / 2560 # 分辨率比例# 设置全局字体大小# 计算字体大小base_font_size 13# 基准字体大小&#xff0c;适合1920*1080分辨…

不要在这些场景中使用LLM或生成式AI

虽然但是&#xff0c;LLM并不是AI的全部&#xff0c;并不是所有的AI应用场景都适合生成式AI。 在某些用例中&#xff0c;应避免或极其谨慎地使用LLM和GenAI&#xff0c;二者可能并非最佳解决方案。 1. 高风险决策 LLM和生成式AI不适合做出可能对现实世界产生重大影响的高风险…

别人做谷歌seo为什么流量比你多?

如果你确认你的网站技术层面没有问题&#xff0c;那其实无非就是两方面&#xff0c;关键词没选好和用户体验不够好&#xff0c;不要妄想一步登天&#xff0c;选那些看起来搜索量很大的热门关键词&#xff0c;这种属于大家都在做&#xff0c;竞争是非常激烈的&#xff0c;在你的…

华宇TAS应用中间件斩获2024鲲鹏应用创新大赛北京赛区总决赛二等奖!

近日&#xff0c;以“数智未来 因你而来”为主题的创客北京2024鲲鹏应用创新大赛华鲲振宇北京赛区总决赛在北京鲲鹏联合创新中心圆满举办&#xff0c;华宇TAS应用中间件凭借产品竞争力、产品兼容性、技术领先性等优势脱颖而出&#xff0c;斩获鲲鹏原生开发赛道&#xff08;泛政…

AI为云游戏带来的革新及解决方案:深度技术剖析与未来展望

近期&#xff0c;科技巨头埃隆马斯克与热门国产游戏《黑神话&#xff1a;悟空》的互动&#xff0c;再次引发了公众对AI技术在游戏产业中应用的关注。马斯克&#xff0c;作为特斯拉和SpaceX的掌门人&#xff0c;不仅在科技领域引领风骚&#xff0c;其个人兴趣也广泛涉猎&#xf…

关于使用Mybatis-Plus 自动填充功能失效问题

关于使用Mybatis-Plus 自动填充功能失效问题 关于使用Mybatis-Plus 自动填充功能失效 首先遇到的第一个问题 自动填充失败 或被填充为NULL 原因&#xff1a;字段类型 与 填充类型 不一致导致 解决方法&#xff1a;将类型替换成一致的类型 全部为Date 或 LocalDateTime 即可解…

828华为云征文 | 使用华为云X实例部署图数据库Virtuoso并存储6500万条大数据的完整过程与性能测评

前言 在大数据时代&#xff0c;图数据库以其强大的关系处理能力在复杂网络、社交媒体分析、知识图谱等领域得到了广泛应用。而在云计算的蓬勃发展下&#xff0c;使用云服务器进行图数据库的部署与管理变得更加方便高效。本篇文章将详细介绍如何在华为云X实例上部署开源图数据…

CANFD和CAN最主要的区别

随着汽车电子的高速发展&#xff0c;车内信息的急剧增多&#xff0c;传统的CAN总线的数据传输能力已经很难满足车辆ECU的数据传输需求了&#xff0c;此时CANFD就应运而生了。 CANFD和CAN最主要的区别就是CANFD的ID段和数据段能够以不同的速率传输数据&#xff0c;这就保证了即…

【大模型专栏—进阶篇】语言模型创新大总结——“后起之秀”

大模型专栏介绍 &#x1f60a;你好&#xff0c;我是小航&#xff0c;一个正在变秃、变强的文艺倾年。 &#x1f514;本文为大模型专栏子篇&#xff0c;大模型专栏将持续更新&#xff0c;主要讲解大模型从入门到实战打怪升级。如有兴趣&#xff0c;欢迎您的阅读。 &#x1f4…

Prompt最佳实践|指定任务步骤,让ChatGPT不懵逼

在OpenAI的官方文档中已经提供了[Prompt Enginerring]的最佳实践&#xff0c;目的就是帮助用户更好的使用ChatGPT 编写优秀的提示词我一共总结了9个分类&#xff0c;本文讲解第4个分类&#xff1a;指定任务步骤 提供更多的细节要求模型扮演角色使用分隔符指定任务步骤提供样例…

e冒泡排序---复杂度O(X^2)

排序原理: 1.比较相邻的元素。如果前一个元素比后一个元素大&#xff0c;就交换这两个元素的位置。 2.对每一对相邻元素做同样的工作,从开始第一对元素到结尾的最后一对元素。最终最后位置的元素就是最大值, public class 冒泡排序 {public static void main(String[] args) {I…