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

Python大麦网智能抢票脚本:三分钟搭建你的自动购票系统

Python大麦网智能抢票脚本&#xff1a;三分钟搭建你的自动购票系统 【免费下载链接】Automatic_ticket_purchase 大麦网抢票脚本 项目地址: https://gitcode.com/GitHub_Trending/au/Automatic_ticket_purchase 还在为抢不到心仪的演唱会门票而烦恼吗&#xff1f;每次开…...

为什么你的`@jit(cache=True)`反而变慢了?Python 3.14 JIT缓存键生成算法变更深度解析(附3.13→3.14 ABI不兼容警告)

第一章&#xff1a;Python 3.14 JIT 编译器性能调优 面试题汇总Python 3.14 引入了实验性内置 JIT&#xff08;Just-In-Time&#xff09;编译器&#xff0c;基于 PGO&#xff08;Profile-Guided Optimization&#xff09;与轻量级字节码重写机制&#xff0c;在 CPU-bound 场景下…...

Adafruit ST7735/ST7789 TFT驱动库详解:SPI接口与GFX分层架构

1. 项目概述 Adafruit ST7735 和 ST7789 库是一个面向嵌入式平台&#xff08;尤其是 Arduino 生态&#xff09;的轻量级图形驱动库&#xff0c;专为基于 Sitronix ST7735、ST7789 及 ST7796S 显示控制器的彩色 TFT 液晶模组设计。该库并非仅适配单一型号&#xff0c;而是通过统…...

计算机毕业设计:Python 汽车推荐系统实战 Django框架 可视化 协同过滤算法 数据分析 大数据 机器学习(建议收藏)✅

博主介绍&#xff1a;✌全网粉丝10W,前互联网大厂软件研发、集结硕博英豪成立工作室。专注于计算机相关专业项目实战6年之久&#xff0c;选择我们就是选择放心、选择安心毕业✌ > &#x1f345;想要获取完整文章或者源码&#xff0c;或者代做&#xff0c;拉到文章底部即可与…...

人脸识别OOD模型在金融领域的身份验证应用

人脸识别OOD模型在金融领域的身份验证应用 1. 引言 想象一下这样的场景&#xff1a;一位银行客户正在通过手机APP进行大额转账&#xff0c;系统需要快速准确地确认他的身份。传统的人脸识别系统可能会因为光线不佳、佩戴口罩或者图像模糊而无法正常工作&#xff0c;甚至可能被…...

别再买错千元投影! 哈趣Q1Pro藏看越级体验

当下的智能投影市场正经历着深度的“去伪存真”变革&#xff0c;行业洗牌加速的同时&#xff0c;也让消费者的选购变得愈发谨慎。洛图科技数据显示&#xff0c;2025年国内智能投影市场整体销量下滑&#xff0c;其中低端投影成为调整重灾区&#xff0c;0-499元价位段销量同比大跌…...

Qwen2.5-14B-Instruct开源大模型实战:像素剧本圣殿8-Bit UI部署详解

Qwen2.5-14B-Instruct开源大模型实战&#xff1a;像素剧本圣殿8-Bit UI部署详解 1. 项目概览 像素剧本圣殿&#xff08;Pixel Script Temple&#xff09;是一款基于Qwen2.5-14B-Instruct大模型深度微调的专业剧本创作工具。这个独特的创作环境将强大的AI推理能力与复古8-Bit视…...

Oni-Duplicity:轻松定制《缺氧》游戏体验,告别资源与角色困扰

Oni-Duplicity&#xff1a;轻松定制《缺氧》游戏体验&#xff0c;告别资源与角色困扰 【免费下载链接】oni-duplicity A web-hosted, locally-running save editor for Oxygen Not Included. 项目地址: https://gitcode.com/gh_mirrors/on/oni-duplicity 你是否曾在《缺…...

从无人机到扫地机器人:拆解VIO技术如何成为智能设备的‘隐形大脑’

从无人机到扫地机器人&#xff1a;拆解VIO技术如何成为智能设备的‘隐形大脑’ 当科沃斯T20扫地机器人在复杂家居环境中精准避开宠物食盆时&#xff0c;当大疆Mavic 3无人机在峡谷间自主返航时&#xff0c;背后都隐藏着一项关键技术——视觉惯性里程计&#xff08;VIO&#xff…...

手把手教你搞定RK3568 Android11平台上的AIC8800 WiFi6模块驱动(附常见报错解决)

RK3568 Android11平台AIC8800 WiFi6模块驱动移植全流程指南 在嵌入式开发领域&#xff0c;WiFi模块的集成往往是项目推进的关键环节。AIC8800作为一款支持WiFi6的芯片&#xff0c;凭借其优异的性能和功耗表现&#xff0c;正逐渐成为RK3568等主流嵌入式平台的热门选择。本文将系…...