diff --git a/pom.xml b/pom.xml index ae3899b..fb34276 100644 --- a/pom.xml +++ b/pom.xml @@ -274,6 +274,12 @@ ruoyi-minio ${ruoyi.version} + + + com.ruoyi + ruoyi-websocket + ${ruoyi.version} + @@ -289,6 +295,7 @@ ruoyi-online ruoyi-mybatis-jpa ruoyi-middleware + ruoyi-websocket pom diff --git a/ruoyi-admin/pom.xml b/ruoyi-admin/pom.xml index f06c417..c108030 100644 --- a/ruoyi-admin/pom.xml +++ b/ruoyi-admin/pom.xml @@ -81,6 +81,12 @@ ruoyi-middleware-starter + + + com.ruoyi + ruoyi-websocket + + com.github.xiaoymin 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 d0958d9..3f7cd64 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 @@ -120,6 +120,7 @@ public class SecurityConfig { .requestMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**", "/*/api-docs/**") .permitAll() + .requestMatchers("/websocket/**").permitAll() // 除上面外的所有请求全部需要鉴权认证 .anyRequest().authenticated(); }) diff --git a/ruoyi-websocket/pom.xml b/ruoyi-websocket/pom.xml new file mode 100644 index 0000000..46dcf9f --- /dev/null +++ b/ruoyi-websocket/pom.xml @@ -0,0 +1,31 @@ + + + + ruoyi + com.ruoyi + 3.8.7.3.2 + + 4.0.0 + + ruoyi-websocket + + + websocket系统模块 + + + + + com.ruoyi + ruoyi-common + + + + + org.springframework.boot + spring-boot-starter-websocket + + + + + diff --git a/ruoyi-websocket/src/main/java/com/ruoyi/websocket/SemaphoreUtils.java b/ruoyi-websocket/src/main/java/com/ruoyi/websocket/SemaphoreUtils.java new file mode 100644 index 0000000..85f1074 --- /dev/null +++ b/ruoyi-websocket/src/main/java/com/ruoyi/websocket/SemaphoreUtils.java @@ -0,0 +1,59 @@ +package com.ruoyi.websocket; + +import java.util.concurrent.Semaphore; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 信号量相关处理 + * + * @author ruoyi + */ +public class SemaphoreUtils +{ + /** + * SemaphoreUtils 日志控制器 + */ + private static final Logger LOGGER = LoggerFactory.getLogger(SemaphoreUtils.class); + + /** + * 获取信号量 + * + * @param semaphore + * @return + */ + public static boolean tryAcquire(Semaphore semaphore) + { + boolean flag = false; + + try + { + flag = semaphore.tryAcquire(); + } + catch (Exception e) + { + LOGGER.error("获取信号量异常", e); + } + + return flag; + } + + /** + * 释放信号量 + * + * @param semaphore + */ + public static void release(Semaphore semaphore) + { + + try + { + semaphore.release(); + } + catch (Exception e) + { + LOGGER.error("释放信号量异常", e); + } + } +} diff --git a/ruoyi-websocket/src/main/java/com/ruoyi/websocket/WebSocketConfig.java b/ruoyi-websocket/src/main/java/com/ruoyi/websocket/WebSocketConfig.java new file mode 100644 index 0000000..6dad3cf --- /dev/null +++ b/ruoyi-websocket/src/main/java/com/ruoyi/websocket/WebSocketConfig.java @@ -0,0 +1,20 @@ +package com.ruoyi.websocket; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.socket.server.standard.ServerEndpointExporter; + +/** + * websocket 配置 + * + * @author ruoyi + */ +@Configuration +public class WebSocketConfig +{ + @Bean + public ServerEndpointExporter serverEndpointExporter() + { + return new ServerEndpointExporter(); + } +} diff --git a/ruoyi-websocket/src/main/java/com/ruoyi/websocket/WebSocketServer.java b/ruoyi-websocket/src/main/java/com/ruoyi/websocket/WebSocketServer.java new file mode 100644 index 0000000..2378837 --- /dev/null +++ b/ruoyi-websocket/src/main/java/com/ruoyi/websocket/WebSocketServer.java @@ -0,0 +1,105 @@ +package com.ruoyi.websocket; + +import java.util.concurrent.Semaphore; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import jakarta.websocket.OnClose; +import jakarta.websocket.OnError; +import jakarta.websocket.OnMessage; +import jakarta.websocket.OnOpen; +import jakarta.websocket.Session; +import jakarta.websocket.server.ServerEndpoint; + +/** + * websocket 消息处理 + * + * @author ruoyi + */ +@Component +@ServerEndpoint("/websocket/message") +public class WebSocketServer +{ + /** + * WebSocketServer 日志控制器 + */ + private static final Logger LOGGER = LoggerFactory.getLogger(WebSocketServer.class); + + /** + * 默认最多允许同时在线人数100 + */ + public static int socketMaxOnlineCount = 100; + + private static Semaphore socketSemaphore = new Semaphore(socketMaxOnlineCount); + + /** + * 连接建立成功调用的方法 + */ + @OnOpen + public void onOpen(Session session) throws Exception + { + boolean semaphoreFlag = false; + // 尝试获取信号量 + semaphoreFlag = SemaphoreUtils.tryAcquire(socketSemaphore); + if (!semaphoreFlag) + { + // 未获取到信号量 + LOGGER.error("\n 当前在线人数超过限制数- {}", socketMaxOnlineCount); + WebSocketUsers.sendMessageToUserByText(session, "当前在线人数超过限制数:" + socketMaxOnlineCount); + session.close(); + } + else + { + // 添加用户 + WebSocketUsers.put(session.getId(), session); + LOGGER.info("\n 建立连接 - {}", session); + LOGGER.info("\n 当前人数 - {}", WebSocketUsers.getUsers().size()); + WebSocketUsers.sendMessageToUserByText(session, "连接成功"); + } + } + + /** + * 连接关闭时处理 + */ + @OnClose + public void onClose(Session session) + { + LOGGER.info("\n 关闭连接 - {}", session); + // 移除用户 + WebSocketUsers.remove(session.getId()); + // 获取到信号量则需释放 + SemaphoreUtils.release(socketSemaphore); + } + + /** + * 抛出异常时处理 + */ + @OnError + public void onError(Session session, Throwable exception) throws Exception + { + if (session.isOpen()) + { + // 关闭连接 + session.close(); + } + String sessionId = session.getId(); + LOGGER.info("\n 连接异常 - {}", sessionId); + LOGGER.info("\n 异常信息 - {}", exception); + // 移出用户 + WebSocketUsers.remove(sessionId); + // 获取到信号量则需释放 + SemaphoreUtils.release(socketSemaphore); + } + + /** + * 服务器接收到客户端消息时调用的方法 + */ + @OnMessage + public void onMessage(String message, Session session) + { + String msg = message.replace("你", "我").replace("吗", ""); + WebSocketUsers.sendMessageToUserByText(session, msg); + } +} diff --git a/ruoyi-websocket/src/main/java/com/ruoyi/websocket/WebSocketUsers.java b/ruoyi-websocket/src/main/java/com/ruoyi/websocket/WebSocketUsers.java new file mode 100644 index 0000000..75fd661 --- /dev/null +++ b/ruoyi-websocket/src/main/java/com/ruoyi/websocket/WebSocketUsers.java @@ -0,0 +1,142 @@ +package com.ruoyi.websocket; + +import java.io.IOException; +import java.util.Collection; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jakarta.websocket.Session; + +/** + * websocket 客户端用户集 + * + * @author ruoyi + */ +public class WebSocketUsers +{ + /** + * WebSocketUsers 日志控制器 + */ + private static final Logger LOGGER = LoggerFactory.getLogger(WebSocketUsers.class); + + /** + * 用户集 + */ + private static Map USERS = new ConcurrentHashMap(); + + /** + * 存储用户 + * + * @param key 唯一键 + * @param session 用户信息 + */ + public static void put(String key, Session session) + { + USERS.put(key, session); + } + + /** + * 移除用户 + * + * @param session 用户信息 + * + * @return 移除结果 + */ + public static boolean remove(Session session) + { + String key = null; + boolean flag = USERS.containsValue(session); + if (flag) + { + Set> entries = USERS.entrySet(); + for (Map.Entry entry : entries) + { + Session value = entry.getValue(); + if (value.equals(session)) + { + key = entry.getKey(); + break; + } + } + } + else + { + return true; + } + return remove(key); + } + + /** + * 移出用户 + * + * @param key 键 + */ + public static boolean remove(String key) + { + LOGGER.info("\n 正在移出用户 - {}", key); + Session remove = USERS.remove(key); + if (remove != null) + { + boolean containsValue = USERS.containsValue(remove); + LOGGER.info("\n 移出结果 - {}", containsValue ? "失败" : "成功"); + return containsValue; + } + else + { + return true; + } + } + + /** + * 获取在线用户列表 + * + * @return 返回用户集合 + */ + public static Map getUsers() + { + return USERS; + } + + /** + * 群发消息文本消息 + * + * @param message 消息内容 + */ + public static void sendMessageToUsersByText(String message) + { + Collection values = USERS.values(); + for (Session value : values) + { + sendMessageToUserByText(value, message); + } + } + + /** + * 发送文本消息 + * + * @param userName 自己的用户名 + * @param message 消息内容 + */ + public static void sendMessageToUserByText(Session session, String message) + { + if (session != null) + { + try + { + session.getBasicRemote().sendText(message); + } + catch (IOException e) + { + LOGGER.error("\n[发送消息异常]", e); + } + } + else + { + LOGGER.info("\n[你已离线]"); + } + } +}