Commit eda2b11dad7386e0e37efb09a2ddb8251b0415c4

Authored by YunaiV
1 parent e9ba4ac7

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

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 }