diff --git a/ruoyi-plugins/pom.xml b/ruoyi-plugins/pom.xml index 358faf8033fe6c3cb0d585bdf4e9e5f7641c04c6..9807d9896eed0fc03e76740061e9bf22db015951 100644 --- a/ruoyi-plugins/pom.xml +++ b/ruoyi-plugins/pom.xml @@ -13,6 +13,7 @@ 3.10.8 3.5.8 + 4.1.112.Final @@ -67,12 +68,25 @@ ruoyi-plugins-starter ${ruoyi.version} + com.ruoyi ruoyi-mybatis-interceptor ${ruoyi.version} + + com.ruoyi + ruoyi-netty + ${ruoyi.version} + + + + io.netty + netty-all + ${netty.version} + + @@ -84,6 +98,7 @@ ruoyi-websocket ruoyi-plugins-starter ruoyi-mybatis-interceptor + ruoyi-netty pom diff --git a/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/aspectj/DataSecurityAspect.java b/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/aspectj/DataSecurityAspect.java index 166b87fc862e52cd155bcf8a41120d91f860fd5f..d6ee916b8afa51c84d6aafabdce3a3291d993126 100644 --- a/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/aspectj/DataSecurityAspect.java +++ b/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/aspectj/DataSecurityAspect.java @@ -1,21 +1,19 @@ package com.ruoyi.mybatisinterceptor.aspectj; import com.ruoyi.mybatisinterceptor.annotation.DataSecurity; +import com.ruoyi.mybatisinterceptor.context.sqlContext.SqlContextHolder; import com.ruoyi.mybatisinterceptor.model.JoinTableModel; import com.ruoyi.mybatisinterceptor.model.WhereModel; -import com.ruoyi.mybatisinterceptor.context.dataSecurity.SqlContextHolder; + import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; - - import com.ruoyi.common.utils.SecurityUtils; import com.ruoyi.common.utils.StringUtils; - @Aspect @Component public class DataSecurityAspect { @@ -50,7 +48,6 @@ public class DataSecurityAspect { if (!StringUtils.isEmpty(dataSecurity.joinTableAlise())) { createByTableModel.setJoinTableAlise(dataSecurity.joinTableAlise()); } - createByTableModel.setFromTableColumn("create_by"); createByTableModel.setJoinTableColumn("user_name"); SqlContextHolder.addJoinTable(createByTableModel); @@ -63,12 +60,10 @@ public class DataSecurityAspect { if (!StringUtils.isEmpty(dataSecurity.joinTableAlise())) { userIdTableModel.setJoinTableAlise(dataSecurity.joinTableAlise()); } - userIdTableModel.setFromTableColumn("user_id"); userIdTableModel.setJoinTableColumn("user_id"); SqlContextHolder.addJoinTable(userIdTableModel); break; - default: break; } diff --git a/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/context/dataSecurity/SqlContextHolder.java b/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/context/sqlContext/SqlContextHolder.java similarity index 67% rename from ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/context/dataSecurity/SqlContextHolder.java rename to ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/context/sqlContext/SqlContextHolder.java index 54407a68984526e28be6c5cb2660cf9643223afa..150856bbec3b982470cc3ed81d282859c4113330 100644 --- a/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/context/dataSecurity/SqlContextHolder.java +++ b/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/context/sqlContext/SqlContextHolder.java @@ -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.JSONObject; @@ -10,11 +10,11 @@ public class SqlContextHolder { private static final ThreadLocal SQL_CONTEXT_HOLDER = new ThreadLocal<>(); public static void startDataSecurity() { - JSONObject jsonObject = new JSONObject(); - jsonObject.put("isSecurity", Boolean.TRUE); - jsonObject.put(SqlType.WHERE.getSqlType(), new JSONArray()); - jsonObject.put(SqlType.JOIN.getSqlType(), new JSONArray()); - SQL_CONTEXT_HOLDER.set(jsonObject); + SQL_CONTEXT_HOLDER.get().put("isSecurity", Boolean.TRUE); + } + + public static void startLogicSelect() { + SQL_CONTEXT_HOLDER.get().put("isLogic", Boolean.TRUE); } public static void addWhereParam(WhereModel whereModel) { @@ -26,7 +26,6 @@ public class SqlContextHolder { } public static boolean isSecurity() { - return SQL_CONTEXT_HOLDER.get() != null && SQL_CONTEXT_HOLDER.get().getBooleanValue("isSecurity"); } @@ -42,4 +41,15 @@ public class SqlContextHolder { public static JSONArray getJoinTables() { 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); + } } diff --git a/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/sql/MybatisAfterHandler.java b/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/handler/MybatisAfterHandler.java similarity index 68% rename from ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/sql/MybatisAfterHandler.java rename to ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/handler/MybatisAfterHandler.java index 133f1116f80b5a5371836b6a56a31ec897712b6a..dafed1ebc9de6b5aa588ba6356941fa2b185a745 100644 --- a/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/sql/MybatisAfterHandler.java +++ b/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/handler/MybatisAfterHandler.java @@ -1,4 +1,4 @@ -package com.ruoyi.mybatisinterceptor.sql; +package com.ruoyi.mybatisinterceptor.handler; public interface MybatisAfterHandler { diff --git a/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/sql/MybatisPreHandler.java b/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/handler/MybatisPreHandler.java similarity index 92% rename from ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/sql/MybatisPreHandler.java rename to ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/handler/MybatisPreHandler.java index b0a61fe9187b7d9525fc12375d0c476ca8990fa5..db17e53d3f89db9c332a81da278ebbc7a3aed1d7 100644 --- a/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/sql/MybatisPreHandler.java +++ b/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/handler/MybatisPreHandler.java @@ -1,4 +1,4 @@ -package com.ruoyi.mybatisinterceptor.sql; +package com.ruoyi.mybatisinterceptor.handler; import org.apache.ibatis.cache.CacheKey; 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.session.ResultHandler; import org.apache.ibatis.session.RowBounds; + public interface MybatisPreHandler { void preHandle(Executor executor, MappedStatement mappedStatement, Object params, diff --git a/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/sql/dataSecurity/DataSecurityPreHandler.java b/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/handler/dataSecurity/DataSecurityPreHandler.java similarity index 58% rename from ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/sql/dataSecurity/DataSecurityPreHandler.java rename to ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/handler/dataSecurity/DataSecurityPreHandler.java index c1e8c8671e4c17eef21d4af2f75f02f3681f9489..0bc4a5e28beebb731a9ca5e7f64d46f5af792b75 100644 --- a/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/sql/dataSecurity/DataSecurityPreHandler.java +++ b/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/handler/dataSecurity/DataSecurityPreHandler.java @@ -1,6 +1,7 @@ -package com.ruoyi.mybatisinterceptor.sql.dataSecurity; +package com.ruoyi.mybatisinterceptor.handler.dataSecurity; import java.lang.reflect.Field; +import java.util.ArrayList; import java.util.List; import org.apache.ibatis.cache.CacheKey; @@ -12,13 +13,14 @@ import org.apache.ibatis.session.RowBounds; import org.springframework.stereotype.Component; import org.springframework.util.ReflectionUtils; +import com.alibaba.fastjson2.JSONArray; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.sql.SqlUtil; 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.WhereModel; -import com.ruoyi.mybatisinterceptor.sql.MybatisPreHandler; import net.sf.jsqlparser.JSQLParserException; 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.Table; 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.PlainSelect; import net.sf.jsqlparser.statement.select.Select; +import net.sf.jsqlparser.statement.update.Update; @MybatisHandlerOrder(1) @Component @@ -43,7 +47,7 @@ public class DataSecurityPreHandler implements MybatisPreHandler { @Override public void preHandle(Executor executor, MappedStatement mappedStatement, Object params, RowBounds rowBounds, - ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws Throwable { + ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws Throwable { if (SqlContextHolder.isSecurity()) { Statement sql = parseSql(SqlUtil.parseSql(boundSql.getSql())); sqlFiled.set(boundSql, sql.toString()); @@ -62,27 +66,56 @@ public class DataSecurityPreHandler implements MybatisPreHandler { } } - private static void handleWhere(Select select) throws JSQLParserException { - PlainSelect plain = select.getPlainSelect(); - Expression expWhere = plain.getWhere(); + private static void handleWhere(Statement statement) throws JSQLParserException { + if (statement instanceof Select) { + 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(" "); String where = expWhere != null ? expWhere.toString() : null; 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()); }); - 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(); - plain.setWhere(CCJSqlParserUtil.parseCondExpression(where)); + return CCJSqlParserUtil.parseCondExpression(where); } - private static void handleJoin(Select select) { - PlainSelect selectBody = select.getPlainSelect(); + private static void handleJoin(Statement statement) { if (SqlContextHolder.getJoinTables() == null || SqlContextHolder.getJoinTables().size() <= 0) { 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 getConfigedJoinExpression() { + List joins = new ArrayList<>(); SqlContextHolder.getJoinTables().forEach(item -> { JoinTableModel tableModel = (JoinTableModel) item; Table table = new Table(tableModel.getJoinTable()); @@ -93,8 +126,8 @@ public class DataSecurityPreHandler implements MybatisPreHandler { Expression onExpression = new EqualsTo(new Column(tableModel.getFromTableColumnString()), new Column(tableModel.getJoinTableColumnString())); join.setOnExpressions(List.of(onExpression)); - selectBody.addJoins(join); + joins.add(join); }); + return joins; } - } diff --git a/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/sql/page/PageAfterHandler.java b/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/handler/page/PageAfterHandler.java similarity index 87% rename from ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/sql/page/PageAfterHandler.java rename to ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/handler/page/PageAfterHandler.java index 4826924b4ee648708fc54c4fe73170953031d5ab..bdd91b1349a4b1acf37f3a930dcc6615be011a9b 100644 --- a/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/sql/page/PageAfterHandler.java +++ b/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/handler/page/PageAfterHandler.java @@ -1,4 +1,4 @@ -package com.ruoyi.mybatisinterceptor.sql.page; +package com.ruoyi.mybatisinterceptor.handler.page; import java.util.List; @@ -7,7 +7,7 @@ import org.springframework.stereotype.Component; import com.ruoyi.mybatisinterceptor.annotation.MybatisHandlerOrder; import com.ruoyi.mybatisinterceptor.context.page.PageContextHolder; import com.ruoyi.mybatisinterceptor.context.page.model.TableInfo; -import com.ruoyi.mybatisinterceptor.sql.MybatisAfterHandler; +import com.ruoyi.mybatisinterceptor.handler.MybatisAfterHandler; @MybatisHandlerOrder(1) @Component diff --git a/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/sql/page/PagePreHandler.java b/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/handler/page/PagePreHandler.java similarity index 98% rename from ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/sql/page/PagePreHandler.java rename to ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/handler/page/PagePreHandler.java index 0e21921c13609c2a3adb8699e3134384c699e7c5..5fde0b15d4ca266f09e2b1664e3088ff624ca7c4 100644 --- a/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/sql/page/PagePreHandler.java +++ b/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/handler/page/PagePreHandler.java @@ -1,4 +1,4 @@ -package com.ruoyi.mybatisinterceptor.sql.page; +package com.ruoyi.mybatisinterceptor.handler.page; import java.lang.reflect.Field; 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.context.page.PageContextHolder; 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.statement.Statement; diff --git a/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/interceptor/mybatis/MybatisInterceptor.java b/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/interceptor/mybatis/MybatisInterceptor.java index 98af73ab395513f03f1d6267fa989e40466df649..2afd1d221a746722c98e1e856c0aef3a2a28423c 100644 --- a/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/interceptor/mybatis/MybatisInterceptor.java +++ b/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/interceptor/mybatis/MybatisInterceptor.java @@ -17,8 +17,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import com.ruoyi.mybatisinterceptor.annotation.MybatisHandlerOrder; -import com.ruoyi.mybatisinterceptor.sql.MybatisAfterHandler; -import com.ruoyi.mybatisinterceptor.sql.MybatisPreHandler; +import com.ruoyi.mybatisinterceptor.handler.MybatisAfterHandler; +import com.ruoyi.mybatisinterceptor.handler.MybatisPreHandler; import jakarta.annotation.PostConstruct; diff --git a/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/util/DataSecurityUtil.java b/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/util/DataSecurityUtil.java index cb360d59d8632a88c5ddbede3dfa3d083a18e1da..a7590fa0d61afc0a6b7a0c2e5d8e6f22c635f571 100644 --- a/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/util/DataSecurityUtil.java +++ b/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/util/DataSecurityUtil.java @@ -1,7 +1,6 @@ package com.ruoyi.mybatisinterceptor.util; - -import com.ruoyi.mybatisinterceptor.context.dataSecurity.SqlContextHolder; +import com.ruoyi.mybatisinterceptor.context.sqlContext.SqlContextHolder; public class DataSecurityUtil { diff --git a/ruoyi-plugins/ruoyi-netty/pom.xml b/ruoyi-plugins/ruoyi-netty/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..71b0b4703387eb33f2d397d55dadaeacfa8d5899 --- /dev/null +++ b/ruoyi-plugins/ruoyi-netty/pom.xml @@ -0,0 +1,30 @@ + + + + ruoyi-plugins + com.ruoyi + 3.8.8.3.1 + + 4.0.0 + + ruoyi-netty + + + 19 + 19 + UTF-8 + + + + + io.netty + netty-all + + + com.ruoyi + ruoyi-common + + + diff --git a/ruoyi-plugins/ruoyi-netty/src/main/java/com/ruoyi/netty/websocket/NettyServerRunner.java b/ruoyi-plugins/ruoyi-netty/src/main/java/com/ruoyi/netty/websocket/NettyServerRunner.java new file mode 100644 index 0000000000000000000000000000000000000000..fa69d6d3e025c90c367d8c029c741beeee880829 --- /dev/null +++ b/ruoyi-plugins/ruoyi-netty/src/main/java/com/ruoyi/netty/websocket/NettyServerRunner.java @@ -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(); + } + +} diff --git a/ruoyi-plugins/ruoyi-netty/src/main/java/com/ruoyi/netty/websocket/annotations/NettyWebSocketEndpoint.java b/ruoyi-plugins/ruoyi-netty/src/main/java/com/ruoyi/netty/websocket/annotations/NettyWebSocketEndpoint.java new file mode 100644 index 0000000000000000000000000000000000000000..15f64ae7f60964ece067706dc67535be46d95fbd --- /dev/null +++ b/ruoyi-plugins/ruoyi-netty/src/main/java/com/ruoyi/netty/websocket/annotations/NettyWebSocketEndpoint.java @@ -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(); +} diff --git a/ruoyi-plugins/ruoyi-netty/src/main/java/com/ruoyi/netty/websocket/endpoints/TestNettyWebSocket.java b/ruoyi-plugins/ruoyi-netty/src/main/java/com/ruoyi/netty/websocket/endpoints/TestNettyWebSocket.java new file mode 100644 index 0000000000000000000000000000000000000000..3e827f69d33a2d85ef04fa22eb5deefb45cf467b --- /dev/null +++ b/ruoyi-plugins/ruoyi-netty/src/main/java/com/ruoyi/netty/websocket/endpoints/TestNettyWebSocket.java @@ -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 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); + } +} diff --git a/ruoyi-plugins/ruoyi-netty/src/main/java/com/ruoyi/netty/websocket/nettyServer/NettyWebSocketEndpointHandler.java b/ruoyi-plugins/ruoyi-netty/src/main/java/com/ruoyi/netty/websocket/nettyServer/NettyWebSocketEndpointHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..357c1c484a36d90e1dace381bcdfe5ccfcc393e2 --- /dev/null +++ b/ruoyi-plugins/ruoyi-netty/src/main/java/com/ruoyi/netty/websocket/nettyServer/NettyWebSocketEndpointHandler.java @@ -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 pathParam; + + private Map 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 getPathParam() { + return pathParam; + } + + public void setPathParam(Map pathParam) { + this.pathParam = pathParam; + } + + public Map getUrlParam() { + return urlParam; + } + + public void setUrlParam(Map 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); + } +} diff --git a/ruoyi-plugins/ruoyi-netty/src/main/java/com/ruoyi/netty/websocket/nettyServer/NettyWebSocketServer.java b/ruoyi-plugins/ruoyi-netty/src/main/java/com/ruoyi/netty/websocket/nettyServer/NettyWebSocketServer.java new file mode 100644 index 0000000000000000000000000000000000000000..6350541f9b8be925c99856fe28c2df9d65761cb0 --- /dev/null +++ b/ruoyi-plugins/ruoyi-netty/src/main/java/com/ruoyi/netty/websocket/nettyServer/NettyWebSocketServer.java @@ -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() { + @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; + } +} diff --git a/ruoyi-plugins/ruoyi-netty/src/main/java/com/ruoyi/netty/websocket/nettyServer/handler/WebSocketHandler.java b/ruoyi-plugins/ruoyi-netty/src/main/java/com/ruoyi/netty/websocket/nettyServer/handler/WebSocketHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..da50f7180b02d03556cb718435af933365bd87fc --- /dev/null +++ b/ruoyi-plugins/ruoyi-netty/src/main/java/com/ruoyi/netty/websocket/nettyServer/handler/WebSocketHandler.java @@ -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 { + + @Autowired + private List handlers; + + private static final Map uriHandlerMapper = new ConcurrentHashMap<>(); + + public static final ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); + + public static final Map 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 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 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 pathParams; + + private final String path; + + private final Constructor handlerConstructor; + + public PathMatchModel(List pathParams, String path, Constructor handlerConstructor) { + this.pathParams = pathParams; + this.path = path; + this.handlerConstructor = handlerConstructor; + } + + } +} diff --git a/ruoyi-plugins/ruoyi-netty/src/main/java/com/ruoyi/netty/websocket/utils/CommonUtil.java b/ruoyi-plugins/ruoyi-netty/src/main/java/com/ruoyi/netty/websocket/utils/CommonUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..9c029db3e508ee5a75a29b030ca000fbe81b9d7d --- /dev/null +++ b/ruoyi-plugins/ruoyi-netty/src/main/java/com/ruoyi/netty/websocket/utils/CommonUtil.java @@ -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 parseQueryParameters(String query) { + if (query == null || query.isEmpty()) { + return Map.of(); + } + + Map 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 parsePathParam(String uri, List pathParams, String uriTemplate) { + int index = 0; + String[] split = uriTemplate.split("/"); + String[] split2 = uri.split("/"); + Map 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 set) { + Optional 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(); + } +} diff --git a/ruoyi-plugins/ruoyi-plugins-starter/pom.xml b/ruoyi-plugins/ruoyi-plugins-starter/pom.xml index 183d4cd608fb03ac0dc269ebb08c7c88fed1cfcc..fc7eb6d8ff58c282fa0520ec20b3c7a8741c1377 100644 --- a/ruoyi-plugins/ruoyi-plugins-starter/pom.xml +++ b/ruoyi-plugins/ruoyi-plugins-starter/pom.xml @@ -50,6 +50,11 @@ com.ruoyi ruoyi-mybatis-interceptor + + + com.ruoyi + ruoyi-netty + diff --git a/ruoyi-plugins/ruoyi-websocket/pom.xml b/ruoyi-plugins/ruoyi-websocket/pom.xml index 66fccdb85ab1819d27404533b36061212d2e448f..ee7ac534338600c93a35ca406378b43280637999 100644 --- a/ruoyi-plugins/ruoyi-websocket/pom.xml +++ b/ruoyi-plugins/ruoyi-websocket/pom.xml @@ -26,6 +26,11 @@ spring-boot-starter-websocket + + io.netty + netty-all + +