Commit 6954150fcb1f2392eb5b1e8d38b4848ec3448074

Authored by xingyu
1 parent ed30f06a

feat: websocket init

Showing 15 changed files with 297 additions and 2 deletions
yudao-dependencies/pom.xml
@@ -23,8 +23,8 @@ @@ -23,8 +23,8 @@
23 <servlet.versoin>2.5</servlet.versoin> 23 <servlet.versoin>2.5</servlet.versoin>
24 <!-- DB 相关 --> 24 <!-- DB 相关 -->
25 <druid.version>1.2.15</druid.version> 25 <druid.version>1.2.15</druid.version>
26 - <mybatis-plus.version>3.5.3</mybatis-plus.version>  
27 - <mybatis-plus-generator.version>3.5.2</mybatis-plus-generator.version> 26 + <mybatis-plus.version>3.5.3.1</mybatis-plus.version>
  27 + <mybatis-plus-generator.version>3.5.3.1</mybatis-plus-generator.version>
28 <dynamic-datasource.version>3.6.1</dynamic-datasource.version> 28 <dynamic-datasource.version>3.6.1</dynamic-datasource.version>
29 <redisson.version>3.18.0</redisson.version> 29 <redisson.version>3.18.0</redisson.version>
30 <!-- 服务保障相关 --> 30 <!-- 服务保障相关 -->
yudao-framework/pom.xml
@@ -40,6 +40,7 @@ @@ -40,6 +40,7 @@
40 40
41 <module>yudao-spring-boot-starter-flowable</module> 41 <module>yudao-spring-boot-starter-flowable</module>
42 <module>yudao-spring-boot-starter-captcha</module> 42 <module>yudao-spring-boot-starter-captcha</module>
  43 + <module>yudao-spring-boot-starter-websocket</module>
43 </modules> 44 </modules>
44 45
45 <artifactId>yudao-framework</artifactId> 46 <artifactId>yudao-framework</artifactId>
yudao-framework/yudao-spring-boot-starter-websocket/pom.xml 0 → 100644
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<project xmlns="http://maven.apache.org/POM/4.0.0"
  3 + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5 + <parent>
  6 + <groupId>cn.iocoder.boot</groupId>
  7 + <artifactId>yudao-framework</artifactId>
  8 + <version>${revision}</version>
  9 + </parent>
  10 + <modelVersion>4.0.0</modelVersion>
  11 + <artifactId>yudao-spring-boot-starter-websocket</artifactId>
  12 + <packaging>jar</packaging>
  13 +
  14 + <name>${project.artifactId}</name>
  15 + <description>WebSocket</description>
  16 + <url>https://github.com/YunaiV/ruoyi-vue-pro</url>
  17 +
  18 +
  19 + <dependencies>
  20 +
  21 + <dependency>
  22 + <groupId>cn.iocoder.boot</groupId>
  23 + <artifactId>yudao-common</artifactId>
  24 + </dependency>
  25 +
  26 + <dependency>
  27 + <groupId>cn.iocoder.boot</groupId>
  28 + <artifactId>yudao-spring-boot-starter-security</artifactId>
  29 + </dependency>
  30 +
  31 + <dependency>
  32 + <groupId>org.springframework.boot</groupId>
  33 + <artifactId>spring-boot-starter-websocket</artifactId>
  34 + </dependency>
  35 + </dependencies>
  36 +
  37 +</project>
0 \ No newline at end of file 38 \ No newline at end of file
yudao-framework/yudao-spring-boot-starter-websocket/src/main/java/cn/iocoder/yudao/framework/websocket/config/WebSocketHandlerConfig.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.websocket.config;
  2 +
  3 +import cn.iocoder.yudao.framework.websocket.core.UserHandshakeInterceptor;
  4 +import org.springframework.boot.context.properties.EnableConfigurationProperties;
  5 +import org.springframework.context.annotation.Bean;
  6 +import org.springframework.web.socket.server.HandshakeInterceptor;
  7 +
  8 +@EnableConfigurationProperties(WebSocketProperties.class)
  9 +public class WebSocketHandlerConfig {
  10 + @Bean
  11 + public HandshakeInterceptor handshakeInterceptor() {
  12 + return new UserHandshakeInterceptor();
  13 + }
  14 +}
yudao-framework/yudao-spring-boot-starter-websocket/src/main/java/cn/iocoder/yudao/framework/websocket/config/WebSocketProperties.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.websocket.config;
  2 +
  3 +import lombok.Data;
  4 +import org.springframework.boot.context.properties.ConfigurationProperties;
  5 +import org.springframework.validation.annotation.Validated;
  6 +
  7 +/**
  8 + * WebSocket 配置项
  9 + *
  10 + * @author xingyu4j
  11 + */
  12 +@ConfigurationProperties("yudao.websocket")
  13 +@Data
  14 +@Validated
  15 +public class WebSocketProperties {
  16 +
  17 + /**
  18 + * 路径
  19 + */
  20 + private String path = "";
  21 + /**
  22 + * 默认最多允许同时在线用户数
  23 + */
  24 + private int maxOnlineCount = 0;
  25 + /**
  26 + * 是否保存session
  27 + */
  28 + private boolean sessionMap = true;
  29 +}
yudao-framework/yudao-spring-boot-starter-websocket/src/main/java/cn/iocoder/yudao/framework/websocket/config/YudaoWebSocketAutoConfiguration.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.websocket.config;
  2 +
  3 +import org.springframework.boot.autoconfigure.AutoConfiguration;
  4 +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
  5 +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
  6 +import org.springframework.boot.context.properties.EnableConfigurationProperties;
  7 +import org.springframework.context.annotation.Bean;
  8 +import org.springframework.web.socket.WebSocketHandler;
  9 +import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
  10 +import org.springframework.web.socket.server.HandshakeInterceptor;
  11 +
  12 +import java.util.List;
  13 +
  14 +/**
  15 + * WebSocket 自动配置
  16 + *
  17 + * @author xingyu4j
  18 + */
  19 +@AutoConfiguration
  20 +// 允许使用 yudao.websocket.enable=false 禁用websocket
  21 +@ConditionalOnProperty(prefix = "yudao.websocket", value = "enable", matchIfMissing = true)
  22 +@EnableConfigurationProperties(WebSocketProperties.class)
  23 +public class YudaoWebSocketAutoConfiguration {
  24 + @Bean
  25 + @ConditionalOnMissingBean
  26 + public WebSocketConfigurer webSocketConfigurer(List<HandshakeInterceptor> handshakeInterceptor,
  27 + WebSocketHandler webSocketHandler,
  28 + WebSocketProperties webSocketProperties) {
  29 +
  30 + return registry -> registry
  31 + .addHandler(webSocketHandler, webSocketProperties.getPath())
  32 + .addInterceptors(handshakeInterceptor.toArray(new HandshakeInterceptor[0]));
  33 + }
  34 +}
yudao-framework/yudao-spring-boot-starter-websocket/src/main/java/cn/iocoder/yudao/framework/websocket/core/UserHandshakeInterceptor.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.websocket.core;
  2 +
  3 +import cn.iocoder.yudao.framework.security.core.LoginUser;
  4 +import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
  5 +import org.springframework.http.server.ServerHttpRequest;
  6 +import org.springframework.http.server.ServerHttpResponse;
  7 +import org.springframework.web.socket.WebSocketHandler;
  8 +import org.springframework.web.socket.server.HandshakeInterceptor;
  9 +
  10 +import java.util.Map;
  11 +
  12 +public class UserHandshakeInterceptor implements HandshakeInterceptor {
  13 + @Override
  14 + public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
  15 + LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();
  16 + attributes.put(WebSocketKeyDefine.LOGIN_USER, loginUser);
  17 + return true;
  18 + }
  19 +
  20 + @Override
  21 + public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {
  22 +
  23 + }
  24 +}
yudao-framework/yudao-spring-boot-starter-websocket/src/main/java/cn/iocoder/yudao/framework/websocket/core/WebSocketKeyDefine.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.websocket.core;
  2 +
  3 +
  4 +import lombok.Data;
  5 +
  6 +@Data
  7 +public class WebSocketKeyDefine {
  8 + public static final String LOGIN_USER ="LOGIN_USER";
  9 +}
yudao-framework/yudao-spring-boot-starter-websocket/src/main/java/cn/iocoder/yudao/framework/websocket/core/WebSocketMessageDO.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.websocket.core;
  2 +
  3 +import lombok.Data;
  4 +import lombok.experimental.Accessors;
  5 +
  6 +import java.util.List;
  7 +
  8 +@Data
  9 +@Accessors(chain = true)
  10 +public class WebSocketMessageDO {
  11 + /**
  12 + * 接收消息的seesion
  13 + */
  14 + private List<Object> seesionKeyList;
  15 + /**
  16 + * 发送消息
  17 + */
  18 + private String msgText;
  19 +
  20 + public static WebSocketMessageDO build(List<Object> seesionKeyList, String msgText) {
  21 + return new WebSocketMessageDO().setMsgText(msgText).setSeesionKeyList(seesionKeyList);
  22 + }
  23 +
  24 +}
yudao-framework/yudao-spring-boot-starter-websocket/src/main/java/cn/iocoder/yudao/framework/websocket/core/WebSocketSessionHandler.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.websocket.core;
  2 +
  3 +import org.springframework.web.socket.WebSocketSession;
  4 +
  5 +import java.util.Collection;
  6 +import java.util.Map;
  7 +import java.util.Set;
  8 +import java.util.concurrent.ConcurrentHashMap;
  9 +
  10 +public final class WebSocketSessionHandler {
  11 + private WebSocketSessionHandler() {
  12 + }
  13 +
  14 + private static final Map<String, WebSocketSession> SESSION_MAP = new ConcurrentHashMap<>();
  15 +
  16 + public static void addSession(Object sessionKey, WebSocketSession session) {
  17 + SESSION_MAP.put(sessionKey.toString(), session);
  18 + }
  19 +
  20 + public static void removeSession(Object sessionKey) {
  21 + SESSION_MAP.remove(sessionKey.toString());
  22 + }
  23 +
  24 + public static WebSocketSession getSession(Object sessionKey) {
  25 + return SESSION_MAP.get(sessionKey.toString());
  26 + }
  27 +
  28 + public static Collection<WebSocketSession> getSessions() {
  29 + return SESSION_MAP.values();
  30 + }
  31 +
  32 + public static Set<String> getSessionKeys() {
  33 + return SESSION_MAP.keySet();
  34 + }
  35 +
  36 +}
yudao-framework/yudao-spring-boot-starter-websocket/src/main/java/cn/iocoder/yudao/framework/websocket/core/WebSocketUtils.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.websocket.core;
  2 +
  3 +import lombok.extern.slf4j.Slf4j;
  4 +import org.springframework.web.socket.TextMessage;
  5 +import org.springframework.web.socket.WebSocketSession;
  6 +
  7 +import java.io.IOException;
  8 +
  9 +@Slf4j
  10 +public class WebSocketUtils {
  11 + public static boolean sendMessage(WebSocketSession seesion, String message) {
  12 + if (seesion == null) {
  13 + log.error("seesion 不存在");
  14 + return false;
  15 + }
  16 + if (seesion.isOpen()) {
  17 + try {
  18 + seesion.sendMessage(new TextMessage(message));
  19 + } catch (IOException e) {
  20 + log.error("WebSocket 消息发送异常 Session={} | msg= {} | exception={}", seesion, message, e);
  21 + return false;
  22 + }
  23 + }
  24 + return true;
  25 + }
  26 +
  27 + public static boolean sendMessage(Object sessionKey, String message) {
  28 + WebSocketSession session = WebSocketSessionHandler.getSession(sessionKey);
  29 + return sendMessage(session, message);
  30 + }
  31 +}
yudao-framework/yudao-spring-boot-starter-websocket/src/main/java/cn/iocoder/yudao/framework/websocket/core/YudaoWebSocketHandlerDecorator.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.websocket.core;
  2 +
  3 +import cn.iocoder.yudao.framework.security.core.LoginUser;
  4 +import org.springframework.web.socket.CloseStatus;
  5 +import org.springframework.web.socket.WebSocketHandler;
  6 +import org.springframework.web.socket.WebSocketSession;
  7 +import org.springframework.web.socket.handler.WebSocketHandlerDecorator;
  8 +
  9 +public class YudaoWebSocketHandlerDecorator extends WebSocketHandlerDecorator {
  10 + public YudaoWebSocketHandlerDecorator(WebSocketHandler delegate) {
  11 + super(delegate);
  12 + }
  13 +
  14 + /**
  15 + * websocket 连接时执行的动作
  16 + * @param session websocket session 对象
  17 + * @throws Exception 异常对象
  18 + */
  19 + @Override
  20 + public void afterConnectionEstablished(final WebSocketSession session) throws Exception {
  21 + Object sessionKey = sessionKeyGen(session);
  22 + WebSocketSessionHandler.addSession(sessionKey, session);
  23 + }
  24 +
  25 + /**
  26 + * websocket 关闭连接时执行的动作
  27 + * @param session websocket session 对象
  28 + * @param closeStatus 关闭状态对象
  29 + * @throws Exception 异常对象
  30 + */
  31 + @Override
  32 + public void afterConnectionClosed(final WebSocketSession session, CloseStatus closeStatus) throws Exception {
  33 + Object sessionKey = sessionKeyGen(session);
  34 + WebSocketSessionHandler.removeSession(sessionKey);
  35 + }
  36 +
  37 + public Object sessionKeyGen(WebSocketSession webSocketSession) {
  38 +
  39 + Object obj = webSocketSession.getAttributes().get(WebSocketKeyDefine.LOGIN_USER);
  40 +
  41 + if (obj instanceof LoginUser) {
  42 + LoginUser loginUser = (LoginUser) obj;
  43 + // userId 作为唯一区分
  44 + return String.valueOf(loginUser.getId());
  45 + }
  46 +
  47 + return null;
  48 + }
  49 +}
yudao-framework/yudao-spring-boot-starter-websocket/src/main/java/cn/iocoder/yudao/framework/websocket/package-info.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.websocket;
yudao-framework/yudao-spring-boot-starter-websocket/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 0 → 100644
  1 +cn.iocoder.yudao.framework.websocket.config.YudaoWebSocketAutoConfiguration
0 \ No newline at end of file 2 \ No newline at end of file
yudao-server/src/main/resources/application.yaml
@@ -92,6 +92,11 @@ yudao: @@ -92,6 +92,11 @@ yudao:
92 security: 92 security:
93 permit-all_urls: 93 permit-all_urls:
94 - /admin-ui/** # /resources/admin-ui 目录下的静态资源 94 - /admin-ui/** # /resources/admin-ui 目录下的静态资源
  95 + websocket:
  96 + enable: true # websocket的开关
  97 + path: /websocket/message # 路径
  98 + maxOnlineCount: 0 # 最大连接人数
  99 + sessionMap: true # 保存sessionMap
95 swagger: 100 swagger:
96 title: 管理后台 101 title: 管理后台
97 description: 提供管理员管理的所有功能 102 description: 提供管理员管理的所有功能