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

SpringBoot(十三)SpringBoot配置webSocket

在PHP版本的博客中,我使用PHP+swoole实现了webscoket即时聊天的功能。

在java版本的博客中,我也想使用webscoket来实现即时聊天的功能,下边是我实现过程的一个记录。

一:在pom.xml中添加记录

<!-- spring-websocket start --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency><!-- spring-websocket end -->

二:在config目录添加WebSocketConfig.java文件

package com.springbootblog.config;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.web.socket.config.annotation.EnableWebSocket;import org.springframework.web.socket.server.standard.ServerEndpointExporter;/*** @ Description: 开启WebSocket支持*/@Configuration@EnableWebSocketpublic class WebSocketConfig{@Beanpublic ServerEndpointExporter serverEndpointExporter() {return new ServerEndpointExporter();}}

三:解决@ServerEndpoint注解中的@Autowired注解失效的问题

原理是WebSocket是线程安全的,有用户连接时就会创建一个新的端点实例,一个端WebSocket是多对象的,使用的spring却是单例模式。这两者刚好冲突。

所以在@ServerEndpoint注解下不能使用Autowired注解注入对象,那我在webscoket的控制器中也是需要操作redis以及操作数据库的,那我该如何去处理这个问题呢?

我百思不得其解。最后在百度上一位大神的指点下解决了这个问题。

使用从容器中取对象的工具类

在utils目录中添加SpringUtil.java文件

package com.springbootblog.utils;import org.springframework.beans.BeansException;import org.springframework.context.ApplicationContext;import org.springframework.context.ApplicationContextAware;import org.springframework.stereotype.Component;@Componentpublic class SpringUtil implements ApplicationContextAware{private static ApplicationContext applicationContext;@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {SpringUtil.applicationContext = applicationContext;}public ApplicationContext getApplicationContext(){return applicationContext;}public static Object getBean(String beanName){return applicationContext.getBean(beanName);}public static <T> T getBean(Class<T> clazz){try{return (T)applicationContext.getBean(clazz);}catch(Exception e){return null;}}}

四:在SpringBoot中使用webscoket

package com.springbootblog.controller.fontend;import com.alibaba.fastjson.JSONObject;import com.springbootblog.dao.ChatRecordDao;import com.springbootblog.dao.UserDao;import com.springbootblog.pojo.ChatRecord;import com.springbootblog.pojo.User;import com.springbootblog.utils.Function;import com.springbootblog.utils.RedisUtil;import com.springbootblog.utils.SpringUtil;import lombok.extern.slf4j.Slf4j;import org.json.JSONException;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Component;import org.springframework.stereotype.Service;import javax.websocket.*;import javax.websocket.server.PathParam;import javax.websocket.server.ServerEndpoint;import java.io.IOException;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;import java.util.concurrent.CopyOnWriteArraySet;@Component                              // 组件@Slf4j                                  // 日志依赖@Service@ServerEndpoint("/api/websocket/{sid}") // 配置webscoket访问链接public class WebSocketServer{/*** 注入redis工具类*/// @Autowired// private RedisUtil redisUtil;private RedisUtil redisUtil = SpringUtil.getBean(RedisUtil.class);/*** 自动装载(dao接口注入)*/// @Autowired// private UserDao userDao;private UserDao userDao = SpringUtil.getBean(UserDao.class);/*** 自动装载(dao接口注入)*/// @Autowired// private ChatRecordDao chatDao;private ChatRecordDao chatDao = SpringUtil.getBean(ChatRecordDao.class);//静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。public static int onlineCount = 0;//concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<>();//与某个客户端的连接会话,需要通过它来给客户端发送数据private Session session;//接收sidpublic String sid = "";/*** 连接建立成功调用的方法*/@OnOpenpublic void onOpen(Session session, @PathParam("sid") String sid){this.session = session;//加入set中webSocketSet.add(this);this.sid = sid;//在线数加1addOnlineCount();try{sendMessage("conn_success");System.out.println("有新窗口开始监听:" + sid + ",当前在线人数为:" + getOnlineCount());}catch (IOException | EncodeException e){System.out.println("websocket IO Exception");}}/*** 连接关闭调用的方法*/@OnClosepublic void onClose(){webSocketSet.remove(this);  //从set中删除subOnlineCount();           //在线数减1//断开连接情况下,更新主板占用情况为释放System.out.println("释放的sid为:"+sid);//这里写你 释放的时候,要处理的业务System.out.println("有一连接关闭!当前在线人数为" + getOnlineCount());}/*** 收到客户端消息后调用的方法* @ Param message 客户端发送过来的消息*/@OnMessagepublic void onMessage(String message, Session session) throws JSONException, IOException, EncodeException {this.session = session;System.out.println("收到来自窗口" + sid + "的信息:" + message);// 获取key对应的值String type = Function.stringToJson(message,"type");String from_user_id = Function.stringToJson(message,"from_user_id");String to_user_id = Function.stringToJson(message,"to_user_id");String msg = Function.stringToJson(message,"msg");String fd = Function.stringToJson(message,"fd");// 用户聊天if ((from_user_id.equals("") || to_user_id.equals("")) && type.equals("userchat")){Map<String,Object> result = new HashMap<>(2);result.put("code", -1);result.put("msg", "参数错误!请重新登录~");sendMessage(message);}if(type.equals("ping")){// 心跳// 在线用户列表Map<String,Object> array = getWebscoketUserList(from_user_id, session.getId());ArrayList<Map<String,Object>> userList = (ArrayList<Map<String, Object>>) array.get("userList");Integer totalUnReadNumber = (Integer) array.get("totalUnReadNumber");// System.out.println("totalUnReadNumber:"+totalUnReadNumber);// System.out.println("userList:"+userList);Map<String,Object> result = new HashMap<>(4);result.put("code", 1);result.put("type", type);result.put("list", userList);result.put("totalUnReadNumber", totalUnReadNumber);// System.out.println(JSONObject.toJSONString(result));sendMessage(JSONObject.toJSONString(result));}else if(type.equals("userchat")){// 用户一对一聊天// System.out.println("type:"+type);// System.out.println("from_user_id:"+from_user_id);// System.out.println("to_user_id:"+to_user_id);// System.out.println("msg:"+msg);// System.out.println("fd:"+fd);// 保存聊天记录saveChatRecord(from_user_id,to_user_id,msg);// Map<String,Object> param = new HashMap<>(1);// param.put("answer", msg);Map<String,Object> result = new HashMap<>(2);result.put("response", "");result.put("type", type);sendMessage(JSONObject.toJSONString(result));}session.getId();//群发消息/*for (WebSocketServer item : webSocketSet){try{item.sendMessage(message);}catch (IOException e){e.printStackTrace();}}//*/}public Map<String,Object> saveChatRecord(String from_user_id, String to_user_id,String msg){ChatRecord chatRecord = new ChatRecord();chatRecord.setFrom_user_id(Integer.valueOf(from_user_id));chatRecord.setTo_user_id(Integer.valueOf(to_user_id));chatRecord.setMsg(msg);Integer res = chatDao.addChatRecord(chatRecord);Map<String,Object> result = new HashMap<>(2);result.put("code", 1);result.put("id", res);return result;}/*** 获取webscoket链接的用户列表* @param user_id* @param fd* @return*/public Map<String,Object> getWebscoketUserList(String user_id,String fd){// 存redis ,10分钟过期,防止用户退出之后redis中存储的数据一直存在。// 存储格式:webscoket-用户id : webscoket连接主键// 组装redis-keyString redisKey = "webscoket-" + user_id;String webscoketFD = "";// 首先获取当前key值(webscoket 连接主键)// System.out.println(redisKey);// System.out.println("redisUtil:"+redisUtil.get(redisKey));// System.out.println("fd:"+fd);if(redisUtil.get(redisKey) != null ){webscoketFD = redisUtil.get(redisKey).toString();}// System.out.println("webscoketFD:"+webscoketFD);// 判断当前主键是否redis中记录的主键一致。(就是判断这个玩意redis中有没有)if(!webscoketFD.equals(fd)){// 设置一个有过期时间的keyredisUtil.set(redisKey, fd,60*60*2);// System.out.println("存储之后的redis:"+redisUtil.get(redisKey));}// 获取指定前缀的Key(返回一个数组)List<String> keyList = redisUtil.findKeysByPattern("webscoket-*");// System.out.println(keyList);Integer totalUnReadNumber = 0;ArrayList<Map<String,Object>> array = new ArrayList<>();for (int i = 0; i < keyList.size(); i++){Object key = keyList.get(i);// System.out.println(key);String[] arr = key.toString().split("-");if(arr.length < 2){break;}String to_user_id = arr[1];String value = redisUtil.get(key.toString()).toString();// System.out.println(to_user_id);// System.out.println(value);Map<String,Object> temp = new HashMap<>(5);temp.put("fd", value);// 查未读消息数量Integer unReadNumber = chatDao.getUnReadNumber(user_id,to_user_id);totalUnReadNumber += unReadNumber;// 查用户信息User userinfo = userDao.getUserInfoById(to_user_id);if(userinfo == null){redisUtil.del(key.toString());continue;}if(!userinfo.getFigureurl_wx().equals("")){temp.put("userlogo", userinfo.getFigureurl_wx());}else{temp.put("userlogo", userinfo.getFigureurl_qq());}temp.put("username", userinfo.getNickname());temp.put("id", userinfo.getId());array.add(temp);}Map<String,Object> result = new HashMap<>(2);result.put("userList", array);result.put("totalUnReadNumber", totalUnReadNumber);return result;}/*** @ Param session* @ Param error*/@OnErrorpublic void onError(Session session, Throwable error){// log.error("发生错误");error.printStackTrace();}/*** 实现服务器主动推送*/public void sendMessage(String message) throws IOException, EncodeException{this.session.getBasicRemote().sendText(message);// this.session.getBasicRemote().sendObject(message);}/*** 群发自定义消息*/public static void sendInfo(String message, @PathParam("sid") String sid) throws IOException{// System.out.println("推送消息到窗口" + sid + ",推送内容:" + message);for (WebSocketServer item : webSocketSet){try{//这里可以设定只推送给这个sid的,为null则全部推送if (sid == null){// item.sendMessage(message);}else if (item.sid.equals(sid)){item.sendMessage(message);}}catch (IOException e){continue;} catch (EncodeException e) {throw new RuntimeException(e);}}}public static synchronized int getOnlineCount(){return onlineCount;}public static synchronized void addOnlineCount(){WebSocketServer.onlineCount++;}public static synchronized void subOnlineCount(){WebSocketServer.onlineCount--;}}

以上大概就是我在SpringBoot中对webscoket的基本使用。

有好的建议,请在下方输入你的评论。

相关文章:

SpringBoot(十三)SpringBoot配置webSocket

在PHP版本的博客中&#xff0c;我使用PHPswoole实现了webscoket即时聊天的功能。 在java版本的博客中&#xff0c;我也想使用webscoket来实现即时聊天的功能&#xff0c;下边是我实现过程的一个记录。 一&#xff1a;在pom.xml中添加记录 <!-- spring-websocket start --&…...

OA系统都有哪些功能?OA办公系统功能大测评

随着现代企业对效率和协作的需求不断增加&#xff0c;OA办公系统已成为许多企业日常运营的重要工具。 一个功能完备的OA系统不仅能帮助企业提高办公效率&#xff0c;还能优化各类工作流程&#xff0c;从文档管理到审批流程、任务管理等&#xff0c;它为企业提供了全方位的支持…...

优化布线拥塞

Note&#xff1a;文章内容以 Xilinx 系列 FPGA 进行讲解 随着设计规模的增大和复杂度的提升&#xff0c;布线拥塞成为常见的问题&#xff0c;尤其是在用UltraScale FPGA或UltraScale FPGA时&#xff0c;布线拥塞往往成为时序收敛的瓶颈&#xff0c;也成为编译时间过长的“罪魁…...

盲盒APP开发,电商模式下盲盒的未知乐趣

在互联网电商模式逐渐成熟的当下&#xff0c;盲盒电商为消费者提供了一个全新的娱乐购物体验&#xff0c;让众多粉丝和消费者通过手机系统就可以体验拆盲盒的惊喜感和刺激性。在消费者享受线上拆盲盒的乐趣时&#xff0c;企业也能够获得新的发展机遇&#xff0c;扩大发展空间。…...

RocketMQ-02 集群架构部署

根据上一章《RocketMQ消费模型和部署模型》得知&#xff0c;启动rocketmq非常简单&#xff0c;只需要分别执行mqnamesrv启动NameServer&#xff0c;执行mqbroker启动Broker即可。但生产环境不可能仅使用单节点MQ&#xff0c;为提高可用性和吞吐量&#xff0c;生产常使用集群模式…...

处理报文后 展示在qtdesigner界面 ,有大量数据存储 怎样创建临时文件减少内存占用

处理大量数据并将其展示在 Qt Designer 创建的界面中时&#xff0c;确实可能会遇到内存占用过高的问题。使用临时文件来存储和管理数据是一种有效的解决方案&#xff0c;可以帮助减少内存占用。 import os import shutil import tempfile from PyQt5.QtWidgets import QApplica…...

后端-实现excel的导出功能(超详细讲解)

首先&#xff0c;不管是一大段文字还是一个几行几列的表格实现方式都是一样的。把动态的内容使用英文单词代替。动态的内容加不加下划线都可以&#xff0c;加了下划线最后生成的表格动态内容部分带有下划线&#xff0c;不加下划线最后生成的表格动态内容部分不带下划线。大家各…...

Docker compose部署portainer

整个工具的代码都在Gitee或者Github地址内 gitee&#xff1a;solomon-parent: 这个项目主要是总结了工作上遇到的问题以及学习一些框架用于整合例如:rabbitMq、reids、Mqtt、S3协议的文件服务器、mongodb github&#xff1a;GitHub - ZeroNing/solomon-parent: 这个项目主要是…...

【游戏引擎之路】登神长阶(十四)——OpenGL教程:士别三日,当刮目相看

【游戏引擎之路】登神长阶&#xff08;十四&#xff09;——OpenGL教程&#xff1a;士别三日&#xff0c;当刮目相看 2024年 5月20日-6月4日&#xff1a;攻克2D物理引擎。 2024年 6月4日-6月13日&#xff1a;攻克《3D数学基础》。 2024年 6月13日-6月20日&#xff1a;攻克《3D…...

相等日期问题(c++方法解决)

问题描述 对于一个日期&#xff0c;我们可以计算出年份的各个数位上的数字之和&#xff0c;也可以分别计算月和日的各位数字之和。请问从 1900 年 1 月 1 日至 9999 年 12 月 31 日&#xff0c;总共有多少天&#xff0c;年份的数位数字之和等于月的数位数字之和加日的数位数字之…...

深度学习——优化算法、激活函数、归一化、正则化

文章目录 &#x1f33a;深度学习面试八股汇总&#x1f33a;优化算法方法梯度下降 (Gradient Descent, GD)动量法 (Momentum)AdaGrad (Adaptive Gradient Algorithm)RMSProp (Root Mean Square Propagation)Adam (Adaptive Moment Estimation)AdamW 优化算法总结 经验和实践建议…...

Android 老项目适配 Compose 混合开发

在Android项目中使用Jetpack Compose进行混合开发时&#xff0c;可以通过以下步骤进行适配&#xff1a; 1.更新项目的build.gradle文件&#xff0c;确保使用最新的Compose库版本。 dependencies { implementation androidx.compose.ui:ui:<latest_version> implementat…...

PH热榜 | 2024-11-14

DevNow 是一个精简的开源技术博客项目模版&#xff0c;支持 Vercel 一键部署&#xff0c;支持评论、搜索等功能&#xff0c;欢迎大家体验。 [在线预览](https://www.laughingzhu.c 1. Vocera 标语&#xff1a;利用模拟和监控加速语音代理上线 这句话的意思是&#xff1a;通过…...

删库跑路,启动!

起因&#xff1a;这是一个悲伤的故事&#xff0c;在抓logcat时 device待机自动回根目录了&#xff0c;而题主对当前路径的印象还停留在文件夹下&#xff0c;不小心在根目录执行了rm -rf * … 所以&#xff0c;这是个悲伤的故事&#xff0c;东西全没了…device也黑屏了&#xff…...

Vue 3 在现代前端开发中的应用

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 Vue 3 在现代前端开发中的应用 Vue 3 在现代前端开发中的应用 Vue 3 在现代前端开发中的应用 引言 Vue 3 概述 定义与原理 发展历…...

【HarmonyOS】Hdc server port XXXX has been used.Configure environment variable

【HarmonyOS】Hdc server port XXXX has been used.Configure environment variable 一、 问题背景&#xff1a; 无法调试debug应用&#xff0c;IDE右下角显示该弹窗&#xff1a; Hdc server port XXXX has been used.Configure environment variable ‘OHOS_HDC_SERVER_POR…...

使用 ts-node 运行 ts文件,启动 nodejs项目

最近在写一个nodejs项目&#xff0c;使用 ts-node 启动项目。遇到了一些问题&#xff0c;在此记录一下。 ts-node 是 TypeScript 执行引擎和 Node.js 的 REPL(一个简单的交互式的编程环境)。 它能够直接在 Node.js 上执行 TypeScript&#xff0c;而无需预编译。 这是通过挂接…...

scala中的case class

package test_27 //Set的特点&#xff1a;唯一&#xff08;元素不同&#xff09;&#xff1b;无序 //case class定义一组数据 case class Book(var bookName:String,var author:String,var price:Double){} object caseclass {def main(args: Array[String]): Unit {//定义一个…...

探索 HTTP 请求方法:GET、POST、PUT、DELETE 等的用法详解

文章目录 前言一、GET 方法&#xff1a;用于获取资源二、POST 方法&#xff1a;用于提交数据三、PUT 方法&#xff1a;用于更新资源四、DELETE 方法&#xff1a;用于删除资源五、PATCH 方法&#xff1a;用于部分更新资源六、HEAD 方法&#xff1a;用于请求响应头七、OPTIONS 方…...

前端项目中,public文件下的system.js有什么作用

‌SystemJS在前端项目中的作用主要包括模块加载、资源管理和兼容性处理。‌ 模块加载 SystemJS是一个通用的模块加载器&#xff0c;支持多种模块规范&#xff0c;包括CommonJS、AMD、全局模块对象和ES6模块。通过使用插件&#xff0c;它还可以加载CoffeeScript和TypeScript。…...

web vue 项目 Docker化部署

Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段&#xff1a; 构建阶段&#xff08;Build Stage&#xff09;&#xff1a…...

阿里云ACP云计算备考笔记 (5)——弹性伸缩

目录 第一章 概述 第二章 弹性伸缩简介 1、弹性伸缩 2、垂直伸缩 3、优势 4、应用场景 ① 无规律的业务量波动 ② 有规律的业务量波动 ③ 无明显业务量波动 ④ 混合型业务 ⑤ 消息通知 ⑥ 生命周期挂钩 ⑦ 自定义方式 ⑧ 滚的升级 5、使用限制 第三章 主要定义 …...

为什么需要建设工程项目管理?工程项目管理有哪些亮点功能?

在建筑行业&#xff0c;项目管理的重要性不言而喻。随着工程规模的扩大、技术复杂度的提升&#xff0c;传统的管理模式已经难以满足现代工程的需求。过去&#xff0c;许多企业依赖手工记录、口头沟通和分散的信息管理&#xff0c;导致效率低下、成本失控、风险频发。例如&#…...

定时器任务——若依源码分析

分析util包下面的工具类schedule utils&#xff1a; ScheduleUtils 是若依中用于与 Quartz 框架交互的工具类&#xff0c;封装了定时任务的 创建、更新、暂停、删除等核心逻辑。 createScheduleJob createScheduleJob 用于将任务注册到 Quartz&#xff0c;先构建任务的 JobD…...

【SQL学习笔记1】增删改查+多表连接全解析(内附SQL免费在线练习工具)

可以使用Sqliteviz这个网站免费编写sql语句&#xff0c;它能够让用户直接在浏览器内练习SQL的语法&#xff0c;不需要安装任何软件。 链接如下&#xff1a; sqliteviz 注意&#xff1a; 在转写SQL语法时&#xff0c;关键字之间有一个特定的顺序&#xff0c;这个顺序会影响到…...

P3 QT项目----记事本(3.8)

3.8 记事本项目总结 项目源码 1.main.cpp #include "widget.h" #include <QApplication> int main(int argc, char *argv[]) {QApplication a(argc, argv);Widget w;w.show();return a.exec(); } 2.widget.cpp #include "widget.h" #include &q…...

MySQL中【正则表达式】用法

MySQL 中正则表达式通过 REGEXP 或 RLIKE 操作符实现&#xff08;两者等价&#xff09;&#xff0c;用于在 WHERE 子句中进行复杂的字符串模式匹配。以下是核心用法和示例&#xff1a; 一、基础语法 SELECT column_name FROM table_name WHERE column_name REGEXP pattern; …...

QT: `long long` 类型转换为 `QString` 2025.6.5

在 Qt 中&#xff0c;将 long long 类型转换为 QString 可以通过以下两种常用方法实现&#xff1a; 方法 1&#xff1a;使用 QString::number() 直接调用 QString 的静态方法 number()&#xff0c;将数值转换为字符串&#xff1a; long long value 1234567890123456789LL; …...

Rust 开发环境搭建

环境搭建 1、开发工具RustRover 或者vs code 2、Cygwin64 安装 https://cygwin.com/install.html 在工具终端执行&#xff1a; rustup toolchain install stable-x86_64-pc-windows-gnu rustup default stable-x86_64-pc-windows-gnu ​ 2、Hello World fn main() { println…...

c# 局部函数 定义、功能与示例

C# 局部函数&#xff1a;定义、功能与示例 1. 定义与功能 局部函数&#xff08;Local Function&#xff09;是嵌套在另一个方法内部的私有方法&#xff0c;仅在包含它的方法内可见。 • 作用&#xff1a;封装仅用于当前方法的逻辑&#xff0c;避免污染类作用域&#xff0c;提升…...