feat: 添加 spring-boot-websocket starter.
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
<?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>com.orion.ops</groupId>
|
||||
<artifactId>orion-ops-framework</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>orion-ops-spring-boot-starter-websocket</artifactId>
|
||||
<name>${project.artifactId}</name>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<description>项目 websocket 配置包</description>
|
||||
<url>https://github.com/lijiahangmax/orion-ops-pro</url>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.orion.ops</groupId>
|
||||
<artifactId>orion-ops-common</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-websocket</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.orion.ops.framework.websocket.config;
|
||||
|
||||
import com.orion.ops.framework.websocket.core.WebsocketContainerConfig;
|
||||
import com.orion.ops.framework.websocket.interceptor.UserHandshakeInterceptor;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.web.socket.config.annotation.EnableWebSocket;
|
||||
import org.springframework.web.socket.server.HandshakeInterceptor;
|
||||
import org.springframework.web.socket.server.standard.ServletServerContainerFactoryBean;
|
||||
|
||||
/**
|
||||
* websocket 配置类
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2023/6/25 19:45
|
||||
*/
|
||||
@EnableWebSocket
|
||||
@AutoConfiguration
|
||||
@EnableConfigurationProperties(WebsocketContainerConfig.class)
|
||||
public class OrionWebsocketAutoConfiguration {
|
||||
|
||||
/**
|
||||
* @return websocket 缓冲区大小配置
|
||||
*/
|
||||
@Bean
|
||||
public ServletServerContainerFactoryBean servletServerContainerFactoryBean(WebsocketContainerConfig config) {
|
||||
ServletServerContainerFactoryBean factory = new ServletServerContainerFactoryBean();
|
||||
factory.setMaxBinaryMessageBufferSize(config.getBinaryBufferSize());
|
||||
factory.setMaxTextMessageBufferSize(config.getBinaryBufferSize());
|
||||
factory.setMaxSessionIdleTimeout(config.getSessionIdleTimeout());
|
||||
return factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 用户认证拦截器 按需注入
|
||||
*/
|
||||
@Bean
|
||||
public HandshakeInterceptor userHandshakeInterceptor() {
|
||||
return new UserHandshakeInterceptor();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.orion.ops.framework.websocket.constant;
|
||||
|
||||
/**
|
||||
* websocket 属性
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2023/6/25 20:25
|
||||
*/
|
||||
public interface WsAttr {
|
||||
|
||||
String USER = "user";
|
||||
|
||||
String UID = "uid";
|
||||
|
||||
String TOKEN = "token";
|
||||
|
||||
String READONLY = "readonly";
|
||||
|
||||
String CONNECTED = "connected";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
package com.orion.ops.framework.websocket.constant;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* ws服务端关闭code
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2021/6/16 15:18
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public enum WsCloseCode {
|
||||
|
||||
/**
|
||||
* 未查询到token
|
||||
*/
|
||||
INCORRECT_TOKEN(4100, WsCloseReason.CLOSED_CONNECTION),
|
||||
|
||||
/**
|
||||
* 伪造token
|
||||
*/
|
||||
FORGE_TOKEN(4120, WsCloseReason.CLOSED_CONNECTION),
|
||||
|
||||
/**
|
||||
* token已被绑定
|
||||
*/
|
||||
TOKEN_BIND(4125, WsCloseReason.CLOSED_CONNECTION),
|
||||
|
||||
/**
|
||||
* 未知的连接
|
||||
*/
|
||||
UNKNOWN_CONNECT(4130, WsCloseReason.CLOSED_CONNECTION),
|
||||
|
||||
/**
|
||||
* 认证失败 id不匹配
|
||||
*/
|
||||
IDENTITY_MISMATCH(4140, WsCloseReason.IDENTITY_MISMATCH),
|
||||
|
||||
/**
|
||||
* 认证信息不匹配
|
||||
*/
|
||||
VALID(4150, WsCloseReason.AUTHENTICATION_FAILURE),
|
||||
|
||||
/**
|
||||
* 机器不合法
|
||||
*/
|
||||
INVALID_MACHINE(4200, WsCloseReason.CLOSED_CONNECTION),
|
||||
|
||||
/**
|
||||
* 连接远程服务器连接超时
|
||||
*/
|
||||
CONNECTION_TIMEOUT(4201, WsCloseReason.CONNECTION_TIMEOUT),
|
||||
|
||||
/**
|
||||
* 连接远程服务器失败
|
||||
*/
|
||||
CONNECTION_FAILURE(4202, WsCloseReason.REMOTE_SERVER_UNREACHABLE),
|
||||
|
||||
/**
|
||||
* 远程服务器认证失败
|
||||
*/
|
||||
CONNECTION_AUTH_FAILURE(4205, WsCloseReason.REMOTE_SERVER_AUTHENTICATION_FAILURE),
|
||||
|
||||
/**
|
||||
* 远程服务器认证出现异常
|
||||
*/
|
||||
CONNECTION_EXCEPTION(4210, WsCloseReason.UNABLE_TO_CONNECT_REMOTE_SERVER),
|
||||
|
||||
/**
|
||||
* 机器未启用
|
||||
*/
|
||||
MACHINE_DISABLED(4215, WsCloseReason.MACHINE_DISABLED),
|
||||
|
||||
/**
|
||||
* 打开shell出现异常
|
||||
*/
|
||||
OPEN_SHELL_EXCEPTION(4220, WsCloseReason.UNABLE_TO_CONNECT_REMOTE_SERVER),
|
||||
|
||||
/**
|
||||
* 打开command出现异常
|
||||
*/
|
||||
OPEN_COMMAND_EXCEPTION(4225, WsCloseReason.UNABLE_TO_CONNECT_REMOTE_SERVER),
|
||||
|
||||
/**
|
||||
* 打开sftp出现异常
|
||||
*/
|
||||
OPEN_SFTP_EXCEPTION(4230, WsCloseReason.UNABLE_TO_CONNECT_REMOTE_SERVER),
|
||||
|
||||
/**
|
||||
* 服务出现异常
|
||||
*/
|
||||
RUNTIME_EXCEPTION(4300, WsCloseReason.CLOSED_CONNECTION),
|
||||
|
||||
/**
|
||||
* 心跳结束
|
||||
*/
|
||||
HEART_DOWN(4310, WsCloseReason.CLOSED_CONNECTION),
|
||||
|
||||
/**
|
||||
* 用户关闭
|
||||
*/
|
||||
DISCONNECT(4320, WsCloseReason.CLOSED_CONNECTION),
|
||||
|
||||
/**
|
||||
* 结束
|
||||
*/
|
||||
EOF(4330, WsCloseReason.CLOSED_CONNECTION),
|
||||
|
||||
/**
|
||||
* 读取失败
|
||||
*/
|
||||
READ_EXCEPTION(4335, WsCloseReason.CLOSED_CONNECTION),
|
||||
|
||||
/**
|
||||
* 强制下线
|
||||
*/
|
||||
FORCED_OFFLINE(4500, WsCloseReason.FORCED_OFFLINE),
|
||||
|
||||
;
|
||||
|
||||
private final int code;
|
||||
|
||||
private final String reason;
|
||||
|
||||
public static WsCloseCode of(int code) {
|
||||
for (WsCloseCode value : values()) {
|
||||
if (value.code == code) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.orion.ops.framework.websocket.constant;
|
||||
|
||||
/**
|
||||
* ws服务端关闭reason
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2021/6/16 15:21
|
||||
*/
|
||||
public interface WsCloseReason {
|
||||
|
||||
String CLOSED_CONNECTION = "closed connection...";
|
||||
|
||||
String IDENTITY_MISMATCH = "identity mismatch...";
|
||||
|
||||
String AUTHENTICATION_FAILURE = "authentication failure...";
|
||||
|
||||
String REMOTE_SERVER_UNREACHABLE = "remote server unreachable...";
|
||||
|
||||
String CONNECTION_TIMEOUT = "connection timeout...";
|
||||
|
||||
String REMOTE_SERVER_AUTHENTICATION_FAILURE = "remote server authentication failure...";
|
||||
|
||||
String MACHINE_DISABLED = "machine disabled...";
|
||||
|
||||
String UNABLE_TO_CONNECT_REMOTE_SERVER = "unable to connect remote server...";
|
||||
|
||||
String FORCED_OFFLINE = "forced offline...";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package com.orion.ops.framework.websocket.constant;
|
||||
|
||||
import com.orion.lang.utils.Exceptions;
|
||||
import com.orion.lang.utils.Strings;
|
||||
import com.orion.lang.utils.Valid;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* ws服务端响应常量
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2021/4/16 21:48
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
public enum WsProtocol {
|
||||
|
||||
/**
|
||||
* 正常返回
|
||||
*/
|
||||
OK("0"),
|
||||
|
||||
/**
|
||||
* 连接成功
|
||||
*/
|
||||
CONNECTED("1"),
|
||||
|
||||
/**
|
||||
* ping
|
||||
*/
|
||||
PING("2"),
|
||||
|
||||
/**
|
||||
* pong
|
||||
*/
|
||||
PONG("3"),
|
||||
|
||||
/**
|
||||
* 未知操作
|
||||
*/
|
||||
ERROR("4"),
|
||||
|
||||
;
|
||||
|
||||
private final String code;
|
||||
|
||||
/**
|
||||
* 分隔符
|
||||
*/
|
||||
public static final String SYMBOL = "|";
|
||||
|
||||
public byte[] get() {
|
||||
return Strings.bytes(code);
|
||||
}
|
||||
|
||||
public byte[] msg(String body) {
|
||||
Valid.notNull(body);
|
||||
return this.msg(Strings.bytes(body));
|
||||
}
|
||||
|
||||
public byte[] msg(byte[] body) {
|
||||
return this.msg(body, 0, body.length);
|
||||
}
|
||||
|
||||
public byte[] msg(byte[] body, int offset, int len) {
|
||||
Valid.notNull(body);
|
||||
try (ByteArrayOutputStream o = new ByteArrayOutputStream()) {
|
||||
o.write(Strings.bytes(code + SYMBOL));
|
||||
o.write(body, offset, len);
|
||||
return o.toByteArray();
|
||||
} catch (IOException e) {
|
||||
throw Exceptions.ioRuntime(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.orion.ops.framework.websocket.core;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* websocket 配置属性
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2023/6/25 19:57
|
||||
*/
|
||||
@Data
|
||||
@ConfigurationProperties("spring.websocket")
|
||||
public class WebsocketContainerConfig {
|
||||
|
||||
/**
|
||||
* 二进制消息缓冲区大小 byte
|
||||
*/
|
||||
private Integer binaryBufferSize;
|
||||
|
||||
/**
|
||||
* 文本消息缓冲区大小 byte
|
||||
*/
|
||||
private Integer textBufferSize;
|
||||
|
||||
/**
|
||||
* session 最大超时时间 ms
|
||||
*/
|
||||
private Long sessionIdleTimeout;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.orion.ops.framework.websocket.interceptor;
|
||||
|
||||
import com.orion.ops.framework.websocket.constant.WsAttr;
|
||||
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;
|
||||
|
||||
/**
|
||||
* 用户拦截器
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2023/6/25 20:16
|
||||
*/
|
||||
public class UserHandshakeInterceptor implements HandshakeInterceptor {
|
||||
|
||||
@Override
|
||||
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) {
|
||||
// TODO 获取当前用户
|
||||
attributes.put(WsAttr.USER, 1);
|
||||
// if (user == null){
|
||||
// return false;
|
||||
// }
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package com.orion.ops.framework.websocket.utils;
|
||||
|
||||
import com.orion.lang.exception.AuthenticationException;
|
||||
import com.orion.lang.exception.ConnectionRuntimeException;
|
||||
import com.orion.lang.exception.DisabledException;
|
||||
import com.orion.lang.exception.TimeoutException;
|
||||
import com.orion.lang.utils.Exceptions;
|
||||
import com.orion.lang.utils.Urls;
|
||||
import com.orion.ops.framework.websocket.constant.WsCloseCode;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.server.ServerHttpRequest;
|
||||
import org.springframework.web.socket.CloseStatus;
|
||||
import org.springframework.web.socket.TextMessage;
|
||||
import org.springframework.web.socket.WebSocketSession;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* websocket 工具类
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2021/6/14 0:36
|
||||
*/
|
||||
@Slf4j
|
||||
public class WebSockets {
|
||||
|
||||
private WebSockets() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送消息 忽略并发报错
|
||||
*
|
||||
* @param session session
|
||||
* @param message message
|
||||
*/
|
||||
public static void sendText(WebSocketSession session, byte[] message) {
|
||||
if (!session.isOpen()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
// 响应
|
||||
session.sendMessage(new TextMessage(message));
|
||||
} catch (IllegalStateException e) {
|
||||
// 并发异常
|
||||
log.error("发送消息失败 {}", Exceptions.getDigest(e));
|
||||
} catch (IOException e) {
|
||||
throw Exceptions.ioRuntime(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭会话
|
||||
*
|
||||
* @param session session
|
||||
* @param code code
|
||||
*/
|
||||
public static void close(WebSocketSession session, WsCloseCode code) {
|
||||
if (!session.isOpen()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
session.close(new CloseStatus(code.getCode(), code.getReason()));
|
||||
} catch (Exception e) {
|
||||
log.error("websocket close failure", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 urlToken
|
||||
*
|
||||
* @param request request
|
||||
* @return token
|
||||
*/
|
||||
public static String getToken(ServerHttpRequest request) {
|
||||
return Urls.getUrlSource(Objects.requireNonNull(request.getURI().toString()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 urlToken
|
||||
*
|
||||
* @param session session
|
||||
* @return token
|
||||
*/
|
||||
public static String getToken(WebSocketSession session) {
|
||||
return Urls.getUrlSource(Objects.requireNonNull(session.getUri()).toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开 session 异常关闭
|
||||
*
|
||||
* @param session session
|
||||
* @param e e
|
||||
*/
|
||||
public static void openSessionStoreThrowClose(WebSocketSession session, Exception e) {
|
||||
if (Exceptions.isCausedBy(e, TimeoutException.class)) {
|
||||
close(session, WsCloseCode.CONNECTION_TIMEOUT);
|
||||
} else if (Exceptions.isCausedBy(e, ConnectionRuntimeException.class)) {
|
||||
close(session, WsCloseCode.CONNECTION_FAILURE);
|
||||
} else if (Exceptions.isCausedBy(e, AuthenticationException.class)) {
|
||||
close(session, WsCloseCode.CONNECTION_AUTH_FAILURE);
|
||||
} else if (Exceptions.isCausedBy(e, DisabledException.class)) {
|
||||
close(session, WsCloseCode.MACHINE_DISABLED);
|
||||
} else {
|
||||
close(session, WsCloseCode.CONNECTION_EXCEPTION);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
com.orion.ops.framework.websocket.config.OrionWebsocketAutoConfiguration
|
||||
@@ -22,6 +22,8 @@
|
||||
<module>orion-ops-spring-boot-starter-swagger</module>
|
||||
<module>orion-ops-spring-boot-starter-datasource</module>
|
||||
<module>orion-ops-spring-boot-starter-mybatis</module>
|
||||
<module>orion-ops-spring-boot-starter-job</module>
|
||||
<module>orion-ops-spring-boot-starter-websocket</module>
|
||||
</modules>
|
||||
|
||||
</project>
|
||||
Reference in New Issue
Block a user