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:
周敏 2024-08-14 09:36:21 +00:00
commit a0782721c9
16 changed files with 1306 additions and 0 deletions

View 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>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

@ -0,0 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.axzo.framework.datapermission.config.DataPermissionConfig

View File

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