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

2. 增加数据权限的 AOP DataPermissionAnnotationInterceptor
3. 重命名 DataPermissionDatabaseInterceptor
This commit is contained in:
YunaiV 2021-12-12 00:56:16 +08:00
parent 2334e177c5
commit f9b15fe70d
11 changed files with 340 additions and 15 deletions

View File

@ -0,0 +1,32 @@
package cn.iocoder.yudao.framework.datapermission.config;
import cn.iocoder.yudao.framework.datapermission.core.aop.DataPermissionAnnotationAdvisor;
import cn.iocoder.yudao.framework.datapermission.core.db.DataPermissionDatabaseInterceptor;
import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRule;
import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRuleFactory;
import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRuleFactoryImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.List;
@Configuration
public class DataPermissionAutoConfiguration {
@Bean
public DataPermissionRuleFactory dataPermissionRuleFactory(List<DataPermissionRule> rules) {
return new DataPermissionRuleFactoryImpl(rules);
}
@Bean
public DataPermissionDatabaseInterceptor dataPermissionDatabaseInterceptor(List<DataPermissionRule> rules) {
DataPermissionRuleFactory ruleFactory = dataPermissionRuleFactory(rules);
return new DataPermissionDatabaseInterceptor(ruleFactory);
}
@Bean
public DataPermissionAnnotationAdvisor dataPermissionAnnotationAdvisor() {
return new DataPermissionAnnotationAdvisor();
}
}

View File

@ -0,0 +1,36 @@
package cn.iocoder.yudao.framework.datapermission.core.aop;
import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import org.aopalliance.aop.Advice;
import org.springframework.aop.Pointcut;
import org.springframework.aop.support.AbstractPointcutAdvisor;
import org.springframework.aop.support.ComposablePointcut;
import org.springframework.aop.support.annotation.AnnotationMatchingPointcut;
/**
* {@link cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission} 注解的 Advisor 实现类
*
* @author 芋道源码
*/
@Getter
@EqualsAndHashCode(callSuper = true)
public class DataPermissionAnnotationAdvisor extends AbstractPointcutAdvisor {
private final Advice advice;
private final Pointcut pointcut;
public DataPermissionAnnotationAdvisor() {
this.advice = new DataPermissionAnnotationInterceptor();
this.pointcut = this.buildPointcut();
}
protected Pointcut buildPointcut() {
Pointcut classPointcut = new AnnotationMatchingPointcut(DataPermission.class, true);
Pointcut methodPointcut = new AnnotationMatchingPointcut(null, DataPermission.class, true);
return new ComposablePointcut(classPointcut).union(methodPointcut);
}
}

View File

@ -0,0 +1,72 @@
package cn.iocoder.yudao.framework.datapermission.core.aop;
import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
import lombok.Getter;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.core.MethodClassKey;
import org.springframework.core.annotation.AnnotationUtils;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* {@link DataPermission} 注解的拦截器
* 1. 在执行方法前 @DataPermission 注解入栈
* 2. 在执行方法后 @DataPermission 注解出栈
*
* @author 芋道源码
*/
@DataPermission // 该注解用于 {@link DATA_PERMISSION_NULL} 的空对象
public class DataPermissionAnnotationInterceptor implements MethodInterceptor {
/**
* DataPermission 空对象用于方法无 {@link DataPermission} 注解时使用 DATA_PERMISSION_NULL 进行占位
*/
static final DataPermission DATA_PERMISSION_NULL = DataPermissionAnnotationInterceptor.class.getAnnotation(DataPermission.class);
@Getter
private final Map<MethodClassKey, DataPermission> dataPermissionCache = new ConcurrentHashMap<>();
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
// 入栈
DataPermission dataPermission = this.findAnnotation(methodInvocation);
if (dataPermission != null) {
DataPermissionContextHolder.push(dataPermission);
}
try {
// 执行逻辑
return methodInvocation.proceed();
} finally {
// 出栈
if (dataPermission != null) {
DataPermissionContextHolder.poll();
}
}
}
private DataPermission findAnnotation(MethodInvocation methodInvocation) {
// 1. 从缓存中获取
Method method = methodInvocation.getMethod();
Object targetObject = methodInvocation.getThis();
Class<?> clazz = targetObject != null ? targetObject.getClass() : method.getDeclaringClass();
MethodClassKey methodClassKey = new MethodClassKey(method, clazz);
DataPermission dataPermission = dataPermissionCache.get(methodClassKey);
if (dataPermission != null) {
return dataPermission != DATA_PERMISSION_NULL ? dataPermission : null;
}
// 2.1 从方法中获取
dataPermission = AnnotationUtils.findAnnotation(method, DataPermission.class);
// 2.2 从类上获取
if (dataPermission == null) {
dataPermission = AnnotationUtils.findAnnotation(clazz, DataPermission.class);
}
// 2.3 添加到缓存中
dataPermissionCache.put(methodClassKey, dataPermission != null ? dataPermission : DATA_PERMISSION_NULL);
return dataPermission;
}
}

View File

@ -0,0 +1,51 @@
package cn.iocoder.yudao.framework.datapermission.core.aop;
import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
import com.alibaba.ttl.TransmittableThreadLocal;
import java.util.ArrayDeque;
import java.util.Deque;
/**
* {@link DataPermission} 注解的 Context 上下文
*
* @author 芋道源码
*/
public class DataPermissionContextHolder {
private static final ThreadLocal<Deque<DataPermission>> DATA_PERMISSIONS =
TransmittableThreadLocal.withInitial(ArrayDeque::new);
/**
* 获得当前的 DataPermission 注解
*
* @return DataPermission 注解
*/
public static DataPermission peek() {
return DATA_PERMISSIONS.get().remove();
}
/**
* 入栈 DataPermission 注解
*
* @param dataPermission DataPermission 注解
*/
public static void push(DataPermission dataPermission) {
DATA_PERMISSIONS.get().push(dataPermission);
}
/**
* 出栈 DataPermission 注解
*
* @return DataPermission 注解
*/
public static DataPermission poll() {
DataPermission dataPermission = DATA_PERMISSIONS.get().poll();
// 无元素时清空 ThreadLocal
if (dataPermission == null) {
DATA_PERMISSIONS.remove();
}
return dataPermission;
}
}

View File

@ -1,4 +1,4 @@
package cn.iocoder.yudao.framework.datapermission.core.interceptor;
package cn.iocoder.yudao.framework.datapermission.core.db;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
@ -45,7 +45,7 @@ import java.util.concurrent.ConcurrentHashMap;
* @author 芋道源码
*/
@RequiredArgsConstructor
public class DataPermissionInterceptor extends JsqlParserSupport implements InnerInterceptor {
public class DataPermissionDatabaseInterceptor extends JsqlParserSupport implements InnerInterceptor {
private final DataPermissionRuleFactory ruleFactory;

View File

@ -0,0 +1,25 @@
package cn.iocoder.yudao.framework.datapermission.core.rule;
import lombok.RequiredArgsConstructor;
import java.util.List;
@RequiredArgsConstructor
public class DataPermissionRuleFactoryImpl implements DataPermissionRuleFactory {
/**
* 数据权限规则数组
*/
private final List<DataPermissionRule> rules;
@Override
public List<DataPermissionRule> getDataPermissionRules() {
return rules;
}
@Override
public List<DataPermissionRule> getDataPermissionRule(String mappedStatementId) {
return null;
}
}

View File

@ -0,0 +1,108 @@
package cn.iocoder.yudao.framework.datapermission.core.aop;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
import org.aopalliance.intercept.MethodInvocation;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import java.lang.reflect.Method;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.when;
/**
* {@link DataPermissionAnnotationInterceptor} 的单元测试
*
* @author 芋道源码
*/
public class DataPermissionAnnotationInterceptorTest extends BaseMockitoUnitTest {
@InjectMocks
private DataPermissionAnnotationInterceptor interceptor;
@Mock
private MethodInvocation methodInvocation;
@BeforeEach
public void setUp() {
interceptor.getDataPermissionCache().clear();
}
@Test // @DataPermission 注解
public void testInvoke_none() throws Throwable {
// 参数
mockMethodInvocation(TestNone.class);
// 调用
Object result = interceptor.invoke(methodInvocation);
// 断言
assertEquals("none", result);
assertEquals(1, interceptor.getDataPermissionCache().size());
assertTrue(CollUtil.getFirst(interceptor.getDataPermissionCache().values()).enable());
}
@Test // Method 上有 @DataPermission 注解
public void testInvoke_method() throws Throwable {
// 参数
mockMethodInvocation(TestMethod.class);
// 调用
Object result = interceptor.invoke(methodInvocation);
// 断言
assertEquals("method", result);
assertEquals(1, interceptor.getDataPermissionCache().size());
assertFalse(CollUtil.getFirst(interceptor.getDataPermissionCache().values()).enable());
}
@Test // Class 上有 @DataPermission 注解
public void testInvoke_class() throws Throwable {
// 参数
mockMethodInvocation(TestClass.class);
// 调用
Object result = interceptor.invoke(methodInvocation);
// 断言
assertEquals("class", result);
assertEquals(1, interceptor.getDataPermissionCache().size());
assertFalse(CollUtil.getFirst(interceptor.getDataPermissionCache().values()).enable());
}
private void mockMethodInvocation(Class<?> clazz) throws Throwable {
Object targetObject = clazz.newInstance();
Method method = targetObject.getClass().getMethod("echo");
when(methodInvocation.getThis()).thenReturn(targetObject);
when(methodInvocation.getMethod()).thenReturn(method);
when(methodInvocation.proceed()).then(invocationOnMock -> method.invoke(targetObject));
}
static class TestMethod {
@DataPermission(enable = false)
public String echo() {
return "method";
}
}
@DataPermission(enable = false)
static class TestClass {
public String echo() {
return "class";
}
}
static class TestNone {
public String echo() {
return "none";
}
}
}

View File

@ -1,4 +1,4 @@
package cn.iocoder.yudao.framework.datapermission.core.interceptor;
package cn.iocoder.yudao.framework.datapermission.core.db;
import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRule;
@ -29,17 +29,17 @@ import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
/**
* {@link DataPermissionInterceptor} 的单元测试
* 主要测试 {@link DataPermissionInterceptor#beforePrepare(StatementHandler, Connection, Integer)}
* {@link DataPermissionInterceptor#beforeUpdate(Executor, MappedStatement, Object)}
* {@link DataPermissionDatabaseInterceptor} 的单元测试
* 主要测试 {@link DataPermissionDatabaseInterceptor#beforePrepare(StatementHandler, Connection, Integer)}
* {@link DataPermissionDatabaseInterceptor#beforeUpdate(Executor, MappedStatement, Object)}
* 以及在这个过程中ContextHolder MappedStatementCache
*
* @author 芋道源码
*/
public class DataPermissionInterceptorTest extends BaseMockitoUnitTest {
public class DataPermissionDatabaseInterceptorTest extends BaseMockitoUnitTest {
@InjectMocks
private DataPermissionInterceptor interceptor;
private DataPermissionDatabaseInterceptor interceptor;
@Mock
private DataPermissionRuleFactory ruleFactory;
@ -47,7 +47,7 @@ public class DataPermissionInterceptorTest extends BaseMockitoUnitTest {
@BeforeEach
public void setUp() {
// 清理上下文
DataPermissionInterceptor.ContextHolder.clear();
DataPermissionDatabaseInterceptor.ContextHolder.clear();
// 清空缓存
interceptor.getMappedStatementCache().clear();
}

View File

@ -1,4 +1,4 @@
package cn.iocoder.yudao.framework.datapermission.core.interceptor;
package cn.iocoder.yudao.framework.datapermission.core.db;
import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRule;
import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRuleFactory;
@ -23,16 +23,16 @@ import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* {@link DataPermissionInterceptor} 的单元测试
* {@link DataPermissionDatabaseInterceptor} 的单元测试
* 主要复用了 MyBatis Plus TenantLineInnerInterceptorTest 的单元测试
* 不过它的单元测试不是很规范考虑到是复用的所以暂时不进行修改~
*
* @author 芋道源码
*/
public class DataPermissionInterceptorTest2 extends BaseMockitoUnitTest {
public class DataPermissionDatabaseInterceptorTest2 extends BaseMockitoUnitTest {
@InjectMocks
private DataPermissionInterceptor interceptor;
private DataPermissionDatabaseInterceptor interceptor;
@Mock
private DataPermissionRuleFactory ruleFactory;
@ -78,7 +78,7 @@ public class DataPermissionInterceptorTest2 extends BaseMockitoUnitTest {
};
// 设置到上下文保证
DataPermissionInterceptor.ContextHolder.init(Arrays.asList(tenantRule, deptRule));
DataPermissionDatabaseInterceptor.ContextHolder.init(Arrays.asList(tenantRule, deptRule));
}
@Test

View File

@ -0,0 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.iocoder.yudao.framework.datapermission.config.DataPermissionAutoConfiguration