Commit e9385219c21eee812f5fa3541a141e8cce316447
1 parent
eda2b11d
数据权限的逻辑处理,暂未测试
Showing
2 changed files
with
132 additions
and
75 deletions
yudao-framework/yudao-spring-boot-starter-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/core/interceptor/DataPermissionInterceptor.java
| ... | ... | @@ -4,18 +4,20 @@ import cn.hutool.core.collection.CollUtil; |
| 4 | 4 | import cn.iocoder.yudao.framework.common.util.collection.SetUtils; |
| 5 | 5 | import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRule; |
| 6 | 6 | import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRuleFactory; |
| 7 | +import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils; | |
| 7 | 8 | import com.alibaba.ttl.TransmittableThreadLocal; |
| 8 | 9 | import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; |
| 9 | 10 | import com.baomidou.mybatisplus.core.toolkit.PluginUtils; |
| 10 | -import com.baomidou.mybatisplus.core.toolkit.StringPool; | |
| 11 | 11 | import com.baomidou.mybatisplus.extension.parser.JsqlParserSupport; |
| 12 | 12 | import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor; |
| 13 | 13 | import lombok.RequiredArgsConstructor; |
| 14 | 14 | import net.sf.jsqlparser.expression.*; |
| 15 | 15 | import net.sf.jsqlparser.expression.operators.conditional.AndExpression; |
| 16 | 16 | import net.sf.jsqlparser.expression.operators.conditional.OrExpression; |
| 17 | -import net.sf.jsqlparser.expression.operators.relational.*; | |
| 18 | -import net.sf.jsqlparser.schema.Column; | |
| 17 | +import net.sf.jsqlparser.expression.operators.relational.ExistsExpression; | |
| 18 | +import net.sf.jsqlparser.expression.operators.relational.ExpressionList; | |
| 19 | +import net.sf.jsqlparser.expression.operators.relational.InExpression; | |
| 20 | +import net.sf.jsqlparser.expression.operators.relational.ItemsList; | |
| 19 | 21 | import net.sf.jsqlparser.schema.Table; |
| 20 | 22 | import net.sf.jsqlparser.statement.delete.Delete; |
| 21 | 23 | import net.sf.jsqlparser.statement.select.*; |
| ... | ... | @@ -32,6 +34,15 @@ import java.sql.Connection; |
| 32 | 34 | import java.util.*; |
| 33 | 35 | import java.util.concurrent.ConcurrentHashMap; |
| 34 | 36 | |
| 37 | +/** | |
| 38 | + * 数据权限拦截器,通过 {@link DataPermissionRule} 数据权限规则,重写 SQL 的方式来实现 | |
| 39 | + * 主要的 SQL 重写方法,可见 {@link #builderExpression(Expression, Table)} 方法 | |
| 40 | + * | |
| 41 | + * 整体的代码实现上,参考 {@link com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor} 实现。 | |
| 42 | + * 所以每次 MyBatis Plus 升级时,需要 Review 下其具体的实现是否有变更! | |
| 43 | + * | |
| 44 | + * @author 芋道源码 | |
| 45 | + */ | |
| 35 | 46 | @RequiredArgsConstructor |
| 36 | 47 | public class DataPermissionInterceptor extends JsqlParserSupport implements InnerInterceptor { |
| 37 | 48 | |
| ... | ... | @@ -40,7 +51,8 @@ public class DataPermissionInterceptor extends JsqlParserSupport implements Inne |
| 40 | 51 | private final MappedStatementCache mappedStatementCache = new MappedStatementCache(); |
| 41 | 52 | |
| 42 | 53 | @Override // SELECT 场景 |
| 43 | - public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { | |
| 54 | + public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, | |
| 55 | + RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { | |
| 44 | 56 | // 获得 Mapper 对应的数据权限的规则 |
| 45 | 57 | List<DataPermissionRule> rules = ruleFactory.getDataPermissionRule(ms.getId()); |
| 46 | 58 | if (mappedStatementCache.noRewritable(ms, rules)) { // 如果无需重写,则跳过 |
| ... | ... | @@ -59,12 +71,12 @@ public class DataPermissionInterceptor extends JsqlParserSupport implements Inne |
| 59 | 71 | } |
| 60 | 72 | } |
| 61 | 73 | |
| 62 | - @Override // 只处理 UPDATE / DELETE 场景 | |
| 74 | + @Override // 只处理 UPDATE / DELETE 场景,不处理 INSERT 场景 | |
| 63 | 75 | public void beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) { |
| 64 | 76 | PluginUtils.MPStatementHandler mpSh = PluginUtils.mpStatementHandler(sh); |
| 65 | 77 | MappedStatement ms = mpSh.mappedStatement(); |
| 66 | 78 | SqlCommandType sct = ms.getSqlCommandType(); |
| 67 | - if (sct == SqlCommandType.UPDATE || sct == SqlCommandType.DELETE) { // 无需处理 Insert 语句 | |
| 79 | + if (sct == SqlCommandType.UPDATE || sct == SqlCommandType.DELETE) { | |
| 68 | 80 | // 获得 Mapper 对应的数据权限的规则 |
| 69 | 81 | List<DataPermissionRule> rules = ruleFactory.getDataPermissionRule(ms.getId()); |
| 70 | 82 | if (mappedStatementCache.noRewritable(ms, rules)) { // 如果无需重写,则跳过 |
| ... | ... | @@ -117,7 +129,8 @@ public class DataPermissionInterceptor extends JsqlParserSupport implements Inne |
| 117 | 129 | @Override |
| 118 | 130 | protected void processUpdate(Update update, int index, String sql, Object obj) { |
| 119 | 131 | final Table table = update.getTable(); |
| 120 | - update.setWhere(this.andExpression(table, update.getWhere())); | |
| 132 | +// update.setWhere(this.andExpression(table, update.getWhere())); | |
| 133 | + update.setWhere(this.builderExpression(update.getWhere(), table)); | |
| 121 | 134 | } |
| 122 | 135 | |
| 123 | 136 | /** |
| ... | ... | @@ -125,26 +138,27 @@ public class DataPermissionInterceptor extends JsqlParserSupport implements Inne |
| 125 | 138 | */ |
| 126 | 139 | @Override |
| 127 | 140 | protected void processDelete(Delete delete, int index, String sql, Object obj) { |
| 128 | - delete.setWhere(this.andExpression(delete.getTable(), delete.getWhere())); | |
| 141 | +// delete.setWhere(this.andExpression(delete.getTable(), delete.getWhere())); | |
| 142 | + delete.setWhere(this.builderExpression(delete.getWhere(), delete.getTable())); | |
| 129 | 143 | } |
| 130 | 144 | |
| 131 | - /** | |
| 132 | - * delete update 语句 where 处理 | |
| 133 | - */ | |
| 134 | - protected BinaryExpression andExpression(Table table, Expression where) { | |
| 135 | - //获得where条件表达式 | |
| 136 | - EqualsTo equalsTo = new EqualsTo(); | |
| 137 | - equalsTo.setLeftExpression(this.getAliasColumn(table)); | |
| 138 | - equalsTo.setRightExpression(getTenantId()); | |
| 139 | - if (null != where) { | |
| 140 | - if (where instanceof OrExpression) { | |
| 141 | - return new AndExpression(equalsTo, new Parenthesis(where)); | |
| 142 | - } else { | |
| 143 | - return new AndExpression(equalsTo, where); | |
| 144 | - } | |
| 145 | - } | |
| 146 | - return equalsTo; | |
| 147 | - } | |
| 145 | +// /** | |
| 146 | +// * delete update 语句 where 处理 | |
| 147 | +// */ | |
| 148 | +// protected BinaryExpression andExpression(Table table, Expression where) { | |
| 149 | +// //获得where条件表达式 | |
| 150 | +// EqualsTo equalsTo = new EqualsTo(); | |
| 151 | +// equalsTo.setLeftExpression(this.getAliasColumn(table)); | |
| 152 | +// equalsTo.setRightExpression(getTenantId()); | |
| 153 | +// if (null != where) { | |
| 154 | +// if (where instanceof OrExpression) { | |
| 155 | +// return new AndExpression(equalsTo, new Parenthesis(where)); | |
| 156 | +// } else { | |
| 157 | +// return new AndExpression(equalsTo, where); | |
| 158 | +// } | |
| 159 | +// } | |
| 160 | +// return equalsTo; | |
| 161 | +// } | |
| 148 | 162 | |
| 149 | 163 | /** |
| 150 | 164 | * 处理 PlainSelect |
| ... | ... | @@ -155,10 +169,11 @@ public class DataPermissionInterceptor extends JsqlParserSupport implements Inne |
| 155 | 169 | processWhereSubSelect(where); |
| 156 | 170 | if (fromItem instanceof Table) { |
| 157 | 171 | Table fromTable = (Table) fromItem; |
| 158 | - if (!ignoreTable(fromTable.getName())) { | |
| 159 | - //#1186 github | |
| 160 | - plainSelect.setWhere(builderExpression(where, fromTable)); | |
| 161 | - } | |
| 172 | +// if (!ignoreTable(fromTable.getName())) { | |
| 173 | +// //#1186 github | |
| 174 | +// plainSelect.setWhere(builderExpression(where, fromTable)); | |
| 175 | +// } | |
| 176 | + plainSelect.setWhere(builderExpression(where, fromTable)); | |
| 162 | 177 | } else { |
| 163 | 178 | processFromItem(fromItem); |
| 164 | 179 | } |
| ... | ... | @@ -311,20 +326,21 @@ public class DataPermissionInterceptor extends JsqlParserSupport implements Inne |
| 311 | 326 | processJoin(join); |
| 312 | 327 | continue; |
| 313 | 328 | } |
| 314 | - // 当前表是否忽略 | |
| 315 | - boolean needIgnore = ignoreTable(fromTable.getName()); | |
| 316 | - // 表名压栈,忽略的表压入 null,以便后续不处理 | |
| 317 | - tables.push(needIgnore ? null : fromTable); | |
| 329 | +// // 当前表是否忽略 | |
| 330 | +// boolean needIgnore = ignoreTable(fromTable.getName()); | |
| 331 | +// // 表名压栈,忽略的表压入 null,以便后续不处理 | |
| 332 | +// tables.push(needIgnore ? null : fromTable); | |
| 318 | 333 | // 尾缀多个 on 表达式的时候统一处理 |
| 319 | 334 | if (originOnExpressions.size() > 1) { |
| 320 | 335 | Collection<Expression> onExpressions = new LinkedList<>(); |
| 321 | 336 | for (Expression originOnExpression : originOnExpressions) { |
| 322 | 337 | Table currentTable = tables.poll(); |
| 323 | - if (currentTable == null) { | |
| 324 | - onExpressions.add(originOnExpression); | |
| 325 | - } else { | |
| 326 | - onExpressions.add(builderExpression(originOnExpression, currentTable)); | |
| 327 | - } | |
| 338 | +// if (currentTable == null) { | |
| 339 | +// onExpressions.add(originOnExpression); | |
| 340 | +// } else { | |
| 341 | +// onExpressions.add(builderExpression(originOnExpression, currentTable)); | |
| 342 | +// } | |
| 343 | + onExpressions.add(builderExpression(originOnExpression, currentTable)); | |
| 328 | 344 | } |
| 329 | 345 | join.setOnExpressions(onExpressions); |
| 330 | 346 | } |
| ... | ... | @@ -341,15 +357,18 @@ public class DataPermissionInterceptor extends JsqlParserSupport implements Inne |
| 341 | 357 | protected void processJoin(Join join) { |
| 342 | 358 | if (join.getRightItem() instanceof Table) { |
| 343 | 359 | Table fromTable = (Table) join.getRightItem(); |
| 344 | - if (ignoreTable(fromTable.getName())) { | |
| 345 | - // 过滤退出执行 | |
| 346 | - return; | |
| 347 | - } | |
| 360 | +// if (ignoreTable(fromTable.getName())) { | |
| 361 | +// // 过滤退出执行 | |
| 362 | +// return; | |
| 363 | +// } | |
| 348 | 364 | // 走到这里说明 on 表达式肯定只有一个 |
| 349 | - Collection<Expression> originOnExpressions = join.getOnExpressions(); | |
| 350 | - List<Expression> onExpressions = new LinkedList<>(); | |
| 351 | - onExpressions.add(builderExpression(originOnExpressions.iterator().next(), fromTable)); | |
| 352 | - join.setOnExpressions(onExpressions); | |
| 365 | +// Collection<Expression> originOnExpressions = join.getOnExpressions(); | |
| 366 | +// List<Expression> onExpressions = new LinkedList<>(); | |
| 367 | +// onExpressions.add(builderExpression(originOnExpressions.iterator().next(), fromTable)); | |
| 368 | +// join.setOnExpressions(onExpressions); | |
| 369 | + Expression originOnExpression = CollUtil.getFirst(join.getOnExpressions()); | |
| 370 | + originOnExpression = builderExpression(originOnExpression, fromTable); | |
| 371 | + join.setOnExpressions(CollUtil.newArrayList(originOnExpression)); | |
| 353 | 372 | } |
| 354 | 373 | } |
| 355 | 374 | |
| ... | ... | @@ -357,50 +376,69 @@ public class DataPermissionInterceptor extends JsqlParserSupport implements Inne |
| 357 | 376 | * 处理条件 |
| 358 | 377 | */ |
| 359 | 378 | protected Expression builderExpression(Expression currentExpression, Table table) { |
| 360 | - EqualsTo equalsTo = new EqualsTo(); | |
| 361 | - equalsTo.setLeftExpression(this.getAliasColumn(table)); | |
| 362 | - equalsTo.setRightExpression(getTenantId()); | |
| 379 | + // 获得 Table 对应的数据权限条件 | |
| 380 | + Expression equalsTo = buildDataPermissionExpression(table); | |
| 381 | + if (equalsTo == null) { // 如果没条件,则返回 currentExpression 默认 | |
| 382 | + return currentExpression; | |
| 383 | + } | |
| 384 | + | |
| 385 | + // 表达式为空,则直接返回 equalsTo | |
| 363 | 386 | if (currentExpression == null) { |
| 364 | 387 | return equalsTo; |
| 365 | 388 | } |
| 389 | + // 如果表达式为 Or,则需要 (currentExpression) AND equalsTo | |
| 366 | 390 | if (currentExpression instanceof OrExpression) { |
| 367 | 391 | return new AndExpression(new Parenthesis(currentExpression), equalsTo); |
| 368 | - } else { | |
| 369 | - return new AndExpression(currentExpression, equalsTo); | |
| 370 | 392 | } |
| 393 | + // 如果表达式为 And,则直接返回 currentExpression AND equalsTo | |
| 394 | + return new AndExpression(currentExpression, equalsTo); | |
| 371 | 395 | } |
| 372 | 396 | |
| 397 | +// /** | |
| 398 | +// * 租户字段别名设置 | |
| 399 | +// * <p>tenantId 或 tableAlias.tenantId</p> | |
| 400 | +// * | |
| 401 | +// * @param table 表对象 | |
| 402 | +// * @return 字段 | |
| 403 | +// */ | |
| 404 | +// protected Column getAliasColumn(Table table) { | |
| 405 | +// StringBuilder column = new StringBuilder(); | |
| 406 | +// if (table.getAlias() != null) { | |
| 407 | +// column.append(table.getAlias().getName()).append(StringPool.DOT); | |
| 408 | +// } | |
| 409 | +// column.append(getTenantIdColumn()); | |
| 410 | +// return new Column(column.toString()); | |
| 411 | +// } | |
| 412 | + | |
| 373 | 413 | /** |
| 374 | - * 租户字段别名设置 | |
| 375 | - * <p>tenantId 或 tableAlias.tenantId</p> | |
| 414 | + * 构建指定表的数据权限的 Expression 过滤条件 | |
| 376 | 415 | * |
| 377 | - * @param table 表对象 | |
| 378 | - * @return 字段 | |
| 416 | + * @param table 表 | |
| 417 | + * @return Expression 过滤条件 | |
| 379 | 418 | */ |
| 380 | - protected Column getAliasColumn(Table table) { | |
| 381 | - StringBuilder column = new StringBuilder(); | |
| 382 | - if (table.getAlias() != null) { | |
| 383 | - column.append(table.getAlias().getName()).append(StringPool.DOT); | |
| 419 | + private Expression buildDataPermissionExpression(Table table) { | |
| 420 | + // 生成条件 | |
| 421 | + Expression allExpression = null; | |
| 422 | + for (DataPermissionRule rule : ContextHolder.getRules()) { | |
| 423 | + // 判断表名是否匹配 | |
| 424 | + if (!rule.getTableNames().contains(table.getName())) { | |
| 425 | + continue; | |
| 426 | + } | |
| 427 | + // 单条规则的条件 | |
| 428 | + String tableName = MyBatisUtils.getTableName(table); | |
| 429 | + Expression oneExpress = rule.getExpression(tableName, table.getAlias()); | |
| 430 | + // 拼接到 allExpression 中 | |
| 431 | + allExpression = allExpression == null ? oneExpress | |
| 432 | + : new AndExpression(allExpression, oneExpress); | |
| 384 | 433 | } |
| 385 | - column.append(getTenantIdColumn()); | |
| 386 | - return new Column(column.toString()); | |
| 387 | - } | |
| 388 | - | |
| 389 | - // TODO 芋艿:未实现 | |
| 390 | 434 | |
| 391 | - private boolean ignoreTable(String tableName) { | |
| 392 | - return false; | |
| 393 | - } | |
| 394 | - | |
| 395 | - private String getTenantIdColumn() { | |
| 396 | - return "dept_id"; | |
| 397 | - } | |
| 398 | - | |
| 399 | - private Expression getTenantId() { | |
| 400 | - return new LongValue(1L); | |
| 435 | + // 如果条件非空,说明已经重写了 | |
| 436 | + if (allExpression != null) { | |
| 437 | + ContextHolder.setRewrite(true); | |
| 438 | + } | |
| 439 | + return allExpression; | |
| 401 | 440 | } |
| 402 | 441 | |
| 403 | - | |
| 404 | 442 | /** |
| 405 | 443 | * 判断 SQL 是否重写。如果没有重写,则添加到 {@link MappedStatementCache} 中 |
| 406 | 444 | * | ... | ... |
yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/util/MyBatisUtils.java
| ... | ... | @@ -7,6 +7,7 @@ import com.baomidou.mybatisplus.core.metadata.OrderItem; |
| 7 | 7 | import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; |
| 8 | 8 | import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor; |
| 9 | 9 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| 10 | +import net.sf.jsqlparser.schema.Table; | |
| 10 | 11 | |
| 11 | 12 | import java.util.ArrayList; |
| 12 | 13 | import java.util.Collection; |
| ... | ... | @@ -18,6 +19,8 @@ import java.util.stream.Collectors; |
| 18 | 19 | */ |
| 19 | 20 | public class MyBatisUtils { |
| 20 | 21 | |
| 22 | + private static final String MYSQL_ESCAPE_CHARACTER = "`"; | |
| 23 | + | |
| 21 | 24 | public static <T> Page<T> buildPage(PageParam pageParam) { |
| 22 | 25 | return buildPage(pageParam, null); |
| 23 | 26 | } |
| ... | ... | @@ -48,4 +51,20 @@ public class MyBatisUtils { |
| 48 | 51 | interceptor.setInterceptors(inners); |
| 49 | 52 | } |
| 50 | 53 | |
| 54 | + /** | |
| 55 | + * 获得 Table 对应的表名 | |
| 56 | + * | |
| 57 | + * 兼容 MySQL 转义表名 `t_xxx` | |
| 58 | + * | |
| 59 | + * @param table 表 | |
| 60 | + * @return 去除转移字符后的表名 | |
| 61 | + */ | |
| 62 | + public static String getTableName(Table table) { | |
| 63 | + String tableName = table.getName(); | |
| 64 | + if (tableName.startsWith(MYSQL_ESCAPE_CHARACTER) && tableName.endsWith(MYSQL_ESCAPE_CHARACTER)) { | |
| 65 | + tableName = tableName.substring(1, tableName.length() - 1); | |
| 66 | + } | |
| 67 | + return tableName; | |
| 68 | + } | |
| 69 | + | |
| 51 | 70 | } | ... | ... |