Commit eda2b11dad7386e0e37efb09a2ddb8251b0415c4

Authored by YunaiV
1 parent e9ba4ac7

增加数据权限的 SQL 重写的上下文

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
... ... @@ -15,7 +15,7 @@ public abstract class AbstractChannelMessage extends AbstractRedisMessage {
15 15 *
16 16 * @return Channel
17 17 */
18   - @JsonIgnore // 避免序列化
  18 + @JsonIgnore // 避免序列化。原因是,Redis 发布 Channel 消息的时候,已经会指定。
19 19 public abstract String getChannel();
20 20  
21 21 }
... ...