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,8 +29,8 @@ | ||
| 29 | 29 | ||
| 30 | <!-- Test 测试相关 --> | 30 | <!-- Test 测试相关 --> |
| 31 | <dependency> | 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 | <scope>test</scope> | 34 | <scope>test</scope> |
| 35 | </dependency> | 35 | </dependency> |
| 36 | </dependencies> | 36 | </dependencies> |
yudao-framework/yudao-spring-boot-starter-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/core/interceptor/DataPermissionInterceptor.java
| 1 | package cn.iocoder.yudao.framework.datapermission.core.interceptor; | 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 | import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; | 8 | import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; |
| 5 | import com.baomidou.mybatisplus.core.toolkit.PluginUtils; | 9 | import com.baomidou.mybatisplus.core.toolkit.PluginUtils; |
| 6 | import com.baomidou.mybatisplus.core.toolkit.StringPool; | 10 | import com.baomidou.mybatisplus.core.toolkit.StringPool; |
| 7 | import com.baomidou.mybatisplus.extension.parser.JsqlParserSupport; | 11 | import com.baomidou.mybatisplus.extension.parser.JsqlParserSupport; |
| 8 | import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor; | 12 | import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor; |
| 13 | +import lombok.RequiredArgsConstructor; | ||
| 9 | import net.sf.jsqlparser.expression.*; | 14 | import net.sf.jsqlparser.expression.*; |
| 10 | import net.sf.jsqlparser.expression.operators.conditional.AndExpression; | 15 | import net.sf.jsqlparser.expression.operators.conditional.AndExpression; |
| 11 | import net.sf.jsqlparser.expression.operators.conditional.OrExpression; | 16 | import net.sf.jsqlparser.expression.operators.conditional.OrExpression; |
| @@ -24,33 +29,58 @@ import org.apache.ibatis.session.ResultHandler; | @@ -24,33 +29,58 @@ import org.apache.ibatis.session.ResultHandler; | ||
| 24 | import org.apache.ibatis.session.RowBounds; | 29 | import org.apache.ibatis.session.RowBounds; |
| 25 | 30 | ||
| 26 | import java.sql.Connection; | 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 | public class DataPermissionInterceptor extends JsqlParserSupport implements InnerInterceptor { | 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 | public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { | 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 | PluginUtils.MPBoundSql mpBs = PluginUtils.mpBoundSql(boundSql); | 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 | public void beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) { | 63 | public void beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) { |
| 47 | PluginUtils.MPStatementHandler mpSh = PluginUtils.mpStatementHandler(sh); | 64 | PluginUtils.MPStatementHandler mpSh = PluginUtils.mpStatementHandler(sh); |
| 48 | MappedStatement ms = mpSh.mappedStatement(); | 65 | MappedStatement ms = mpSh.mappedStatement(); |
| 49 | SqlCommandType sct = ms.getSqlCommandType(); | 66 | SqlCommandType sct = ms.getSqlCommandType(); |
| 50 | if (sct == SqlCommandType.UPDATE || sct == SqlCommandType.DELETE) { // 无需处理 Insert 语句 | 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 | PluginUtils.MPBoundSql mpBs = mpSh.mPBoundSql(); | 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,10 +117,6 @@ public class DataPermissionInterceptor extends JsqlParserSupport implements Inne | ||
| 87 | @Override | 117 | @Override |
| 88 | protected void processUpdate(Update update, int index, String sql, Object obj) { | 118 | protected void processUpdate(Update update, int index, String sql, Object obj) { |
| 89 | final Table table = update.getTable(); | 119 | final Table table = update.getTable(); |
| 90 | - if (ignoreTable(table.getName())) { | ||
| 91 | - // 过滤退出执行 | ||
| 92 | - return; | ||
| 93 | - } | ||
| 94 | update.setWhere(this.andExpression(table, update.getWhere())); | 120 | update.setWhere(this.andExpression(table, update.getWhere())); |
| 95 | } | 121 | } |
| 96 | 122 | ||
| @@ -99,10 +125,6 @@ public class DataPermissionInterceptor extends JsqlParserSupport implements Inne | @@ -99,10 +125,6 @@ public class DataPermissionInterceptor extends JsqlParserSupport implements Inne | ||
| 99 | */ | 125 | */ |
| 100 | @Override | 126 | @Override |
| 101 | protected void processDelete(Delete delete, int index, String sql, Object obj) { | 127 | protected void processDelete(Delete delete, int index, String sql, Object obj) { |
| 102 | - if (ignoreTable(delete.getTable().getName())) { | ||
| 103 | - // 过滤退出执行 | ||
| 104 | - return; | ||
| 105 | - } | ||
| 106 | delete.setWhere(this.andExpression(delete.getTable(), delete.getWhere())); | 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,4 +400,116 @@ public class DataPermissionInterceptor extends JsqlParserSupport implements Inne | ||
| 378 | return new LongValue(1L); | 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 | package cn.iocoder.yudao.framework.datapermission.core.rule; | 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 | public interface DataPermissionRuleFactory { | 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 | package cn.iocoder.yudao.framework.datapermission.core.interceptor; | 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 | import org.junit.jupiter.api.Test; | 5 | import org.junit.jupiter.api.Test; |
| 6 | +import org.mockito.InjectMocks; | ||
| 7 | +import org.mockito.Mock; | ||
| 4 | 8 | ||
| 5 | import static org.assertj.core.api.Assertions.assertThat; | 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 | @Test | 19 | @Test |
| 12 | public void selectSingle() { | 20 | public void selectSingle() { |
yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/core/pubsub/AbstractChannelMessage.java
| @@ -15,7 +15,7 @@ public abstract class AbstractChannelMessage extends AbstractRedisMessage { | @@ -15,7 +15,7 @@ public abstract class AbstractChannelMessage extends AbstractRedisMessage { | ||
| 15 | * | 15 | * |
| 16 | * @return Channel | 16 | * @return Channel |
| 17 | */ | 17 | */ |
| 18 | - @JsonIgnore // 避免序列化 | 18 | + @JsonIgnore // 避免序列化。原因是,Redis 发布 Channel 消息的时候,已经会指定。 |
| 19 | public abstract String getChannel(); | 19 | public abstract String getChannel(); |
| 20 | 20 | ||
| 21 | } | 21 | } |