Commit f9b15fe70d738d0896016312679278e5ea9bd5d0

Authored by YunaiV
1 parent 2334e177

1. 增加数据权限的自动配置 DataPermissionAutoConfiguration

2. 增加数据权限的 AOP DataPermissionAnnotationInterceptor
3. 重命名 DataPermissionDatabaseInterceptor
Showing 11 changed files with 340 additions and 15 deletions
yudao-framework/yudao-spring-boot-starter-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/config/DataPermissionAutoConfiguration.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.datapermission.config;
  2 +
  3 +import cn.iocoder.yudao.framework.datapermission.core.aop.DataPermissionAnnotationAdvisor;
  4 +import cn.iocoder.yudao.framework.datapermission.core.db.DataPermissionDatabaseInterceptor;
  5 +import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRule;
  6 +import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRuleFactory;
  7 +import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRuleFactoryImpl;
  8 +import org.springframework.context.annotation.Bean;
  9 +import org.springframework.context.annotation.Configuration;
  10 +
  11 +import java.util.List;
  12 +
  13 +@Configuration
  14 +public class DataPermissionAutoConfiguration {
  15 +
  16 + @Bean
  17 + public DataPermissionRuleFactory dataPermissionRuleFactory(List<DataPermissionRule> rules) {
  18 + return new DataPermissionRuleFactoryImpl(rules);
  19 + }
  20 +
  21 + @Bean
  22 + public DataPermissionDatabaseInterceptor dataPermissionDatabaseInterceptor(List<DataPermissionRule> rules) {
  23 + DataPermissionRuleFactory ruleFactory = dataPermissionRuleFactory(rules);
  24 + return new DataPermissionDatabaseInterceptor(ruleFactory);
  25 + }
  26 +
  27 + @Bean
  28 + public DataPermissionAnnotationAdvisor dataPermissionAnnotationAdvisor() {
  29 + return new DataPermissionAnnotationAdvisor();
  30 + }
  31 +
  32 +}
... ...
yudao-framework/yudao-spring-boot-starter-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/core/aop/DataPermissionAnnotationAdvisor.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.datapermission.core.aop;
  2 +
  3 +import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
  4 +import lombok.EqualsAndHashCode;
  5 +import lombok.Getter;
  6 +import org.aopalliance.aop.Advice;
  7 +import org.springframework.aop.Pointcut;
  8 +import org.springframework.aop.support.AbstractPointcutAdvisor;
  9 +import org.springframework.aop.support.ComposablePointcut;
  10 +import org.springframework.aop.support.annotation.AnnotationMatchingPointcut;
  11 +
  12 +/**
  13 + * {@link cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission} 注解的 Advisor 实现类
  14 + *
  15 + * @author 芋道源码
  16 + */
  17 +@Getter
  18 +@EqualsAndHashCode(callSuper = true)
  19 +public class DataPermissionAnnotationAdvisor extends AbstractPointcutAdvisor {
  20 +
  21 + private final Advice advice;
  22 +
  23 + private final Pointcut pointcut;
  24 +
  25 + public DataPermissionAnnotationAdvisor() {
  26 + this.advice = new DataPermissionAnnotationInterceptor();
  27 + this.pointcut = this.buildPointcut();
  28 + }
  29 +
  30 + protected Pointcut buildPointcut() {
  31 + Pointcut classPointcut = new AnnotationMatchingPointcut(DataPermission.class, true);
  32 + Pointcut methodPointcut = new AnnotationMatchingPointcut(null, DataPermission.class, true);
  33 + return new ComposablePointcut(classPointcut).union(methodPointcut);
  34 + }
  35 +
  36 +}
... ...
yudao-framework/yudao-spring-boot-starter-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/core/aop/DataPermissionAnnotationInterceptor.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.datapermission.core.aop;
  2 +
  3 +import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
  4 +import lombok.Getter;
  5 +import org.aopalliance.intercept.MethodInterceptor;
  6 +import org.aopalliance.intercept.MethodInvocation;
  7 +import org.springframework.core.MethodClassKey;
  8 +import org.springframework.core.annotation.AnnotationUtils;
  9 +
  10 +import java.lang.reflect.Method;
  11 +import java.util.Map;
  12 +import java.util.concurrent.ConcurrentHashMap;
  13 +
  14 +/**
  15 + * {@link DataPermission} 注解的拦截器
  16 + * 1. 在执行方法前,将 @DataPermission 注解入栈
  17 + * 2. 在执行方法后,将 @DataPermission 注解出栈
  18 + *
  19 + * @author 芋道源码
  20 + */
  21 +@DataPermission // 该注解,用于 {@link DATA_PERMISSION_NULL} 的空对象
  22 +public class DataPermissionAnnotationInterceptor implements MethodInterceptor {
  23 +
  24 + /**
  25 + * DataPermission 空对象,用于方法无 {@link DataPermission} 注解时,使用 DATA_PERMISSION_NULL 进行占位
  26 + */
  27 + static final DataPermission DATA_PERMISSION_NULL = DataPermissionAnnotationInterceptor.class.getAnnotation(DataPermission.class);
  28 +
  29 + @Getter
  30 + private final Map<MethodClassKey, DataPermission> dataPermissionCache = new ConcurrentHashMap<>();
  31 +
  32 + @Override
  33 + public Object invoke(MethodInvocation methodInvocation) throws Throwable {
  34 + // 入栈
  35 + DataPermission dataPermission = this.findAnnotation(methodInvocation);
  36 + if (dataPermission != null) {
  37 + DataPermissionContextHolder.push(dataPermission);
  38 + }
  39 + try {
  40 + // 执行逻辑
  41 + return methodInvocation.proceed();
  42 + } finally {
  43 + // 出栈
  44 + if (dataPermission != null) {
  45 + DataPermissionContextHolder.poll();
  46 + }
  47 + }
  48 + }
  49 +
  50 + private DataPermission findAnnotation(MethodInvocation methodInvocation) {
  51 + // 1. 从缓存中获取
  52 + Method method = methodInvocation.getMethod();
  53 + Object targetObject = methodInvocation.getThis();
  54 + Class<?> clazz = targetObject != null ? targetObject.getClass() : method.getDeclaringClass();
  55 + MethodClassKey methodClassKey = new MethodClassKey(method, clazz);
  56 + DataPermission dataPermission = dataPermissionCache.get(methodClassKey);
  57 + if (dataPermission != null) {
  58 + return dataPermission != DATA_PERMISSION_NULL ? dataPermission : null;
  59 + }
  60 +
  61 + // 2.1 从方法中获取
  62 + dataPermission = AnnotationUtils.findAnnotation(method, DataPermission.class);
  63 + // 2.2 从类上获取
  64 + if (dataPermission == null) {
  65 + dataPermission = AnnotationUtils.findAnnotation(clazz, DataPermission.class);
  66 + }
  67 + // 2.3 添加到缓存中
  68 + dataPermissionCache.put(methodClassKey, dataPermission != null ? dataPermission : DATA_PERMISSION_NULL);
  69 + return dataPermission;
  70 + }
  71 +
  72 +}
... ...
yudao-framework/yudao-spring-boot-starter-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/core/aop/DataPermissionContextHolder.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.datapermission.core.aop;
  2 +
  3 +import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
  4 +import com.alibaba.ttl.TransmittableThreadLocal;
  5 +
  6 +import java.util.ArrayDeque;
  7 +import java.util.Deque;
  8 +
  9 +/**
  10 + * {@link DataPermission} 注解的 Context 上下文
  11 + *
  12 + * @author 芋道源码
  13 + */
  14 +public class DataPermissionContextHolder {
  15 +
  16 + private static final ThreadLocal<Deque<DataPermission>> DATA_PERMISSIONS =
  17 + TransmittableThreadLocal.withInitial(ArrayDeque::new);
  18 +
  19 + /**
  20 + * 获得当前的 DataPermission 注解
  21 + *
  22 + * @return DataPermission 注解
  23 + */
  24 + public static DataPermission peek() {
  25 + return DATA_PERMISSIONS.get().remove();
  26 + }
  27 +
  28 + /**
  29 + * 入栈 DataPermission 注解
  30 + *
  31 + * @param dataPermission DataPermission 注解
  32 + */
  33 + public static void push(DataPermission dataPermission) {
  34 + DATA_PERMISSIONS.get().push(dataPermission);
  35 + }
  36 +
  37 + /**
  38 + * 出栈 DataPermission 注解
  39 + *
  40 + * @return DataPermission 注解
  41 + */
  42 + public static DataPermission poll() {
  43 + DataPermission dataPermission = DATA_PERMISSIONS.get().poll();
  44 + // 无元素时,清空 ThreadLocal
  45 + if (dataPermission == null) {
  46 + DATA_PERMISSIONS.remove();
  47 + }
  48 + return dataPermission;
  49 + }
  50 +
  51 +}
... ...
yudao-framework/yudao-spring-boot-starter-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/core/interceptor/DataPermissionInterceptor.java renamed to yudao-framework/yudao-spring-boot-starter-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/core/db/DataPermissionDatabaseInterceptor.java
1   -package cn.iocoder.yudao.framework.datapermission.core.interceptor;
  1 +package cn.iocoder.yudao.framework.datapermission.core.db;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
... ... @@ -45,7 +45,7 @@ import java.util.concurrent.ConcurrentHashMap;
45 45 * @author 芋道源码
46 46 */
47 47 @RequiredArgsConstructor
48   -public class DataPermissionInterceptor extends JsqlParserSupport implements InnerInterceptor {
  48 +public class DataPermissionDatabaseInterceptor extends JsqlParserSupport implements InnerInterceptor {
49 49  
50 50 private final DataPermissionRuleFactory ruleFactory;
51 51  
... ...
yudao-framework/yudao-spring-boot-starter-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/core/package-info.java deleted
1   -package cn.iocoder.yudao.framework.datapermission.core;
yudao-framework/yudao-spring-boot-starter-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/core/rule/DataPermissionRuleFactoryImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.datapermission.core.rule;
  2 +
  3 +import lombok.RequiredArgsConstructor;
  4 +
  5 +import java.util.List;
  6 +
  7 +@RequiredArgsConstructor
  8 +public class DataPermissionRuleFactoryImpl implements DataPermissionRuleFactory {
  9 +
  10 + /**
  11 + * 数据权限规则数组
  12 + */
  13 + private final List<DataPermissionRule> rules;
  14 +
  15 + @Override
  16 + public List<DataPermissionRule> getDataPermissionRules() {
  17 + return rules;
  18 + }
  19 +
  20 + @Override
  21 + public List<DataPermissionRule> getDataPermissionRule(String mappedStatementId) {
  22 + return null;
  23 + }
  24 +
  25 +}
... ...
yudao-framework/yudao-spring-boot-starter-data-permission/src/test/java/cn/iocoder/yudao/framework/datapermission/core/aop/DataPermissionAnnotationInterceptorTest.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.datapermission.core.aop;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
  5 +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
  6 +import org.aopalliance.intercept.MethodInvocation;
  7 +import org.junit.jupiter.api.BeforeEach;
  8 +import org.junit.jupiter.api.Test;
  9 +import org.mockito.InjectMocks;
  10 +import org.mockito.Mock;
  11 +
  12 +import java.lang.reflect.Method;
  13 +
  14 +import static org.junit.jupiter.api.Assertions.*;
  15 +import static org.mockito.Mockito.when;
  16 +
  17 +/**
  18 + * {@link DataPermissionAnnotationInterceptor} 的单元测试
  19 + *
  20 + * @author 芋道源码
  21 + */
  22 +public class DataPermissionAnnotationInterceptorTest extends BaseMockitoUnitTest {
  23 +
  24 + @InjectMocks
  25 + private DataPermissionAnnotationInterceptor interceptor;
  26 +
  27 + @Mock
  28 + private MethodInvocation methodInvocation;
  29 +
  30 + @BeforeEach
  31 + public void setUp() {
  32 + interceptor.getDataPermissionCache().clear();
  33 + }
  34 +
  35 + @Test // 无 @DataPermission 注解
  36 + public void testInvoke_none() throws Throwable {
  37 + // 参数
  38 + mockMethodInvocation(TestNone.class);
  39 +
  40 + // 调用
  41 + Object result = interceptor.invoke(methodInvocation);
  42 + // 断言
  43 + assertEquals("none", result);
  44 + assertEquals(1, interceptor.getDataPermissionCache().size());
  45 + assertTrue(CollUtil.getFirst(interceptor.getDataPermissionCache().values()).enable());
  46 + }
  47 +
  48 + @Test // 在 Method 上有 @DataPermission 注解
  49 + public void testInvoke_method() throws Throwable {
  50 + // 参数
  51 + mockMethodInvocation(TestMethod.class);
  52 +
  53 + // 调用
  54 + Object result = interceptor.invoke(methodInvocation);
  55 + // 断言
  56 + assertEquals("method", result);
  57 + assertEquals(1, interceptor.getDataPermissionCache().size());
  58 + assertFalse(CollUtil.getFirst(interceptor.getDataPermissionCache().values()).enable());
  59 + }
  60 +
  61 + @Test // 在 Class 上有 @DataPermission 注解
  62 + public void testInvoke_class() throws Throwable {
  63 + // 参数
  64 + mockMethodInvocation(TestClass.class);
  65 +
  66 + // 调用
  67 + Object result = interceptor.invoke(methodInvocation);
  68 + // 断言
  69 + assertEquals("class", result);
  70 + assertEquals(1, interceptor.getDataPermissionCache().size());
  71 + assertFalse(CollUtil.getFirst(interceptor.getDataPermissionCache().values()).enable());
  72 + }
  73 +
  74 + private void mockMethodInvocation(Class<?> clazz) throws Throwable {
  75 + Object targetObject = clazz.newInstance();
  76 + Method method = targetObject.getClass().getMethod("echo");
  77 + when(methodInvocation.getThis()).thenReturn(targetObject);
  78 + when(methodInvocation.getMethod()).thenReturn(method);
  79 + when(methodInvocation.proceed()).then(invocationOnMock -> method.invoke(targetObject));
  80 + }
  81 +
  82 + static class TestMethod {
  83 +
  84 + @DataPermission(enable = false)
  85 + public String echo() {
  86 + return "method";
  87 + }
  88 +
  89 + }
  90 +
  91 + @DataPermission(enable = false)
  92 + static class TestClass {
  93 +
  94 + public String echo() {
  95 + return "class";
  96 + }
  97 +
  98 + }
  99 +
  100 + static class TestNone {
  101 +
  102 + public String echo() {
  103 + return "none";
  104 + }
  105 +
  106 + }
  107 +
  108 +}
... ...
yudao-framework/yudao-spring-boot-starter-data-permission/src/test/java/cn/iocoder/yudao/framework/datapermission/core/interceptor/DataPermissionInterceptorTest.java renamed to yudao-framework/yudao-spring-boot-starter-data-permission/src/test/java/cn/iocoder/yudao/framework/datapermission/core/db/DataPermissionDatabaseInterceptorTest.java
1   -package cn.iocoder.yudao.framework.datapermission.core.interceptor;
  1 +package cn.iocoder.yudao.framework.datapermission.core.db;
2 2  
3 3 import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
4 4 import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRule;
... ... @@ -29,17 +29,17 @@ import static org.junit.jupiter.api.Assertions.*;
29 29 import static org.mockito.Mockito.*;
30 30  
31 31 /**
32   - * {@link DataPermissionInterceptor} 的单元测试
33   - * 主要测试 {@link DataPermissionInterceptor#beforePrepare(StatementHandler, Connection, Integer)}
34   - * 和 {@link DataPermissionInterceptor#beforeUpdate(Executor, MappedStatement, Object)}
  32 + * {@link DataPermissionDatabaseInterceptor} 的单元测试
  33 + * 主要测试 {@link DataPermissionDatabaseInterceptor#beforePrepare(StatementHandler, Connection, Integer)}
  34 + * 和 {@link DataPermissionDatabaseInterceptor#beforeUpdate(Executor, MappedStatement, Object)}
35 35 * 以及在这个过程中,ContextHolder 和 MappedStatementCache
36 36 *
37 37 * @author 芋道源码
38 38 */
39   -public class DataPermissionInterceptorTest extends BaseMockitoUnitTest {
  39 +public class DataPermissionDatabaseInterceptorTest extends BaseMockitoUnitTest {
40 40  
41 41 @InjectMocks
42   - private DataPermissionInterceptor interceptor;
  42 + private DataPermissionDatabaseInterceptor interceptor;
43 43  
44 44 @Mock
45 45 private DataPermissionRuleFactory ruleFactory;
... ... @@ -47,7 +47,7 @@ public class DataPermissionInterceptorTest extends BaseMockitoUnitTest {
47 47 @BeforeEach
48 48 public void setUp() {
49 49 // 清理上下文
50   - DataPermissionInterceptor.ContextHolder.clear();
  50 + DataPermissionDatabaseInterceptor.ContextHolder.clear();
51 51 // 清空缓存
52 52 interceptor.getMappedStatementCache().clear();
53 53 }
... ...
yudao-framework/yudao-spring-boot-starter-data-permission/src/test/java/cn/iocoder/yudao/framework/datapermission/core/interceptor/DataPermissionInterceptorTest2.java renamed to yudao-framework/yudao-spring-boot-starter-data-permission/src/test/java/cn/iocoder/yudao/framework/datapermission/core/db/DataPermissionDatabaseInterceptorTest2.java
1   -package cn.iocoder.yudao.framework.datapermission.core.interceptor;
  1 +package cn.iocoder.yudao.framework.datapermission.core.db;
2 2  
3 3 import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRule;
4 4 import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRuleFactory;
... ... @@ -23,16 +23,16 @@ import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet;
23 23 import static org.junit.jupiter.api.Assertions.assertEquals;
24 24  
25 25 /**
26   - * {@link DataPermissionInterceptor} 的单元测试
  26 + * {@link DataPermissionDatabaseInterceptor} 的单元测试
27 27 * 主要复用了 MyBatis Plus 的 TenantLineInnerInterceptorTest 的单元测试
28 28 * 不过它的单元测试不是很规范,考虑到是复用的,所以暂时不进行修改~
29 29 *
30 30 * @author 芋道源码
31 31 */
32   -public class DataPermissionInterceptorTest2 extends BaseMockitoUnitTest {
  32 +public class DataPermissionDatabaseInterceptorTest2 extends BaseMockitoUnitTest {
33 33  
34 34 @InjectMocks
35   - private DataPermissionInterceptor interceptor;
  35 + private DataPermissionDatabaseInterceptor interceptor;
36 36  
37 37 @Mock
38 38 private DataPermissionRuleFactory ruleFactory;
... ... @@ -78,7 +78,7 @@ public class DataPermissionInterceptorTest2 extends BaseMockitoUnitTest {
78 78  
79 79 };
80 80 // 设置到上下文,保证
81   - DataPermissionInterceptor.ContextHolder.init(Arrays.asList(tenantRule, deptRule));
  81 + DataPermissionDatabaseInterceptor.ContextHolder.init(Arrays.asList(tenantRule, deptRule));
82 82 }
83 83  
84 84 @Test
... ...
yudao-framework/yudao-spring-boot-starter-data-permission/src/test/resources/META-INF/spring.factories 0 → 100644
  1 +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  2 + cn.iocoder.yudao.framework.datapermission.config.DataPermissionAutoConfiguration
... ...