diff --git a/ballcat-starters/ballcat-spring-boot-starter-datascope/pom.xml b/ballcat-starters/ballcat-spring-boot-starter-datascope/pom.xml new file mode 100644 index 00000000..c8986398 --- /dev/null +++ b/ballcat-starters/ballcat-spring-boot-starter-datascope/pom.xml @@ -0,0 +1,34 @@ + + + + ballcat-starters + com.hccake + ${revision} + + 4.0.0 + + ballcat-spring-boot-starter-datascope + + + + com.github.jsqlparser + jsqlparser + 3.2 + + + + + org.slf4j + slf4j-api + + + org.mybatis + mybatis + 3.5.5 + compile + + + + \ No newline at end of file diff --git a/ballcat-starters/ballcat-spring-boot-starter-datascope/src/main/java/com/hccake/ballcat/common/datascope/DataScope.java b/ballcat-starters/ballcat-spring-boot-starter-datascope/src/main/java/com/hccake/ballcat/common/datascope/DataScope.java new file mode 100644 index 00000000..6613a60e --- /dev/null +++ b/ballcat-starters/ballcat-spring-boot-starter-datascope/src/main/java/com/hccake/ballcat/common/datascope/DataScope.java @@ -0,0 +1,36 @@ +package com.hccake.ballcat.common.datascope; + +import net.sf.jsqlparser.expression.Alias; +import net.sf.jsqlparser.expression.Expression; + +import java.util.Collection; + +/** + * @author Hccake 2020/9/28 + * @version 1.0 + */ +public interface DataScope { + + /** + * 数据所对应的资源 + * @return 资源标识 + */ + String getResource(); + + /** + * 该资源相关的所有表,推荐使用 Set 类型。
+ * 如需忽略表名大小写判断,则可以使用 TreeSet,并设置忽略大小写的自定义Comparator。
+ * eg. new TreeSet<>(String.CASE_INSENSITIVE_ORDER); + * @return tableNames + */ + Collection getTableNames(); + + /** + * 根据表名和表别名,动态生成的 where/or 筛选条件 + * @param tableName 表名 + * @param tableAlias 表别名,可能为空 + * @return 数据规则表达式 + */ + Expression getExpression(String tableName, Alias tableAlias); + +} diff --git a/ballcat-starters/ballcat-spring-boot-starter-datascope/src/main/java/com/hccake/ballcat/common/datascope/DataScopeHolder.java b/ballcat-starters/ballcat-spring-boot-starter-datascope/src/main/java/com/hccake/ballcat/common/datascope/DataScopeHolder.java new file mode 100644 index 00000000..94ec2940 --- /dev/null +++ b/ballcat-starters/ballcat-spring-boot-starter-datascope/src/main/java/com/hccake/ballcat/common/datascope/DataScopeHolder.java @@ -0,0 +1,42 @@ +package com.hccake.ballcat.common.datascope; + +import java.util.HashMap; +import java.util.Map; + +/** + * DataScope 持有类 内部维护一个 ThreadLocal,用于存储当前用户的所有 DataScope + * + * @author Hccake 2020/9/27 + * @version 1.0 + */ +public class DataScopeHolder { + + private DataScopeHolder() { + } + + private static final ThreadLocal> DATA_SCOPE_LOCAL = new InheritableThreadLocal<>(); + + public static Map getDataScopes() { + return DataScopeHolder.DATA_SCOPE_LOCAL.get(); + } + + public static void setDataScopes(Map dataScopes) { + DataScopeHolder.DATA_SCOPE_LOCAL.set(dataScopes); + } + + public static DataScope putDataScope(String key, DataScope dataScope) { + if (DataScopeHolder.getDataScopes() == null) { + setDataScopes(new HashMap<>(8)); + } + return DataScopeHolder.getDataScopes().put(key, dataScope); + } + + public static DataScope removeDataScope(String key) { + return DataScopeHolder.getDataScopes().remove(key); + } + + public static void clear() { + DATA_SCOPE_LOCAL.remove(); + } + +} diff --git a/ballcat-starters/ballcat-spring-boot-starter-datascope/src/main/java/com/hccake/ballcat/common/datascope/annotation/DataPermission.java b/ballcat-starters/ballcat-spring-boot-starter-datascope/src/main/java/com/hccake/ballcat/common/datascope/annotation/DataPermission.java new file mode 100644 index 00000000..21aaff3c --- /dev/null +++ b/ballcat-starters/ballcat-spring-boot-starter-datascope/src/main/java/com/hccake/ballcat/common/datascope/annotation/DataPermission.java @@ -0,0 +1,27 @@ +package com.hccake.ballcat.common.datascope.annotation; + +import java.lang.annotation.*; + +/** + * 数据权限注解,注解在 Mapper类 或者 对应方法上 用于提供该 mapper 对应表,所需控制的实体信息 + * @author Hccake 2020/9/27 + * @version 1.0 + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface DataPermission { + + /** + * 资源类型 + * @return 资源类型数组 + */ + String[] resources(); + + /** + * 用于在全局开启或者关闭数据权限时,对指定类或者指定方法进行开关控制 + * @return boolean 默认返回 true + */ + boolean enabled() default true; + +} diff --git a/ballcat-starters/ballcat-spring-boot-starter-datascope/src/main/java/com/hccake/ballcat/common/datascope/handler/DataPermissionHandler.java b/ballcat-starters/ballcat-spring-boot-starter-datascope/src/main/java/com/hccake/ballcat/common/datascope/handler/DataPermissionHandler.java new file mode 100644 index 00000000..006b928d --- /dev/null +++ b/ballcat-starters/ballcat-spring-boot-starter-datascope/src/main/java/com/hccake/ballcat/common/datascope/handler/DataPermissionHandler.java @@ -0,0 +1,20 @@ +package com.hccake.ballcat.common.datascope.handler; + +import com.hccake.ballcat.common.datascope.DataScope; +import lombok.Getter; +import lombok.Setter; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Hccake 2020/9/28 + * @version 1.0 + */ +@Getter +@Setter +public class DataPermissionHandler { + + List dataScopes = new ArrayList<>(); + +} diff --git a/ballcat-starters/ballcat-spring-boot-starter-datascope/src/main/java/com/hccake/ballcat/common/datascope/interceptor/DataPermissionInterceptor.java b/ballcat-starters/ballcat-spring-boot-starter-datascope/src/main/java/com/hccake/ballcat/common/datascope/interceptor/DataPermissionInterceptor.java new file mode 100644 index 00000000..1d7aafd5 --- /dev/null +++ b/ballcat-starters/ballcat-spring-boot-starter-datascope/src/main/java/com/hccake/ballcat/common/datascope/interceptor/DataPermissionInterceptor.java @@ -0,0 +1,61 @@ +package com.hccake.ballcat.common.datascope.interceptor; + +import com.hccake.ballcat.common.datascope.processor.DataScopeSqlProcessor; +import com.hccake.ballcat.common.datascope.util.PluginUtils; +import lombok.RequiredArgsConstructor; +import org.apache.ibatis.executor.statement.StatementHandler; +import org.apache.ibatis.mapping.MappedStatement; +import org.apache.ibatis.mapping.SqlCommandType; +import org.apache.ibatis.plugin.*; + +import java.sql.Connection; +import java.util.Properties; + +/** + * 数据权限拦截器 + * + * @author Hccake 2020/9/28 + * @version 1.0 + */ +@RequiredArgsConstructor +@Intercepts({ + @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class, Integer.class }) }) +public class DataPermissionInterceptor implements Interceptor { + + private final DataScopeSqlProcessor dataScopeSqlProcessor; + + @Override + public Object intercept(Invocation invocation) throws Throwable { + // 第一版,测试用 + Object target = invocation.getTarget(); + StatementHandler sh = (StatementHandler) target; + PluginUtils.MPStatementHandler mpSh = PluginUtils.mpStatementHandler(sh); + MappedStatement ms = mpSh.mappedStatement(); + SqlCommandType sct = ms.getSqlCommandType(); + PluginUtils.MPBoundSql mpBs = mpSh.mPBoundSql(); + + // TODO 根据用户权限判断是否需要拦截,例如管理员可以查看所有,则直接放行 + // TODO 动态生成 DataPermissionHandler, 根据注解进行一些此次 sql 执行中需要忽略的点 + if (sct == SqlCommandType.SELECT) { + mpBs.sql(dataScopeSqlProcessor.parserSingle(mpBs.sql(), null)); + } + else if (sct == SqlCommandType.INSERT || sct == SqlCommandType.UPDATE || sct == SqlCommandType.DELETE) { + mpBs.sql(dataScopeSqlProcessor.parserMulti(mpBs.sql(), null)); + } + return invocation.proceed(); + } + + @Override + public Object plugin(Object target) { + if (target instanceof StatementHandler) { + return Plugin.wrap(target, this); + } + return target; + } + + @Override + public void setProperties(Properties properties) { + + } + +} diff --git a/ballcat-starters/ballcat-spring-boot-starter-datascope/src/main/java/com/hccake/ballcat/common/datascope/parser/JsqlParserSupport.java b/ballcat-starters/ballcat-spring-boot-starter-datascope/src/main/java/com/hccake/ballcat/common/datascope/parser/JsqlParserSupport.java new file mode 100644 index 00000000..bd7cdc5e --- /dev/null +++ b/ballcat-starters/ballcat-spring-boot-starter-datascope/src/main/java/com/hccake/ballcat/common/datascope/parser/JsqlParserSupport.java @@ -0,0 +1,114 @@ +package com.hccake.ballcat.common.datascope.parser; + +import lombok.extern.slf4j.Slf4j; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.Statements; +import net.sf.jsqlparser.statement.delete.Delete; +import net.sf.jsqlparser.statement.insert.Insert; +import net.sf.jsqlparser.statement.select.Select; +import net.sf.jsqlparser.statement.update.Update; + +/** + * https://github.com/JSQLParser/JSqlParser + * + * @author miemie hccake + * @since 2020-06-22 + */ +@Slf4j +public abstract class JsqlParserSupport { + + public String parserSingle(String sql, Object obj) { + if (log.isDebugEnabled()) { + log.debug("original SQL: " + sql); + } + try { + Statement statement = CCJSqlParserUtil.parse(sql); + return processParser(statement, 0, sql, obj); + } + catch (JSQLParserException e) { + throw new RuntimeException(String.format("Failed to process, Error SQL: %s", sql), e); + } + } + + public String parserMulti(String sql, Object obj) { + if (log.isDebugEnabled()) { + log.debug("original SQL: " + sql); + } + try { + // fixed github pull/295 + StringBuilder sb = new StringBuilder(); + Statements statements = CCJSqlParserUtil.parseStatements(sql); + int i = 0; + for (Statement statement : statements.getStatements()) { + if (i > 0) { + sb.append(";"); + } + sb.append(processParser(statement, i, sql, obj)); + i++; + } + return sb.toString(); + } + catch (JSQLParserException e) { + throw new RuntimeException(String.format("Failed to process, Error SQL: %s", sql), e); + } + } + + /** + * 执行 SQL 解析 + * @param statement JsqlParser Statement + * @return sql + */ + protected String processParser(Statement statement, int index, String sql, Object obj) { + if (log.isDebugEnabled()) { + log.debug("SQL to parse, SQL: " + sql); + } + if (statement instanceof Insert) { + this.processInsert((Insert) statement, index, sql, obj); + } + else if (statement instanceof Select) { + this.processSelect((Select) statement, index, sql, obj); + } + else if (statement instanceof Update) { + this.processUpdate((Update) statement, index, sql, obj); + } + else if (statement instanceof Delete) { + this.processDelete((Delete) statement, index, sql, obj); + } + sql = statement.toString(); + if (log.isDebugEnabled()) { + log.debug("parse the finished SQL: " + sql); + } + return sql; + } + + /** + * 新增 + */ + protected void processInsert(Insert insert, int index, String sql, Object obj) { + throw new UnsupportedOperationException(); + } + + /** + * 删除 + */ + protected void processDelete(Delete delete, int index, String sql, Object obj) { + throw new UnsupportedOperationException(); + } + + /** + * 更新 + */ + protected void processUpdate(Update update, int index, String sql, Object obj) { + throw new UnsupportedOperationException(); + } + + /** + * 查询 + */ + protected void processSelect(Select select, int index, String sql, Object obj) { + throw new UnsupportedOperationException(); + } + +} diff --git a/ballcat-starters/ballcat-spring-boot-starter-datascope/src/main/java/com/hccake/ballcat/common/datascope/processor/DataScopeSqlProcessor.java b/ballcat-starters/ballcat-spring-boot-starter-datascope/src/main/java/com/hccake/ballcat/common/datascope/processor/DataScopeSqlProcessor.java new file mode 100644 index 00000000..0160bbaa --- /dev/null +++ b/ballcat-starters/ballcat-spring-boot-starter-datascope/src/main/java/com/hccake/ballcat/common/datascope/processor/DataScopeSqlProcessor.java @@ -0,0 +1,257 @@ +package com.hccake.ballcat.common.datascope.processor; + +import com.hccake.ballcat.common.datascope.handler.DataPermissionHandler; +import com.hccake.ballcat.common.datascope.parser.JsqlParserSupport; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import net.sf.jsqlparser.expression.BinaryExpression; +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.expression.NotExpression; +import net.sf.jsqlparser.expression.Parenthesis; +import net.sf.jsqlparser.expression.operators.conditional.AndExpression; +import net.sf.jsqlparser.expression.operators.conditional.OrExpression; +import net.sf.jsqlparser.expression.operators.relational.ExistsExpression; +import net.sf.jsqlparser.expression.operators.relational.InExpression; +import net.sf.jsqlparser.expression.operators.relational.ItemsList; +import net.sf.jsqlparser.schema.Column; +import net.sf.jsqlparser.schema.Table; +import net.sf.jsqlparser.statement.delete.Delete; +import net.sf.jsqlparser.statement.insert.Insert; +import net.sf.jsqlparser.statement.select.*; +import net.sf.jsqlparser.statement.update.Update; + +import java.util.List; +import java.util.Objects; + +/** + * 数据权限 sql 处理器 参考 mybatis-plus 租户拦截器,解析 sql where 部分,进行查询表达式注入 + * + * @author Hccake 2020/9/26 + * @version 1.0 + */ +@RequiredArgsConstructor +@Slf4j +public class DataScopeSqlProcessor extends JsqlParserSupport { + + private final DataPermissionHandler dataPermissionHandler; + + /** + * select 类型SQL处理 + * @param select jsqlparser Statement Select + */ + @Override + protected void processSelect(Select select, int index, String sql, Object obj) { + processSelectBody(select.getSelectBody()); + List withItemsList = select.getWithItemsList(); + if (withItemsList != null && withItemsList.size() != 0) { + withItemsList.forEach(this::processSelectBody); + } + } + + protected void processSelectBody(SelectBody selectBody) { + if (selectBody == null) { + return; + } + if (selectBody instanceof PlainSelect) { + processPlainSelect((PlainSelect) selectBody); + } + else if (selectBody instanceof WithItem) { + WithItem withItem = (WithItem) selectBody; + processSelectBody(withItem.getSelectBody()); + } + else { + SetOperationList operationList = (SetOperationList) selectBody; + if (operationList.getSelects() != null && operationList.getSelects().size() > 0) { + operationList.getSelects().forEach(this::processSelectBody); + } + } + } + + /** + * insert 类型SQL处理 + * @param insert jsqlparser Statement Insert + */ + @Override + protected void processInsert(Insert insert, int index, String sql, Object obj) { + // insert 暂时不处理 + } + + /** + * update 类型SQL处理 + * @param update jsqlparser Statement Update + */ + @Override + protected void processUpdate(Update update, int index, String sql, Object obj) { + update.setWhere(this.injectExpression(update.getWhere(), update.getTable())); + } + + /** + * delete 类型SQL处理 + * @param delete jsqlparser Statement Delete + */ + @Override + protected void processDelete(Delete delete, int index, String sql, Object obj) { + delete.setWhere(this.injectExpression(delete.getWhere(), delete.getTable())); + } + + /** + * 处理 PlainSelect + */ + protected void processPlainSelect(PlainSelect plainSelect) { + FromItem fromItem = plainSelect.getFromItem(); + Expression where = plainSelect.getWhere(); + processWhereSubSelect(where); + if (fromItem instanceof Table) { + Table fromTable = (Table) fromItem; + // #1186 github + plainSelect.setWhere(injectExpression(plainSelect.getWhere(), fromTable)); + } + else { + processFromItem(fromItem); + } + List joins = plainSelect.getJoins(); + if (joins != null && joins.size() > 0) { + joins.forEach(j -> { + processJoin(j); + processFromItem(j.getRightItem()); + }); + } + } + + /** + * 处理where条件内的子查询 + *

+ * 支持如下: 1. in 2. = 3. > 4. < 5. >= 6. <= 7. <> 8. EXISTS 9. NOT EXISTS + *

+ * 前提条件: 1. 子查询必须放在小括号中 2. 子查询一般放在比较操作符的右边 + * @param where where 条件 + */ + protected void processWhereSubSelect(Expression where) { + if (where == null) { + return; + } + if (where instanceof FromItem) { + processFromItem((FromItem) where); + return; + } + if (where.toString().indexOf("SELECT") > 0) { + // 有子查询 + if (where instanceof BinaryExpression) { + // 比较符号 , and , or , 等等 + BinaryExpression expression = (BinaryExpression) where; + processWhereSubSelect(expression.getLeftExpression()); + processWhereSubSelect(expression.getRightExpression()); + } + else if (where instanceof InExpression) { + // in + InExpression expression = (InExpression) where; + ItemsList itemsList = expression.getRightItemsList(); + if (itemsList instanceof SubSelect) { + processSelectBody(((SubSelect) itemsList).getSelectBody()); + } + } + else if (where instanceof ExistsExpression) { + // exists + ExistsExpression expression = (ExistsExpression) where; + processWhereSubSelect(expression.getRightExpression()); + } + else if (where instanceof NotExpression) { + // not exists + NotExpression expression = (NotExpression) where; + processWhereSubSelect(expression.getExpression()); + } + else if (where instanceof Parenthesis) { + Parenthesis expression = (Parenthesis) where; + processWhereSubSelect(expression.getExpression()); + } + } + } + + /** + * 处理子查询等 + */ + protected void processFromItem(FromItem fromItem) { + if (fromItem instanceof SubJoin) { + SubJoin subJoin = (SubJoin) fromItem; + if (subJoin.getJoinList() != null) { + subJoin.getJoinList().forEach(this::processJoin); + } + if (subJoin.getLeft() != null) { + processFromItem(subJoin.getLeft()); + } + } + else if (fromItem instanceof SubSelect) { + SubSelect subSelect = (SubSelect) fromItem; + if (subSelect.getSelectBody() != null) { + processSelectBody(subSelect.getSelectBody()); + } + } + else if (fromItem instanceof ValuesList) { + log.debug("Perform a subquery, if you do not give us feedback"); + } + else if (fromItem instanceof LateralSubSelect) { + LateralSubSelect lateralSubSelect = (LateralSubSelect) fromItem; + if (lateralSubSelect.getSubSelect() != null) { + SubSelect subSelect = lateralSubSelect.getSubSelect(); + if (subSelect.getSelectBody() != null) { + processSelectBody(subSelect.getSelectBody()); + } + } + } + } + + /** + * 处理联接语句 + */ + protected void processJoin(Join join) { + if (join.getRightItem() instanceof Table) { + Table fromTable = (Table) join.getRightItem(); + join.setOnExpression(injectExpression(join.getOnExpression(), fromTable)); + } + } + + /** + * 根据 DataScope ,将数据过滤的表达式注入原本的 where/or 条件 + * @param currentExpression Expression where/or + * @param table 表信息 + * @return 修改后的 where/or 条件 + */ + private Expression injectExpression(Expression currentExpression, Table table) { + // TODO 重写 dataPermissionHandler + // TODO 当用户检索到 dataScope 所属字段时,需要判断该值是否在 scope 中,然后将其合并 + String tableName = table.getName(); + Expression dataFilterExpression = dataPermissionHandler.getDataScopes().stream() + .filter(x -> x.getTableNames().contains(tableName)) + .map(x -> x.getExpression(tableName, table.getAlias())).filter(Objects::nonNull) + .reduce(AndExpression::new).orElse(null); + + if (currentExpression == null) { + return dataFilterExpression; + } + if (dataFilterExpression == null) { + return currentExpression; + } + if (currentExpression instanceof OrExpression) { + return new AndExpression(new Parenthesis(currentExpression), dataFilterExpression); + } + else { + return new AndExpression(currentExpression, dataFilterExpression); + } + } + + /** + * 根据当前表是否有别名,动态对字段名前添加表别名 eg. 表名: table_1 as t 原始字段:column1 返回: t.column1 + * @param table 表信息 + * @param column 字段名 + * @return 原始字段名,或者添加了表别名的字段名 + */ + protected Column getAliasColumn(Table table, String column) { + StringBuilder columnBuilder = new StringBuilder(); + if (table.getAlias() != null) { + columnBuilder.append(table.getAlias().getName()).append("."); + } + columnBuilder.append(column); + return new Column(columnBuilder.toString()); + } + +} diff --git a/ballcat-starters/ballcat-spring-boot-starter-datascope/src/main/java/com/hccake/ballcat/common/datascope/util/PluginUtils.java b/ballcat-starters/ballcat-spring-boot-starter-datascope/src/main/java/com/hccake/ballcat/common/datascope/util/PluginUtils.java new file mode 100644 index 00000000..7bf9871d --- /dev/null +++ b/ballcat-starters/ballcat-spring-boot-starter-datascope/src/main/java/com/hccake/ballcat/common/datascope/util/PluginUtils.java @@ -0,0 +1,163 @@ +/* + * Copyright (c) 2011-2020, baomidou (jobob@qq.com). + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.hccake.ballcat.common.datascope.util; + +import org.apache.ibatis.executor.Executor; +import org.apache.ibatis.executor.parameter.ParameterHandler; +import org.apache.ibatis.executor.statement.StatementHandler; +import org.apache.ibatis.mapping.BoundSql; +import org.apache.ibatis.mapping.MappedStatement; +import org.apache.ibatis.mapping.ParameterMapping; +import org.apache.ibatis.reflection.MetaObject; +import org.apache.ibatis.reflection.SystemMetaObject; +import org.apache.ibatis.session.Configuration; + +import java.lang.reflect.Proxy; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +/** + * 插件工具类 + * + * @author TaoYu , hubin + * @since 2017-06-20 + */ +public abstract class PluginUtils { + + public static final String DELEGATE_BOUNDSQL_SQL = "delegate.boundSql.sql"; + + /** + * 获得真正的处理对象,可能多层代理. + */ + @SuppressWarnings("unchecked") + public static T realTarget(Object target) { + if (Proxy.isProxyClass(target.getClass())) { + MetaObject metaObject = SystemMetaObject.forObject(target); + return realTarget(metaObject.getValue("h.target")); + } + return (T) target; + } + + /** + * 给 BoundSql 设置 additionalParameters + * @param boundSql BoundSql + * @param additionalParameters additionalParameters + */ + public static void setAdditionalParameter(BoundSql boundSql, Map additionalParameters) { + additionalParameters.forEach(boundSql::setAdditionalParameter); + } + + public static MPBoundSql mpBoundSql(BoundSql boundSql) { + return new MPBoundSql(boundSql); + } + + public static MPStatementHandler mpStatementHandler(StatementHandler statementHandler) { + statementHandler = realTarget(statementHandler); + MetaObject object = SystemMetaObject.forObject(statementHandler); + return new MPStatementHandler(SystemMetaObject.forObject(object.getValue("delegate"))); + } + + /** + * {@link org.apache.ibatis.executor.statement.BaseStatementHandler} + */ + public static class MPStatementHandler { + + private final MetaObject statementHandler; + + MPStatementHandler(MetaObject statementHandler) { + this.statementHandler = statementHandler; + } + + public ParameterHandler parameterHandler() { + return get("parameterHandler"); + } + + public MappedStatement mappedStatement() { + return get("mappedStatement"); + } + + public Executor executor() { + return get("executor"); + } + + public MPBoundSql mPBoundSql() { + return new MPBoundSql(boundSql()); + } + + public BoundSql boundSql() { + return get("boundSql"); + } + + public Configuration configuration() { + return get("configuration"); + } + + @SuppressWarnings("unchecked") + private T get(String property) { + return (T) statementHandler.getValue(property); + } + + } + + /** + * {@link BoundSql} + */ + public static class MPBoundSql { + + private final MetaObject boundSql; + + private final BoundSql delegate; + + MPBoundSql(BoundSql boundSql) { + this.delegate = boundSql; + this.boundSql = SystemMetaObject.forObject(boundSql); + } + + public String sql() { + return delegate.getSql(); + } + + public void sql(String sql) { + boundSql.setValue("sql", sql); + } + + public List parameterMappings() { + List parameterMappings = delegate.getParameterMappings(); + return new ArrayList<>(parameterMappings); + } + + public void parameterMappings(List parameterMappings) { + boundSql.setValue("parameterMappings", Collections.unmodifiableList(parameterMappings)); + } + + public Object parameterObject() { + return get("parameterObject"); + } + + public Map additionalParameters() { + return get("additionalParameters"); + } + + @SuppressWarnings("unchecked") + private T get(String property) { + return (T) boundSql.getValue(property); + } + + } + +} diff --git a/ballcat-starters/ballcat-spring-boot-starter-datascope/src/test/java/com/hccake/ballcat/common/datascope/test/SqlParseTest.java b/ballcat-starters/ballcat-spring-boot-starter-datascope/src/test/java/com/hccake/ballcat/common/datascope/test/SqlParseTest.java new file mode 100644 index 00000000..92c88968 --- /dev/null +++ b/ballcat-starters/ballcat-spring-boot-starter-datascope/src/test/java/com/hccake/ballcat/common/datascope/test/SqlParseTest.java @@ -0,0 +1,64 @@ +package com.hccake.ballcat.common.datascope.test; + +import com.hccake.ballcat.common.datascope.DataScope; +import com.hccake.ballcat.common.datascope.DataScopeHolder; +import com.hccake.ballcat.common.datascope.handler.DataPermissionHandler; +import com.hccake.ballcat.common.datascope.processor.DataScopeSqlProcessor; +import net.sf.jsqlparser.expression.Alias; +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.expression.StringValue; +import net.sf.jsqlparser.expression.operators.relational.ExpressionList; +import net.sf.jsqlparser.expression.operators.relational.InExpression; +import net.sf.jsqlparser.schema.Column; +import org.junit.Test; + +import java.util.*; + +/** + * @author Hccake 2020/9/28 + * @version 1.0 + */ +public class SqlParseTest { + + @Test + public void test() { + DataScope dataScope = new DataScope() { + final String columnId = "order_id"; + + @Override + public String getResource() { + return "order"; + } + + @Override + public Collection getTableNames() { + Set tableNames = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); + tableNames.addAll(Arrays.asList("t_order", "t_order_info")); + return tableNames; + } + + @Override + public Expression getExpression(String tableName, Alias tableAlias) { + Column column = new Column(tableAlias == null ? columnId : tableAlias.getName() + "." + columnId); + ExpressionList expressionList = new ExpressionList(); + expressionList.setExpressions(Arrays.asList(new StringValue("1"), new StringValue("2"))); + return new InExpression(column, expressionList); + } + }; + + DataPermissionHandler dataPermissionHandler = new DataPermissionHandler(); + List list = new ArrayList<>(); + list.add(dataScope); + dataPermissionHandler.setDataScopes(list); + + DataScopeSqlProcessor dataScopeSqlProcessor = new DataScopeSqlProcessor(dataPermissionHandler); + + DataScopeHolder.putDataScope("order", dataScope); + String sql = "select o.order_id,o.order_name,oi.order_price " + + "from t_ORDER o left join t_order_info oi on o.order_id = oi.order_id " + + "where oi.order_price > 100"; + + dataScopeSqlProcessor.parserSingle(sql, null); + } + +} diff --git a/ballcat-starters/pom.xml b/ballcat-starters/pom.xml index 6d437e77..2913f846 100644 --- a/ballcat-starters/pom.xml +++ b/ballcat-starters/pom.xml @@ -13,15 +13,16 @@ pom - ballcat-spring-boot-starter-log - ballcat-spring-boot-starter-job - ballcat-spring-boot-starter-swagger - ballcat-spring-boot-starter-storage - ballcat-spring-boot-starter-mail - ballcat-spring-boot-starter-easyexcel - ballcat-spring-boot-starter-redis + ballcat-spring-boot-starter-datascope ballcat-spring-boot-starter-dingtalk - ballcat-spring-boot-starter-kafka + ballcat-spring-boot-starter-easyexcel + ballcat-spring-boot-starter-job + ballcat-spring-boot-starter-kafka + ballcat-spring-boot-starter-log + ballcat-spring-boot-starter-mail + ballcat-spring-boot-starter-redis + ballcat-spring-boot-starter-storage + ballcat-spring-boot-starter-swagger \ No newline at end of file