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

Vim 调用外部命令学习笔记

Vim 外部命令集成完全指南 文章目录 Vim 外部命令集成完全指南核心概念理解命令语法解析语法对比 常用外部命令详解文本排序与去重文本筛选与搜索高级 grep 搜索技巧文本替换与编辑字符处理高级文本处理编程语言处理其他实用命令 范围操作示例指定行范围处理复合命令示例 实用技…...

关于nvm与node.js

1 安装nvm 安装过程中手动修改 nvm的安装路径&#xff0c; 以及修改 通过nvm安装node后正在使用的node的存放目录【这句话可能难以理解&#xff0c;但接着往下看你就了然了】 2 修改nvm中settings.txt文件配置 nvm安装成功后&#xff0c;通常在该文件中会出现以下配置&…...

零基础设计模式——行为型模式 - 责任链模式

第四部分&#xff1a;行为型模式 - 责任链模式 (Chain of Responsibility Pattern) 欢迎来到行为型模式的学习&#xff01;行为型模式关注对象之间的职责分配、算法封装和对象间的交互。我们将学习的第一个行为型模式是责任链模式。 核心思想&#xff1a;使多个对象都有机会处…...

c#开发AI模型对话

AI模型 前面已经介绍了一般AI模型本地部署&#xff0c;直接调用现成的模型数据。这里主要讲述讲接口集成到我们自己的程序中使用方式。 微软提供了ML.NET来开发和使用AI模型&#xff0c;但是目前国内可能使用不多&#xff0c;至少实践例子很少看见。开发训练模型就不介绍了&am…...

【数据分析】R版IntelliGenes用于生物标志物发现的可解释机器学习

禁止商业或二改转载&#xff0c;仅供自学使用&#xff0c;侵权必究&#xff0c;如需截取部分内容请后台联系作者! 文章目录 介绍流程步骤1. 输入数据2. 特征选择3. 模型训练4. I-Genes 评分计算5. 输出结果 IntelliGenesR 安装包1. 特征选择2. 模型训练和评估3. I-Genes 评分计…...

微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据

微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据 Power Query 具有大量专门帮助您清理和准备数据以供分析的功能。 您将了解如何简化复杂模型、更改数据类型、重命名对象和透视数据。 您还将了解如何分析列&#xff0c;以便知晓哪些列包含有价值的数据&#xff0c;…...

基于Java+MySQL实现(GUI)客户管理系统

客户资料管理系统的设计与实现 第一章 需求分析 1.1 需求总体介绍 本项目为了方便维护客户信息为了方便维护客户信息&#xff0c;对客户进行统一管理&#xff0c;可以把所有客户信息录入系统&#xff0c;进行维护和统计功能。可通过文件的方式保存相关录入数据&#xff0c;对…...

Java编程之桥接模式

定义 桥接模式&#xff08;Bridge Pattern&#xff09;属于结构型设计模式&#xff0c;它的核心意图是将抽象部分与实现部分分离&#xff0c;使它们可以独立地变化。这种模式通过组合关系来替代继承关系&#xff0c;从而降低了抽象和实现这两个可变维度之间的耦合度。 用例子…...

Caliper 配置文件解析:fisco-bcos.json

config.yaml 文件 config.yaml 是 Caliper 的主配置文件,通常包含以下内容: test:name: fisco-bcos-test # 测试名称description: Performance test of FISCO-BCOS # 测试描述workers:type: local # 工作进程类型number: 5 # 工作进程数量monitor:type: - docker- pro…...

API网关Kong的鉴权与限流:高并发场景下的核心实践

&#x1f525;「炎码工坊」技术弹药已装填&#xff01; 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 引言 在微服务架构中&#xff0c;API网关承担着流量调度、安全防护和协议转换的核心职责。作为云原生时代的代表性网关&#xff0c;Kong凭借其插件化架构…...