From 6ac75110191ec0d5eb6e5f9483be8b5a09da9b46 Mon Sep 17 00:00:00 2001 From: chenbowen Date: Tue, 30 Dec 2025 18:38:07 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=BF=BD=E7=95=A5=E5=85=AC?= =?UTF-8?q?=E5=8F=B8=E4=BB=A5=E5=8F=8A=E9=83=A8=E9=97=A8=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E6=9D=83=E9=99=90=E7=9A=84=E6=B3=A8=E8=A7=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/util/BusinessDeptHandleUtil.java | 4 + .../docs/数据权限忽略与上下文说明.md | 42 +++++++++++ .../ZtDataPermissionAutoConfiguration.java | 12 +++ .../CompanyDataPermissionIgnore.java | 21 ++++++ .../annotation/DeptDataPermissionIgnore.java | 21 ++++++ .../CompanyDataPermissionIgnoreAspect.java | 31 ++++++++ .../aop/DeptDataPermissionIgnoreAspect.java | 31 ++++++++ .../core/db/DataPermissionRuleHandler.java | 11 +++ .../company/CompanyDataPermissionRule.java | 5 ++ .../rule/dept/DeptDataPermissionRule.java | 21 ++++-- .../rule/dept/DeptDataPermissionRuleTest.java | 75 +++++++++++++++++-- 11 files changed, 261 insertions(+), 13 deletions(-) create mode 100644 zt-framework/zt-spring-boot-starter-biz-data-permission/docs/数据权限忽略与上下文说明.md create mode 100644 zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/annotation/CompanyDataPermissionIgnore.java create mode 100644 zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/annotation/DeptDataPermissionIgnore.java create mode 100644 zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/aop/CompanyDataPermissionIgnoreAspect.java create mode 100644 zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/aop/DeptDataPermissionIgnoreAspect.java diff --git a/zt-framework/zt-spring-boot-starter-biz-business/src/main/java/com/zt/plat/framework/business/core/util/BusinessDeptHandleUtil.java b/zt-framework/zt-spring-boot-starter-biz-business/src/main/java/com/zt/plat/framework/business/core/util/BusinessDeptHandleUtil.java index 03502d9c..0f18c4bb 100644 --- a/zt-framework/zt-spring-boot-starter-biz-business/src/main/java/com/zt/plat/framework/business/core/util/BusinessDeptHandleUtil.java +++ b/zt-framework/zt-spring-boot-starter-biz-business/src/main/java/com/zt/plat/framework/business/core/util/BusinessDeptHandleUtil.java @@ -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; } } diff --git a/zt-framework/zt-spring-boot-starter-biz-data-permission/docs/数据权限忽略与上下文说明.md b/zt-framework/zt-spring-boot-starter-biz-data-permission/docs/数据权限忽略与上下文说明.md new file mode 100644 index 00000000..669a0729 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-biz-data-permission/docs/数据权限忽略与上下文说明.md @@ -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` 中恢复旧值,嵌套调用安全。 +- 若需要同时忽略公司与部门数据权限,可叠加两个注解或在业务代码中分别设置忽略标记。 diff --git a/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/config/ZtDataPermissionAutoConfiguration.java b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/config/ZtDataPermissionAutoConfiguration.java index 0a1c5350..1c7d9e91 100644 --- a/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/config/ZtDataPermissionAutoConfiguration.java +++ b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/config/ZtDataPermissionAutoConfiguration.java @@ -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(); + } + } diff --git a/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/annotation/CompanyDataPermissionIgnore.java b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/annotation/CompanyDataPermissionIgnore.java new file mode 100644 index 00000000..ecc4f1bb --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/annotation/CompanyDataPermissionIgnore.java @@ -0,0 +1,21 @@ +package com.zt.plat.framework.datapermission.core.annotation; + +import java.lang.annotation.*; + +/** + * 忽略公司数据权限的注解。 + *

+ * 标记在方法或类上时,匹配的调用会临时忽略公司类型的数据权限规则。 + */ +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +public @interface CompanyDataPermissionIgnore { + + /** + * 是否开启忽略,默认开启。 + * 支持 Spring EL 表达式,返回 true 时生效。 + */ + String enable() default "true"; +} \ No newline at end of file diff --git a/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/annotation/DeptDataPermissionIgnore.java b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/annotation/DeptDataPermissionIgnore.java new file mode 100644 index 00000000..de80a7d1 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/annotation/DeptDataPermissionIgnore.java @@ -0,0 +1,21 @@ +package com.zt.plat.framework.datapermission.core.annotation; + +import java.lang.annotation.*; + +/** + * 忽略部门数据权限的注解。 + *

+ * 标记在方法或类上时,匹配的调用会临时忽略部门类型的数据权限规则。 + */ +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +public @interface DeptDataPermissionIgnore { + + /** + * 是否开启忽略,默认开启。 + * 支持 Spring EL 表达式,返回 true 时生效。 + */ + String enable() default "true"; +} \ No newline at end of file diff --git a/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/aop/CompanyDataPermissionIgnoreAspect.java b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/aop/CompanyDataPermissionIgnoreAspect.java new file mode 100644 index 00000000..ae051a25 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/aop/CompanyDataPermissionIgnoreAspect.java @@ -0,0 +1,31 @@ +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 { + Object enable = SpringExpressionUtils.parseExpression(companyDataPermissionIgnore.enable()); + if (Boolean.TRUE.equals(enable)) { + CompanyContextHolder.setIgnore(true); + } + return joinPoint.proceed(); + } finally { + CompanyContextHolder.setIgnore(oldIgnore); + } + } +} \ No newline at end of file diff --git a/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/aop/DeptDataPermissionIgnoreAspect.java b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/aop/DeptDataPermissionIgnoreAspect.java new file mode 100644 index 00000000..4ee9054e --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/aop/DeptDataPermissionIgnoreAspect.java @@ -0,0 +1,31 @@ +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 { + Object enable = SpringExpressionUtils.parseExpression(deptDataPermissionIgnore.enable()); + if (Boolean.TRUE.equals(enable)) { + DeptContextHolder.setIgnore(true); + } + return joinPoint.proceed(); + } finally { + DeptContextHolder.setIgnore(oldIgnore); + } + } +} \ No newline at end of file diff --git a/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/db/DataPermissionRuleHandler.java b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/db/DataPermissionRuleHandler.java index aa93b5fe..d23e9ac8 100644 --- a/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/db/DataPermissionRuleHandler.java +++ b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/db/DataPermissionRuleHandler.java @@ -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 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) { diff --git a/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/rule/company/CompanyDataPermissionRule.java b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/rule/company/CompanyDataPermissionRule.java index 5b46c28f..c02348c6 100644 --- a/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/rule/company/CompanyDataPermissionRule.java +++ b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/rule/company/CompanyDataPermissionRule.java @@ -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.*; @@ -46,6 +47,10 @@ public class CompanyDataPermissionRule implements DataPermissionRule { @Override public Expression getExpression(String tableName, Alias tableAlias) { + // 显式忽略公司数据权限时直接放行 + if (CompanyContextHolder.isIgnore()) { + return null; + } // 业务拼接 Company 的条件 if (getLoginUserCompanyId() == null) { // 如果没有登录用户的公司编号,则不需要拼接条件 diff --git a/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/rule/dept/DeptDataPermissionRule.java b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/rule/dept/DeptDataPermissionRule.java index e54a7f54..ea4a8a93 100644 --- a/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/rule/dept/DeptDataPermissionRule.java +++ b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/rule/dept/DeptDataPermissionRule.java @@ -156,21 +156,28 @@ public class DeptDataPermissionRule implements DataPermissionRule { } } - // 情况一,如果是 ALL 可查看全部,则无需拼接条件 - if (deptDataPermission.getAll()) { + // 计算有效的部门与自查标记:当存在上下文部门且未被忽略时,强制仅使用该部门,以避免默认全量或空权限分支 + Set 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({}) 构建的条件为空]", diff --git a/zt-framework/zt-spring-boot-starter-biz-data-permission/src/test/java/com/zt/plat/framework/datapermission/core/rule/dept/DeptDataPermissionRuleTest.java b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/test/java/com/zt/plat/framework/datapermission/core/rule/dept/DeptDataPermissionRuleTest.java index 77f194ce..e68941db 100644 --- a/zt-framework/zt-spring-boot-starter-biz-data-permission/src/test/java/com/zt/plat/framework/datapermission/core/rule/dept/DeptDataPermissionRuleTest.java +++ b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/test/java/com/zt/plat/framework/datapermission/core/rule/dept/DeptDataPermissionRuleTest.java @@ -264,7 +264,7 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest { } } - @Test // 上下文部门存在且公司一致时,清空原集合并覆盖为单一 deptId + @Test // 上下文部门存在且公司一致时,表达式按上下文 deptId 生效,但不修改原数据权限集合 void testGetExpression_deptContextOverride_companyMatch() { try (MockedStatic secMock = mockStatic(SecurityFrameworkUtils.class); MockedStatic deptCtxMock = mockStatic(DeptContextHolder.class); @@ -292,12 +292,13 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest { Expression expression = rule.getExpression(tableName, tableAlias); assertEquals("u.dept_id IN (99)", expression.toString()); - assertEquals(CollUtil.newLinkedHashSet(99L), deptDataPermission.getDeptIds()); + // 原始权限对象不被修改,只是临时使用上下文 deptId 计算 + assertEquals(CollUtil.newLinkedHashSet(10L, 20L), deptDataPermission.getDeptIds()); assertEquals(1L, deptDataPermission.getCompanyId()); } } - @Test // 上下文部门存在但公司不一致时,记录告警并保持原逻辑(不覆盖) + @Test // 上下文部门存在但公司不一致时,仍按上下文 deptId 过滤,原数据权限保持不变 void testGetExpression_deptContextOverride_companyMismatch() { try (MockedStatic secMock = mockStatic(SecurityFrameworkUtils.class); MockedStatic deptCtxMock = mockStatic(DeptContextHolder.class); @@ -324,10 +325,72 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest { Expression expression = rule.getExpression(tableName, tableAlias); - assertEquals("u.dept_id IN (10)", expression.toString()); - assertEquals(CollUtil.newLinkedHashSet(10L), deptDataPermission.getDeptIds()); - assertEquals(1L, deptDataPermission.getCompanyId()); + assertEquals("u.dept_id IN (99)", expression.toString()); + // 原始权限对象不被修改 + assertEquals(CollUtil.newLinkedHashSet(10L), deptDataPermission.getDeptIds()); + assertEquals(1L, deptDataPermission.getCompanyId()); } } + @Test // ALL 权限但存在上下文部门时,仍按上下文部门过滤 + void testGetExpression_allPermission_withCtxDept() { + try (MockedStatic secMock = mockStatic(SecurityFrameworkUtils.class); + MockedStatic deptCtxMock = mockStatic(DeptContextHolder.class); + MockedStatic companyCtxMock = mockStatic(CompanyContextHolder.class)) { + + String tableName = "t_user"; + Alias tableAlias = new Alias("u"); + LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L) + .setUserType(UserTypeEnum.ADMIN.getValue())); + secMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser); + + DeptDataPermissionRespDTO deptDataPermission = new DeptDataPermissionRespDTO() + .setAll(true) + .setDeptIds(CollUtil.newLinkedHashSet(10L)); + when(permissionApi.getDeptDataPermission(same(1L))).thenReturn(success(deptDataPermission)); + + deptCtxMock.when(DeptContextHolder::shouldIgnore).thenReturn(false); + deptCtxMock.when(DeptContextHolder::getDeptId).thenReturn(99L); + deptCtxMock.when(DeptContextHolder::getCompanyId).thenReturn(1L); + companyCtxMock.when(CompanyContextHolder::getCompanyId).thenReturn(1L); + companyCtxMock.when(CompanyContextHolder::isIgnore).thenReturn(false); + + rule.addDeptColumn(tableName, "dept_id"); + + Expression expression = rule.getExpression(tableName, tableAlias); + + assertEquals("u.dept_id IN (99)", expression.toString()); + } + } + + @Test // 无部门且不可查看自己,但上下文提供部门时,应使用上下文部门而非判定无权限 + void testGetExpression_noDeptNoSelf_withCtxDept() { + try (MockedStatic secMock = mockStatic(SecurityFrameworkUtils.class); + MockedStatic deptCtxMock = mockStatic(DeptContextHolder.class); + MockedStatic companyCtxMock = mockStatic(CompanyContextHolder.class)) { + + String tableName = "t_user"; + Alias tableAlias = new Alias("u"); + LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L) + .setUserType(UserTypeEnum.ADMIN.getValue())); + secMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser); + + DeptDataPermissionRespDTO deptDataPermission = new DeptDataPermissionRespDTO() + .setSelf(false); + when(permissionApi.getDeptDataPermission(same(1L))).thenReturn(success(deptDataPermission)); + + deptCtxMock.when(DeptContextHolder::shouldIgnore).thenReturn(false); + deptCtxMock.when(DeptContextHolder::getDeptId).thenReturn(88L); + deptCtxMock.when(DeptContextHolder::getCompanyId).thenReturn(1L); + companyCtxMock.when(CompanyContextHolder::getCompanyId).thenReturn(1L); + companyCtxMock.when(CompanyContextHolder::isIgnore).thenReturn(false); + + rule.addDeptColumn(tableName, "dept_id"); + + Expression expression = rule.getExpression(tableName, tableAlias); + + assertEquals("u.dept_id IN (88)", expression.toString()); + } + } + }