u
This commit is contained in:
parent
8b552fa632
commit
59ccedce9b
@ -68,11 +68,19 @@
|
|||||||
<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>
|
<dependency>
|
||||||
<groupId>io.netty</groupId>
|
<groupId>io.netty</groupId>
|
||||||
<artifactId>netty-all</artifactId>
|
<artifactId>netty-all</artifactId>
|
||||||
@ -90,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>
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package com.ruoyi.mybatisinterceptor.sql;
|
package com.ruoyi.mybatisinterceptor.handler;
|
||||||
|
|
||||||
public interface MybatisAfterHandler {
|
public interface MybatisAfterHandler {
|
||||||
|
|
@ -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,
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -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
|
@ -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;
|
@ -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;
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
|
||||||
|
30
ruoyi-plugins/ruoyi-netty/pom.xml
Normal file
30
ruoyi-plugins/ruoyi-netty/pom.xml
Normal 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>
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -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>
|
||||||
|
@ -75,4 +75,5 @@ public class CommonUtil {
|
|||||||
}
|
}
|
||||||
return maxNumber.get();
|
return maxNumber.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user