1. 增加数据权限的自动配置 DataPermissionAutoConfiguration
2. 增加数据权限的 AOP DataPermissionAnnotationInterceptor 3. 重命名 DataPermissionDatabaseInterceptorpull/2/head
parent
2334e177c5
commit
f9b15fe70d
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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.hutool.core.collection.CollUtil;
|
||||||
import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
|
import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
|
||||||
|
@ -45,7 +45,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||||
* @author 芋道源码
|
* @author 芋道源码
|
||||||
*/
|
*/
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class DataPermissionInterceptor extends JsqlParserSupport implements InnerInterceptor {
|
public class DataPermissionDatabaseInterceptor extends JsqlParserSupport implements InnerInterceptor {
|
||||||
|
|
||||||
private final DataPermissionRuleFactory ruleFactory;
|
private final DataPermissionRuleFactory ruleFactory;
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
package cn.iocoder.yudao.framework.datapermission.core;
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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.common.util.collection.SetUtils;
|
||||||
import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRule;
|
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.*;
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link DataPermissionInterceptor} 的单元测试
|
* {@link DataPermissionDatabaseInterceptor} 的单元测试
|
||||||
* 主要测试 {@link DataPermissionInterceptor#beforePrepare(StatementHandler, Connection, Integer)}
|
* 主要测试 {@link DataPermissionDatabaseInterceptor#beforePrepare(StatementHandler, Connection, Integer)}
|
||||||
* 和 {@link DataPermissionInterceptor#beforeUpdate(Executor, MappedStatement, Object)}
|
* 和 {@link DataPermissionDatabaseInterceptor#beforeUpdate(Executor, MappedStatement, Object)}
|
||||||
* 以及在这个过程中,ContextHolder 和 MappedStatementCache
|
* 以及在这个过程中,ContextHolder 和 MappedStatementCache
|
||||||
*
|
*
|
||||||
* @author 芋道源码
|
* @author 芋道源码
|
||||||
*/
|
*/
|
||||||
public class DataPermissionInterceptorTest extends BaseMockitoUnitTest {
|
public class DataPermissionDatabaseInterceptorTest extends BaseMockitoUnitTest {
|
||||||
|
|
||||||
@InjectMocks
|
@InjectMocks
|
||||||
private DataPermissionInterceptor interceptor;
|
private DataPermissionDatabaseInterceptor interceptor;
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private DataPermissionRuleFactory ruleFactory;
|
private DataPermissionRuleFactory ruleFactory;
|
||||||
|
@ -47,7 +47,7 @@ public class DataPermissionInterceptorTest extends BaseMockitoUnitTest {
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
// 清理上下文
|
// 清理上下文
|
||||||
DataPermissionInterceptor.ContextHolder.clear();
|
DataPermissionDatabaseInterceptor.ContextHolder.clear();
|
||||||
// 清空缓存
|
// 清空缓存
|
||||||
interceptor.getMappedStatementCache().clear();
|
interceptor.getMappedStatementCache().clear();
|
||||||
}
|
}
|
|
@ -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.DataPermissionRule;
|
||||||
import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRuleFactory;
|
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;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link DataPermissionInterceptor} 的单元测试
|
* {@link DataPermissionDatabaseInterceptor} 的单元测试
|
||||||
* 主要复用了 MyBatis Plus 的 TenantLineInnerInterceptorTest 的单元测试
|
* 主要复用了 MyBatis Plus 的 TenantLineInnerInterceptorTest 的单元测试
|
||||||
* 不过它的单元测试不是很规范,考虑到是复用的,所以暂时不进行修改~
|
* 不过它的单元测试不是很规范,考虑到是复用的,所以暂时不进行修改~
|
||||||
*
|
*
|
||||||
* @author 芋道源码
|
* @author 芋道源码
|
||||||
*/
|
*/
|
||||||
public class DataPermissionInterceptorTest2 extends BaseMockitoUnitTest {
|
public class DataPermissionDatabaseInterceptorTest2 extends BaseMockitoUnitTest {
|
||||||
|
|
||||||
@InjectMocks
|
@InjectMocks
|
||||||
private DataPermissionInterceptor interceptor;
|
private DataPermissionDatabaseInterceptor interceptor;
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private DataPermissionRuleFactory ruleFactory;
|
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
|
@Test
|
|
@ -0,0 +1,2 @@
|
||||||
|
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||||
|
cn.iocoder.yudao.framework.datapermission.config.DataPermissionAutoConfiguration
|
Loading…
Reference in New Issue