!7 netty-websocket

Merge pull request !7 from 魏开煜/master
This commit is contained in:
Dftre 2024-10-25 06:57:25 +00:00 committed by Gitee
commit ab726c802f
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
20 changed files with 584 additions and 39 deletions

View File

@ -13,6 +13,7 @@
<properties> <properties>
<ehcache.version>3.10.8</ehcache.version> <ehcache.version>3.10.8</ehcache.version>
<mybatis-plus.version>3.5.8</mybatis-plus.version> <mybatis-plus.version>3.5.8</mybatis-plus.version>
<netty.version>4.1.112.Final</netty.version>
</properties> </properties>
<description> <description>
@ -67,12 +68,25 @@
<artifactId>ruoyi-plugins-starter</artifactId> <artifactId>ruoyi-plugins-starter</artifactId>
<version>${ruoyi.version}</version> <version>${ruoyi.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<artifactId>ruoyi-mybatis-interceptor</artifactId> <artifactId>ruoyi-mybatis-interceptor</artifactId>
<version>${ruoyi.version}</version> <version>${ruoyi.version}</version>
</dependency> </dependency>
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-netty</artifactId>
<version>${ruoyi.version}</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>${netty.version}</version>
</dependency>
</dependencies> </dependencies>
</dependencyManagement> </dependencyManagement>
@ -84,6 +98,7 @@
<module>ruoyi-websocket</module> <module>ruoyi-websocket</module>
<module>ruoyi-plugins-starter</module> <module>ruoyi-plugins-starter</module>
<module>ruoyi-mybatis-interceptor</module> <module>ruoyi-mybatis-interceptor</module>
<module>ruoyi-netty</module>
</modules> </modules>
<packaging>pom</packaging> <packaging>pom</packaging>
</project> </project>

View File

@ -1,21 +1,19 @@
package com.ruoyi.mybatisinterceptor.aspectj; package com.ruoyi.mybatisinterceptor.aspectj;
import com.ruoyi.mybatisinterceptor.annotation.DataSecurity; import com.ruoyi.mybatisinterceptor.annotation.DataSecurity;
import com.ruoyi.mybatisinterceptor.context.sqlContext.SqlContextHolder;
import com.ruoyi.mybatisinterceptor.model.JoinTableModel; import com.ruoyi.mybatisinterceptor.model.JoinTableModel;
import com.ruoyi.mybatisinterceptor.model.WhereModel; import com.ruoyi.mybatisinterceptor.model.WhereModel;
import com.ruoyi.mybatisinterceptor.context.dataSecurity.SqlContextHolder;
import org.aspectj.lang.JoinPoint; import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import com.ruoyi.common.utils.SecurityUtils; import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
@Aspect @Aspect
@Component @Component
public class DataSecurityAspect { public class DataSecurityAspect {
@ -50,7 +48,6 @@ public class DataSecurityAspect {
if (!StringUtils.isEmpty(dataSecurity.joinTableAlise())) { if (!StringUtils.isEmpty(dataSecurity.joinTableAlise())) {
createByTableModel.setJoinTableAlise(dataSecurity.joinTableAlise()); createByTableModel.setJoinTableAlise(dataSecurity.joinTableAlise());
} }
createByTableModel.setFromTableColumn("create_by"); createByTableModel.setFromTableColumn("create_by");
createByTableModel.setJoinTableColumn("user_name"); createByTableModel.setJoinTableColumn("user_name");
SqlContextHolder.addJoinTable(createByTableModel); SqlContextHolder.addJoinTable(createByTableModel);
@ -63,12 +60,10 @@ public class DataSecurityAspect {
if (!StringUtils.isEmpty(dataSecurity.joinTableAlise())) { if (!StringUtils.isEmpty(dataSecurity.joinTableAlise())) {
userIdTableModel.setJoinTableAlise(dataSecurity.joinTableAlise()); userIdTableModel.setJoinTableAlise(dataSecurity.joinTableAlise());
} }
userIdTableModel.setFromTableColumn("user_id"); userIdTableModel.setFromTableColumn("user_id");
userIdTableModel.setJoinTableColumn("user_id"); userIdTableModel.setJoinTableColumn("user_id");
SqlContextHolder.addJoinTable(userIdTableModel); SqlContextHolder.addJoinTable(userIdTableModel);
break; break;
default: default:
break; break;
} }

View File

@ -1,4 +1,4 @@
package com.ruoyi.mybatisinterceptor.context.dataSecurity; package com.ruoyi.mybatisinterceptor.context.sqlContext;
import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject; import com.alibaba.fastjson2.JSONObject;
@ -10,11 +10,11 @@ public class SqlContextHolder {
private static final ThreadLocal<JSONObject> SQL_CONTEXT_HOLDER = new ThreadLocal<>(); private static final ThreadLocal<JSONObject> SQL_CONTEXT_HOLDER = new ThreadLocal<>();
public static void startDataSecurity() { public static void startDataSecurity() {
JSONObject jsonObject = new JSONObject(); SQL_CONTEXT_HOLDER.get().put("isSecurity", Boolean.TRUE);
jsonObject.put("isSecurity", Boolean.TRUE); }
jsonObject.put(SqlType.WHERE.getSqlType(), new JSONArray());
jsonObject.put(SqlType.JOIN.getSqlType(), new JSONArray()); public static void startLogicSelect() {
SQL_CONTEXT_HOLDER.set(jsonObject); SQL_CONTEXT_HOLDER.get().put("isLogic", Boolean.TRUE);
} }
public static void addWhereParam(WhereModel whereModel) { public static void addWhereParam(WhereModel whereModel) {
@ -26,7 +26,6 @@ public class SqlContextHolder {
} }
public static boolean isSecurity() { public static boolean isSecurity() {
return SQL_CONTEXT_HOLDER.get() != null return SQL_CONTEXT_HOLDER.get() != null
&& SQL_CONTEXT_HOLDER.get().getBooleanValue("isSecurity"); && SQL_CONTEXT_HOLDER.get().getBooleanValue("isSecurity");
} }
@ -42,4 +41,15 @@ public class SqlContextHolder {
public static JSONArray getJoinTables() { public static JSONArray getJoinTables() {
return SQL_CONTEXT_HOLDER.get().getJSONArray(SqlType.JOIN.getSqlType()); return SQL_CONTEXT_HOLDER.get().getJSONArray(SqlType.JOIN.getSqlType());
} }
public static void startInterceptor() {
JSONObject jsonObject = SQL_CONTEXT_HOLDER.get();
if (jsonObject != null) {
return;
}
JSONObject object = new JSONObject();
object.put(SqlType.JOIN.getSqlType(), new JSONArray());
object.put(SqlType.WHERE.getSqlType(), new JSONArray());
SQL_CONTEXT_HOLDER.set(object);
}
} }

View File

@ -1,4 +1,4 @@
package com.ruoyi.mybatisinterceptor.sql; package com.ruoyi.mybatisinterceptor.handler;
public interface MybatisAfterHandler { public interface MybatisAfterHandler {

View File

@ -1,4 +1,4 @@
package com.ruoyi.mybatisinterceptor.sql; package com.ruoyi.mybatisinterceptor.handler;
import org.apache.ibatis.cache.CacheKey; import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor; import org.apache.ibatis.executor.Executor;
@ -6,6 +6,7 @@ import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.session.ResultHandler; import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds; import org.apache.ibatis.session.RowBounds;
public interface MybatisPreHandler { public interface MybatisPreHandler {
void preHandle(Executor executor, MappedStatement mappedStatement, Object params, void preHandle(Executor executor, MappedStatement mappedStatement, Object params,

View File

@ -1,6 +1,7 @@
package com.ruoyi.mybatisinterceptor.sql.dataSecurity; package com.ruoyi.mybatisinterceptor.handler.dataSecurity;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.apache.ibatis.cache.CacheKey; import org.apache.ibatis.cache.CacheKey;
@ -12,13 +13,14 @@ import org.apache.ibatis.session.RowBounds;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils;
import com.alibaba.fastjson2.JSONArray;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.sql.SqlUtil; import com.ruoyi.common.utils.sql.SqlUtil;
import com.ruoyi.mybatisinterceptor.annotation.MybatisHandlerOrder; import com.ruoyi.mybatisinterceptor.annotation.MybatisHandlerOrder;
import com.ruoyi.mybatisinterceptor.context.dataSecurity.SqlContextHolder; import com.ruoyi.mybatisinterceptor.context.sqlContext.SqlContextHolder;
import com.ruoyi.mybatisinterceptor.handler.MybatisPreHandler;
import com.ruoyi.mybatisinterceptor.model.JoinTableModel; import com.ruoyi.mybatisinterceptor.model.JoinTableModel;
import com.ruoyi.mybatisinterceptor.model.WhereModel; import com.ruoyi.mybatisinterceptor.model.WhereModel;
import com.ruoyi.mybatisinterceptor.sql.MybatisPreHandler;
import net.sf.jsqlparser.JSQLParserException; import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.Alias; import net.sf.jsqlparser.expression.Alias;
@ -28,9 +30,11 @@ import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.schema.Column; import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table; import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.Statement; import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.delete.Delete;
import net.sf.jsqlparser.statement.select.Join; import net.sf.jsqlparser.statement.select.Join;
import net.sf.jsqlparser.statement.select.PlainSelect; import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.Select; import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.statement.update.Update;
@MybatisHandlerOrder(1) @MybatisHandlerOrder(1)
@Component @Component
@ -62,27 +66,56 @@ public class DataSecurityPreHandler implements MybatisPreHandler {
} }
} }
private static void handleWhere(Select select) throws JSQLParserException { private static void handleWhere(Statement statement) throws JSQLParserException {
PlainSelect plain = select.getPlainSelect(); if (statement instanceof Select) {
Expression expWhere = plain.getWhere(); Select select = (Select) statement;
PlainSelect plainSelect = select.getPlainSelect();
plainSelect.setWhere(getConfigedWhereExpression(plainSelect.getWhere()));
} else if (statement instanceof Update) {
Update update = (Update) statement;
update.setWhere(getConfigedWhereExpression(update.getWhere()));
} else if (statement instanceof Delete) {
Delete delete = (Delete) statement;
delete.setWhere(getConfigedWhereExpression(delete.getWhere()));
}
}
private static Expression getConfigedWhereExpression(Expression expWhere) throws JSQLParserException {
StringBuilder whereParam = new StringBuilder(" "); StringBuilder whereParam = new StringBuilder(" ");
String where = expWhere != null ? expWhere.toString() : null; String where = expWhere != null ? expWhere.toString() : null;
if (SqlContextHolder.getWhere() == null || SqlContextHolder.getWhere().size() <= 0) { if (SqlContextHolder.getWhere() == null || SqlContextHolder.getWhere().size() <= 0) {
return; return expWhere;
} }
SqlContextHolder.getWhere().forEach(item -> { JSONArray wehreArray = SqlContextHolder.getWhere();
wehreArray.forEach(item -> {
whereParam.append(((WhereModel) item).getSqlString()); whereParam.append(((WhereModel) item).getSqlString());
}); });
where = StringUtils.isEmpty(where) ? whereParam.toString().substring(5, whereParam.length()) WhereModel whereModel = (WhereModel) wehreArray.get(0);
where = StringUtils.isEmpty(where)
? whereParam.toString().substring(whereModel.getConnectType().length() + 2, whereParam.length())
: where + " " + whereParam.toString(); : where + " " + whereParam.toString();
plain.setWhere(CCJSqlParserUtil.parseCondExpression(where)); return CCJSqlParserUtil.parseCondExpression(where);
} }
private static void handleJoin(Select select) { private static void handleJoin(Statement statement) {
PlainSelect selectBody = select.getPlainSelect();
if (SqlContextHolder.getJoinTables() == null || SqlContextHolder.getJoinTables().size() <= 0) { if (SqlContextHolder.getJoinTables() == null || SqlContextHolder.getJoinTables().size() <= 0) {
return; return;
} }
if (statement instanceof Select) {
Select select = (Select) statement;
select.getPlainSelect().addJoins(getConfigedJoinExpression());
} else if (statement instanceof Update) {
Update update = (Update) statement;
update.addJoins(getConfigedJoinExpression());
} else if (statement instanceof Delete) {
Delete delete = (Delete) statement;
delete.addJoins(getConfigedJoinExpression());
}
}
private static List<Join> getConfigedJoinExpression() {
List<Join> joins = new ArrayList<>();
SqlContextHolder.getJoinTables().forEach(item -> { SqlContextHolder.getJoinTables().forEach(item -> {
JoinTableModel tableModel = (JoinTableModel) item; JoinTableModel tableModel = (JoinTableModel) item;
Table table = new Table(tableModel.getJoinTable()); Table table = new Table(tableModel.getJoinTable());
@ -93,8 +126,8 @@ public class DataSecurityPreHandler implements MybatisPreHandler {
Expression onExpression = new EqualsTo(new Column(tableModel.getFromTableColumnString()), Expression onExpression = new EqualsTo(new Column(tableModel.getFromTableColumnString()),
new Column(tableModel.getJoinTableColumnString())); new Column(tableModel.getJoinTableColumnString()));
join.setOnExpressions(List.of(onExpression)); join.setOnExpressions(List.of(onExpression));
selectBody.addJoins(join); joins.add(join);
}); });
return joins;
} }
} }

View File

@ -1,4 +1,4 @@
package com.ruoyi.mybatisinterceptor.sql.page; package com.ruoyi.mybatisinterceptor.handler.page;
import java.util.List; import java.util.List;
@ -7,7 +7,7 @@ import org.springframework.stereotype.Component;
import com.ruoyi.mybatisinterceptor.annotation.MybatisHandlerOrder; import com.ruoyi.mybatisinterceptor.annotation.MybatisHandlerOrder;
import com.ruoyi.mybatisinterceptor.context.page.PageContextHolder; import com.ruoyi.mybatisinterceptor.context.page.PageContextHolder;
import com.ruoyi.mybatisinterceptor.context.page.model.TableInfo; import com.ruoyi.mybatisinterceptor.context.page.model.TableInfo;
import com.ruoyi.mybatisinterceptor.sql.MybatisAfterHandler; import com.ruoyi.mybatisinterceptor.handler.MybatisAfterHandler;
@MybatisHandlerOrder(1) @MybatisHandlerOrder(1)
@Component @Component

View File

@ -1,4 +1,4 @@
package com.ruoyi.mybatisinterceptor.sql.page; package com.ruoyi.mybatisinterceptor.handler.page;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.sql.SQLException; import java.sql.SQLException;
@ -21,7 +21,7 @@ import com.ruoyi.common.utils.sql.SqlUtil;
import com.ruoyi.mybatisinterceptor.annotation.MybatisHandlerOrder; import com.ruoyi.mybatisinterceptor.annotation.MybatisHandlerOrder;
import com.ruoyi.mybatisinterceptor.context.page.PageContextHolder; import com.ruoyi.mybatisinterceptor.context.page.PageContextHolder;
import com.ruoyi.mybatisinterceptor.context.page.model.PageInfo; import com.ruoyi.mybatisinterceptor.context.page.model.PageInfo;
import com.ruoyi.mybatisinterceptor.sql.MybatisPreHandler; import com.ruoyi.mybatisinterceptor.handler.MybatisPreHandler;
import net.sf.jsqlparser.schema.Column; import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.statement.Statement; import net.sf.jsqlparser.statement.Statement;

View File

@ -17,8 +17,8 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import com.ruoyi.mybatisinterceptor.annotation.MybatisHandlerOrder; import com.ruoyi.mybatisinterceptor.annotation.MybatisHandlerOrder;
import com.ruoyi.mybatisinterceptor.sql.MybatisAfterHandler; import com.ruoyi.mybatisinterceptor.handler.MybatisAfterHandler;
import com.ruoyi.mybatisinterceptor.sql.MybatisPreHandler; import com.ruoyi.mybatisinterceptor.handler.MybatisPreHandler;
import jakarta.annotation.PostConstruct; import jakarta.annotation.PostConstruct;

View File

@ -1,7 +1,6 @@
package com.ruoyi.mybatisinterceptor.util; package com.ruoyi.mybatisinterceptor.util;
import com.ruoyi.mybatisinterceptor.context.sqlContext.SqlContextHolder;
import com.ruoyi.mybatisinterceptor.context.dataSecurity.SqlContextHolder;
public class DataSecurityUtil { public class DataSecurityUtil {

View File

@ -0,0 +1,30 @@
<?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>
<artifactId>ruoyi-plugins</artifactId>
<groupId>com.ruoyi</groupId>
<version>3.8.8.3.1</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ruoyi-netty</artifactId>
<properties>
<maven.compiler.source>19</maven.compiler.source>
<maven.compiler.target>19</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
</dependency>
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,21 @@
package com.ruoyi.netty.websocket;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
import com.ruoyi.netty.websocket.nettyServer.NettyWebSocketServer;
@Component
public class NettyServerRunner implements ApplicationRunner {
@Autowired
private NettyWebSocketServer server;
@Override
public void run(ApplicationArguments args) throws Exception {
server.start();
}
}

View File

@ -0,0 +1,12 @@
package com.ruoyi.netty.websocket.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface NettyWebSocketEndpoint {
String path();
}

View File

@ -0,0 +1,44 @@
package com.ruoyi.netty.websocket.endpoints;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.stereotype.Component;
import com.ruoyi.netty.websocket.annotations.NettyWebSocketEndpoint;
import com.ruoyi.netty.websocket.nettyServer.NettyWebSocketEndpointHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.FullHttpMessage;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
@Component
@NettyWebSocketEndpoint(path = "/test/{seqNumber}")
public class TestNettyWebSocket extends NettyWebSocketEndpointHandler {
private static final Map<String, ChannelHandlerContext> map = new ConcurrentHashMap<>();
@Override
public void onMessage(ChannelHandlerContext channelHandlerContext, TextWebSocketFrame textWebSocketFrame) {
System.out.println(textWebSocketFrame.text());
}
@Override
public void onOpen(ChannelHandlerContext channelHandlerContext, FullHttpMessage fullHttpMessage) {
map.put(getPathParam("seqNumber"), channelHandlerContext);
}
@Override
public void onClose(ChannelHandlerContext channelHandlerContext) {
map.remove(getPathParam("seqNumber"));
}
@Override
public void onError(ChannelHandlerContext channelHandlerContext, Throwable throwable) {
}
public static void send(String seqNumber, String msg) {
sendMsg(map.get(seqNumber), msg);
}
}

View File

@ -0,0 +1,66 @@
package com.ruoyi.netty.websocket.nettyServer;
import java.util.Map;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.handler.codec.http.FullHttpMessage;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.concurrent.GlobalEventExecutor;
public abstract class NettyWebSocketEndpointHandler {
private Map<String, String> pathParam;
private Map<String, String> urlParam;
public static void sendMsg(ChannelHandlerContext context, String msg) {
TextWebSocketFrame textWebSocketFrame = new TextWebSocketFrame(msg);
context.channel().writeAndFlush(textWebSocketFrame);
}
public abstract void onMessage(ChannelHandlerContext channelHandlerContext, TextWebSocketFrame textWebSocketFrame);
public abstract void onOpen(ChannelHandlerContext channelHandlerContext, FullHttpMessage fullHttpMessage);
public abstract void onClose(ChannelHandlerContext channelHandlerContext);
public abstract void onError(ChannelHandlerContext channelHandlerContext, Throwable throwable);
public Map<String, String> getPathParam() {
return pathParam;
}
public void setPathParam(Map<String, String> pathParam) {
this.pathParam = pathParam;
}
public Map<String, String> getUrlParam() {
return urlParam;
}
public void setUrlParam(Map<String, String> urlParam) {
this.urlParam = urlParam;
}
public Long getLongPathParam(String key) {
return Long.valueOf(pathParam.get(key));
}
public String getPathParam(String key) {
return pathParam.get(key);
}
public Double getDoublePathParam(String key) {
return Double.parseDouble(pathParam.get(key));
}
public void closeChannel(ChannelHandlerContext channelHandlerContext) {
channelHandlerContext.close().addListener(ChannelFutureListener.CLOSE);
}
}

View File

@ -0,0 +1,64 @@
package com.ruoyi.netty.websocket.nettyServer;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import com.ruoyi.netty.websocket.nettyServer.handler.WebSocketHandler;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
@Component
public class NettyWebSocketServer {
private static ServerBootstrap serverBootstrap;
@Value("${netty.websocket.maxMessageSize}")
private Long messageSize;
@Value("${netty.websocket.bossThreads}")
private Long bossThreads;
@Value("${netty.websocket.workerThreads}")
private Long workerThreads;
@Value("${netty.websocket.port}")
private Long port;
@Value("${netty.websocket.enable}")
private Boolean enable;
public ServerBootstrap start() throws InterruptedException {
if (!enable) {
return null;
}
ServerBootstrap serverBootstrap = new ServerBootstrap();
NioEventLoopGroup boss = new NioEventLoopGroup(4);
NioEventLoopGroup worker = new NioEventLoopGroup(workerThreads.intValue());
serverBootstrap.group(boss, worker);
serverBootstrap.channel(NioServerSocketChannel.class);
serverBootstrap.childOption(ChannelOption.SO_KEEPALIVE, true);
serverBootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel channel) throws Exception {
ChannelPipeline pipeline = channel.pipeline();
pipeline.addLast(new HttpServerCodec());
pipeline.addLast(new HttpObjectAggregator(messageSize.intValue()));
pipeline.addLast(new WebSocketHandler());
pipeline.addLast(new WebSocketServerProtocolHandler("/", true));
}
});
serverBootstrap.bind(port.intValue()).sync();
System.out.println(
"----------------------------------------------------------------------------------- \n Arknights!");
NettyWebSocketServer.serverBootstrap = serverBootstrap;
return serverBootstrap;
}
}

View File

@ -0,0 +1,167 @@
package com.ruoyi.netty.websocket.nettyServer.handler;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.netty.websocket.annotations.NettyWebSocketEndpoint;
import com.ruoyi.netty.websocket.nettyServer.NettyWebSocketEndpointHandler;
import com.ruoyi.netty.websocket.utils.CommonUtil;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelId;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.concurrent.GlobalEventExecutor;
import jakarta.annotation.PostConstruct;
import java.lang.reflect.Constructor;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;
@Component
public class WebSocketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
@Autowired
private List<NettyWebSocketEndpointHandler> handlers;
private static final Map<String, PathMatchModel> uriHandlerMapper = new ConcurrentHashMap<>();
public static final ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
public static final Map<ChannelId, NettyWebSocketEndpointHandler> channelHandlerMap = new ConcurrentHashMap<>();
@PostConstruct
private void init() throws URISyntaxException, NoSuchMethodException, SecurityException {
for (NettyWebSocketEndpointHandler handler : handlers) {
Class<?> handlerClass = handler.getClass();
NettyWebSocketEndpoint annotation = handlerClass.getAnnotation(NettyWebSocketEndpoint.class);
if (annotation == null || StringUtils.isEmpty(annotation.path())) {
throw new RuntimeException("未配置路径的 netty websocket endpoint ");
}
// uriHandlerMap.put(uri.getPath(), handler);
PathMatchModel pathMachModel = parseHandler(annotation.path(), handlerClass);
uriHandlerMapper.put(pathMachModel.path, pathMachModel);
}
}
@Override
protected void channelRead0(ChannelHandlerContext context, TextWebSocketFrame webSocketFrame) throws Exception {
NettyWebSocketEndpointHandler handler = channelHandlerMap.get(context.channel().id());
handler.onMessage(context, webSocketFrame);
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
channelGroup.add(ctx.channel());
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
NettyWebSocketEndpointHandler handler = channelHandlerMap.get(ctx.channel().id());
if (handler != null) {
handler.onClose(ctx);
}
channelHandlerMap.remove(ctx.channel().id());
channelGroup.remove(ctx.channel());
}
@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
channelHandlerMap.get(ctx.channel().id()).onError(ctx, cause);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof FullHttpRequest) {
FullHttpRequest fullHttpRequest = (FullHttpRequest) msg;
if (channelHandlerMap.get(ctx.channel().id()) != null) {
super.channelRead(ctx, fullHttpRequest);
return;
}
URI uri = new URI(fullHttpRequest.uri());
PathMatchModel mathPathMachModel = mathPathMachModel(uri.getPath());
if (mathPathMachModel == null) {
ctx.channel()
.writeAndFlush(new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.NOT_FOUND));
ctx.close().addListener(ChannelFutureListener.CLOSE);
return;
}
NettyWebSocketEndpointHandler newInstance = (NettyWebSocketEndpointHandler) mathPathMachModel.handlerConstructor
.newInstance();
if (!(mathPathMachModel.pathParams == null || mathPathMachModel.pathParams.isEmpty())) {
newInstance.setPathParam(
CommonUtil.parsePathParam(uri.getPath(), mathPathMachModel.pathParams, mathPathMachModel.path));
super.channelRead(ctx, msg);
}
newInstance.setUrlParam(CommonUtil.parseQueryParameters(uri.getQuery()));
channelHandlerMap.put(ctx.channel().id(), newInstance);
newInstance.onOpen(ctx, fullHttpRequest);
} else if (msg instanceof TextWebSocketFrame) {
super.channelRead(ctx, msg);
}
}
private static PathMatchModel parseHandler(String path, Class<?> handlerClass)
throws NoSuchMethodException, SecurityException {
List<String> paramName = new ArrayList<>();
String[] split = path.split("/");
for (int index = 1; index < split.length; index++) {
String item = split[index];
if (item.startsWith("{") && item.endsWith("}")) {
paramName.add(item.substring(1, item.length() - 1).trim());
split[index] = "?";
}
}
StringBuilder finalPath = new StringBuilder("");
for (int index = 1; index < split.length; index++) {
finalPath.append("/").append(split[index]);
}
return new PathMatchModel(paramName, finalPath.toString(), handlerClass.getDeclaredConstructor());
}
private static PathMatchModel mathPathMachModel(String uri) {
Map<Integer, PathMatchModel> map = new HashMap<>();
for (String key : uriHandlerMapper.keySet()) {
int mathUri = CommonUtil.mathUri(uri, key);
if (mathUri > 0) {
map.put(mathUri, uriHandlerMapper.get(key));
}
}
if (map.keySet() == null || map.keySet().isEmpty()) {
return null;
}
Integer max = CommonUtil.getMax(map.keySet());
return map.get(max);
}
private static final class PathMatchModel {
private final List<String> pathParams;
private final String path;
private final Constructor<?> handlerConstructor;
public PathMatchModel(List<String> pathParams, String path, Constructor<?> handlerConstructor) {
this.pathParams = pathParams;
this.path = path;
this.handlerConstructor = handlerConstructor;
}
}
}

View File

@ -0,0 +1,78 @@
package com.ruoyi.netty.websocket.utils;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
public class CommonUtil {
/**
* @param uri
* @param uriTemplates
* @return
*/
public static int mathUri(String uri, String uriTemplate) {
String[] uriSplit = uri.split("/");
String[] tempalteSplit = uriTemplate.split("/");
if (uriSplit.length != tempalteSplit.length) {
return -1;
}
int mathLevel = 0;
for (int index = 1; index < tempalteSplit.length; index++) {
if (tempalteSplit[index].equals("?")) {
mathLevel = mathLevel + index;
continue;
}
if (!tempalteSplit[index].equals(uriSplit[index])) {
return -1;
} else {
mathLevel = mathLevel + tempalteSplit.length + 1;
}
}
return mathLevel;
}
public static Map<String, String> parseQueryParameters(String query) {
if (query == null || query.isEmpty()) {
return Map.of();
}
Map<String, String> params = new HashMap<>();
String[] pairs = query.split("&");
for (String pair : pairs) {
String[] keyValue = pair.split("=");
if (keyValue.length > 1) {
params.put(keyValue[0], keyValue[1]);
} else {
params.put(keyValue[0], "");
}
}
return params;
}
public static Map<String, String> parsePathParam(String uri, List<String> pathParams, String uriTemplate) {
int index = 0;
String[] split = uriTemplate.split("/");
String[] split2 = uri.split("/");
Map<String, String> map = new HashMap<>();
for (int i = 1; i < split.length; i++) {
if (split[i].equals("?")) {
map.put(pathParams.get(index), split2[i]);
index++;
}
}
return map;
}
public static Integer getMax(Set<Integer> set) {
Optional<Integer> maxNumber = set.stream().max(Integer::compare);
if (maxNumber.isPresent()) {
System.out.println("Max number: " + maxNumber.get());
} else {
System.out.println("The list is empty");
}
return maxNumber.get();
}
}

View File

@ -50,6 +50,11 @@
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<artifactId>ruoyi-mybatis-interceptor</artifactId> <artifactId>ruoyi-mybatis-interceptor</artifactId>
</dependency> </dependency>
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-netty</artifactId>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@ -26,6 +26,11 @@
<artifactId>spring-boot-starter-websocket</artifactId> <artifactId>spring-boot-starter-websocket</artifactId>
</dependency> </dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
</dependency>
</dependencies> </dependencies>
</project> </project>