From dbefa0b24eeb141a2d61146ec753bcf880200488 Mon Sep 17 00:00:00 2001
From: 18792927508 <1322446236@qq.com>
Date: Sat, 25 Nov 2023 18:15:40 +0800
Subject: [PATCH] =?UTF-8?q?=E5=A4=9A=E7=BA=BF=E7=A8=8B=E5=B7=A5=E5=85=B7?=
=?UTF-8?q?=E7=B1=BB=EF=BC=8Cwebsocket?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
ruoyi-common/pom.xml | 5 +-
.../ruoyi/common/annotation/VoidFunction.java | 13 +
.../com/ruoyi/common/constant/Constants.java | 1 +
.../utils/thread/MultipleThreadListParam.java | 22 +
.../thread/MultipleThreadStringParam.java | 21 +
.../utils/thread/MultipleThreadWorkUtil.java | 547 ++++++++++++++++++
.../common/websocket/MyWebSocketHandler.java | 140 +++++
.../common/websocket/WebSocketConfig.java | 26 +
.../framework/config/SecurityConfig.java | 2 +-
9 files changed, 775 insertions(+), 2 deletions(-)
create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/annotation/VoidFunction.java
create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/utils/thread/MultipleThreadListParam.java
create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/utils/thread/MultipleThreadStringParam.java
create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/utils/thread/MultipleThreadWorkUtil.java
create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/websocket/MyWebSocketHandler.java
create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/websocket/WebSocketConfig.java
diff --git a/ruoyi-common/pom.xml b/ruoyi-common/pom.xml
index 2a11d03..03046cf 100644
--- a/ruoyi-common/pom.xml
+++ b/ruoyi-common/pom.xml
@@ -184,7 +184,10 @@
spring-boot-starter-mail
3.1.4
-
+
+ org.springframework.boot
+ spring-boot-starter-websocket
+
javax.activation
activation
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/annotation/VoidFunction.java b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/VoidFunction.java
new file mode 100644
index 0000000..3022ec0
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/VoidFunction.java
@@ -0,0 +1,13 @@
+package com.ruoyi.common.annotation;
+/**
+ * 一个参数、没有返回
+ * @author wangqiong
+ */
+@FunctionalInterface
+public interface VoidFunction {
+ /**
+ * 有一个参数
+ * @param param
+ */
+ void apply(T param);
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java b/ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java
index 5b5ab5a..7fdb47f 100644
--- a/ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java
@@ -129,6 +129,7 @@ public class Constants
*/
public static final String LOOKUP_LDAPS = "ldaps:";
public static final String DEFAULT_PASSWORD = "123456";
+ public static final String SPLIT_COMMA =",";
/**
* 自动识别json对象白名单配置(仅允许解析的包名,范围越小越安全)
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/thread/MultipleThreadListParam.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/thread/MultipleThreadListParam.java
new file mode 100644
index 0000000..71c0882
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/thread/MultipleThreadListParam.java
@@ -0,0 +1,22 @@
+package com.ruoyi.common.utils.thread;
+
+import lombok.Data;
+
+import java.util.List;
+import java.util.function.Function;
+
+/**
+ * @author wangqiong
+ * @date 2023/11/25 9:25
+ **/
+@Data
+public class MultipleThreadListParam {
+ /**需要执行的方法*/
+ private Function,R> function;
+ /**参数ID*/
+ private List list;
+ public MultipleThreadListParam(Function,R> function, List list){
+ this.function=function;
+ this.list=list;
+ }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/thread/MultipleThreadStringParam.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/thread/MultipleThreadStringParam.java
new file mode 100644
index 0000000..32fa680
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/thread/MultipleThreadStringParam.java
@@ -0,0 +1,21 @@
+package com.ruoyi.common.utils.thread;
+
+import lombok.Data;
+
+import java.util.function.Function;
+
+/**
+ * @author wangqiong
+ * @date 2023/11/25 9:25
+ **/
+@Data
+public class MultipleThreadStringParam {
+ /**需要执行的方法*/
+ private Function function;
+ /**参数ID*/
+ private String ids;
+ public MultipleThreadStringParam(Function function, String ids){
+ this.function=function;
+ this.ids=ids;
+ }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/thread/MultipleThreadWorkUtil.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/thread/MultipleThreadWorkUtil.java
new file mode 100644
index 0000000..81b9023
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/thread/MultipleThreadWorkUtil.java
@@ -0,0 +1,547 @@
+package com.ruoyi.common.utils.thread;
+
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.StrUtil;
+import com.ruoyi.common.annotation.VoidFunction;
+import com.ruoyi.common.constant.Constants;
+import com.ruoyi.common.exception.ServiceException;
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.*;
+import java.util.concurrent.*;
+import java.util.function.Function;
+
+/**
+ * @description 多线程操作数据库,一个线程异常全部回滚
+ * @Author wangqiong
+ * @Date 2023/11/25 14:02
+ * @Version V1.0
+ **/
+@Slf4j
+public class MultipleThreadWorkUtil {
+ private static int SIMPLE_TIME_COUNT=1000;
+
+ public static List exec(Function,R> execFun, List list){
+ List returnList=new ArrayList<>();
+ if(CollectionUtil.isEmpty(list)){
+ return returnList;
+ }
+ if(list.size()0){
+ times++;
+ }
+ CountDownLatch mainLatch=new CountDownLatch(1);
+ //监控子线程
+ CountDownLatch threadLatch=new CountDownLatch(times);
+ //根据子线程执行结果判断是否需要回滚
+ BlockingDeque resultList=new LinkedBlockingDeque<>();
+ //必须使用对象,如果使用变量会造成线程之间不能共享变量值
+ RollBack rollBack=new RollBack(false);
+ ExecutorService executorService=Executors.newFixedThreadPool(times);
+ List> futureList=new ArrayList<>();
+ for (int i = 0; i future=executorService.submit(new ExecThread(mainLatch,threadLatch,rollBack,resultList,list.subList(i*SIMPLE_TIME_COUNT,i*SIMPLE_TIME_COUNT+SIMPLE_TIME_COUNT),execFun));
+ futureList.add(future);
+ }else{
+ Future future=executorService.submit(new ExecThread(mainLatch,threadLatch,rollBack,resultList,list.subList(i*SIMPLE_TIME_COUNT,list.size()),execFun));
+ futureList.add(future);
+ }
+ }
+ setResult(executorService,returnList,futureList,resultList,
+ mainLatch,threadLatch,rollBack,times);
+ return returnList;
+ }
+
+ public static void exec(VoidFunction> execFun, Set set){
+ if(set.size()0){
+ times++;
+ }
+ CountDownLatch mainLatch=new CountDownLatch(1);
+ //监控子线程
+ CountDownLatch threadLatch=new CountDownLatch(times);
+ //根据子线程执行结果判断是否需要回滚
+ BlockingDeque resultList=new LinkedBlockingDeque<>();
+ //必须使用对象,如果使用变量会造成线程之间不能共享变量值
+ RollBack rollBack=new RollBack(false);
+ ExecutorService executorService=Executors.newFixedThreadPool(times);
+ List list=new ArrayList<>(set);
+ List futureList=new ArrayList<>();
+ for (int i = 0; i backUpResult=new ArrayList<>();
+ try{
+ //
+ boolean await=threadLatch.await(times*3,TimeUnit.SECONDS);
+ if(!await){
+ rollBack.setRollBack(true);
+ }else{
+ //查看执行情况,如果有存在需要回滚的线程,则全部回滚
+ for (int i = 0; i List execFun(MultipleThreadStringParam...params){
+ List returnList=new ArrayList<>();
+ if(ArrayUtil.isEmpty(params)){
+ return returnList;
+ }
+ List threadCountList=new ArrayList<>();
+ for (MultipleThreadStringParam param : params) {
+ List idList= Arrays.asList(param.getIds().split(Constants.SPLIT_COMMA));
+ if(idList.size()0){
+ threadCountList.add(idList.size()/SIMPLE_TIME_COUNT+1);
+ }else{
+ threadCountList.add(idList.size()/SIMPLE_TIME_COUNT);
+ }
+ }
+ }
+ int times=0;
+ for (Integer count : threadCountList) {
+ times+=count;
+ }
+ CountDownLatch mainLatch=new CountDownLatch(1);
+ //监控子线程
+ CountDownLatch threadLatch=new CountDownLatch(times);
+ //根据子线程执行结果判断是否需要回滚
+ BlockingDeque resultList=new LinkedBlockingDeque<>();
+ //必须使用对象,如果使用变量会造成线程之间不能共享变量值
+ RollBack rollBack=new RollBack(false);
+ ExecutorService executorService=Executors.newFixedThreadPool(times);
+ List> futureList=new ArrayList<>();
+ for (int i = 0; i < params.length; i++) {
+ MultipleThreadStringParam param=params[i];
+ List idList= Arrays.asList(param.getIds().split(Constants.SPLIT_COMMA));
+ for (int j = 0; j future=executorService.submit(new ExecByIdsStringThread<>(mainLatch,threadLatch,rollBack,resultList,StrUtil.join(Constants.SPLIT_COMMA,idList.subList(j*SIMPLE_TIME_COUNT,j*SIMPLE_TIME_COUNT+SIMPLE_TIME_COUNT)),param.getFunction()));
+ futureList.add(future);
+ }else{
+ Future future=executorService.submit(new ExecByIdsStringThread(mainLatch,threadLatch,rollBack,resultList,StrUtil.join(Constants.SPLIT_COMMA,idList.subList(j*SIMPLE_TIME_COUNT,idList.size())),param.getFunction()));
+ futureList.add(future);
+ }
+ }
+ }
+ setResult(executorService,returnList,futureList,resultList,
+ mainLatch,threadLatch,rollBack,times);
+ return returnList;
+ }
+
+ public static List execByIds(Function execFun,String ids){
+ List returnList=new ArrayList<>();
+ if(StrUtil.isEmpty(ids)){
+ return returnList;
+ }
+ List idList= Arrays.asList(ids.split(Constants.SPLIT_COMMA));
+ if(idList.size()0){
+ times++;
+ }
+ CountDownLatch mainLatch=new CountDownLatch(1);
+ //监控子线程
+ CountDownLatch threadLatch=new CountDownLatch(times);
+ //根据子线程执行结果判断是否需要回滚
+ BlockingDeque resultList=new LinkedBlockingDeque<>();
+ //必须使用对象,如果使用变量会造成线程之间不能共享变量值
+ RollBack rollBack=new RollBack(false);
+ ExecutorService executorService=Executors.newFixedThreadPool(times);
+ List> futureList=new ArrayList<>();
+ for (int i = 0; i future=executorService.submit(new ExecByIdsStringThread<>(mainLatch,threadLatch,rollBack,resultList,StrUtil.join(Constants.SPLIT_COMMA,idList.subList(i*SIMPLE_TIME_COUNT,i*SIMPLE_TIME_COUNT+SIMPLE_TIME_COUNT)),execFun));
+ futureList.add(future);
+ }else{
+ Future future=executorService.submit(new ExecByIdsStringThread(mainLatch,threadLatch,rollBack,resultList,StrUtil.join(Constants.SPLIT_COMMA,idList.subList(i*SIMPLE_TIME_COUNT,idList.size())),execFun));
+ futureList.add(future);
+ }
+ }
+ setResult(executorService,returnList,futureList,resultList,
+ mainLatch,threadLatch,rollBack,times);
+ return returnList;
+ }
+
+ public static List execByIds(Function,List> execFun,List idList){
+ List returnList=new ArrayList<>();
+ if(CollectionUtil.isEmpty(idList)){
+ return returnList;
+ }
+ if(idList.size()0){
+ times++;
+ }
+ CountDownLatch mainLatch=new CountDownLatch(1);
+ //监控子线程
+ CountDownLatch threadLatch=new CountDownLatch(times);
+ //根据子线程执行结果判断是否需要回滚
+ BlockingDeque resultList=new LinkedBlockingDeque<>();
+ //必须使用对象,如果使用变量会造成线程之间不能共享变量值
+ RollBack rollBack=new RollBack(false);
+ ExecutorService executorService=Executors.newFixedThreadPool(times);
+ List>> futureList=new ArrayList<>();
+ for (int i = 0; i > future=executorService.submit(new ExecByIdsStringListThread<>(mainLatch,threadLatch,rollBack,resultList,idList.subList(i*SIMPLE_TIME_COUNT,i*SIMPLE_TIME_COUNT+SIMPLE_TIME_COUNT),execFun));
+ futureList.add(future);
+ }else{
+ Future> future=executorService.submit(new ExecByIdsStringListThread(mainLatch,threadLatch,rollBack,resultList,idList.subList(i*SIMPLE_TIME_COUNT,idList.size()),execFun));
+ futureList.add(future);
+ }
+ }
+ /**存放子线程返回结果*/
+ List backUpResult=new ArrayList<>();
+ try{
+ //
+ boolean await=threadLatch.await(times*3,TimeUnit.SECONDS);
+ if(!await){
+ rollBack.setRollBack(true);
+ }else{
+ //查看执行情况,如果有存在需要回滚的线程,则全部回滚
+ for (int i = 0; i > future : futureList) {
+ try {
+ returnList.addAll(future.get());
+ } catch (Exception e) {
+ throw new ServiceException("多线程执行异常");
+ }
+ }
+ return returnList;
+ }
+
+ private static void setResult(ExecutorService executorService,List returnList,List> futureList,BlockingDeque resultList
+ ,CountDownLatch mainLatch,CountDownLatch threadLatch,RollBack rollBack,int times){
+ /**存放子线程返回结果*/
+ List backUpResult=new ArrayList<>();
+ try{
+ //
+ boolean await=threadLatch.await(times*3,TimeUnit.SECONDS);
+ if(!await){
+ rollBack.setRollBack(true);
+ }else{
+ //查看执行情况,如果有存在需要回滚的线程,则全部回滚
+ for (int i = 0; i future : futureList) {
+ try {
+ returnList.add(future.get());
+ } catch (Exception e) {
+ throw new ServiceException("多线程执行异常");
+ }
+ }
+ }
+
+ static class QueryThread implements Callable>{
+ private String ids;
+ private Function> execFun;
+ public QueryThread(String ids,Function> execFun){
+ this.ids=ids;
+ this.execFun=execFun;
+ }
+ @Override
+ public List call(){
+ return execFun.apply(ids);
+ }
+ }
+
+ static class ExecThread implements Callable{
+ /**主线程监控*/
+ private CountDownLatch mainLatch;
+ /**子线程监控*/
+ private CountDownLatch threadLatch;
+ /**是否回滚*/
+ private RollBack rollBack;
+ private BlockingDeque resultList;
+ private List list;
+ private Function,R> execFun;
+ public ExecThread(CountDownLatch mainLatch,CountDownLatch threadLatch,RollBack rollBack,BlockingDeque resultList,List list,Function,R> execFun){
+ this.mainLatch=mainLatch;
+ this.threadLatch=threadLatch;
+ this.rollBack=rollBack;
+ this.resultList=resultList;
+ this.list=list;
+ this.execFun=execFun;
+ }
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public R call(){
+ // 是否回滚
+ Boolean result=false;
+ R r=null;
+ try{
+ // 对数据库进行操作
+ r=execFun.apply(list);
+ }catch (Exception e){
+ e.printStackTrace();
+ result=true;
+ }
+ resultList.add(result);
+ // 子线程-1,切换到主线程执行
+ threadLatch.countDown();
+ try{
+ // 等待主线程执行
+ mainLatch.await();
+ }catch (InterruptedException e){
+ throw new ServiceException("多线程执行异常");
+ }
+ if(rollBack.getRollBack()){
+ throw new ServiceException("多线程执行异常");
+ }
+ return r;
+ }
+ }
+
+ static class VoidExecThread implements Runnable{
+ /**主线程监控*/
+ private CountDownLatch mainLatch;
+ /**子线程监控*/
+ private CountDownLatch threadLatch;
+ /**是否回滚*/
+ private RollBack rollBack;
+ private BlockingDeque resultList;
+ private List list;
+ private VoidFunction> execFun;
+ public VoidExecThread(CountDownLatch mainLatch,CountDownLatch threadLatch,RollBack rollBack,BlockingDeque resultList,List list,VoidFunction> execFun){
+ this.mainLatch=mainLatch;
+ this.threadLatch=threadLatch;
+ this.rollBack=rollBack;
+ this.resultList=resultList;
+ this.list=list;
+ this.execFun=execFun;
+ }
+ @Override
+ public void run() {
+ Boolean result=false;
+ try{
+ execFun.apply(new HashSet<>(list));
+ }catch (Exception e){
+ e.printStackTrace();
+ result=true;
+ }
+ resultList.add(result);
+ threadLatch.countDown();
+ try{
+ mainLatch.await();
+ }catch (InterruptedException e){
+ throw new ServiceException("多线程执行异常");
+ }
+ if(rollBack.getRollBack()){
+ throw new ServiceException("多线程执行异常");
+ }
+ }
+ }
+
+
+ static class ExecByIdsStringThread implements Callable{
+ /**主线程监控*/
+ private CountDownLatch mainLatch;
+ /**子线程监控*/
+ private CountDownLatch threadLatch;
+ /**是否回滚*/
+ private RollBack rollBack;
+ private BlockingDeque resultList;
+ private String ids;
+ private Function execFun;
+ public ExecByIdsStringThread(CountDownLatch mainLatch,CountDownLatch threadLatch,RollBack rollBack,BlockingDeque resultList,String ids,Function execFun){
+ this.mainLatch=mainLatch;
+ this.threadLatch=threadLatch;
+ this.rollBack=rollBack;
+ this.resultList=resultList;
+ this.ids=ids;
+ this.execFun=execFun;
+ }
+ @Override
+ public R call(){
+ Boolean result=false;
+ R r=null;
+ try{
+ r=execFun.apply(ids);
+ }catch (Exception e){
+ e.printStackTrace();
+ result=true;
+ }
+ resultList.add(result);
+ threadLatch.countDown();
+ try{
+ mainLatch.await();
+ }catch (InterruptedException e){
+ throw new ServiceException("多线程执行异常");
+ }
+ if(rollBack.getRollBack()){
+ throw new ServiceException("多线程执行异常");
+ }
+ return r;
+ }
+ }
+
+
+
+
+ static class ExecByIdsStringListThread implements Callable{
+ /**主线程监控*/
+ private CountDownLatch mainLatch;
+ /**子线程监控*/
+ private CountDownLatch threadLatch;
+ /**是否回滚*/
+ private RollBack rollBack;
+ private BlockingDeque resultList;
+ private List ids;
+ private Function,R> execFun;
+ public ExecByIdsStringListThread(CountDownLatch mainLatch,CountDownLatch threadLatch,RollBack rollBack,BlockingDeque resultList,List ids,Function,R> execFun){
+ this.mainLatch=mainLatch;
+ this.threadLatch=threadLatch;
+ this.rollBack=rollBack;
+ this.resultList=resultList;
+ this.ids=ids;
+ this.execFun=execFun;
+ }
+ @Override
+ public R call(){
+ Boolean result=false;
+ R r=null;
+ try{
+ r=execFun.apply(ids);
+ }catch (Exception e){
+ e.printStackTrace();
+ result=true;
+ }
+ resultList.add(result);
+ threadLatch.countDown();
+ try{
+ mainLatch.await();
+ }catch (InterruptedException e){
+ throw new ServiceException("多线程执行异常");
+ }
+ if(rollBack.getRollBack()){
+ throw new ServiceException("多线程执行异常");
+ }
+ return r;
+ }
+ }
+
+ @Data
+ static class RollBack{
+ private Boolean rollBack;
+ public RollBack(Boolean rollBack){
+ this.rollBack=rollBack;
+ }
+ }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/websocket/MyWebSocketHandler.java b/ruoyi-common/src/main/java/com/ruoyi/common/websocket/MyWebSocketHandler.java
new file mode 100644
index 0000000..4da9fcb
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/websocket/MyWebSocketHandler.java
@@ -0,0 +1,140 @@
+package com.ruoyi.common.websocket;
+
+
+import com.ruoyi.common.exception.ServiceException;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import javax.websocket.*;
+import javax.websocket.server.PathParam;
+import javax.websocket.server.ServerEndpoint;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+/**
+ * @author wangqiong
+ * @description
+ * @date 2023-11-25 15:16
+ */
+@Component
+@Slf4j
+@ServerEndpoint("/websocket/{userName}")
+public class MyWebSocketHandler {
+ // 接口路径 ws://127.0.0.1:9000/websocket;
+ private Session session;
+
+ //concurrent包的线程安全Set,用来存放每个客户端对应的WebSocket对象。
+ private static CopyOnWriteArraySet sessions = new CopyOnWriteArraySet<>();
+ // 用来存在线连接数
+ private static Map sessionPool = new HashMap<>();
+
+ /**
+ * 链接成功调用的方法
+ */
+ @OnOpen
+ public void onOpen(Session session, @PathParam(value = "userName") String userName) {
+ try {
+ sessions.add(session);
+ sessionPool.put(userName, session);
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * 链接关闭调用的方法
+ */
+ @OnClose
+ public void onClose(Session session,@PathParam(value = "userName") String userName) {
+ try {
+ if(session!=null && session.isOpen()){
+ session.close();
+ sessions.remove(session);
+ sessionPool.remove(userName);
+ }
+
+ log.info("【websocket消息】连接断开,总数为:" + sessions.size());
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * 收到客户端消息后调用的方法
+ *
+ * @param message
+ * @param
+ */
+ @OnMessage
+ public void onMessage(@PathParam(value = "userName") String userName, String message) {
+
+ System.out.println("【websocket消息】收到客户端消息:" + message);
+ // 将消息广播给其它用户
+ for (Session session : sessions) {
+ if(session!=null && session.isOpen()){
+ try {
+ session.getBasicRemote().sendText(message);
+ }catch (Exception e){
+ throw new ServiceException(e.getMessage());
+ }
+ }else {
+ try {
+ session.close();
+ } catch (IOException e) {
+ throw new ServiceException(e.getMessage());
+ }
+ sessions.remove(session);
+ sessionPool.remove(userName);
+ }
+ }
+
+ }
+
+ /**
+ * 发送错误时的处理
+ *
+ * @param session
+ * @param error
+ */
+ @OnError
+ public void onError(Session session, Throwable error) {
+
+ throw new ServiceException(error.getMessage());
+ }
+
+
+ /**
+ * 推消息给前端
+ *
+ * @param userId
+ * @param message
+ * @return
+ */
+ public static Runnable sendOneMessage(String userId, String message) {
+ Session session = sessionPool.get(userId);
+ if (session != null && session.isOpen()) {
+ try {
+ log.info("【推给前端消息】 :" + message);
+
+ //高并发下,防止session占用期间,被其他线程调用
+ synchronized (session) {
+ session.getBasicRemote().sendText(message);
+ }
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ return null;
+ }
+
+
+
+
+
+}
+
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/websocket/WebSocketConfig.java b/ruoyi-common/src/main/java/com/ruoyi/common/websocket/WebSocketConfig.java
new file mode 100644
index 0000000..1084cbd
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/websocket/WebSocketConfig.java
@@ -0,0 +1,26 @@
+package com.ruoyi.common.websocket;//package com.ruoyi.common.websocket;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.socket.server.standard.ServerEndpointExporter;
+
+/**
+ * @author wangqiong
+ * @description
+ * @date 2023-11-25 15:19
+ */
+
+@Configuration
+public class WebSocketConfig {
+
+ /**
+ * 注入ServerEndpointExporter,
+ * 这个bean会自动注册使用了@ServerEndpoint注解声明的Websocket endpoint
+ */
+
+ @Bean
+ public ServerEndpointExporter serverEndpointExporter() {
+ return new ServerEndpointExporter();
+ }
+
+}
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java
index 7078344..aa522ae 100644
--- a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java
@@ -111,7 +111,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter
// 过滤请求
.authorizeRequests()
// 对于登录login 注册register 验证码captchaImage 允许匿名访问
- .antMatchers("/login", "/register", "/captchaImage","/uploadPath/**").permitAll()
+ .antMatchers("/login", "/register", "/captchaImage","/uploadPath/**","/websocket/**").permitAll()
// 静态资源,可匿名访问
.antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
.antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()