Commit eda2b11dad7386e0e37efb09a2ddb8251b0415c4
1 parent
e9ba4ac7
增加数据权限的 SQL 重写的上下文
Showing
5 changed files
with
191 additions
and
30 deletions
yudao-framework/yudao-spring-boot-starter-data-permission/pom.xml
| ... | ... | @@ -29,8 +29,8 @@ |
| 29 | 29 | |
| 30 | 30 | <!-- Test 测试相关 --> |
| 31 | 31 | <dependency> |
| 32 | - <groupId>org.springframework.boot</groupId> | |
| 33 | - <artifactId>spring-boot-starter-test</artifactId> | |
| 32 | + <groupId>cn.iocoder.boot</groupId> | |
| 33 | + <artifactId>yudao-spring-boot-starter-test</artifactId> | |
| 34 | 34 | <scope>test</scope> |
| 35 | 35 | </dependency> |
| 36 | 36 | </dependencies> | ... | ... |
yudao-framework/yudao-spring-boot-starter-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/core/interceptor/DataPermissionInterceptor.java
| 1 | 1 | package cn.iocoder.yudao.framework.datapermission.core.interceptor; |
| 2 | 2 | |
| 3 | -import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper; | |
| 3 | +import cn.hutool.core.collection.CollUtil; | |
| 4 | +import cn.iocoder.yudao.framework.common.util.collection.SetUtils; | |
| 5 | +import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRule; | |
| 6 | +import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRuleFactory; | |
| 7 | +import com.alibaba.ttl.TransmittableThreadLocal; | |
| 4 | 8 | import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; |
| 5 | 9 | import com.baomidou.mybatisplus.core.toolkit.PluginUtils; |
| 6 | 10 | import com.baomidou.mybatisplus.core.toolkit.StringPool; |
| 7 | 11 | import com.baomidou.mybatisplus.extension.parser.JsqlParserSupport; |
| 8 | 12 | import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor; |
| 13 | +import lombok.RequiredArgsConstructor; | |
| 9 | 14 | import net.sf.jsqlparser.expression.*; |
| 10 | 15 | import net.sf.jsqlparser.expression.operators.conditional.AndExpression; |
| 11 | 16 | import net.sf.jsqlparser.expression.operators.conditional.OrExpression; |
| ... | ... | @@ -24,33 +29,58 @@ import org.apache.ibatis.session.ResultHandler; |
| 24 | 29 | import org.apache.ibatis.session.RowBounds; |
| 25 | 30 | |
| 26 | 31 | import java.sql.Connection; |
| 27 | -import java.util.Collection; | |
| 28 | -import java.util.Deque; | |
| 29 | -import java.util.LinkedList; | |
| 30 | -import java.util.List; | |
| 32 | +import java.util.*; | |
| 33 | +import java.util.concurrent.ConcurrentHashMap; | |
| 31 | 34 | |
| 35 | +@RequiredArgsConstructor | |
| 32 | 36 | public class DataPermissionInterceptor extends JsqlParserSupport implements InnerInterceptor { |
| 33 | 37 | |
| 34 | -// private TenantLineHandler tenantLineHandler; | |
| 38 | + private final DataPermissionRuleFactory ruleFactory; | |
| 35 | 39 | |
| 36 | - @Override | |
| 40 | + private final MappedStatementCache mappedStatementCache = new MappedStatementCache(); | |
| 41 | + | |
| 42 | + @Override // SELECT 场景 | |
| 37 | 43 | public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { |
| 38 | - // TODO 芋艿:这个判断,后续读懂下 | |
| 39 | - if (InterceptorIgnoreHelper.willIgnoreTenantLine(ms.getId())) return; | |
| 44 | + // 获得 Mapper 对应的数据权限的规则 | |
| 45 | + List<DataPermissionRule> rules = ruleFactory.getDataPermissionRule(ms.getId()); | |
| 46 | + if (mappedStatementCache.noRewritable(ms, rules)) { // 如果无需重写,则跳过 | |
| 47 | + return; | |
| 48 | + } | |
| 49 | + | |
| 40 | 50 | PluginUtils.MPBoundSql mpBs = PluginUtils.mpBoundSql(boundSql); |
| 41 | - // TODO 芋艿:null=》DataScope | |
| 42 | - mpBs.sql(parserSingle(mpBs.sql(), null)); | |
| 51 | + try { | |
| 52 | + // 初始化上下文 | |
| 53 | + ContextHolder.init(rules); | |
| 54 | + // 处理 SQL | |
| 55 | + mpBs.sql(parserSingle(mpBs.sql(), null)); | |
| 56 | + } finally { | |
| 57 | + addMappedStatementCache(ms); | |
| 58 | + ContextHolder.clear(); | |
| 59 | + } | |
| 43 | 60 | } |
| 44 | 61 | |
| 45 | - @Override | |
| 62 | + @Override // 只处理 UPDATE / DELETE 场景 | |
| 46 | 63 | public void beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) { |
| 47 | 64 | PluginUtils.MPStatementHandler mpSh = PluginUtils.mpStatementHandler(sh); |
| 48 | 65 | MappedStatement ms = mpSh.mappedStatement(); |
| 49 | 66 | SqlCommandType sct = ms.getSqlCommandType(); |
| 50 | 67 | if (sct == SqlCommandType.UPDATE || sct == SqlCommandType.DELETE) { // 无需处理 Insert 语句 |
| 51 | - if (InterceptorIgnoreHelper.willIgnoreTenantLine(ms.getId())) return; | |
| 68 | + // 获得 Mapper 对应的数据权限的规则 | |
| 69 | + List<DataPermissionRule> rules = ruleFactory.getDataPermissionRule(ms.getId()); | |
| 70 | + if (mappedStatementCache.noRewritable(ms, rules)) { // 如果无需重写,则跳过 | |
| 71 | + return; | |
| 72 | + } | |
| 73 | + | |
| 52 | 74 | PluginUtils.MPBoundSql mpBs = mpSh.mPBoundSql(); |
| 53 | - mpBs.sql(parserMulti(mpBs.sql(), null)); | |
| 75 | + try { | |
| 76 | + // 初始化上下文 | |
| 77 | + ContextHolder.init(rules); | |
| 78 | + // 处理 SQL | |
| 79 | + mpBs.sql(parserMulti(mpBs.sql(), null)); | |
| 80 | + } finally { | |
| 81 | + addMappedStatementCache(ms); | |
| 82 | + ContextHolder.clear(); | |
| 83 | + } | |
| 54 | 84 | } |
| 55 | 85 | } |
| 56 | 86 | |
| ... | ... | @@ -87,10 +117,6 @@ public class DataPermissionInterceptor extends JsqlParserSupport implements Inne |
| 87 | 117 | @Override |
| 88 | 118 | protected void processUpdate(Update update, int index, String sql, Object obj) { |
| 89 | 119 | final Table table = update.getTable(); |
| 90 | - if (ignoreTable(table.getName())) { | |
| 91 | - // 过滤退出执行 | |
| 92 | - return; | |
| 93 | - } | |
| 94 | 120 | update.setWhere(this.andExpression(table, update.getWhere())); |
| 95 | 121 | } |
| 96 | 122 | |
| ... | ... | @@ -99,10 +125,6 @@ public class DataPermissionInterceptor extends JsqlParserSupport implements Inne |
| 99 | 125 | */ |
| 100 | 126 | @Override |
| 101 | 127 | protected void processDelete(Delete delete, int index, String sql, Object obj) { |
| 102 | - if (ignoreTable(delete.getTable().getName())) { | |
| 103 | - // 过滤退出执行 | |
| 104 | - return; | |
| 105 | - } | |
| 106 | 128 | delete.setWhere(this.andExpression(delete.getTable(), delete.getWhere())); |
| 107 | 129 | } |
| 108 | 130 | |
| ... | ... | @@ -378,4 +400,116 @@ public class DataPermissionInterceptor extends JsqlParserSupport implements Inne |
| 378 | 400 | return new LongValue(1L); |
| 379 | 401 | } |
| 380 | 402 | |
| 403 | + | |
| 404 | + /** | |
| 405 | + * 判断 SQL 是否重写。如果没有重写,则添加到 {@link MappedStatementCache} 中 | |
| 406 | + * | |
| 407 | + * @param ms MappedStatement | |
| 408 | + */ | |
| 409 | + private void addMappedStatementCache(MappedStatement ms) { | |
| 410 | + if (ContextHolder.getRewrite()) { | |
| 411 | + return; | |
| 412 | + } | |
| 413 | + // 有重写,进行添加 | |
| 414 | + mappedStatementCache.addNoRewritable(ms, ContextHolder.getRules()); | |
| 415 | + } | |
| 416 | + | |
| 417 | + /** | |
| 418 | + * SQL 解析上下文,方便透传 {@link DataPermissionRule} 规则 | |
| 419 | + * | |
| 420 | + * @author 芋道源码 | |
| 421 | + */ | |
| 422 | + private static final class ContextHolder { | |
| 423 | + | |
| 424 | + /** | |
| 425 | + * 该 {@link MappedStatement} 对应的规则 | |
| 426 | + */ | |
| 427 | + private static final ThreadLocal<List<DataPermissionRule>> RULES = new TransmittableThreadLocal<>(); | |
| 428 | + /** | |
| 429 | + * SQL 是否进行重写 | |
| 430 | + */ | |
| 431 | + private static final ThreadLocal<Boolean> REWRITE = new TransmittableThreadLocal<>(); | |
| 432 | + | |
| 433 | + public static void init(List<DataPermissionRule> rules) { | |
| 434 | + RULES.set(rules); | |
| 435 | + REWRITE.set(false); | |
| 436 | + } | |
| 437 | + | |
| 438 | + public static void clear() { | |
| 439 | + RULES.remove(); | |
| 440 | + REWRITE.remove(); | |
| 441 | + } | |
| 442 | + | |
| 443 | + public static boolean getRewrite() { | |
| 444 | + return REWRITE.get(); | |
| 445 | + } | |
| 446 | + | |
| 447 | + public static void setRewrite(boolean rewrite) { | |
| 448 | + REWRITE.set(rewrite); | |
| 449 | + } | |
| 450 | + | |
| 451 | + public static List<DataPermissionRule> getRules() { | |
| 452 | + return RULES.get(); | |
| 453 | + } | |
| 454 | + | |
| 455 | + } | |
| 456 | + | |
| 457 | + /** | |
| 458 | + * {@link MappedStatement} 缓存 | |
| 459 | + * 目前主要用于,记录 {@link DataPermissionRule} 是否对指定 {@link MappedStatement} 无效 | |
| 460 | + * 如果无效,则可以避免 SQL 的解析,加快速度 | |
| 461 | + * | |
| 462 | + * @author 芋道源码 | |
| 463 | + */ | |
| 464 | + private static final class MappedStatementCache { | |
| 465 | + | |
| 466 | + /** | |
| 467 | + * 无需重写的映射 | |
| 468 | + * | |
| 469 | + * value:{@link MappedStatement#getId()} 编号 | |
| 470 | + */ | |
| 471 | + private final Map<Class<? extends DataPermissionRule>, Set<String>> noRewritableMappedStatements = new ConcurrentHashMap<>(); | |
| 472 | + | |
| 473 | + /** | |
| 474 | + * 判断是否无需重写 | |
| 475 | + * ps:虽然有点中文式英语,但是容易读懂即可 | |
| 476 | + * | |
| 477 | + * @param ms MappedStatement | |
| 478 | + * @param rules 数据权限规则数组 | |
| 479 | + * @return 是否无需重写 | |
| 480 | + */ | |
| 481 | + public boolean noRewritable(MappedStatement ms, List<DataPermissionRule> rules) { | |
| 482 | + // 如果规则为空,说明无需重写 | |
| 483 | + if (CollUtil.isEmpty(rules)) { | |
| 484 | + return true; | |
| 485 | + } | |
| 486 | + // 任一规则不在 noRewritableMap 中,则说明可能需要重写 | |
| 487 | + for (DataPermissionRule rule : rules) { | |
| 488 | + Set<String> mappedStatementIds = noRewritableMappedStatements.get(rule.getClass()); | |
| 489 | + if (!CollUtil.contains(mappedStatementIds, ms.getId())) { // 不存在,则说明可能要重写 | |
| 490 | + return false; | |
| 491 | + } | |
| 492 | + } | |
| 493 | + return true; | |
| 494 | + } | |
| 495 | + | |
| 496 | + /** | |
| 497 | + * 添加无需重写的 MappedStatement | |
| 498 | + * | |
| 499 | + * @param ms MappedStatement | |
| 500 | + * @param rules 数据权限规则数组 | |
| 501 | + */ | |
| 502 | + public void addNoRewritable(MappedStatement ms, List<DataPermissionRule> rules) { | |
| 503 | + for (DataPermissionRule rule : rules) { | |
| 504 | + Set<String> mappedStatementIds = noRewritableMappedStatements.get(rule.getClass()); | |
| 505 | + if (CollUtil.isNotEmpty(mappedStatementIds)) { | |
| 506 | + mappedStatementIds.add(ms.getId()); | |
| 507 | + } else { | |
| 508 | + noRewritableMappedStatements.put(rule.getClass(), SetUtils.asSet(ms.getId())); | |
| 509 | + } | |
| 510 | + } | |
| 511 | + } | |
| 512 | + | |
| 513 | + } | |
| 514 | + | |
| 381 | 515 | } | ... | ... |
yudao-framework/yudao-spring-boot-starter-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/core/rule/DataPermissionRuleFactory.java
| 1 | 1 | package cn.iocoder.yudao.framework.datapermission.core.rule; |
| 2 | 2 | |
| 3 | +import java.util.List; | |
| 4 | + | |
| 3 | 5 | /** |
| 4 | - * {@link DataPermissionRule} 工厂接口,提供如下能力: | |
| 5 | - * 1. {@link DataPermissionRule} 的容器 | |
| 6 | - * 2. TODO 芋艿: | |
| 6 | + * {@link DataPermissionRule} 工厂接口 | |
| 7 | + * 作为 {@link DataPermissionRule} 的容器,提供管理能力 | |
| 8 | + * | |
| 9 | + * @author 芋道源码 | |
| 7 | 10 | */ |
| 8 | 11 | public interface DataPermissionRuleFactory { |
| 12 | + | |
| 13 | + /** | |
| 14 | + * 获得所有数据权限规则数组 | |
| 15 | + * | |
| 16 | + * @return 数据权限规则数组 | |
| 17 | + */ | |
| 18 | + List<DataPermissionRule> getDataPermissionRules(); | |
| 19 | + | |
| 20 | + /** | |
| 21 | + * 获得指定 Mapper 的数据权限规则数组 | |
| 22 | + * | |
| 23 | + * @param mappedStatementId 指定 Mapper 的编号 | |
| 24 | + * @return 数据权限规则数组 | |
| 25 | + */ | |
| 26 | + List<DataPermissionRule> getDataPermissionRule(String mappedStatementId); | |
| 27 | + | |
| 9 | 28 | } | ... | ... |
yudao-framework/yudao-spring-boot-starter-data-permission/src/test/java/cn/iocoder/yudao/framework/datapermission/core/interceptor/DataPermissionInterceptorTest.java
| 1 | 1 | package cn.iocoder.yudao.framework.datapermission.core.interceptor; |
| 2 | 2 | |
| 3 | +import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRuleFactory; | |
| 4 | +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; | |
| 3 | 5 | import org.junit.jupiter.api.Test; |
| 6 | +import org.mockito.InjectMocks; | |
| 7 | +import org.mockito.Mock; | |
| 4 | 8 | |
| 5 | 9 | import static org.assertj.core.api.Assertions.assertThat; |
| 6 | 10 | |
| 7 | -public class DataPermissionInterceptorTest { | |
| 11 | +public class DataPermissionInterceptorTest extends BaseMockitoUnitTest { | |
| 8 | 12 | |
| 9 | - private final DataPermissionInterceptor interceptor = new DataPermissionInterceptor(); | |
| 13 | + @InjectMocks | |
| 14 | + private DataPermissionInterceptor interceptor; | |
| 15 | + | |
| 16 | + @Mock | |
| 17 | + private DataPermissionRuleFactory ruleFactory; | |
| 10 | 18 | |
| 11 | 19 | @Test |
| 12 | 20 | public void selectSingle() { | ... | ... |
yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/core/pubsub/AbstractChannelMessage.java