feat(REQ-2300):数据权限返回数据处理Advice功能

This commit is contained in:
李昆鹏 2024-06-13 15:29:04 +08:00
parent 9771a5affa
commit 0198b16874
5 changed files with 326 additions and 0 deletions

View File

@ -0,0 +1,94 @@
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();
} else {
dataPermission = returnType.getMethodAnnotation(DataPermission.class);
return dataPermission != null && dataPermission.enable();
}
}
@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);
} catch (Exception e) {
log.error("DataPermissionResponseBodyAdvice error", e);
}
// 最终清除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;
}
}

View File

@ -0,0 +1,123 @@
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.Lists;
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.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* @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 final DataObjectApi dataObjectApi;
public DataPermissionResponseExecutor(DataObjectApi dataObjectApi) {
this.dataObjectApi = dataObjectApi;
}
public void processNode(DataPermissionContextHolder.DataPermissionContext context, JsonNode dataNode) {
DataPermission dataPermission = context.getDataPermission();
List<ExamineDpColumnsReq.OrgNodeIdAndPersonId> orgNodeIdAndPersonIds = Lists.newArrayList();
recursiveGetOrgNodeIdAndPersonIds(dataPermission, dataNode, orgNodeIdAndPersonIds);
if (CollectionUtils.isEmpty(orgNodeIdAndPersonIds)) {
log.warn("api result has no organizationalNodeId and 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())
.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结果的数据对象
recursiveGetOrgNodeIdAndPersonIds(dataPermission, dataNode, examineDpColumnsResp.getAttributeDpResultMap());
}
private void recursiveGetOrgNodeIdAndPersonIds(DataPermission dataPermission, JsonNode dataNode, List<ExamineDpColumnsReq.OrgNodeIdAndPersonId> orgNodeIdAndPersonIds) {
if (dataNode.isObject()) {
Long resultOrganizationalNodeId = Objects.nonNull(dataNode.get(dataPermission.key_organizationalNodeId())) ? dataNode.get(dataPermission.key_organizationalNodeId()).asLong() : 0L;
Long resultPersonId = Objects.nonNull(dataNode.get(dataPermission.key_personId())) ? dataNode.get(dataPermission.key_personId()).asLong() : 0L;
if (resultOrganizationalNodeId.equals(0L) && resultPersonId.equals(0L)) {
return;
}
orgNodeIdAndPersonIds.add(ExamineDpColumnsReq.OrgNodeIdAndPersonId.builder().organizationalNodeId(resultOrganizationalNodeId).personId(resultPersonId).build());
}
if (dataNode.isArray()) {
ArrayNode arrayNode = (ArrayNode) dataNode;
Iterator<JsonNode> elements = arrayNode.elements();
while (elements.hasNext()) {
JsonNode element = elements.next();
recursiveGetOrgNodeIdAndPersonIds(dataPermission, element, orgNodeIdAndPersonIds);
}
}
}
private void recursiveGetOrgNodeIdAndPersonIds(DataPermission dataPermission, JsonNode dataNode,
Map<String, Map<String, ExamineDpColumnsResp.AttributePermissionBasicDTO>> attributeDpResultMap) {
if (dataNode.isObject()) {
Long resultOrganizationalNodeId = Objects.nonNull(dataNode.get(dataPermission.key_organizationalNodeId())) ? dataNode.get(dataPermission.key_organizationalNodeId()).asLong() : 0L;
Long resultPersonId = Objects.nonNull(dataNode.get(dataPermission.key_personId())) ? dataNode.get(dataPermission.key_personId()).asLong() : 0L;
if (resultOrganizationalNodeId.equals(0L) && resultPersonId.equals(0L)) {
return;
}
Map<String, ExamineDpColumnsResp.AttributePermissionBasicDTO> attributeMap = attributeDpResultMap.get(resultOrganizationalNodeId + "_" + resultPersonId);
if (MapUtils.isEmpty(attributeMap)) {
return;
}
for (Map.Entry<String, ExamineDpColumnsResp.AttributePermissionBasicDTO> entry : attributeMap.entrySet()) {
ExamineDpColumnsResp.AttributePermissionBasicDTO basic = entry.getValue();
if (dataNode.has(basic.getAttrCode())) {
if (!ATTRIBUTE_ISUNMASKABLE.equals(basic.getIsUnmaskable())) {
ObjectNode objectNode = (ObjectNode) dataNode;
objectNode.remove(basic.getAttrCode());
}
if (Objects.nonNull(basic.getIsEditable())) {
ObjectNode objectNode = (ObjectNode) dataNode;
objectNode.put(ATTRIBUTE_ADD_ALLOW_EDIT_PREFIX + basic.getAttrCode(), ATTRIBUTE_ISEDITABLE.equals(basic.getIsEditable()));
}
}
}
}
if (dataNode.isArray()) {
ArrayNode arrayNode = (ArrayNode) dataNode;
Iterator<JsonNode> elements = arrayNode.elements();
while (elements.hasNext()) {
JsonNode element = elements.next();
recursiveGetOrgNodeIdAndPersonIds(dataPermission, element, attributeDpResultMap);
}
}
}
}

View File

@ -30,4 +30,15 @@ public @interface DataPermission {
*/
String bizCode() default "";
/**
* ApiResult返回的机构节点的ID
* @return
*/
String key_organizationalNodeId() default "organizationalNodeId";
/**
* ApiResult返回的用户的ID
* @return
*/
String key_personId() default "personId";
}

View File

@ -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);
}
}

View File

@ -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;
}
}