当前位置: 首页 > 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…...

后进先出(LIFO)详解

LIFO 是 Last In, First Out 的缩写&#xff0c;中文译为后进先出。这是一种数据结构的工作原则&#xff0c;类似于一摞盘子或一叠书本&#xff1a; 最后放进去的元素最先出来 -想象往筒状容器里放盘子&#xff1a; &#xff08;1&#xff09;你放进的最后一个盘子&#xff08…...

【kafka】Golang实现分布式Masscan任务调度系统

要求&#xff1a; 输出两个程序&#xff0c;一个命令行程序&#xff08;命令行参数用flag&#xff09;和一个服务端程序。 命令行程序支持通过命令行参数配置下发IP或IP段、端口、扫描带宽&#xff0c;然后将消息推送到kafka里面。 服务端程序&#xff1a; 从kafka消费者接收…...

React Native 导航系统实战(React Navigation)

导航系统实战&#xff08;React Navigation&#xff09; React Navigation 是 React Native 应用中最常用的导航库之一&#xff0c;它提供了多种导航模式&#xff0c;如堆栈导航&#xff08;Stack Navigator&#xff09;、标签导航&#xff08;Tab Navigator&#xff09;和抽屉…...

Spring AI 入门:Java 开发者的生成式 AI 实践之路

一、Spring AI 简介 在人工智能技术快速迭代的今天&#xff0c;Spring AI 作为 Spring 生态系统的新生力量&#xff0c;正在成为 Java 开发者拥抱生成式 AI 的最佳选择。该框架通过模块化设计实现了与主流 AI 服务&#xff08;如 OpenAI、Anthropic&#xff09;的无缝对接&…...

深入解析C++中的extern关键字:跨文件共享变量与函数的终极指南

&#x1f680; C extern 关键字深度解析&#xff1a;跨文件编程的终极指南 &#x1f4c5; 更新时间&#xff1a;2025年6月5日 &#x1f3f7;️ 标签&#xff1a;C | extern关键字 | 多文件编程 | 链接与声明 | 现代C 文章目录 前言&#x1f525;一、extern 是什么&#xff1f;&…...

MySQL用户和授权

开放MySQL白名单 可以通过iptables-save命令确认对应客户端ip是否可以访问MySQL服务&#xff1a; test: # iptables-save | grep 3306 -A mp_srv_whitelist -s 172.16.14.102/32 -p tcp -m tcp --dport 3306 -j ACCEPT -A mp_srv_whitelist -s 172.16.4.16/32 -p tcp -m tcp -…...

3-11单元格区域边界定位(End属性)学习笔记

返回一个Range 对象&#xff0c;只读。该对象代表包含源区域的区域上端下端左端右端的最后一个单元格。等同于按键 End 向上键(End(xlUp))、End向下键(End(xlDown))、End向左键(End(xlToLeft)End向右键(End(xlToRight)) 注意&#xff1a;它移动的位置必须是相连的有内容的单元格…...

听写流程自动化实践,轻量级教育辅助

随着智能教育工具的发展&#xff0c;越来越多的传统学习方式正在被数字化、自动化所优化。听写作为语文、英语等学科中重要的基础训练形式&#xff0c;也迎来了更高效的解决方案。 这是一款轻量但功能强大的听写辅助工具。它是基于本地词库与可选在线语音引擎构建&#xff0c;…...

纯 Java 项目(非 SpringBoot)集成 Mybatis-Plus 和 Mybatis-Plus-Join

纯 Java 项目&#xff08;非 SpringBoot&#xff09;集成 Mybatis-Plus 和 Mybatis-Plus-Join 1、依赖1.1、依赖版本1.2、pom.xml 2、代码2.1、SqlSession 构造器2.2、MybatisPlus代码生成器2.3、获取 config.yml 配置2.3.1、config.yml2.3.2、项目配置类 2.4、ftl 模板2.4.1、…...

GitHub 趋势日报 (2025年06月06日)

&#x1f4ca; 由 TrendForge 系统生成 | &#x1f310; https://trendforge.devlive.org/ &#x1f310; 本日报中的项目描述已自动翻译为中文 &#x1f4c8; 今日获星趋势图 今日获星趋势图 590 cognee 551 onlook 399 project-based-learning 348 build-your-own-x 320 ne…...