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

@@ -25,7 +25,7 @@ CREATE TABLE infra_api_access_log (
user_ip varchar(50) NOT NULL,
user_agent varchar(512) NOT NULL,
operate_module varchar(50) DEFAULT NULL NULL,
operate_name varchar(50) DEFAULT NULL NULL,
operate_name varchar(256) DEFAULT NULL NULL,
operate_type smallint DEFAULT 0 NULL,
begin_time datetime NOT NULL,
end_time datetime NOT NULL,

View File

@@ -25,6 +25,7 @@ public interface GlobalErrorCodeConstants {
ErrorCode METHOD_NOT_ALLOWED = new ErrorCode(405, "请求方法不正确");
ErrorCode LOCKED = new ErrorCode(423, "请求失败,请稍后重试"); // 并发请求,不允许
ErrorCode TOO_MANY_REQUESTS = new ErrorCode(429, "请求过于频繁,请稍后重试");
ErrorCode NOT_NULL_REQUEST_ERROR = new ErrorCode(430, "请求参数不能为空");
// ========== 服务端错误段 ==========

View File

@@ -8,6 +8,7 @@ import com.zt.plat.framework.common.util.json.JsonUtils;
import com.zt.plat.framework.common.util.spring.SpringUtils;
import com.zt.plat.framework.security.core.LoginUser;
import com.zt.plat.framework.tenant.core.context.CompanyContextHolder;
import com.zt.plat.framework.tenant.core.context.DeptContextHolder;
import com.zt.plat.framework.web.core.util.WebFrameworkUtils;
import com.zt.plat.module.system.api.dept.DeptApi;
import com.zt.plat.module.system.api.dept.dto.CompanyDeptInfoRespDTO;
@@ -197,6 +198,9 @@ public class BusinessDeptHandleUtil {
}
CompanyContextHolder.setIgnore(false);
CompanyContextHolder.setCompanyId(Long.valueOf(info.getCompanyId()));
DeptContextHolder.setIgnore(false);
DeptContextHolder.setCompanyId(Long.valueOf(info.getCompanyId()));
DeptContextHolder.setDeptId(Long.valueOf(info.getDeptId()));
return true;
}
}

View File

@@ -0,0 +1,42 @@
# 数据权限忽略与上下文覆盖说明
本文说明新增的公司/部门数据权限忽略能力,以及部门上下文对数据权限的覆盖策略。
## 新增注解
- `@CompanyDataPermissionIgnore(enable = "true")`
- `@DeptDataPermissionIgnore(enable = "true")`
用法:
- 可标记在类或方法上。
- `enable` 支持 Spring EL计算结果为 `true` 时生效,默认开启。
- 生效后,在方法执行期间临时设置忽略标记,结束后自动恢复。
## 忽略生效范围
- 公司数据权限:切面在进入方法时将 `CompanyContextHolder.setIgnore(true)`,数据权限规则检测到后直接放行。
- 部门数据权限:切面在进入方法时将 `DeptContextHolder.setIgnore(true)`,部门数据权限规则检测到后直接放行。
## 部门上下文覆盖策略
当未忽略部门数据权限且上下文存在有效部门 ID 时:
- 优先使用上下文中的单一部门作为过滤条件,避免因默认的 `ALL` 或“无部门且不可查看自己”导致放行或误判无权。
- 上下文部门不会修改原有的数据权限 DTO仅在当前计算中使用。
- 若上下文公司与缓存公司不一致,会记录告警日志,但仍按上下文部门过滤。
## 典型场景
1) **任务/全局调用需要暂时关闭数据权限**
- 在方法上标记 `@DeptDataPermissionIgnore``@CompanyDataPermissionIgnore`
2) **带部门上下文的接口调用**
- 请求预先设置 `DeptContextHolder.setContext(deptId, companyId)`
- 即便数据权限声明为 `all=true`,也会按该部门过滤,避免读出全量。
3) **无部门权限但指定了上下文部门**
- 即使 `deptIds` 为空且 `self=false`,只要上下文提供部门,也会使用该部门过滤,而非直接判定无权。
## 注意事项
- 忽略标记只作用于当前线程上下文,切面会在 `finally` 中恢复旧值,嵌套调用安全。
- 若需要同时忽略公司与部门数据权限,可叠加两个注解或在业务代码中分别设置忽略标记。

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) {

Some files were not shown because too many files have changed in this diff Show More