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