当前位置: 首页 > news >正文

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

最近在做一个功能,就是需要实时给用户推送消息,所以就需要用到 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: 1001logging:config: classpath:logback-spring.xmlspring: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@Beanpublic 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*/@OnOpenpublic 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*/@OnClosepublic void onClose(@PathParam(value = "userId") String userId, Session session) {log.info("WebSocket连接断开,用户ID: " + userId);// 删除映射关系WebsocketUtil.removeSession(userId);// 广播通知,用户下线了
//        WebsocketUtil.sendMessageForAll(message);}/*** 当接收到用户上传的消息** @param userId* @param session*/@OnMessagepublic void onMessage(@PathParam(value = "userId") String userId, Session session, String message) {log.info("用户ID: " + userId + " 发送消息: " + message);// 直接广播
//        WebsocketUtil.sendMessageForAll(msg);}/*** 处理用户活连接异常** @param session* @param throwable*/@OnErrorpublic 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;}

然后就可以连接了

在这里插入图片描述

相关文章:

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

最近在做一个功能&#xff0c;就是需要实时给用户推送消息&#xff0c;所以就需要用到 websocket springboot 接入 websocket 非常简单&#xff0c;只需要下面几个配置即可 pom 文件 <!-- spring-boot-web启动器 --><dependency><groupId>org.springframewo…...

使用 QEMU 模拟器运行 FreeRTOS 实时操作系统

文章目录 QEMU 官网QEMU 文档QEMU 简介QEMU 安装QEMU 命令启动虚拟机串口控制台监控命令行 FreeRTOS安装编译工具FreeRTOS 源码RISC-V-Qemu-virt_GCC 示例编译 RISC-V-Qemu-virt_GCC启动虚拟机运行 FreeRTOS QEMU 官网 https://www.qemu.org/ QEMU 文档 https://www.qemu.or…...

Oracle EBS中AR模块的财务流程概览

应收账款 (AR) 模块是Oracle E-Business Suite (EBS) 中另一个重要的财务管理模块&#xff0c;主要用于管理企业销售过程中的账款回收。下面是AR模块中的一些关键财务流程及其详细说明&#xff1a; 1. 销售订单管理 创建销售订单&#xff1a;当客户下单时&#xff0c;销售人员…...

Minitab 的直方图结果分析解释

Minitab 的直方图结果分析解释 步骤 1&#xff1a;评估关键特征 检查分布的尖峰和散布。评估样本数量对直方图外观的影响。 标识尖峰&#xff08;即&#xff0c;条的最高聚类&#xff09;&#xff1a; 尖峰表示样本中最常见的值。评估样本的散布以了解数据的变异程度。例如…...

AgentRE:用智能体框架提升知识图谱构建效果,重点是开源!

发布时间&#xff1a;2024 年 09 月 13 日 Agent应用 AgentRE: An Agent-Based Framework for Navigating Complex Information Landscapes in Relation Extraction 在复杂场景中&#xff0c;关系抽取 (RE) 因关系类型多样和实体间关系模糊而挑战重重&#xff0c;影响了传统 “…...

力扣题解2390

大家好&#xff0c;欢迎来到无限大的频道。 今日继续给大家带来力扣题解。 题目描述​&#xff08;中等&#xff09;&#xff1a; 从字符串中移除星号 给你一个包含若干星号 * 的字符串 s 。 在一步操作中&#xff0c;你可以&#xff1a; 选中 s 中的一个星号。 移除星号…...

用Python获取PDF页面的大小、方向和旋转角度

在文档管理和自动化领域&#xff0c;了解PDF文档的内在属性&#xff08;如页面大小、方向和旋转角度&#xff09;对于确保一致的文档处理和布局保真度至关重要。这些属性在内容重用、归档以及PDF无缝集成到网络环境或其他数字工作流程中起着关键作用&#xff0c;因为它们直接影…...

【即时通讯】轮询方式实现

技术栈 LayUI、jQuery实现前端效果。django4.2、django-ninja实现后端接口。 代码仓 - 后端 代码仓 - 前端 实现功能 首次访问页面并发送消息时需要设置昵称发送内容为空时要提示用户不能发送空消息前端定时获取消息&#xff0c;然后展示在页面上。 效果展示 首次发送需要…...

Flock 明牌空投教程

FLock 旨在为人工智能构建一个去中心化的隐私保护解决方案。FLock提出了一项名为联合学习区块&#xff08;简称 FLocks&#xff09;的研究计划&#xff0c;该计划使用区块链作为数据持有者之间的协调平台来进行机器学习&#xff0c;同时数据保持本地和隐私。通过用区块链取代收…...

项目内部调用的远程接口开发

编写一个项目内部调用的远程接口通常是为了在分布式系统或者微服务架构中&#xff0c;实现各个服务之间的通信和数据交换。这样的远程接口专门用于服务之间的调用&#xff0c;而不是直接暴露给外部用户或前端。 项目内部的远程接口统一放在api工程 首先进入api编写接口&#x…...

影响IP代理池稳定性的因素有哪些?

IP代理池在提供网络服务时&#xff0c;稳定性是一项决定性指标。多个外部和内部因素可能会影响这个稳定性&#xff0c;因此深入理解这些影响因素&#xff0c;可以帮助优化IP代理池的性能与服务质量。 1. IP来源质量 纯净度与使用频次&#xff1a;优质的IP来源常常被描述为纯净…...

基于Prometheus和Grafana的现代服务器监控体系构建

构建一个基于 Prometheus 和 Grafana 的现代服务器监控体系涉及多个步骤。以下是大体的流程和步骤说明&#xff1a; 1. Prometheus 监控系统 Prometheus 是一个开源的系统监控和报警工具&#xff0c;专门设计用于抓取时间序列数据。 1.1 Prometheus 的安装 Docker 安装 Prom…...

原生 input 中的 “type=file“ 上传文件

目标&#xff1a;实现文件上传功能 原型图&#xff1a; HTML部分&#xff1a; <div class"invoice-item"><div class"invoice-title">增值税专用发票</div><div class"invoice-box"><el-form-item label"标准…...

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

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

《PostMan(一):配置全局令牌》

文章目录 一、配置全局token1、设置2、添加全局3、添加全局变量名称4、选中全局&#xff0c;并查看5、添加赋值脚本6、配置令牌取值7、即可成功获取用户信息 一、配置全局token 1、设置 2、添加全局 3、添加全局变量名称 4、选中全局&#xff0c;并查看 5、添加赋值脚本 // 把…...

如何理解Configurational entropy

Configurational entropy 是热力学和统计力学中的一个重要概念&#xff0c;它描述的是系统中由于其微观状态排列&#xff08;即配置&#xff09;导致的不确定性或混乱程度。不同于热力学中的热熵&#xff08;thermal entropy&#xff09;&#xff0c;它特指那些与系统中的粒子、…...

H5端接入萤石监控

官方文档 EZOPEN协议 下滑至-平台架构 web/h5端使用文档 <template><div :id"video-container${index}${index2}" class"w-full bg-black"></div> </template><script>export default {data() {return {EZVIZAToken:…...

SSD1306 OLED显示屏驱动方案简介

SSD1306是一种常见的单色OLED(有机发光二极管)显示屏驱动芯片。以下从它的基本特点、工作原理和应用领域进行详细介绍&#xff1a; 一、基本特点 1. 尺寸与分辨率&#xff1a; SSD1306芯片支持多种尺寸的OLED显示屏&#xff0c;常见的有0.96寸、1.3寸等。不同尺寸的屏幕具有不…...

React18快速入门

需要先安装并配置React相关的工具和插件 下载安装Node.js&#xff0c;这里以MacOS Node.js v22.6.0为例 终端命令行检查是否安装成功 node -v npm -vNode.js快速入门 npm设置镜像源 #设置为阿里镜像源 npm config set registry https://registry.npmmirror.com #查看是否生…...

Day11笔记-字典基本使用系统功能字典推导式

二、字典【重点掌握】 1.概念 列表和元组的使用缺点&#xff1a;当存储的数据要动态添加、删除的时候&#xff0c;我们一般使用列表&#xff0c;但是列表有时会遇到一些麻烦,定位元素比较麻烦 # 一个列表/元组保存5个学生的成绩&#xff0c; score_list [66,100,70,78,99] sc…...

Java 语言特性(面试系列2)

一、SQL 基础 1. 复杂查询 &#xff08;1&#xff09;连接查询&#xff08;JOIN&#xff09; 内连接&#xff08;INNER JOIN&#xff09;&#xff1a;返回两表匹配的记录。 SELECT e.name, d.dept_name FROM employees e INNER JOIN departments d ON e.dept_id d.dept_id; 左…...

CTF show Web 红包题第六弹

提示 1.不是SQL注入 2.需要找关键源码 思路 进入页面发现是一个登录框&#xff0c;很难让人不联想到SQL注入&#xff0c;但提示都说了不是SQL注入&#xff0c;所以就不往这方面想了 ​ 先查看一下网页源码&#xff0c;发现一段JavaScript代码&#xff0c;有一个关键类ctfs…...

VB.net复制Ntag213卡写入UID

本示例使用的发卡器&#xff1a;https://item.taobao.com/item.htm?ftt&id615391857885 一、读取旧Ntag卡的UID和数据 Private Sub Button15_Click(sender As Object, e As EventArgs) Handles Button15.Click轻松读卡技术支持:网站:Dim i, j As IntegerDim cardidhex, …...

Mybatis逆向工程,动态创建实体类、条件扩展类、Mapper接口、Mapper.xml映射文件

今天呢&#xff0c;博主的学习进度也是步入了Java Mybatis 框架&#xff0c;目前正在逐步杨帆旗航。 那么接下来就给大家出一期有关 Mybatis 逆向工程的教学&#xff0c;希望能对大家有所帮助&#xff0c;也特别欢迎大家指点不足之处&#xff0c;小生很乐意接受正确的建议&…...

Qwen3-Embedding-0.6B深度解析:多语言语义检索的轻量级利器

第一章 引言&#xff1a;语义表示的新时代挑战与Qwen3的破局之路 1.1 文本嵌入的核心价值与技术演进 在人工智能领域&#xff0c;文本嵌入技术如同连接自然语言与机器理解的“神经突触”——它将人类语言转化为计算机可计算的语义向量&#xff0c;支撑着搜索引擎、推荐系统、…...

数据链路层的主要功能是什么

数据链路层&#xff08;OSI模型第2层&#xff09;的核心功能是在相邻网络节点&#xff08;如交换机、主机&#xff09;间提供可靠的数据帧传输服务&#xff0c;主要职责包括&#xff1a; &#x1f511; 核心功能详解&#xff1a; 帧封装与解封装 封装&#xff1a; 将网络层下发…...

04-初识css

一、css样式引入 1.1.内部样式 <div style"width: 100px;"></div>1.2.外部样式 1.2.1.外部样式1 <style>.aa {width: 100px;} </style> <div class"aa"></div>1.2.2.外部样式2 <!-- rel内表面引入的是style样…...

SAP学习笔记 - 开发26 - 前端Fiori开发 OData V2 和 V4 的差异 (Deepseek整理)

上一章用到了V2 的概念&#xff0c;其实 Fiori当中还有 V4&#xff0c;咱们这一章来总结一下 V2 和 V4。 SAP学习笔记 - 开发25 - 前端Fiori开发 Remote OData Service(使用远端Odata服务)&#xff0c;代理中间件&#xff08;ui5-middleware-simpleproxy&#xff09;-CSDN博客…...

佰力博科技与您探讨热释电测量的几种方法

热释电的测量主要涉及热释电系数的测定&#xff0c;这是表征热释电材料性能的重要参数。热释电系数的测量方法主要包括静态法、动态法和积分电荷法。其中&#xff0c;积分电荷法最为常用&#xff0c;其原理是通过测量在电容器上积累的热释电电荷&#xff0c;从而确定热释电系数…...

【7色560页】职场可视化逻辑图高级数据分析PPT模版

7种色调职场工作汇报PPT&#xff0c;橙蓝、黑红、红蓝、蓝橙灰、浅蓝、浅绿、深蓝七种色调模版 【7色560页】职场可视化逻辑图高级数据分析PPT模版&#xff1a;职场可视化逻辑图分析PPT模版https://pan.quark.cn/s/78aeabbd92d1...