Merge remote-tracking branch 'base-version/main' into dev

This commit is contained in:
chenbowen
2026-01-06 15:24:13 +08:00
39 changed files with 1457 additions and 35 deletions

View File

@@ -1,6 +1,8 @@
package com.zt.plat.framework.datapermission.config;
import com.zt.plat.framework.datapermission.core.aop.CompanyDataPermissionIgnoreAspect;
import com.zt.plat.framework.datapermission.core.aop.DataPermissionAnnotationAdvisor;
import com.zt.plat.framework.datapermission.core.aop.DeptDataPermissionIgnoreAspect;
import com.zt.plat.framework.datapermission.core.db.DataPermissionRuleHandler;
import com.zt.plat.framework.datapermission.core.rule.DataPermissionRule;
import com.zt.plat.framework.datapermission.core.rule.DataPermissionRuleFactory;
@@ -43,4 +45,14 @@ public class ZtDataPermissionAutoConfiguration {
return new DataPermissionAnnotationAdvisor();
}
@Bean
public DeptDataPermissionIgnoreAspect deptDataPermissionIgnoreAspect() {
return new DeptDataPermissionIgnoreAspect();
}
@Bean
public CompanyDataPermissionIgnoreAspect companyDataPermissionIgnoreAspect() {
return new CompanyDataPermissionIgnoreAspect();
}
}

View File

@@ -0,0 +1,21 @@
package com.zt.plat.framework.datapermission.core.annotation;
import java.lang.annotation.*;
/**
* 忽略公司数据权限的注解。
* <p>
* 标记在方法或类上时,匹配的调用会临时忽略公司类型的数据权限规则。
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface CompanyDataPermissionIgnore {
/**
* 是否开启忽略,默认开启。
* 支持 Spring EL 表达式,返回 true 时生效。
*/
String enable() default "true";
}

View File

@@ -0,0 +1,21 @@
package com.zt.plat.framework.datapermission.core.annotation;
import java.lang.annotation.*;
/**
* 忽略部门数据权限的注解。
* <p>
* 标记在方法或类上时,匹配的调用会临时忽略部门类型的数据权限规则。
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface DeptDataPermissionIgnore {
/**
* 是否开启忽略,默认开启。
* 支持 Spring EL 表达式,返回 true 时生效。
*/
String enable() default "true";
}

View File

@@ -0,0 +1,35 @@
package com.zt.plat.framework.datapermission.core.aop;
import com.zt.plat.framework.common.util.spring.SpringExpressionUtils;
import com.zt.plat.framework.datapermission.core.annotation.CompanyDataPermissionIgnore;
import com.zt.plat.framework.tenant.core.context.CompanyContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
/**
* 公司数据权限忽略切面,基于 {@link CompanyDataPermissionIgnore} 注解。
*/
@Aspect
@Slf4j
public class CompanyDataPermissionIgnoreAspect {
@Around("@within(companyDataPermissionIgnore) || @annotation(companyDataPermissionIgnore)")
public Object around(ProceedingJoinPoint joinPoint, CompanyDataPermissionIgnore companyDataPermissionIgnore) throws Throwable {
boolean oldIgnore = CompanyContextHolder.isIgnore();
try {
if (companyDataPermissionIgnore == null) {
Class<?> targetClass = joinPoint.getTarget().getClass();
companyDataPermissionIgnore = targetClass.getAnnotation(CompanyDataPermissionIgnore.class);
}
Object enable = SpringExpressionUtils.parseExpression(companyDataPermissionIgnore.enable());
if (Boolean.TRUE.equals(enable)) {
CompanyContextHolder.setIgnore(true);
}
return joinPoint.proceed();
} finally {
CompanyContextHolder.setIgnore(oldIgnore);
}
}
}

View File

@@ -0,0 +1,35 @@
package com.zt.plat.framework.datapermission.core.aop;
import com.zt.plat.framework.common.util.spring.SpringExpressionUtils;
import com.zt.plat.framework.datapermission.core.annotation.DeptDataPermissionIgnore;
import com.zt.plat.framework.tenant.core.context.DeptContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
/**
* 部门数据权限忽略切面,基于 {@link DeptDataPermissionIgnore} 注解。
*/
@Aspect
@Slf4j
public class DeptDataPermissionIgnoreAspect {
@Around("@within(deptDataPermissionIgnore) || @annotation(deptDataPermissionIgnore)")
public Object around(ProceedingJoinPoint joinPoint, DeptDataPermissionIgnore deptDataPermissionIgnore) throws Throwable {
boolean oldIgnore = DeptContextHolder.shouldIgnore();
try {
if (deptDataPermissionIgnore == null) {
Class<?> targetClass = joinPoint.getTarget().getClass();
deptDataPermissionIgnore = targetClass.getAnnotation(DeptDataPermissionIgnore.class);
}
Object enable = SpringExpressionUtils.parseExpression(deptDataPermissionIgnore.enable());
if (Boolean.TRUE.equals(enable)) {
DeptContextHolder.setIgnore(true);
}
return joinPoint.proceed();
} finally {
DeptContextHolder.setIgnore(oldIgnore);
}
}
}

View File

@@ -10,7 +10,9 @@ import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.schema.Table;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import static com.zt.plat.framework.security.core.util.SecurityFrameworkUtils.skipPermissionCheck;
@@ -41,6 +43,7 @@ public class DataPermissionRuleHandler implements MultiDataPermissionHandler {
}
// 生成条件
final Set<String> processed = new HashSet<>();
Expression allExpression = null;
for (DataPermissionRule rule : rules) {
// 判断表名是否匹配
@@ -49,6 +52,14 @@ public class DataPermissionRuleHandler implements MultiDataPermissionHandler {
continue;
}
// 同一张表 + 同一别名 + 同一规则 在一次 SQL 解析内仅处理一次,避免重复拼接条件
String aliasName = table.getAlias() == null ? "" : table.getAlias().getName();
String key = tableName + "|" + aliasName + "|" + rule.getClass().getName();
if (processed.contains(key)) {
continue;
}
processed.add(key);
// 单条规则的条件
Expression oneExpress = rule.getExpression(tableName, table.getAlias());
if (oneExpress == null) {

View File

@@ -5,6 +5,7 @@ import cn.hutool.core.util.StrUtil;
import com.zt.plat.framework.common.util.collection.CollectionUtils;
import com.zt.plat.framework.datapermission.core.rule.DataPermissionRule;
import com.zt.plat.framework.mybatis.core.util.MyBatisUtils;
import com.zt.plat.framework.tenant.core.context.CompanyContextHolder;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.expression.Alias;
@@ -49,6 +50,10 @@ public class CompanyDataPermissionRule implements DataPermissionRule {
@Override
public Expression getExpression(String tableName, Alias tableAlias) {
// 显式忽略公司数据权限时直接放行
if (CompanyContextHolder.isIgnore()) {
return null;
}
// 业务拼接 Company 的条件
if (getLoginUserCompanyId() == null) {
// 如果没有登录用户的公司编号,则不需要拼接条件

View File

@@ -156,21 +156,28 @@ public class DeptDataPermissionRule implements DataPermissionRule {
}
}
// 情况一,如果是 ALL 可查看全部,则无需拼接条件
if (deptDataPermission.getAll()) {
// 计算有效的部门与自查标记:当存在上下文部门且未被忽略时,强制仅使用该部门,以避免默认全量或空权限分支
Set<Long> effectiveDeptIds = deptDataPermission.getDeptIds();
Boolean effectiveSelf = deptDataPermission.getSelf();
if (!DeptContextHolder.shouldIgnore() && ctxDeptId != null && ctxDeptId > 0L) {
effectiveDeptIds = CollUtil.newHashSet(ctxDeptId);
}
// 情况一:仅当不存在上下文部门时,且 ALL 可查看全部,才无需拼接条件;若存在上下文部门则仍需基于该部门过滤
if (ctxDeptId == null && deptDataPermission.getAll()) {
return null;
}
// 情况二,即不能查看部门,又不能查看自己,则说明 100% 无权限
if (CollUtil.isEmpty(deptDataPermission.getDeptIds())
&& Boolean.FALSE.equals(deptDataPermission.getSelf())) {
// 情况二:仅在有效部门集合为空且不可查看自己时,才认为无权限;若上下文提供部门,则跳过该兜底
if (CollUtil.isEmpty(effectiveDeptIds)
&& Boolean.FALSE.equals(effectiveSelf)) {
return new EqualsTo(null, null); // WHERE null = null可以保证返回的数据为空
}
// 情况三,拼接 Dept 和 Company User 的条件,最后组合
Expression deptExpression = buildDeptExpression(tableName, tableAlias, deptDataPermission.getDeptIds());
Expression deptExpression = buildDeptExpression(tableName, tableAlias, effectiveDeptIds);
// Expression deptExpression = buildDeptExpression(tableName, tableAlias, deptDataPermission.getDeptIds());
Expression userExpression = buildUserExpression(tableName, tableAlias, deptDataPermission.getSelf(), loginUser.getId());
Expression userExpression = buildUserExpression(tableName, tableAlias, effectiveSelf, loginUser.getId());
if (deptExpression == null && userExpression == null) {
// TODO ZT获得不到条件的时候暂时不抛出异常而是不返回数据
log.warn("[getExpression][LoginUser({}) Table({}/{}) DeptDataPermission({}) 构建的条件为空]",