feat(REQ-2300):数据权限返回数据处理Advice功能
This commit is contained in:
parent
9771a5affa
commit
0198b16874
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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";
|
||||
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user