Merge branch 'feature/REQ-2300' into 'master'
Feature/req 2300 See merge request universal/framework/backend/axzo-framework-commons!96
This commit is contained in:
commit
a0782721c9
33
axzo-common-data-permission/pom.xml
Normal file
33
axzo-common-data-permission/pom.xml
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>cn.axzo.framework</groupId>
|
||||||
|
<artifactId>axzo-framework-commons</artifactId>
|
||||||
|
<version>1.0.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>axzo-common-data-permission</artifactId>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.source>8</maven.compiler.source>
|
||||||
|
<maven.compiler.target>8</maven.compiler.target>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.baomidou</groupId>
|
||||||
|
<artifactId>mybatis-plus-boot-starter</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.axzo.karma</groupId>
|
||||||
|
<artifactId>karma-api</artifactId>
|
||||||
|
<version>2.0.0-SNAPSHOT</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
||||||
@ -0,0 +1,95 @@
|
|||||||
|
package cn.axzo.framework.datapermission.advice;
|
||||||
|
|
||||||
|
import cn.axzo.framework.datapermission.annotation.DataPermission;
|
||||||
|
import cn.axzo.framework.datapermission.context.DataPermissionContextHolder;
|
||||||
|
import cn.axzo.framework.datapermission.util.DPUtil;
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.core.MethodParameter;
|
||||||
|
import org.springframework.core.annotation.Order;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.converter.HttpMessageConverter;
|
||||||
|
import org.springframework.http.server.ServerHttpRequest;
|
||||||
|
import org.springframework.http.server.ServerHttpResponse;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||||
|
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author likunpeng
|
||||||
|
* @version 1.0
|
||||||
|
* @date 2024/6/7
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Order(-9)
|
||||||
|
@RestControllerAdvice(annotations = RestController.class)
|
||||||
|
public class DataPermissionResponseBodyAdvice implements ResponseBodyAdvice<Object> {
|
||||||
|
|
||||||
|
private final ObjectMapper objectMapper;
|
||||||
|
private final DataPermissionResponseExecutor dataPermissionResponseExecutor;
|
||||||
|
|
||||||
|
public DataPermissionResponseBodyAdvice(ObjectMapper objectMapper, DataPermissionResponseExecutor dataPermissionResponseExecutor) {
|
||||||
|
log.debug("Add ResponseBodyAdvice: DataPermissionResponseBodyAdvice");
|
||||||
|
this.objectMapper = objectMapper;
|
||||||
|
this.dataPermissionResponseExecutor = dataPermissionResponseExecutor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
|
||||||
|
DataPermission dataPermission = returnType.getDeclaringClass().getAnnotation(DataPermission.class);
|
||||||
|
if (dataPermission != null) {
|
||||||
|
return dataPermission.enable() && dataPermission.processResponse();
|
||||||
|
} else {
|
||||||
|
dataPermission = returnType.getMethodAnnotation(DataPermission.class);
|
||||||
|
return dataPermission != null && dataPermission.enable() && dataPermission.processResponse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
|
||||||
|
if (body == null || DPUtil.inIgnoreClass(body.getClass())) {
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
JsonNode node = objectMapper.valueToTree(body);
|
||||||
|
JsonNode dataNode = getDataOrPageData(node);
|
||||||
|
if (Objects.isNull(dataNode)) {
|
||||||
|
log.warn("api result has no data element");
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
if (!dataNode.isContainerNode()) {
|
||||||
|
log.warn("api result is not object or array");
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
DataPermissionContextHolder.DataPermissionContext context = DataPermissionContextHolder.get();
|
||||||
|
if (Objects.isNull(context) || Objects.isNull(context.getPersonId())) {
|
||||||
|
log.warn("threadLocal hos not DataPermissionContext or no personId");
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
|
||||||
|
dataPermissionResponseExecutor.processNode(context, dataNode);
|
||||||
|
return node;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("DataPermissionResponseBodyAdvice error", e);
|
||||||
|
} finally {
|
||||||
|
// 最终清除ThreadLocal的内容
|
||||||
|
DataPermissionContextHolder.remove();
|
||||||
|
}
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonNode getDataOrPageData(JsonNode body) {
|
||||||
|
if (body.has("data")) {
|
||||||
|
JsonNode data = body.get("data");
|
||||||
|
// 分页数据获取
|
||||||
|
if (data.has("list") && data.has("page") && data.get("list").isArray()) {
|
||||||
|
return data.get("list");
|
||||||
|
}
|
||||||
|
return body.get("data");
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,136 @@
|
|||||||
|
package cn.axzo.framework.datapermission.advice;
|
||||||
|
|
||||||
|
import cn.axzo.framework.datapermission.annotation.DataPermission;
|
||||||
|
import cn.axzo.framework.datapermission.context.DataPermissionContextHolder;
|
||||||
|
import cn.axzo.framework.datapermission.util.RpcInternalUtil;
|
||||||
|
import cn.axzo.karma.client.feign.tyr.DataObjectApi;
|
||||||
|
import cn.axzo.karma.client.feign.tyr.request.ExamineDpColumnsReq;
|
||||||
|
import cn.axzo.karma.client.feign.tyr.response.ExamineDpColumnsResp;
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||||
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
|
import com.google.common.collect.Sets;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.collections4.CollectionUtils;
|
||||||
|
import org.apache.commons.collections4.MapUtils;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author likunpeng
|
||||||
|
* @version 1.0
|
||||||
|
* @date 2024/6/7
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class DataPermissionResponseExecutor {
|
||||||
|
|
||||||
|
private static final Integer ATTRIBUTE_ISUNMASKABLE = 2;
|
||||||
|
private static final Integer ATTRIBUTE_ISEDITABLE = 2;
|
||||||
|
private static final String ATTRIBUTE_ADD_ALLOW_EDIT_PREFIX = "dp_allow_edit_";
|
||||||
|
private static final String ATTRIBUTE_ADD_ALLOW_DISPLAY_PREFIX = "dp_allow_display_";
|
||||||
|
|
||||||
|
private final DataObjectApi dataObjectApi;
|
||||||
|
|
||||||
|
public DataPermissionResponseExecutor(DataObjectApi dataObjectApi) {
|
||||||
|
this.dataObjectApi = dataObjectApi;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void processNode(DataPermissionContextHolder.DataPermissionContext context, JsonNode dataNode) {
|
||||||
|
DataPermission dataPermission = context.getDataPermission();
|
||||||
|
Set<Long> personIds = Sets.newHashSet();
|
||||||
|
recursiveGetPersonIds(dataPermission, dataNode, personIds);
|
||||||
|
if (CollectionUtils.isEmpty(personIds)) {
|
||||||
|
log.warn("api result has no personId, not examine data permission for result columns.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ExamineDpColumnsReq request = ExamineDpColumnsReq.builder()
|
||||||
|
.resultKey(context.getResultKey())
|
||||||
|
.ouId(context.getOuId())
|
||||||
|
.personId(context.getPersonId())
|
||||||
|
.workspaceId(context.getWorkspaceId())
|
||||||
|
.resultPersonIds(personIds)
|
||||||
|
.build();
|
||||||
|
// 调用karma获取数据列的校验结果
|
||||||
|
ExamineDpColumnsResp examineDpColumnsResp = RpcInternalUtil.rpcProcessor(() -> dataObjectApi.examineDpColumns(request), "data permission processNode", request).getData();
|
||||||
|
if (Objects.isNull(examineDpColumnsResp) || MapUtils.isEmpty(examineDpColumnsResp.getAttributeDpResultMap())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 根据karma数据列的校验结果,处理api结果的数据对象
|
||||||
|
recursiveProcessPersonIds(dataPermission, dataNode, examineDpColumnsResp.getAttributeDpResultMap());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void recursiveGetPersonIds(DataPermission dataPermission, JsonNode dataNode, Set<Long> personIds) {
|
||||||
|
if (dataNode.isObject()) {
|
||||||
|
if (Objects.nonNull(dataNode.get(dataPermission.key_personId()))) {
|
||||||
|
personIds.add(dataNode.get(dataPermission.key_personId()).asLong());
|
||||||
|
}
|
||||||
|
// 循环处理子field
|
||||||
|
Iterator<Map.Entry<String, JsonNode>> fields = dataNode.fields();
|
||||||
|
while (fields.hasNext()) {
|
||||||
|
Map.Entry<String, JsonNode> entry = fields.next();
|
||||||
|
JsonNode value = entry.getValue();
|
||||||
|
if (value.isContainerNode()) {
|
||||||
|
recursiveGetPersonIds(dataPermission, value, personIds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (dataNode.isArray()) {
|
||||||
|
ArrayNode arrayNode = (ArrayNode) dataNode;
|
||||||
|
Iterator<JsonNode> elements = arrayNode.elements();
|
||||||
|
while (elements.hasNext()) {
|
||||||
|
JsonNode element = elements.next();
|
||||||
|
recursiveGetPersonIds(dataPermission, element, personIds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void recursiveProcessPersonIds(DataPermission dataPermission, JsonNode dataNode,
|
||||||
|
Map<Long, Map<String, ExamineDpColumnsResp.AttributePermissionBasicDTO>> attributeDpResultMap) {
|
||||||
|
if (dataNode.isObject()) {
|
||||||
|
Long resultPersonId = Objects.nonNull(dataNode.get(dataPermission.key_personId())) ? dataNode.get(dataPermission.key_personId()).asLong() : null;
|
||||||
|
|
||||||
|
if (Objects.nonNull(resultPersonId)) {
|
||||||
|
Map<String, ExamineDpColumnsResp.AttributePermissionBasicDTO> attributeMap = attributeDpResultMap.get(resultPersonId);
|
||||||
|
if (MapUtils.isNotEmpty(attributeMap)) {
|
||||||
|
for (Map.Entry<String, ExamineDpColumnsResp.AttributePermissionBasicDTO> entry : attributeMap.entrySet()) {
|
||||||
|
ExamineDpColumnsResp.AttributePermissionBasicDTO basic = entry.getValue();
|
||||||
|
if (dataNode.has(basic.getAttrCode())) {
|
||||||
|
ObjectNode objectNode = (ObjectNode) dataNode;
|
||||||
|
if (Objects.nonNull(basic.getIsEditable())) {
|
||||||
|
objectNode.put(ATTRIBUTE_ADD_ALLOW_EDIT_PREFIX + basic.getAttrCode(), ATTRIBUTE_ISEDITABLE.equals(basic.getIsEditable()));
|
||||||
|
}
|
||||||
|
if (Objects.nonNull(basic.getIsUnmaskable())) {
|
||||||
|
objectNode.put(ATTRIBUTE_ADD_ALLOW_DISPLAY_PREFIX + basic.getAttrCode(), ATTRIBUTE_ISUNMASKABLE.equals(basic.getIsUnmaskable()));
|
||||||
|
}
|
||||||
|
if (dataPermission.removeNotDisplayColumn() && !ATTRIBUTE_ISUNMASKABLE.equals(basic.getIsUnmaskable())) {
|
||||||
|
objectNode.remove(basic.getAttrCode());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 循环处理子field
|
||||||
|
Iterator<Map.Entry<String, JsonNode>> fields = dataNode.fields();
|
||||||
|
while (fields.hasNext()) {
|
||||||
|
Map.Entry<String, JsonNode> entry = fields.next();
|
||||||
|
JsonNode value = entry.getValue();
|
||||||
|
if (value.isContainerNode()) {
|
||||||
|
recursiveProcessPersonIds(dataPermission, value, attributeDpResultMap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dataNode.isArray()) {
|
||||||
|
ArrayNode arrayNode = (ArrayNode) dataNode;
|
||||||
|
Iterator<JsonNode> elements = arrayNode.elements();
|
||||||
|
while (elements.hasNext()) {
|
||||||
|
JsonNode element = elements.next();
|
||||||
|
recursiveProcessPersonIds(dataPermission, element, attributeDpResultMap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,46 @@
|
|||||||
|
package cn.axzo.framework.datapermission.annotation;
|
||||||
|
|
||||||
|
import java.lang.annotation.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据权限数据预制注解
|
||||||
|
* @author tanjie@axzo.cn
|
||||||
|
* @date 2024/5/30 17:57
|
||||||
|
*/
|
||||||
|
@Inherited
|
||||||
|
@Documented
|
||||||
|
@Target({ElementType.METHOD, ElementType.TYPE})
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface DataPermission {
|
||||||
|
boolean enable() default true;
|
||||||
|
|
||||||
|
/** 是否处理ApiResult,默认不处理 **/
|
||||||
|
boolean processResponse() default false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据权限数据对象code
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
String bizCode() default "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ApiResult返回的机构节点的ID
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
String key_organizationalNodeId() default "organizationalNodeId";
|
||||||
|
/**
|
||||||
|
* ApiResult返回的用户的ID
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
String key_personId() default "personId";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求体的workspaceId
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
String requestBodyWorkspaceId() default "";
|
||||||
|
|
||||||
|
/** 是否删除不展示的字段列 **/
|
||||||
|
boolean removeNotDisplayColumn() default false;
|
||||||
|
}
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
package cn.axzo.framework.datapermission.aop;
|
||||||
|
|
||||||
|
import cn.axzo.framework.datapermission.context.DataPermissionContextHolder;
|
||||||
|
import feign.RequestInterceptor;
|
||||||
|
import feign.RequestTemplate;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import static cn.axzo.framework.datapermission.context.DataPermissionContextHolder.DATA_PERMISSION_HEADER;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上下文中有DataPermissionContext,则把数据放在header中
|
||||||
|
* 根据用户调研结果,选择BFF解析规则结果放在上下文,由用户显示的把上下文的数据传递到底层服务,方便排查问题和使用
|
||||||
|
*/
|
||||||
|
//@Component
|
||||||
|
public class DataPermissionFeignInterceptor implements RequestInterceptor {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void apply(RequestTemplate requestTemplate) {
|
||||||
|
|
||||||
|
DataPermissionContextHolder.DataPermissionContext dataPermissionContext = DataPermissionContextHolder.get();
|
||||||
|
if (dataPermissionContext == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 存放缓存的key,是因为减少io数据量以及header有最大长度限制
|
||||||
|
requestTemplate.header(DATA_PERMISSION_HEADER, dataPermissionContext.getResultKey());
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,217 @@
|
|||||||
|
package cn.axzo.framework.datapermission.aop;
|
||||||
|
|
||||||
|
import cn.axzo.framework.auth.AuthException;
|
||||||
|
import cn.axzo.framework.auth.domain.ContextInfo;
|
||||||
|
import cn.axzo.framework.auth.domain.ContextInfoHolder;
|
||||||
|
import cn.axzo.framework.datapermission.annotation.DataPermission;
|
||||||
|
import cn.axzo.framework.datapermission.context.DataPermissionContextHolder;
|
||||||
|
import cn.axzo.framework.datapermission.util.RpcInternalUtil;
|
||||||
|
import cn.axzo.karma.client.feign.tyr.DataObjectApi;
|
||||||
|
import cn.axzo.karma.client.feign.tyr.request.GetMergeMatchDataReq;
|
||||||
|
import cn.axzo.karma.client.feign.tyr.request.MatchDataObjectReq;
|
||||||
|
import cn.axzo.karma.client.feign.tyr.response.MatchDataObjectResp;
|
||||||
|
import cn.axzo.karma.client.feign.tyr.response.MergeMatchDataResp;
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.aspectj.lang.ProceedingJoinPoint;
|
||||||
|
import org.aspectj.lang.annotation.*;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Lazy;
|
||||||
|
import org.springframework.core.annotation.Order;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.context.request.RequestContextHolder;
|
||||||
|
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static cn.axzo.framework.datapermission.context.DataPermissionContextHolder.DATA_PERMISSION_HEADER;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析数据权限规则,把解析后的数据放置在上下文中DataPermissionContext
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Aspect
|
||||||
|
@Order(100)
|
||||||
|
@Component
|
||||||
|
public class DataPermissionInterceptor {
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
@Autowired
|
||||||
|
private DataObjectApi dataObjectApi;
|
||||||
|
|
||||||
|
|
||||||
|
@Pointcut(value = "@annotation(org.springframework.web.bind.annotation.RequestMapping)")
|
||||||
|
public void requestMapping() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Pointcut(value = "@annotation(org.springframework.web.bind.annotation.PostMapping)")
|
||||||
|
public void postMapping() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Pointcut(value = "@annotation(org.springframework.web.bind.annotation.GetMapping)")
|
||||||
|
public void getMapping() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Pointcut(value = "@annotation(org.springframework.web.bind.annotation.PutMapping)")
|
||||||
|
public void putMapping() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Pointcut(value = "@annotation(org.springframework.web.bind.annotation.DeleteMapping)")
|
||||||
|
public void deleteMapping() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Pointcut(value = "@annotation(org.springframework.web.bind.annotation.PatchMapping)")
|
||||||
|
public void patchMapping() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Pointcut("requestMapping() || postMapping() || getMapping() || putMapping() || deleteMapping()|| patchMapping()")
|
||||||
|
public void mappingAnnotations() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 切入含有@DataPermissiont && @RestController 注解的类
|
||||||
|
*/
|
||||||
|
@Around(value = "@within(dataPermission) && @within(restController)")
|
||||||
|
@SneakyThrows
|
||||||
|
public Object classHandler(ProceedingJoinPoint pjp, DataPermission dataPermission, RestController restController) {
|
||||||
|
handle(dataPermission);
|
||||||
|
return pjp.proceed(pjp.getArgs());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 切入含有@DataPermissiont && @RequestMapping/@PostMapping/@GetMapping/@PutMapping/@DeleteMapping/@PatchMapping 之一注解的方法
|
||||||
|
*/
|
||||||
|
@Around(value = "@annotation(dataPermission) && mappingAnnotations()")
|
||||||
|
@SneakyThrows
|
||||||
|
public Object methodHandler(ProceedingJoinPoint pjp, DataPermission dataPermission) {
|
||||||
|
handle(dataPermission);
|
||||||
|
return pjp.proceed(pjp.getArgs());
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterThrowing(value = "(@within(cn.axzo.framework.datapermission.annotation.DataPermission) && @within(org.springframework.web.bind.annotation.RestController)) " +
|
||||||
|
"|| (@annotation(cn.axzo.framework.datapermission.annotation.DataPermission) && mappingAnnotations())", throwing = "e")
|
||||||
|
public void handleException(Exception e) {
|
||||||
|
DataPermissionContextHolder.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handle(DataPermission dataPermission) {
|
||||||
|
|
||||||
|
HttpServletRequest httpRequest = null;
|
||||||
|
try {
|
||||||
|
// 获取request
|
||||||
|
httpRequest = ((ServletRequestAttributes) Objects
|
||||||
|
.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
|
||||||
|
} catch (Exception exception) {
|
||||||
|
log.error("can not get request,there is a error occurrence ==>" + exception.getCause().getMessage());
|
||||||
|
}
|
||||||
|
AuthException.error(Objects.nonNull(httpRequest), "httpRequest cant be null, this is error");
|
||||||
|
|
||||||
|
|
||||||
|
// String dataPermissionHeader = httpRequest.getHeader(DATA_PERMISSION_HEADER);
|
||||||
|
// header中有值,表示数据权限规则已经被解析,只需要获取数据去使用
|
||||||
|
// if (StringUtils.isBlank(dataPermissionHeader)) {
|
||||||
|
resolveRule(dataPermission, httpRequest);
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// DataPermissionContextHolder.DataPermissionContext dataPermissionContext = getCachedDataPermission(dataPermissionHeader);
|
||||||
|
// dataPermissionContext.setDataPermission(dataPermission);
|
||||||
|
// DataPermissionContextHolder.setContext(dataPermissionContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析数据权限规则
|
||||||
|
* @param dataPermission
|
||||||
|
*/
|
||||||
|
private void resolveRule(DataPermission dataPermission, HttpServletRequest httpRequest) {
|
||||||
|
|
||||||
|
ContextInfo contextInfo = ContextInfoHolder.get();
|
||||||
|
if (contextInfo == null || contextInfo.getUserInfo() == null || StringUtils.isBlank(dataPermission.bizCode())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Long responseBodyWorkspaceId = getRequestBodyWorkspaceId(httpRequest, dataPermission);
|
||||||
|
|
||||||
|
Optional<MatchDataObjectResp> matchDataObjectOptional = this.matchRule(dataPermission, responseBodyWorkspaceId);
|
||||||
|
if (!matchDataObjectOptional.isPresent()) {
|
||||||
|
log.warn("no match data rule, bizCode:{}, personId:{}, ouId:{}, workspaceId:{}",
|
||||||
|
dataPermission.bizCode(),
|
||||||
|
contextInfo.getUserInfo().getPersonId(),
|
||||||
|
contextInfo.getOuId(),
|
||||||
|
contextInfo.getWorkspaceId());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DataPermissionContextHolder.DataPermissionContext dataPermissionContext = DataPermissionContextHolder.DataPermissionContext.builder()
|
||||||
|
.dataPermission(dataPermission)
|
||||||
|
.workspaceId(Objects.nonNull(responseBodyWorkspaceId) ? responseBodyWorkspaceId : contextInfo.getWorkspaceId())
|
||||||
|
.ouId(contextInfo.getOuId())
|
||||||
|
.personId(contextInfo.getUserInfo().getPersonId())
|
||||||
|
.resultKey(matchDataObjectOptional.get().getResultKey())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
DataPermissionContextHolder.setContext(dataPermissionContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<MatchDataObjectResp> matchRule(DataPermission dataPermission, Long responseBodyWorkspaceId) {
|
||||||
|
|
||||||
|
ContextInfo contextInfo = ContextInfoHolder.get();
|
||||||
|
|
||||||
|
MatchDataObjectReq matchDataObjectReq = MatchDataObjectReq.builder()
|
||||||
|
.dataObjectCode(dataPermission.bizCode())
|
||||||
|
.ouId(contextInfo.getOuId())
|
||||||
|
.workspaceId(Objects.nonNull(responseBodyWorkspaceId) ? responseBodyWorkspaceId : contextInfo.getWorkspaceId())
|
||||||
|
.personId(contextInfo.getUserInfo().getPersonId())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
MatchDataObjectResp matchDataObjectResp = RpcInternalUtil.rpcProcessor(() -> dataObjectApi.match(matchDataObjectReq),
|
||||||
|
"匹配解析数据权限规则", matchDataObjectReq)
|
||||||
|
.getData();
|
||||||
|
return Optional.ofNullable(matchDataObjectResp);
|
||||||
|
}
|
||||||
|
|
||||||
|
private DataPermissionContextHolder.DataPermissionContext getCachedDataPermission(String redisKey) {
|
||||||
|
|
||||||
|
GetMergeMatchDataReq getMergeMatchDataReq = GetMergeMatchDataReq.builder()
|
||||||
|
.resultKey(redisKey)
|
||||||
|
.build();
|
||||||
|
MergeMatchDataResp mergeMatchDataResp = RpcInternalUtil.rpcProcessor(() -> dataObjectApi.getCached(getMergeMatchDataReq),
|
||||||
|
"根据key查询数据权限规则的解析结果", getMergeMatchDataReq)
|
||||||
|
.getData();
|
||||||
|
|
||||||
|
DataPermissionContextHolder.DataPermissionContext dataPermissionContext = DataPermissionContextHolder.DataPermissionContext.builder().build();
|
||||||
|
if (mergeMatchDataResp == null) {
|
||||||
|
return dataPermissionContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
dataPermissionContext.setNodeIds(mergeMatchDataResp.getNodeIds());
|
||||||
|
dataPermissionContext.setPersonIds(mergeMatchDataResp.getPersonIds());
|
||||||
|
return dataPermissionContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Long getRequestBodyWorkspaceId(HttpServletRequest httpRequest, DataPermission dataPermission) {
|
||||||
|
try {
|
||||||
|
String requestBodyWorkspaceId = dataPermission.requestBodyWorkspaceId();
|
||||||
|
if (StringUtils.isBlank(requestBodyWorkspaceId)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String requestBody = httpRequest.getReader().lines().collect(Collectors.joining(System.lineSeparator()));
|
||||||
|
ObjectMapper mapper = new ObjectMapper();
|
||||||
|
JsonNode rootNode = mapper.readTree(requestBody);
|
||||||
|
String workspaceId = rootNode.path(requestBodyWorkspaceId).asText();
|
||||||
|
if (StringUtils.isNotBlank(workspaceId)) {
|
||||||
|
return Long.valueOf(workspaceId);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("获取提交body里面的workspaceId出错", e);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,338 @@
|
|||||||
|
package cn.axzo.framework.datapermission.aop;
|
||||||
|
|
||||||
|
import cn.axzo.framework.datapermission.annotation.DataPermission;
|
||||||
|
import cn.axzo.framework.datapermission.context.DataPermissionContextFactory;
|
||||||
|
import cn.axzo.framework.datapermission.context.DataPermissionContextHolder;
|
||||||
|
import cn.axzo.framework.datapermission.rule.DataPermissionRuleService;
|
||||||
|
import com.baomidou.mybatisplus.core.parser.SqlParserHelper;
|
||||||
|
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
|
||||||
|
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
|
||||||
|
import com.baomidou.mybatisplus.extension.parser.JsqlParserSupport;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import net.sf.jsqlparser.expression.BinaryExpression;
|
||||||
|
import net.sf.jsqlparser.expression.Expression;
|
||||||
|
import net.sf.jsqlparser.expression.NotExpression;
|
||||||
|
import net.sf.jsqlparser.expression.Parenthesis;
|
||||||
|
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
|
||||||
|
import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
|
||||||
|
import net.sf.jsqlparser.expression.operators.relational.ExistsExpression;
|
||||||
|
import net.sf.jsqlparser.expression.operators.relational.InExpression;
|
||||||
|
import net.sf.jsqlparser.expression.operators.relational.ItemsList;
|
||||||
|
import net.sf.jsqlparser.schema.Table;
|
||||||
|
import net.sf.jsqlparser.statement.delete.Delete;
|
||||||
|
import net.sf.jsqlparser.statement.select.FromItem;
|
||||||
|
import net.sf.jsqlparser.statement.select.Join;
|
||||||
|
import net.sf.jsqlparser.statement.select.LateralSubSelect;
|
||||||
|
import net.sf.jsqlparser.statement.select.PlainSelect;
|
||||||
|
import net.sf.jsqlparser.statement.select.Select;
|
||||||
|
import net.sf.jsqlparser.statement.select.SelectBody;
|
||||||
|
import net.sf.jsqlparser.statement.select.SetOperationList;
|
||||||
|
import net.sf.jsqlparser.statement.select.SubJoin;
|
||||||
|
import net.sf.jsqlparser.statement.select.SubSelect;
|
||||||
|
import net.sf.jsqlparser.statement.select.ValuesList;
|
||||||
|
import net.sf.jsqlparser.statement.select.WithItem;
|
||||||
|
import net.sf.jsqlparser.statement.update.Update;
|
||||||
|
import org.apache.ibatis.executor.Executor;
|
||||||
|
import org.apache.ibatis.executor.statement.StatementHandler;
|
||||||
|
import org.apache.ibatis.mapping.BoundSql;
|
||||||
|
import org.apache.ibatis.mapping.MappedStatement;
|
||||||
|
import org.apache.ibatis.mapping.SqlCommandType;
|
||||||
|
import org.apache.ibatis.session.ResultHandler;
|
||||||
|
import org.apache.ibatis.session.RowBounds;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 参考 #{@link com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor}
|
||||||
|
* @author tanjie@axzo.cn
|
||||||
|
* @date 2024/5/31 11:41
|
||||||
|
*/
|
||||||
|
//@RequiredArgsConstructor
|
||||||
|
public class DataPermissionMybatisInterceptor
|
||||||
|
// extends JsqlParserSupport implements InnerInterceptor
|
||||||
|
{
|
||||||
|
|
||||||
|
// private final DataPermissionContextFactory dataPermissionContextFactory;
|
||||||
|
|
||||||
|
// @Override
|
||||||
|
// public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
|
||||||
|
// if (!filter()) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// PluginUtils.MPBoundSql mpBs = PluginUtils.mpBoundSql(boundSql);
|
||||||
|
// mpBs.sql(parserSingle(mpBs.sql(), null));
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// @Override
|
||||||
|
// public void beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) {
|
||||||
|
// PluginUtils.MPStatementHandler mpSh = PluginUtils.mpStatementHandler(sh);
|
||||||
|
// MappedStatement ms = mpSh.mappedStatement();
|
||||||
|
// SqlCommandType sct = ms.getSqlCommandType();
|
||||||
|
// // 不管insert
|
||||||
|
// if (sct == SqlCommandType.UPDATE || sct == SqlCommandType.DELETE) {
|
||||||
|
// if (!filter()) return;
|
||||||
|
// if (SqlParserHelper.getSqlParserInfo(ms)) return;
|
||||||
|
// PluginUtils.MPBoundSql mpBs = mpSh.mPBoundSql();
|
||||||
|
// mpBs.sql(parserMulti(mpBs.sql(), null));
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// @Override
|
||||||
|
// protected void processSelect(Select select, int index, String sql, Object obj) {
|
||||||
|
// processSelectBody(select.getSelectBody());
|
||||||
|
// List<WithItem> withItemsList = select.getWithItemsList();
|
||||||
|
// if (!CollectionUtils.isEmpty(withItemsList)) {
|
||||||
|
// withItemsList.forEach(this::processSelectBody);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// protected void processSelectBody(SelectBody selectBody) {
|
||||||
|
// if (selectBody == null) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// if (selectBody instanceof PlainSelect) {
|
||||||
|
// processPlainSelect((PlainSelect) selectBody);
|
||||||
|
// } else if (selectBody instanceof WithItem) {
|
||||||
|
// WithItem withItem = (WithItem) selectBody;
|
||||||
|
// processSelectBody(withItem.getSelectBody());
|
||||||
|
// } else {
|
||||||
|
// SetOperationList operationList = (SetOperationList) selectBody;
|
||||||
|
// if (operationList.getSelects() != null && operationList.getSelects().size() > 0) {
|
||||||
|
// operationList.getSelects().forEach(this::processSelectBody);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// /**
|
||||||
|
// * update 语句处理
|
||||||
|
// */
|
||||||
|
// @Override
|
||||||
|
// protected void processUpdate(Update update, int index, String sql, Object obj) {
|
||||||
|
// final Table table = update.getTable();
|
||||||
|
// if (!filter()) {
|
||||||
|
// // 过滤退出执行
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// update.setWhere(this.andExpression(table, update.getWhere()));
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// /**
|
||||||
|
// * delete 语句处理
|
||||||
|
// */
|
||||||
|
// @Override
|
||||||
|
// protected void processDelete(Delete delete, int index, String sql, Object obj) {
|
||||||
|
// if (!filter()) {
|
||||||
|
// // 过滤退出执行
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// delete.setWhere(this.andExpression(delete.getTable(), delete.getWhere()));
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// /**
|
||||||
|
// * delete update 语句 where 处理
|
||||||
|
// */
|
||||||
|
// protected Expression andExpression(Table table, Expression where) {
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// DataPermissionContextHolder.DataPermissionContext dataPermissionContext = DataPermissionContextHolder.get();
|
||||||
|
// DataPermission dataPermission = dataPermissionContext.getDataPermission();
|
||||||
|
// // 接口未配置注解,就不需要自动增加sql
|
||||||
|
// if (dataPermission == null) {
|
||||||
|
// return where;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// List<DataPermissionRuleService> byDataPermission = dataPermissionContextFactory.getRuleByDataPermission();
|
||||||
|
// Optional<DataPermissionRuleService> first = byDataPermission.stream().filter(rule -> rule.getTableName().contains(table.getName())).findFirst();
|
||||||
|
// if (!first.isPresent()) {
|
||||||
|
// return where;
|
||||||
|
// }
|
||||||
|
// DataPermissionRuleService dataPermissionRuleService = first.get();
|
||||||
|
// Expression ruleExpression = dataPermissionRuleService.getExpression(table.getName(), table.getAlias());
|
||||||
|
//
|
||||||
|
// if (null != where) {
|
||||||
|
// if (where instanceof OrExpression) {
|
||||||
|
// return new AndExpression(ruleExpression, new Parenthesis(where));
|
||||||
|
// } else {
|
||||||
|
// return new AndExpression(ruleExpression, where);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// return ruleExpression;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// /**
|
||||||
|
// * 处理 PlainSelect
|
||||||
|
// */
|
||||||
|
// protected void processPlainSelect(PlainSelect plainSelect) {
|
||||||
|
// FromItem fromItem = plainSelect.getFromItem();
|
||||||
|
// Expression where = plainSelect.getWhere();
|
||||||
|
// processWhereSubSelect(where);
|
||||||
|
// if (fromItem instanceof Table) {
|
||||||
|
// Table fromTable = (Table) fromItem;
|
||||||
|
// if (filter()) {
|
||||||
|
// //#1186 github
|
||||||
|
// plainSelect.setWhere(builderExpression(where, fromTable));
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// processFromItem(fromItem);
|
||||||
|
// }
|
||||||
|
// List<Join> joins = plainSelect.getJoins();
|
||||||
|
// if (joins != null && joins.size() > 0) {
|
||||||
|
// joins.forEach(j -> {
|
||||||
|
// processJoin(j);
|
||||||
|
// processFromItem(j.getRightItem());
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// /**
|
||||||
|
// * 处理where条件内的子查询
|
||||||
|
// * <p>
|
||||||
|
// * 支持如下:
|
||||||
|
// * 1. in
|
||||||
|
// * 2. =
|
||||||
|
// * 3. >
|
||||||
|
// * 4. <
|
||||||
|
// * 5. >=
|
||||||
|
// * 6. <=
|
||||||
|
// * 7. <>
|
||||||
|
// * 8. EXISTS
|
||||||
|
// * 9. NOT EXISTS
|
||||||
|
// * <p>
|
||||||
|
// * 前提条件:
|
||||||
|
// * 1. 子查询必须放在小括号中
|
||||||
|
// * 2. 子查询一般放在比较操作符的右边
|
||||||
|
// *
|
||||||
|
// * @param where where 条件
|
||||||
|
// */
|
||||||
|
// protected void processWhereSubSelect(Expression where) {
|
||||||
|
// if (where == null) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// if (where instanceof FromItem) {
|
||||||
|
// processFromItem((FromItem) where);
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// if (where.toString().indexOf("SELECT") > 0) {
|
||||||
|
// // 有子查询
|
||||||
|
// if (where instanceof BinaryExpression) {
|
||||||
|
// // 比较符号 , and , or , 等等
|
||||||
|
// BinaryExpression expression = (BinaryExpression) where;
|
||||||
|
// processWhereSubSelect(expression.getLeftExpression());
|
||||||
|
// processWhereSubSelect(expression.getRightExpression());
|
||||||
|
// } else if (where instanceof InExpression) {
|
||||||
|
// // in
|
||||||
|
// InExpression expression = (InExpression) where;
|
||||||
|
// ItemsList itemsList = expression.getRightItemsList();
|
||||||
|
// if (itemsList instanceof SubSelect) {
|
||||||
|
// processSelectBody(((SubSelect) itemsList).getSelectBody());
|
||||||
|
// }
|
||||||
|
// } else if (where instanceof ExistsExpression) {
|
||||||
|
// // exists
|
||||||
|
// ExistsExpression expression = (ExistsExpression) where;
|
||||||
|
// processWhereSubSelect(expression.getRightExpression());
|
||||||
|
// } else if (where instanceof NotExpression) {
|
||||||
|
// // not exists
|
||||||
|
// NotExpression expression = (NotExpression) where;
|
||||||
|
// processWhereSubSelect(expression.getExpression());
|
||||||
|
// } else if (where instanceof Parenthesis) {
|
||||||
|
// Parenthesis expression = (Parenthesis) where;
|
||||||
|
// processWhereSubSelect(expression.getExpression());
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// /**
|
||||||
|
// * 处理子查询等
|
||||||
|
// */
|
||||||
|
// protected void processFromItem(FromItem fromItem) {
|
||||||
|
// if (fromItem instanceof SubJoin) {
|
||||||
|
// SubJoin subJoin = (SubJoin) fromItem;
|
||||||
|
// if (subJoin.getJoinList() != null) {
|
||||||
|
// subJoin.getJoinList().forEach(this::processJoin);
|
||||||
|
// }
|
||||||
|
// if (subJoin.getLeft() != null) {
|
||||||
|
// processFromItem(subJoin.getLeft());
|
||||||
|
// }
|
||||||
|
// } else if (fromItem instanceof SubSelect) {
|
||||||
|
// SubSelect subSelect = (SubSelect) fromItem;
|
||||||
|
// if (subSelect.getSelectBody() != null) {
|
||||||
|
// processSelectBody(subSelect.getSelectBody());
|
||||||
|
// }
|
||||||
|
// } else if (fromItem instanceof ValuesList) {
|
||||||
|
// logger.debug("Perform a subquery, if you do not give us feedback");
|
||||||
|
// } else if (fromItem instanceof LateralSubSelect) {
|
||||||
|
// LateralSubSelect lateralSubSelect = (LateralSubSelect) fromItem;
|
||||||
|
// if (lateralSubSelect.getSubSelect() != null) {
|
||||||
|
// SubSelect subSelect = lateralSubSelect.getSubSelect();
|
||||||
|
// if (subSelect.getSelectBody() != null) {
|
||||||
|
// processSelectBody(subSelect.getSelectBody());
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// /**
|
||||||
|
// * 处理联接语句
|
||||||
|
// */
|
||||||
|
// protected void processJoin(Join join) {
|
||||||
|
// if (join.getRightItem() instanceof Table) {
|
||||||
|
// Table fromTable = (Table) join.getRightItem();
|
||||||
|
// if (!filter()) {
|
||||||
|
// // 过滤退出执行
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// join.setOnExpression(builderExpression(join.getOnExpression(), fromTable));
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// /**
|
||||||
|
// * 处理条件
|
||||||
|
// */
|
||||||
|
// protected Expression builderExpression(Expression currentExpression, Table table) {
|
||||||
|
//
|
||||||
|
// DataPermissionContextHolder.DataPermissionContext dataPermissionContext = DataPermissionContextHolder.get();
|
||||||
|
// DataPermission dataPermission = dataPermissionContext.getDataPermission();
|
||||||
|
//
|
||||||
|
// // 接口未配置注解,就不需要自动增加sql
|
||||||
|
// if (dataPermission == null) {
|
||||||
|
// return currentExpression;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// List<DataPermissionRuleService> byDataPermission = dataPermissionContextFactory.getRuleByDataPermission();
|
||||||
|
// Optional<DataPermissionRuleService> first = byDataPermission.stream().filter(rule -> rule.getTableName().contains(table.getName())).findFirst();
|
||||||
|
// if (!first.isPresent()) {
|
||||||
|
// return currentExpression;
|
||||||
|
// }
|
||||||
|
// DataPermissionRuleService dataPermissionRuleService = first.get();
|
||||||
|
// Expression ruleExpression = dataPermissionRuleService.getExpression(table.getName(), table.getAlias());
|
||||||
|
// if (currentExpression == null) {
|
||||||
|
// return ruleExpression;
|
||||||
|
// }
|
||||||
|
// if (currentExpression instanceof OrExpression) {
|
||||||
|
// return new AndExpression(new Parenthesis(currentExpression), ruleExpression);
|
||||||
|
// } else {
|
||||||
|
// return new AndExpression(currentExpression, ruleExpression);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// private boolean filter() {
|
||||||
|
// DataPermissionContextHolder.DataPermissionContext dataPermissionContext = DataPermissionContextHolder.get();
|
||||||
|
// if (null == dataPermissionContext
|
||||||
|
// || dataPermissionContext.getDataPermission() == null
|
||||||
|
// || !dataPermissionContext.getDataPermission().enable()) {
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return true;
|
||||||
|
// }
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
package cn.axzo.framework.datapermission.config;
|
||||||
|
|
||||||
|
import cn.axzo.framework.datapermission.aop.DataPermissionMybatisInterceptor;
|
||||||
|
import cn.axzo.framework.datapermission.context.DataPermissionContextFactory;
|
||||||
|
import cn.axzo.framework.datapermission.rule.DataPermissionRuleService;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author tanjie@axzo.cn
|
||||||
|
* @date 2024/5/31 16:34
|
||||||
|
*/
|
||||||
|
//@ConditionalOnBean(MybatisPlusInterceptor.class)
|
||||||
|
public class DataPermissionConfig {
|
||||||
|
|
||||||
|
// @Bean
|
||||||
|
// @ConditionalOnBean(MybatisPlusInterceptor.class)
|
||||||
|
// public DataPermissionMybatisInterceptor mybatisDataPermissionInterceptor(MybatisPlusInterceptor mybatisPlusInterceptor, DataPermissionContextFactory dataPermissionContextFactory) {
|
||||||
|
// DataPermissionMybatisInterceptor dataPermissionMybatisInterceptor = new DataPermissionMybatisInterceptor(dataPermissionContextFactory);
|
||||||
|
// mybatisPlusInterceptor.addInnerInterceptor(dataPermissionMybatisInterceptor);
|
||||||
|
// return dataPermissionMybatisInterceptor;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// @Bean
|
||||||
|
// public DataPermissionContextFactory dataPermissionRuleFactory(List<DataPermissionRuleService> rules) {
|
||||||
|
// return new DataPermissionContextFactory(rules);
|
||||||
|
// }
|
||||||
|
}
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
package cn.axzo.framework.datapermission.context;
|
||||||
|
|
||||||
|
import cn.axzo.framework.datapermission.rule.DataPermissionRuleService;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author tanjie@axzo.cn
|
||||||
|
* @date 2024/5/31 17:58
|
||||||
|
*/
|
||||||
|
//@AllArgsConstructor
|
||||||
|
public class DataPermissionContextFactory {
|
||||||
|
|
||||||
|
List<DataPermissionRuleService> rules;
|
||||||
|
|
||||||
|
|
||||||
|
public List<DataPermissionRuleService> getRules() {
|
||||||
|
return rules;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<DataPermissionRuleService> getRuleByDataPermission() {
|
||||||
|
return rules;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,79 @@
|
|||||||
|
package cn.axzo.framework.datapermission.context;
|
||||||
|
|
||||||
|
import cn.axzo.framework.datapermission.annotation.DataPermission;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据权限上下文
|
||||||
|
*
|
||||||
|
* @author tanjie@axzo.cn
|
||||||
|
* @date 2024/5/31 11:42
|
||||||
|
*/
|
||||||
|
public class DataPermissionContextHolder {
|
||||||
|
|
||||||
|
public static final String DATA_PERMISSION_HEADER = "dataPermission";
|
||||||
|
|
||||||
|
private final static ThreadLocal<DataPermissionContext> DATA_PERMISSION_CONTEXT = new ThreadLocal<>();
|
||||||
|
|
||||||
|
public static void setContext(DataPermissionContext dataPermission) {
|
||||||
|
DATA_PERMISSION_CONTEXT.set(dataPermission);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public static class DataPermissionContext {
|
||||||
|
private DataPermission dataPermission;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前单位id
|
||||||
|
*/
|
||||||
|
private Long ouId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前项目id
|
||||||
|
*/
|
||||||
|
private Long workspaceId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 登录人
|
||||||
|
*/
|
||||||
|
private Long personId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 匹配的解析规则放在redis中的key
|
||||||
|
*/
|
||||||
|
private String resultKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析后的人员id
|
||||||
|
*/
|
||||||
|
private Set<Long> personIds;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析后的部门id
|
||||||
|
*/
|
||||||
|
private Set<Long> nodeIds;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据权限解析后的单位id
|
||||||
|
*/
|
||||||
|
private Set<Long> ouIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void remove() {
|
||||||
|
DATA_PERMISSION_CONTEXT.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DataPermissionContext get() {
|
||||||
|
return DATA_PERMISSION_CONTEXT.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
package cn.axzo.framework.datapermission.rule;
|
||||||
|
|
||||||
|
import net.sf.jsqlparser.expression.Alias;
|
||||||
|
import net.sf.jsqlparser.expression.Expression;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author tanjie@axzo.cn
|
||||||
|
* @date 2024/5/30 18:31
|
||||||
|
*/
|
||||||
|
public interface DataPermissionRuleService {
|
||||||
|
|
||||||
|
Set<String> getTableName();
|
||||||
|
|
||||||
|
Expression getExpression(String tableName, Alias tableAlias);
|
||||||
|
}
|
||||||
@ -0,0 +1,158 @@
|
|||||||
|
package cn.axzo.framework.datapermission.rule;
|
||||||
|
|
||||||
|
import cn.axzo.framework.datapermission.context.DataPermissionContextHolder;
|
||||||
|
import cn.axzo.karma.client.feign.tyr.DataObjectApi;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.TableInfo;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
|
||||||
|
import com.baomidou.mybatisplus.core.toolkit.StringPool;
|
||||||
|
import com.baomidou.mybatisplus.extension.activerecord.Model;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import net.sf.jsqlparser.expression.Alias;
|
||||||
|
import net.sf.jsqlparser.expression.Expression;
|
||||||
|
import net.sf.jsqlparser.expression.LongValue;
|
||||||
|
import net.sf.jsqlparser.expression.NullValue;
|
||||||
|
import net.sf.jsqlparser.expression.Parenthesis;
|
||||||
|
import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
|
||||||
|
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
|
||||||
|
import net.sf.jsqlparser.expression.operators.relational.InExpression;
|
||||||
|
import net.sf.jsqlparser.schema.Column;
|
||||||
|
import org.springframework.util.CollectionUtils;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 基于personId、租户、单位、部门的数据权限
|
||||||
|
* @author tanjie@axzo.cn
|
||||||
|
* @date 2024/5/30 18:35
|
||||||
|
*/
|
||||||
|
@Builder
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Slf4j
|
||||||
|
public class OrgDefaultRuleServiceImpl implements DataPermissionRuleService {
|
||||||
|
static final Expression EXPRESSION_NULL = new NullValue();
|
||||||
|
|
||||||
|
private static final String DEFAULT_PERSON = "person_id";
|
||||||
|
private static final String DEFAULT_NODE = "organizational_node_id";
|
||||||
|
/**
|
||||||
|
* key 表名
|
||||||
|
* value 组织节点字段
|
||||||
|
*/
|
||||||
|
protected final Map<String, String> nodeTable = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* key 表名
|
||||||
|
* value personId字段
|
||||||
|
*/
|
||||||
|
protected final Map<String, String> personTable = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
protected final Set<String> tableName =new HashSet<>();
|
||||||
|
|
||||||
|
private final DataObjectApi dataObjectApi;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<String> getTableName() {
|
||||||
|
return tableName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Expression getExpression(String tableName, Alias tableAlias) {
|
||||||
|
|
||||||
|
DataPermissionContextHolder.DataPermissionContext dataPermissionContext = DataPermissionContextHolder.get();
|
||||||
|
|
||||||
|
if (dataPermissionContext == null) {
|
||||||
|
log.warn("not found dataPermissionContext");
|
||||||
|
return EXPRESSION_NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<Long> nodeIds = dataPermissionContext.getNodeIds();
|
||||||
|
Set<Long> personIds = dataPermissionContext.getPersonIds();
|
||||||
|
|
||||||
|
Expression deptExpression = buildExpression(tableName, tableAlias, nodeIds);
|
||||||
|
Expression userExpression = buildExpression(tableName, tableAlias, personIds);
|
||||||
|
|
||||||
|
if (deptExpression == null && userExpression == null) {
|
||||||
|
// 未获取到数据权限 返回NULL 不查询数据
|
||||||
|
return EXPRESSION_NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deptExpression == null) {
|
||||||
|
return userExpression;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userExpression == null) {
|
||||||
|
return deptExpression;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果两个都有,用or WHERE (organizational_Node_id IN ? OR person_id in ?)
|
||||||
|
return new Parenthesis(new OrExpression(deptExpression, userExpression));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建查询条件
|
||||||
|
*
|
||||||
|
* @param tableName
|
||||||
|
* @param tableAlias
|
||||||
|
* @param ids 可能是nodeids 也可以是personIds
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private Expression buildExpression(String tableName, Alias tableAlias, Set<Long> ids) {
|
||||||
|
// 如果不存在配置,则无需作为条件
|
||||||
|
String columnName = nodeTable.get(tableName);
|
||||||
|
if (!StringUtils.hasText(columnName)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (CollectionUtils.isEmpty(ids)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// 拼接条件
|
||||||
|
return new InExpression(
|
||||||
|
getColumn(tableAlias, columnName)
|
||||||
|
, new ExpressionList(ids.stream().map(LongValue::new).collect(Collectors.toList())));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private Column getColumn(Alias tableAlias, String columnName) {
|
||||||
|
String result = tableAlias.getName() + StringPool.DOT +
|
||||||
|
columnName;
|
||||||
|
return new Column(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加组织节点所对应的字段
|
||||||
|
*
|
||||||
|
* @param entityClass 表所对应的entity
|
||||||
|
* @param columnName 字段名称 如:organizational_node_id
|
||||||
|
*/
|
||||||
|
public void addNodeColumn(Class<? extends Model<?>> entityClass, String columnName) {
|
||||||
|
TableInfo tableInfo = TableInfoHelper.getTableInfo(entityClass);
|
||||||
|
if (null == tableInfo) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
nodeTable.put(tableInfo.getTableName(), columnName);
|
||||||
|
tableName.add(tableInfo.getTableName());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加personId所对应的字段
|
||||||
|
*
|
||||||
|
* @param entityClass 表所对应的entity
|
||||||
|
* @param columnName 字段名称 如:person_id
|
||||||
|
*/
|
||||||
|
public void addPersonColumn(Class<? extends Model<?>> entityClass, String columnName) {
|
||||||
|
TableInfo tableInfo = TableInfoHelper.getTableInfo(entityClass);
|
||||||
|
if (null == tableInfo) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
personTable.put(tableInfo.getTableName(), columnName);
|
||||||
|
tableName.add(tableInfo.getTableName());
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
package cn.axzo.framework.datapermission.util;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author likunpeng
|
||||||
|
* @version 1.0
|
||||||
|
* @date 2024/6/6
|
||||||
|
*/
|
||||||
|
public class DPUtil {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 忽略的class列表
|
||||||
|
*/
|
||||||
|
public static final Set<Class<?>> IGNORE_CLASS = new HashSet<>();
|
||||||
|
|
||||||
|
static {
|
||||||
|
// initIgnoreClass
|
||||||
|
IGNORE_CLASS.add(Byte.class);
|
||||||
|
IGNORE_CLASS.add(Short.class);
|
||||||
|
IGNORE_CLASS.add(Integer.class);
|
||||||
|
IGNORE_CLASS.add(Long.class);
|
||||||
|
IGNORE_CLASS.add(Float.class);
|
||||||
|
IGNORE_CLASS.add(BigDecimal.class);
|
||||||
|
IGNORE_CLASS.add(Double.class);
|
||||||
|
IGNORE_CLASS.add(Boolean.class);
|
||||||
|
IGNORE_CLASS.add(Character.class);
|
||||||
|
IGNORE_CLASS.add(String.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean inIgnoreClass(Class<?> cls) {
|
||||||
|
return IGNORE_CLASS.contains(cls);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,62 @@
|
|||||||
|
package cn.axzo.framework.datapermission.util;
|
||||||
|
|
||||||
|
import cn.axzo.basics.common.util.AssertUtil;
|
||||||
|
import cn.axzo.foundation.result.ApiResult;
|
||||||
|
import cn.axzo.framework.domain.ServiceException;
|
||||||
|
import cn.hutool.core.date.StopWatch;
|
||||||
|
import cn.hutool.core.lang.Assert;
|
||||||
|
import cn.hutool.json.JSONUtil;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author likunpeng
|
||||||
|
* @version 1.0
|
||||||
|
* @date 2024/6/7
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class RpcInternalUtil {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 常用的RPC请求返回值解析,如果 被请求方 返回非200会抛出异常
|
||||||
|
*/
|
||||||
|
public static <T> ApiResult<T> rpcProcessor(Supplier<ApiResult<T>> supplier, String operationType, Object... param) {
|
||||||
|
|
||||||
|
return rpcProcessorMayThrow(supplier, operationType, (msg) -> {
|
||||||
|
throw new ServiceException(msg);
|
||||||
|
}, param);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> ApiResult<T> rpcProcessorMayThrow(Supplier<ApiResult<T>> supplier, String operationType, Consumer<String> throwConsumer, Object... param) {
|
||||||
|
AssertUtil.notNull(throwConsumer, "自定义的异常处理不可为空");
|
||||||
|
log.info(operationType + "-Param: " + JSONUtil.toJsonStr(param));
|
||||||
|
ApiResult<T> result = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
result = printLatency(supplier, operationType);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
log.warn("rpc process error:{}", e.getMessage());
|
||||||
|
throwConsumer.accept("服务调用异常");
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info(operationType + "-Result: " + JSONUtil.toJsonStr(result));
|
||||||
|
Assert.notNull(result, "服务调用异常");
|
||||||
|
// 200自定义处理
|
||||||
|
if (!result.isSuccess()) {
|
||||||
|
throwConsumer.accept(result.getMsg());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <R> R printLatency(Supplier< R> function,String optType) {
|
||||||
|
StopWatch stopWatch = new StopWatch(optType);
|
||||||
|
stopWatch.start(optType);
|
||||||
|
R r = function.get();
|
||||||
|
stopWatch.stop();
|
||||||
|
log.info(stopWatch.shortSummary(TimeUnit.MILLISECONDS));
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||||
|
cn.axzo.framework.datapermission.config.DataPermissionConfig
|
||||||
1
pom.xml
1
pom.xml
@ -36,6 +36,7 @@
|
|||||||
<module>axzo-common-datas</module>
|
<module>axzo-common-datas</module>
|
||||||
<module>axzo-common-cache</module>
|
<module>axzo-common-cache</module>
|
||||||
<module>axzo-common-rocketmq</module>
|
<module>axzo-common-rocketmq</module>
|
||||||
|
<module>axzo-common-data-permission</module>
|
||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user