diff --git a/sql/dm/ruoyi-vue-pro-dm8.sql b/sql/dm/ruoyi-vue-pro-dm8.sql
index 53667219..ba46b297 100644
--- a/sql/dm/ruoyi-vue-pro-dm8.sql
+++ b/sql/dm/ruoyi-vue-pro-dm8.sql
@@ -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,
diff --git a/zt-framework/zt-common/src/main/java/com/zt/plat/framework/common/exception/enums/GlobalErrorCodeConstants.java b/zt-framework/zt-common/src/main/java/com/zt/plat/framework/common/exception/enums/GlobalErrorCodeConstants.java
index ae712088..a01ff26e 100644
--- a/zt-framework/zt-common/src/main/java/com/zt/plat/framework/common/exception/enums/GlobalErrorCodeConstants.java
+++ b/zt-framework/zt-common/src/main/java/com/zt/plat/framework/common/exception/enums/GlobalErrorCodeConstants.java
@@ -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, "请求参数不能为空");
// ========== 服务端错误段 ==========
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..8a5b6279
--- /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,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);
+ }
+ }
+}
\ 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..f00f08e1
--- /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,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);
+ }
+ }
+}
\ 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 0ad24ca5..6f1e3f48 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 f7d7e84d..25909d05 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.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) {
// 如果没有登录用户的公司编号,则不需要拼接条件
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());
+ }
+ }
+
}
diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/BpmProcessInstanceController.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/BpmProcessInstanceController.java
index 53ea6faf..d55f27a0 100644
--- a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/BpmProcessInstanceController.java
+++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/BpmProcessInstanceController.java
@@ -1,5 +1,6 @@
package com.zt.plat.module.bpm.controller.admin.task;
+import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.zt.plat.framework.business.core.util.DeptUtil;
@@ -11,8 +12,10 @@ import com.zt.plat.module.bpm.controller.admin.task.vo.instance.*;
import com.zt.plat.module.bpm.convert.task.BpmProcessInstanceConvert;
import com.zt.plat.module.bpm.dal.dataobject.definition.BpmCategoryDO;
import com.zt.plat.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
+import com.zt.plat.module.bpm.dal.dataobject.task.BpmProcessInstanceCopyDO;
import com.zt.plat.module.bpm.service.definition.BpmCategoryService;
import com.zt.plat.module.bpm.service.definition.BpmProcessDefinitionService;
+import com.zt.plat.module.bpm.service.task.BpmProcessInstanceCopyService;
import com.zt.plat.module.bpm.service.task.BpmProcessInstanceService;
import com.zt.plat.module.bpm.service.task.BpmTaskService;
import com.zt.plat.module.system.api.dept.DeptApi;
@@ -24,6 +27,8 @@ import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.collections4.list.SetUniqueList;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.task.api.Task;
@@ -31,6 +36,8 @@ import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
+import java.util.ArrayList;
+import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -53,6 +60,8 @@ public class BpmProcessInstanceController {
private BpmProcessDefinitionService processDefinitionService;
@Resource
private BpmCategoryService categoryService;
+ @Resource
+ private BpmProcessInstanceCopyService processInstanceCopyService;
@Resource
private AdminUserApi adminUserApi;
@@ -181,6 +190,32 @@ public class BpmProcessInstanceController {
return success(processInstanceService.getApprovalDetail(getLoginUserId(), reqVO));
}
+ @GetMapping("/copy-list-by-process-instance-id")
+ @Operation(summary = "根据流程实例编号获取抄送列表")
+ @Parameter(name = "id", description = "流程实例的编号", required = true)
+ @PreAuthorize("@ss.hasPermission('bpm:process-instance:query')")
+ public CommonResult> getCopyListByProcessInstanceId(@RequestParam("processInstanceId") String processInstanceId) {
+ List copyDOList = processInstanceCopyService.getByProcessInstanceId(processInstanceId);
+ if (CollectionUtils.isEmpty(copyDOList)) {
+ return success(new ArrayList<>(0));
+ }
+ List copyVOList = new ArrayList<>(copyDOList.size());
+ SetUniqueList userIdList = SetUniqueList.setUniqueList(new ArrayList<>());
+ for (BpmProcessInstanceCopyDO copyDO : copyDOList) {
+ BpmProcessInstanceCopyVO copyVO = new BpmProcessInstanceCopyVO();
+ BeanUtil.copyProperties(copyDO, copyVO);
+ copyVOList.add(copyVO);
+ userIdList.add(copyDO.getStartUserId());
+ userIdList.add(copyDO.getUserId());
+ }
+ Map userMap = adminUserApi.getUserMap(userIdList);
+ for (BpmProcessInstanceCopyVO copyVO : copyVOList) {
+ copyVO.setStartUserName(userMap.get(copyVO.getStartUserId()).getNickname());
+ copyVO.setUserName(userMap.get(copyVO.getUserId()).getNickname());
+ }
+ return success(copyVOList);
+ }
+
@GetMapping("/get-next-approval-nodes")
@Operation(summary = "获取下一个执行的流程节点")
@PreAuthorize("@ss.hasPermission('bpm:process-instance:query')")
diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceCopyVO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceCopyVO.java
new file mode 100644
index 00000000..0012c190
--- /dev/null
+++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceCopyVO.java
@@ -0,0 +1,88 @@
+package com.zt.plat.module.bpm.controller.admin.task.vo.instance;
+
+import com.zt.plat.framework.mybatis.core.dataobject.BaseDO;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 流程抄送 VO
+ *
+ * @author kr
+ * @since 2025-12-31
+ */
+@Data
+@NoArgsConstructor
+public class BpmProcessInstanceCopyVO extends BaseDO {
+
+ /**
+ * 编号
+ */
+ private Long id;
+
+ /**
+ * 发起人 Id
+ */
+ @Schema(description ="发起人 Id")
+ private Long startUserId;
+
+ /**
+ * 发起人 姓名
+ */
+ @Schema(description ="发起人 姓名")
+ private String startUserName;
+ /**
+ * 流程名
+ */
+ @Schema(description ="流程名")
+ private String processInstanceName;
+ /**
+ * 流程实例的编号
+ */
+ @Schema(description ="流程实例的编号")
+ private String processInstanceId;
+ /**
+ * 流程实例的流程定义编号
+ */
+ @Schema(description ="流程实例的流程定义编号")
+ private String processDefinitionId;
+ /**
+ * 流程分类
+ */
+ @Schema(description ="流程分类")
+ private String category;
+ /**
+ * 流程活动的编号
+ */
+ @Schema(description ="流程活动的编号")
+ private String activityId;
+ /**
+ * 流程活动的名字
+ */
+ @Schema(description ="流程活动的名字")
+ private String activityName;
+ /**
+ * 流程活动的编号
+ */
+ @Schema(description ="流程活动的编号")
+ private String taskId;
+
+ /**
+ * 用户编号(被抄送的用户编号)
+ */
+ @Schema(description ="用户编号(被抄送的用户编号)")
+ private Long userId;
+
+ /**
+ * 用户姓名(被抄送的用户姓名)
+ */
+ @Schema(description ="用户姓名(被抄送的用户姓名)")
+ private String userName;
+
+ /**
+ * 抄送意见
+ */
+ @Schema(description ="抄送意见")
+ private String reason;
+
+}
diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/mysql/task/BpmProcessInstanceCopyMapper.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/mysql/task/BpmProcessInstanceCopyMapper.java
index 61a73c3c..b35d217d 100644
--- a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/mysql/task/BpmProcessInstanceCopyMapper.java
+++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/mysql/task/BpmProcessInstanceCopyMapper.java
@@ -7,6 +7,8 @@ import com.zt.plat.module.bpm.controller.admin.task.vo.instance.BpmProcessInstan
import com.zt.plat.module.bpm.dal.dataobject.task.BpmProcessInstanceCopyDO;
import org.apache.ibatis.annotations.Mapper;
+import java.util.List;
+
@Mapper
public interface BpmProcessInstanceCopyMapper extends BaseMapperX {
@@ -22,4 +24,8 @@ public interface BpmProcessInstanceCopyMapper extends BaseMapperX getByProcessInstanceId(String processInstanceId) {
+ return selectList(BpmProcessInstanceCopyDO::getProcessInstanceId, processInstanceId);
+ }
+
}
diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/BpmProcessInstanceCopyService.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/BpmProcessInstanceCopyService.java
index e5f06b6f..76350bc8 100644
--- a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/BpmProcessInstanceCopyService.java
+++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/BpmProcessInstanceCopyService.java
@@ -7,6 +7,7 @@ import jakarta.validation.constraints.NotEmpty;
import org.flowable.bpmn.model.FlowNode;
import java.util.Collection;
+import java.util.List;
/**
* 流程抄送 Service 接口
@@ -57,4 +58,12 @@ public interface BpmProcessInstanceCopyService {
*/
void deleteProcessInstanceCopy(String processInstanceId);
+ /**
+ * 获得流程的抄送列表
+ *
+ * @param processInstanceId 流程实例 ID
+ * @return 抄送流程列表
+ */
+ List getByProcessInstanceId(String processInstanceId);
+
}
diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java
index dd842b9f..5fec3947 100644
--- a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java
+++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java
@@ -93,4 +93,9 @@ public class BpmProcessInstanceCopyServiceImpl implements BpmProcessInstanceCopy
processInstanceCopyMapper.deleteByProcessInstanceId(processInstanceId);
}
+ @Override
+ public List getByProcessInstanceId(String processInstanceId) {
+ return processInstanceCopyMapper.getByProcessInstanceId(processInstanceId);
+ }
+
}
diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/resources/logback-spring.xml b/zt-module-bpm/zt-module-bpm-server/src/main/resources/logback-spring.xml
index 0e551414..51a9a489 100644
--- a/zt-module-bpm/zt-module-bpm-server/src/main/resources/logback-spring.xml
+++ b/zt-module-bpm/zt-module-bpm-server/src/main/resources/logback-spring.xml
@@ -73,4 +73,8 @@
+
+
+
+
diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/definition/ApiDefinitionStepSaveReqVO.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/definition/ApiDefinitionStepSaveReqVO.java
index 2f66ed5e..bb158491 100644
--- a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/definition/ApiDefinitionStepSaveReqVO.java
+++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/definition/ApiDefinitionStepSaveReqVO.java
@@ -36,7 +36,7 @@ public class ApiDefinitionStepSaveReqVO {
@Schema(description = "响应映射表达式(JSON)")
private String responseMappingExpr;
- @Schema(description = "超时时间(毫秒)", example = "5000")
+ @Schema(description = "超时时间(毫秒),缺省 20000(20s)", example = "20000")
private Long timeout;
@Schema(description = "降级策略(JSON)")
diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/step/impl/HttpStepHandler.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/step/impl/HttpStepHandler.java
index 58a7c7fd..7f00b6ce 100644
--- a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/step/impl/HttpStepHandler.java
+++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/step/impl/HttpStepHandler.java
@@ -48,6 +48,7 @@ public class HttpStepHandler implements ApiStepHandler {
private static final Duration RETRY_DELAY = Duration.ofMillis(200);
private static final int RETRY_ATTEMPTS = 3;
+ private static final Duration DEFAULT_TIMEOUT = Duration.ofSeconds(20);
private static final Set DEFAULT_FORWARDED_HEADERS = Set.of(
"authorization",
@@ -229,7 +230,7 @@ public class HttpStepHandler implements ApiStepHandler {
private Duration resolveTimeout(ApiStepDefinition stepDefinition) {
Long timeout = stepDefinition.getStep().getTimeout();
if (timeout == null || timeout <= 0) {
- return Duration.ofSeconds(5);
+ return DEFAULT_TIMEOUT;
}
return Duration.ofMillis(timeout);
}
diff --git a/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/esp/EspApi.java b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/esp/EspApi.java
new file mode 100644
index 00000000..54e560a9
--- /dev/null
+++ b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/esp/EspApi.java
@@ -0,0 +1,24 @@
+package com.zt.plat.module.system.api.esp;
+
+import com.zt.plat.framework.common.pojo.CommonResult;
+import com.zt.plat.module.system.api.dept.dto.*;
+import com.zt.plat.module.system.api.esp.dto.EspDto;
+import com.zt.plat.module.system.enums.ApiConstants;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@FeignClient(name = ApiConstants.NAME)
+@Tag(name = "RPC 服务 - 部门")
+public interface EspApi {
+
+ String PREFIX = ApiConstants.PREFIX + "/dept";
+
+ @PostMapping(PREFIX + "/pushMsg")
+ @Operation(summary = "推送消息")
+ CommonResult> pushMsg(@RequestBody DeptSaveReqDTO syncReqDTO);
+
+}
diff --git a/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/esp/dto/EspDto.java b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/esp/dto/EspDto.java
new file mode 100644
index 00000000..d69e1ab2
--- /dev/null
+++ b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/esp/dto/EspDto.java
@@ -0,0 +1,27 @@
+package com.zt.plat.module.system.api.esp.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import java.util.List;
+
+@Schema(description = "RPC 服务 - 推送外部系统配置信息 Response DTO")
+@Data
+public class EspDto {
+
+ @Schema(description = "部门名称,模糊匹配", example = "ZT")
+ private String name;
+
+ @Schema(description = "展示状态,参见 CommonStatusEnum 枚举类", example = "1")
+ private Integer status;
+
+ @Schema(description = "是否公司", example = "false")
+ private Boolean isCompany;
+
+ @Schema(description = "是否集团", example = "false")
+ private Boolean isGroup;
+
+ @Schema(description = "部门编号集合,支持多部门查询", example = "[\"1001\", \"1002\"]")
+ private List ids;
+
+
+}
diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/dept/PostApiImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/dept/PostApiImpl.java
index bb760ec4..80eedad0 100644
--- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/dept/PostApiImpl.java
+++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/dept/PostApiImpl.java
@@ -13,7 +13,6 @@ import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RestController;
-
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/esp/EspApiImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/esp/EspApiImpl.java
new file mode 100644
index 00000000..97c8a031
--- /dev/null
+++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/esp/EspApiImpl.java
@@ -0,0 +1,33 @@
+package com.zt.plat.module.system.api.esp;
+
+import com.zt.plat.framework.common.exception.enums.GlobalErrorCodeConstants;
+import com.zt.plat.framework.common.pojo.CommonResult;
+import com.zt.plat.framework.common.util.object.ObjectUtils;
+import com.zt.plat.module.system.api.dept.dto.DeptSaveReqDTO;
+import com.zt.plat.module.system.api.esp.dto.EspDto;
+import com.zt.plat.module.system.service.dept.IEspService;
+import jakarta.annotation.Resource;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.RestController;
+import java.util.List;
+import java.util.Objects;
+
+@RestController
+@Validated
+public class EspApiImpl implements EspApi {
+
+
+ @Resource
+ private IEspService deptService;
+ @Override
+ public CommonResult> pushMsg(DeptSaveReqDTO syncReqDTO)
+ {
+ if(Objects.isNull(syncReqDTO) || null == syncReqDTO.getId())
+ {
+ return CommonResult.error(GlobalErrorCodeConstants.BAD_REQUEST.getCode(),
+ "ID不能为空");
+ }
+ return CommonResult.success(deptService.pushMsg(syncReqDTO));
+ }
+
+}
diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/dept/EspController.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/dept/EspController.java
new file mode 100644
index 00000000..3a7d88da
--- /dev/null
+++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/dept/EspController.java
@@ -0,0 +1,118 @@
+package com.zt.plat.module.system.controller.admin.dept;
+
+import com.zt.plat.framework.common.pojo.CommonResult;
+import com.zt.plat.framework.common.pojo.PageResult;
+import com.zt.plat.framework.common.util.object.BeanUtils;
+import com.zt.plat.module.system.controller.admin.dept.vo.depexternalcode.EspPageReqVO;
+import com.zt.plat.module.system.controller.admin.dept.vo.depexternalcode.EspSaveRespVo;
+import com.zt.plat.module.system.dal.dataobject.dept.DeptDO;
+import com.zt.plat.module.system.dal.dataobject.dept.DeptPushMsgDO;
+import com.zt.plat.module.system.service.dept.DeptService;
+import com.zt.plat.module.system.service.dept.IEspService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import org.apache.commons.collections.CollectionUtils;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+import static com.zt.plat.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "管理后台 - 部门推送消息")
+@RestController
+@RequestMapping("/system/esp")
+@Validated
+public class EspController
+{
+
+ @Resource
+ private IEspService espService;
+ @Resource
+ private DeptService deptService;
+
+ @PostMapping("/create")
+ @Operation(summary = "创建部门推送消息")
+ @PreAuthorize("@ss.hasPermission('system:esp-external-code:create')")
+ public CommonResult create(@Valid @RequestBody EspSaveRespVo createReqVO) {
+ Long id = espService.createDeptPushMsg(createReqVO);
+ return success(id);
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "修改部门推送消息")
+ @PreAuthorize("@ss.hasPermission('system:esp-external-code:update')")
+ public CommonResult update(@Valid @RequestBody EspSaveRespVo updateReqVO) {
+ espService.updateDeptPushMsg(updateReqVO);
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "删除部门推送消息")
+ @PreAuthorize("@ss.hasPermission('system:esp-external-code:delete')")
+ public CommonResult delete(@RequestParam("id") Long id) {
+ espService.deleteDeptPushMsg(id);
+ return success(true);
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "获取部门推送消息详情")
+ @PreAuthorize("@ss.hasPermission('system:esp-external-code:query')")
+ public CommonResult get(@RequestParam("id") Long id) {
+ DeptPushMsgDO entity = espService.getDeptPushMsgDetails(id);
+ EspSaveRespVo respVO = BeanUtils.toBean(entity, EspSaveRespVo.class);
+ fillDeptInfo(List.of(respVO));
+ return success(respVO);
+ }
+
+ @GetMapping("/page")
+ @Operation(summary = "分页查询部门推送消息")
+ @PreAuthorize("@ss.hasPermission('system:esp-external-code:query')")
+ public CommonResult> page(@Valid EspPageReqVO reqVO) {
+ PageResult pageResult = espService.getDeptExternalCodePage(reqVO);
+ PageResult result = BeanUtils.toBean(pageResult, EspSaveRespVo.class);
+ fillDeptInfo(result.getList());
+ return success(result);
+ }
+
+ @GetMapping("/list-by-dept")
+ @Operation(summary = "根据部门部门推送消息")
+ @Parameter(name = "deptId", description = "部门编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('system:esp-external-code:query')")
+ public CommonResult> listByDept(@RequestParam("deptId") Long deptId) {
+ List list = espService.getPushMsgByDeptId(deptId);
+ List respList = BeanUtils.toBean(list, EspSaveRespVo.class);
+ fillDeptInfo(respList);
+ return success(respList);
+ }
+
+
+
+ private void fillDeptInfo(List list) {
+ if (CollectionUtils.isEmpty(list)) {
+ return;
+ }
+ Set deptIds = list.stream()
+ .map(EspSaveRespVo::getDeptId)
+ .collect(Collectors.toCollection(HashSet::new));
+ if (CollectionUtils.isEmpty(deptIds)) {
+ return;
+ }
+ Map deptMap = deptService.getDeptList(deptIds).stream()
+ .collect(Collectors.toMap(DeptDO::getId, dept -> dept, (left, right) -> left));
+ list.forEach(item -> {
+ DeptDO dept = deptMap.get(item.getDeptId());
+ if (dept != null) {
+ item.setDeptName(dept.getName());
+ item.setDeptCode(dept.getCode());
+ }
+ });
+ }
+
+}
diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/dept/vo/depexternalcode/EspPageReqVO.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/dept/vo/depexternalcode/EspPageReqVO.java
new file mode 100644
index 00000000..10e0ab8b
--- /dev/null
+++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/dept/vo/depexternalcode/EspPageReqVO.java
@@ -0,0 +1,25 @@
+package com.zt.plat.module.system.controller.admin.dept.vo.depexternalcode;
+
+import com.zt.plat.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Schema(description = "管理后台 - 部门外部组织编码映射分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class EspPageReqVO extends PageParam {
+
+ @Schema(description = "部门编号", example = "1024")
+ private Long deptId;
+
+ @Schema(description = "外部系统标识", example = "ERP")
+ private String systemCode;
+
+ @Schema(description = "外部组织编码", example = "100200")
+ private String externalDeptCode;
+
+ @Schema(description = "状态", example = "0")
+ private Integer status;
+
+}
diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/dept/vo/depexternalcode/EspSaveRespVo.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/dept/vo/depexternalcode/EspSaveRespVo.java
new file mode 100644
index 00000000..10ab6aa4
--- /dev/null
+++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/dept/vo/depexternalcode/EspSaveRespVo.java
@@ -0,0 +1,31 @@
+package com.zt.plat.module.system.controller.admin.dept.vo.depexternalcode;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - 部门外消息推送创建/修改 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class EspSaveRespVo extends DeptExternalCodeBaseVO {
+
+ @Schema(description = "映射编号", example = "1024")
+ private Long id;
+
+ @Schema(description = "所属部门名称", example = "技术部")
+ private String deptName;
+
+ @Schema(description = "所属部门编码", example = "DEPT_001")
+ private String deptCode;
+
+ @Schema(description = "创建时间")
+ private LocalDateTime createTime;
+
+ @Schema(description = "最后更新时间")
+ private LocalDateTime updateTime;
+
+ @Schema(description = "是否发送消息")
+ private Integer isSendMsg;
+
+}
diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/integration/iwork/IWorkIntegrationController.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/integration/iwork/IWorkIntegrationController.java
index b5a63c35..88adb502 100644
--- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/integration/iwork/IWorkIntegrationController.java
+++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/integration/iwork/IWorkIntegrationController.java
@@ -6,6 +6,7 @@ import com.zt.plat.module.system.controller.admin.integration.iwork.vo.*;
import com.zt.plat.module.system.service.integration.iwork.IWorkIntegrationService;
import com.zt.plat.module.system.service.integration.iwork.IWorkOrgRestService;
import com.zt.plat.module.system.service.integration.iwork.IWorkSyncService;
+import lombok.extern.slf4j.Slf4j;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.security.PermitAll;
@@ -18,9 +19,7 @@ import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
-
import static com.zt.plat.framework.common.pojo.CommonResult.success;
-
/**
* 提供统一 iWork 流程能力的管理端接口。
*/
@@ -29,6 +28,7 @@ import static com.zt.plat.framework.common.pojo.CommonResult.success;
@RequestMapping("/system/integration/iwork")
@RequiredArgsConstructor
@Validated
+@Slf4j
public class IWorkIntegrationController {
private final IWorkIntegrationService integrationService;
@@ -139,6 +139,17 @@ public class IWorkIntegrationController {
return success(syncService.fullSyncUsers(reqVO));
}
+ // ----------------- 根据ID同步到本地 -----------------
+
+ @PostMapping("/syncById")
+ @Operation(summary = "根据ID触发 iWork 同步公司")
+ public CommonResult syncById(@Valid @RequestBody IWorkSyncByIdReqVO reqVO) {
+
+ log.error("IWork集成后端手动录入syncById{}",reqVO);
+ return success(syncService.manuallySyncData(reqVO));
+ }
+
+
private ResponseEntity buildOaResponse(IWorkOaRawResponse resp) {
if (resp == null) {
return ResponseEntity.internalServerError().body("OA 响应为空");
diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/integration/iwork/vo/IWorkFullSyncReqVO.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/integration/iwork/vo/IWorkFullSyncReqVO.java
index 09fe2d3e..e3793181 100644
--- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/integration/iwork/vo/IWorkFullSyncReqVO.java
+++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/integration/iwork/vo/IWorkFullSyncReqVO.java
@@ -56,4 +56,8 @@ public class IWorkFullSyncReqVO {
}
return resolved;
}
+
+
+
+
}
diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/integration/iwork/vo/IWorkSyncByIdReqVO.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/integration/iwork/vo/IWorkSyncByIdReqVO.java
new file mode 100644
index 00000000..c91a3f66
--- /dev/null
+++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/integration/iwork/vo/IWorkSyncByIdReqVO.java
@@ -0,0 +1,99 @@
+package com.zt.plat.module.system.controller.admin.integration.iwork.vo;
+
+import com.zt.plat.module.system.enums.integration.IWorkSyncEntityTypeEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.Max;
+import jakarta.validation.constraints.Min;
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * iWork 手动同步请求
+ */
+@Data
+public class IWorkSyncByIdReqVO {
+
+
+ @Schema(description = "起始页码,从 1 开始", example = "1")
+ @Min(1)
+ private Integer startPage = 1;
+
+ @Schema(description = "最大处理页数,null 表示处理至 iWork 返回的末页", example = "10")
+ @Min(1)
+ private Integer maxPages;
+
+ @Schema(description = "每次分页从 iWork 拉取的记录数", example = "100")
+ @Min(1)
+ @Max(500)
+ private Integer pageSize = 100;
+
+ @Schema(description = "同步范围列表,默认同步全部。可选:subcompany、department、jobTitle、user")
+ private List scopes;
+
+ @Schema(description = "是否包含已失效(canceled=1)的记录", example = "false")
+ private Boolean includeCanceled = Boolean.FALSE;
+
+ @Schema(description = "是否允许更新已存在的本地实体", example = "false")
+ private Boolean allowUpdate = Boolean.FALSE;
+
+
+ @Schema(description = "指定同步记录的 iWork ID。传入后仅同步对应记录", example = "12345")
+ @NotBlank(message = "ID不能为空")
+ private String id;
+
+ @Schema(description = "部门编码", example = "ZT001")
+ private String code;
+
+ @Schema(description = "部门名称", example = "ZT")
+ private String name;
+
+ @Schema(description = "部门简称", example = "技术")
+ private String shortName;
+
+ @Schema(description = "父部门 ID", example = "1024")
+ private String parentId;
+
+ @Schema(description = "负责人的用户编号", example = "2048")
+ private String leaderUserId;
+
+ @Schema(description = "联系电话", example = "15601691000")
+ private String phone;
+
+ @Schema(description = "邮箱", example = "zt@iocoder.cn")
+ private String email;
+
+ @Schema(description = "状态,见 CommonStatusEnum 枚举0 开启 1 关闭", example = "0")
+ private Integer status;
+
+ private Long tenantId;
+
+ @Schema(description = "是否公司", example = "false")
+ private boolean isCompany;
+
+ @Schema(description = "是否集团", example = "false")
+ private boolean isGroup;
+
+ private boolean hasChildren;
+
+
+ public Set resolveScopes() {
+ EnumSet defaults = EnumSet.allOf(IWorkSyncEntityTypeEnum.class);
+ if (scopes == null || scopes.isEmpty()) {
+ return defaults;
+ }
+ Set resolved = scopes.stream()
+ .map(IWorkSyncEntityTypeEnum::fromCode)
+ .filter(java.util.Objects::nonNull)
+ .collect(Collectors.toCollection(() -> EnumSet.noneOf(IWorkSyncEntityTypeEnum.class)));
+ if (resolved.isEmpty()) {
+ return defaults;
+ }
+ return resolved;
+ }
+
+}
diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/dataobject/dept/DeptPushMsgDO.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/dataobject/dept/DeptPushMsgDO.java
new file mode 100644
index 00000000..2f209c71
--- /dev/null
+++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/dataobject/dept/DeptPushMsgDO.java
@@ -0,0 +1,63 @@
+package com.zt.plat.module.system.dal.dataobject.dept;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.zt.plat.framework.common.enums.CommonStatusEnum;
+import com.zt.plat.framework.tenant.core.db.TenantBaseDO;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 部门推送消息 DO
+ */
+@TableName("system_dept_push_msg")
+@KeySequence("system_dept_push_msg_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class DeptPushMsgDO extends TenantBaseDO {
+
+ /**
+ * 主键编号
+ */
+ @TableId(type = IdType.ASSIGN_ID)
+ private Long id;
+
+ /**
+ * 本系统部门 ID
+ */
+ private Long deptId;
+
+ /**
+ * 外部系统标识
+ */
+ private String systemCode;
+
+ /**
+ * 外部系统组织编码
+ */
+ private String externalDeptCode;
+
+ /**
+ * 外部系统组织名称
+ */
+ private String externalDeptName;
+
+ /**
+ * 映射状态
+ * 枚举 {@link CommonStatusEnum}
+ */
+ private Integer status;
+
+ /**
+ * 备注
+ */
+ private String remark;
+
+ /**
+ * 是否发送消息
+ */
+ private Integer isSendMsg;
+
+}
diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/dept/EspMapper.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/dept/EspMapper.java
new file mode 100644
index 00000000..80466f46
--- /dev/null
+++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/dept/EspMapper.java
@@ -0,0 +1,65 @@
+package com.zt.plat.module.system.dal.mysql.dept;
+
+import com.zt.plat.framework.common.pojo.PageResult;
+import com.zt.plat.framework.mybatis.core.mapper.BaseMapperX;
+import com.zt.plat.framework.mybatis.core.query.LambdaQueryWrapperX;
+import com.zt.plat.module.system.api.dept.dto.DeptSaveReqDTO;
+import com.zt.plat.module.system.controller.admin.dept.vo.depexternalcode.EspPageReqVO;
+import com.zt.plat.module.system.dal.dataobject.dept.DeptPushMsgDO;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+import java.util.List;
+/**
+ * 部门推送消息接口Mapper
+ */
+@Mapper
+public interface EspMapper extends BaseMapperX {
+
+
+ /**
+ * 分页查询
+ * @param reqVO 消息推送VO
+ * @return PageResult
+ */
+ default PageResult selectPage(EspPageReqVO reqVO) {
+ return selectPage(reqVO, new LambdaQueryWrapperX()
+ .eqIfPresent(DeptPushMsgDO::getDeptId,reqVO.getDeptId())
+ .eqIfPresent(DeptPushMsgDO::getSystemCode, reqVO.getSystemCode())
+ .likeIfPresent(DeptPushMsgDO::getExternalDeptCode, reqVO.getExternalDeptCode())
+ .eqIfPresent(DeptPushMsgDO::getStatus, reqVO.getStatus())
+ .orderByDesc(DeptPushMsgDO::getId));
+ }
+
+
+ default DeptPushMsgDO selectBySystemCodeAndDeptId(String systemCode, Long deptId) {
+ return selectOne(new LambdaQueryWrapperX()
+ .eq(DeptPushMsgDO::getSystemCode, systemCode)
+ .eq(DeptPushMsgDO::getDeptId, deptId));
+ }
+
+ default DeptPushMsgDO selectBySystemCodeAndExternalCode(String systemCode, String externalDeptCode) {
+ return selectOne(new LambdaQueryWrapperX()
+ .eq(DeptPushMsgDO::getSystemCode, systemCode)
+ .eq(DeptPushMsgDO::getExternalDeptCode, externalDeptCode));
+ }
+
+ default List selectListByDeptId(Long deptId) {
+ return selectList(DeptPushMsgDO::getDeptId, deptId);
+ }
+
+ default int deleteByDeptId(Long deptId) {
+ return delete(DeptPushMsgDO::getDeptId, deptId);
+ }
+
+ default List selectListBySystemCode(String systemCode) {
+ return selectList(DeptPushMsgDO::getSystemCode, systemCode);
+ }
+
+ @Select("SELECT ID,DEPT_ID, SYSTEM_CODE,EXTERNAL_DEPT_CODE,EXTERNAL_DEPT_NAME,STATUS,REMARK,TENANT_ID,CREATOR,CREATE_TIME,UPDATER,UPDATE_TIME" +
+ "FROM" +
+ "SYSTEM_DEPT_PUSH_MSG" +
+ "WHERE" +
+ " ID = #{id} AND IS_SEND_MSG = '0' AND DELETED = '0' ")
+ List selectpushMsg(@Param("syncReqDTO") DeptSaveReqDTO syncReqDTO);
+}
diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/EspServiceImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/EspServiceImpl.java
new file mode 100644
index 00000000..17a19662
--- /dev/null
+++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/EspServiceImpl.java
@@ -0,0 +1,205 @@
+package com.zt.plat.module.system.service.dept;
+
+import cn.hutool.core.util.StrUtil;
+import com.zt.plat.framework.common.enums.CommonStatusEnum;
+import com.zt.plat.framework.common.pojo.PageResult;
+import com.zt.plat.framework.common.util.object.BeanUtils;
+import com.zt.plat.module.system.api.dept.dto.DeptSaveReqDTO;
+import com.zt.plat.module.system.api.esp.dto.EspDto;
+import com.zt.plat.module.system.controller.admin.dept.vo.depexternalcode.EspPageReqVO;
+import com.zt.plat.module.system.controller.admin.dept.vo.depexternalcode.EspSaveRespVo;
+import com.zt.plat.module.system.dal.dataobject.dept.DeptDO;
+import com.zt.plat.module.system.dal.dataobject.dept.DeptPushMsgDO;
+import com.zt.plat.module.system.dal.mysql.dept.DeptMapper;
+import com.zt.plat.module.system.dal.mysql.dept.EspMapper;
+import com.zt.plat.module.system.dal.redis.RedisKeyConstants;
+import jakarta.annotation.Resource;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.seata.common.result.Result;
+import org.springframework.cache.CacheManager;
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+import java.util.List;
+import java.util.Objects;
+import static com.zt.plat.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static com.zt.plat.module.system.enums.ErrorCodeConstants.*;
+
+/**
+ * 部门推送消息接口ServiceImpl实现类
+ */
+@Service
+@Validated
+public class EspServiceImpl implements IEspService {
+
+ @Resource
+ private EspMapper espMapper;
+ @Resource
+ private DeptMapper deptMapper;
+ @Resource
+ private CacheManager cacheManager;
+
+ @Override
+ @CacheEvict(cacheNames = RedisKeyConstants.DEPT_EXTERNAL_CODE_LIST, key = "#createReqVO.deptId", beforeInvocation = false)
+ public Long createDeptPushMsg(EspSaveRespVo createReqVO) {
+
+ //请求校验
+ normalizeRequest(createReqVO);
+ //冲突禁用-映射
+ disableActiveMappingIfConflict(createReqVO.getDeptId(), createReqVO.getSystemCode(), createReqVO.getExternalDeptCode());
+ validateForCreateOrUpdate(null, createReqVO.getDeptId(), createReqVO.getSystemCode(),
+ createReqVO.getExternalDeptCode());
+
+ DeptPushMsgDO entity = BeanUtils.toBean(createReqVO, DeptPushMsgDO.class);
+ if (entity.getStatus() == null) {
+ entity.setStatus(CommonStatusEnum.ENABLE.getStatus());
+ }
+ entity.setIsSendMsg(0);
+ espMapper.insert(entity);
+ return entity.getId();
+ }
+
+ @Override
+ public void updateDeptPushMsg(EspSaveRespVo updateReqVO) {
+ normalizeRequest(updateReqVO);
+ DeptPushMsgDO exists = validateExists(updateReqVO.getId());
+ disableActiveMappingIfConflict(updateReqVO.getDeptId(), updateReqVO.getSystemCode(), updateReqVO.getExternalDeptCode());
+ validateForCreateOrUpdate(updateReqVO.getId(), updateReqVO.getDeptId(), updateReqVO.getSystemCode(),
+ updateReqVO.getExternalDeptCode());
+
+ DeptPushMsgDO updateObj = BeanUtils.toBean(updateReqVO, DeptPushMsgDO.class);
+ // 保持原有的状态默认值逻辑
+ if (updateObj.getStatus() == null) {
+ updateObj.setStatus(exists.getStatus() == null ? CommonStatusEnum.ENABLE.getStatus() : exists.getStatus());
+ }
+ updateObj.setIsSendMsg(updateReqVO.getIsSendMsg());
+ espMapper.updateById(updateObj);
+ evictCacheSafely(exists.getDeptId());
+ evictCacheSafely(updateObj.getDeptId());
+ }
+
+ @Override
+ public void deleteDeptPushMsg(Long id) {
+ DeptPushMsgDO exists = validateExists(id);
+ espMapper.deleteById(id);
+ evictCacheSafely(exists.getDeptId());
+ }
+
+ @Override
+ public DeptPushMsgDO getDeptPushMsgDetails(Long id) {
+ return espMapper.selectById(id);
+ }
+
+ @Override
+ public PageResult getDeptExternalCodePage(EspPageReqVO reqVO) {
+
+ return espMapper.selectPage(reqVO);
+ }
+
+ @Override
+ @Cacheable(cacheNames = RedisKeyConstants.DEPT_EXTERNAL_CODE_LIST, key = "#deptId")
+ public List getPushMsgByDeptId(Long deptId) {
+ return espMapper.selectListByDeptId(deptId);
+ }
+
+ @Override
+ public List pushMsg(DeptSaveReqDTO syncReqDTO) {
+
+ return BeanUtils.toBean(espMapper.selectpushMsg(syncReqDTO), EspDto.class);
+ }
+
+ private DeptPushMsgDO validateExists(Long id) {
+ if (id == null) {
+ throw exception(DEPT_EXTERNAL_RELATION_NOT_EXISTS);
+ }
+ DeptPushMsgDO entity = espMapper.selectById(id);
+ if (entity == null) {
+ throw exception(DEPT_EXTERNAL_RELATION_NOT_EXISTS);
+ }
+ return entity;
+ }
+
+ private void validateForCreateOrUpdate(Long id, Long deptId, String systemCode, String externalDeptCode) {
+
+ // 校验部门存在
+ DeptDO dept = deptMapper.selectById(deptId);
+ if (dept == null) {
+ throw exception(DEPT_NOT_FOUND);
+ }
+ String normalizedSystemCode = StrUtil.blankToDefault(systemCode, null);
+ String normalizedExternalCode = StrUtil.blankToDefault(externalDeptCode, null);
+
+ // 校验同一系统下部门唯一
+ if (StrUtil.isNotBlank(normalizedSystemCode)) {
+ DeptPushMsgDO sameDept = espMapper
+ .selectBySystemCodeAndDeptId(normalizedSystemCode, deptId);
+ if (sameDept != null && (id == null || !sameDept.getId().equals(id))) {
+ throw exception(DEPT_EXTERNAL_RELATION_EXISTS, normalizedSystemCode);
+ }
+ }
+ // 校验同一系统下外部编码唯一
+ if (StrUtil.isNotBlank(normalizedSystemCode) && StrUtil.isNotBlank(normalizedExternalCode)) {
+ DeptPushMsgDO sameExternal = espMapper
+ .selectBySystemCodeAndExternalCode(normalizedSystemCode, normalizedExternalCode);
+ if (sameExternal != null && (id == null || !sameExternal.getId().equals(id))) {
+ boolean sameDept = Objects.equals(deptId, sameExternal.getDeptId());
+ boolean activeConflict = !sameDept && CommonStatusEnum.isEnable(sameExternal.getStatus());
+ if (activeConflict) {
+ throw exception(DEPT_EXTERNAL_CODE_DUPLICATE, normalizedSystemCode, normalizedExternalCode);
+ }
+ }
+ }
+ }
+
+ private void normalizeRequest(EspSaveRespVo reqVO) {
+ if (reqVO == null) {
+ return;
+ }
+ if (StrUtil.isNotBlank(reqVO.getSystemCode())) {
+ reqVO.setSystemCode(reqVO.getSystemCode().trim());
+ }
+ if (StrUtil.isNotBlank(reqVO.getExternalDeptCode())) {
+ reqVO.setExternalDeptCode(reqVO.getExternalDeptCode().trim());
+ }
+ if (reqVO.getStatus() == null) {
+ reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
+ }
+ }
+
+ private void disableActiveMappingIfConflict(Long targetDeptId, String systemCode, String externalDeptCode) {
+ String normalizedSystem = StrUtil.trimToNull(systemCode);
+ String normalizedExternal = StrUtil.trimToNull(externalDeptCode);
+ if (StrUtil.hasEmpty(normalizedSystem, normalizedExternal) || targetDeptId == null) {
+ return;
+ }
+ DeptPushMsgDO existing = espMapper.selectBySystemCodeAndExternalCode(normalizedSystem, normalizedExternal);
+ if (existing == null) {
+ return;
+ }
+ if (Objects.equals(existing.getDeptId(), targetDeptId)) {
+ return;
+ }
+ if (CommonStatusEnum.isEnable(existing.getStatus())) {
+ DeptPushMsgDO update = new DeptPushMsgDO();
+ update.setId(existing.getId());
+ update.setStatus(CommonStatusEnum.DISABLE.getStatus());
+ espMapper.updateById(update);
+ evictCacheSafely(existing.getDeptId());
+ }
+ }
+
+ private void evictCacheSafely(Long deptId) {
+
+ if (deptId == null || cacheManager == null) {
+ return;
+ }
+ try {
+ if (cacheManager.getCache(RedisKeyConstants.DEPT_EXTERNAL_CODE_LIST) != null) {
+ cacheManager.getCache(RedisKeyConstants.DEPT_EXTERNAL_CODE_LIST).evict(deptId);
+ }
+ } catch (Exception ignore) {
+ // 缓存失效失败不影响主流程
+ }
+ }
+}
diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/IEspService.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/IEspService.java
new file mode 100644
index 00000000..7678def2
--- /dev/null
+++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/IEspService.java
@@ -0,0 +1,57 @@
+package com.zt.plat.module.system.service.dept;
+
+import com.zt.plat.framework.common.pojo.PageResult;
+import com.zt.plat.module.system.api.dept.dto.DeptSaveReqDTO;
+import com.zt.plat.module.system.api.esp.dto.EspDto;
+import com.zt.plat.module.system.controller.admin.dept.vo.depexternalcode.EspPageReqVO;
+import com.zt.plat.module.system.controller.admin.dept.vo.depexternalcode.EspSaveRespVo;
+import com.zt.plat.module.system.dal.dataobject.dept.DeptPushMsgDO;
+import java.util.List;
+
+/**
+ * 部门推送消息 Service 接口
+ */
+public interface IEspService {
+
+ /**
+ * 创建映射关系
+ * @param createReqVO 创建请求
+ * @return 新增记录编号
+ */
+ Long createDeptPushMsg(EspSaveRespVo createReqVO);
+
+ /**
+ * 更新映射关系
+ * @param updateReqVO 更新请求
+ */
+ void updateDeptPushMsg(EspSaveRespVo updateReqVO);
+
+ /**
+ * 删除映射关系
+ * @param id 记录编号
+ */
+ void deleteDeptPushMsg(Long id);
+
+ /**
+ * 获取映射详情
+ */
+ DeptPushMsgDO getDeptPushMsgDetails(Long id);
+
+ /**
+ * 分页查询映射
+ */
+ PageResult getDeptExternalCodePage(EspPageReqVO reqVO);
+
+ /**
+ * 根据部门推送消息
+ */
+ List getPushMsgByDeptId(Long deptId);
+
+
+ /**
+ * 推送部门数据到外部系统
+ * @param syncReqDTO 同步请求
+ */
+ List pushMsg(DeptSaveReqDTO syncReqDTO);
+
+}
diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/IWorkSyncProcessor.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/IWorkSyncProcessor.java
index 17ec9b6a..5fa3a802 100644
--- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/IWorkSyncProcessor.java
+++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/IWorkSyncProcessor.java
@@ -19,19 +19,19 @@ public interface IWorkSyncProcessor {
BatchResult syncSubcompanies(List data,
SyncOptions options);
-
+ // 同步子公司
BatchResult syncSubcompanies(List data,
SyncOptions options,
DeptSyncContext context);
BatchResult syncDepartments(List data, SyncOptions options);
-
+ // 同步部门
BatchResult syncDepartments(List data,
SyncOptions options,
DeptSyncContext context);
-
+ // 同步岗位
BatchResult syncJobTitles(List data, SyncOptions options);
-
+ // 同步用户
BatchResult syncUsers(List data, SyncOptions options);
/**
diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/IWorkSyncService.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/IWorkSyncService.java
index 47c70b70..c39dbaf6 100644
--- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/IWorkSyncService.java
+++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/IWorkSyncService.java
@@ -2,6 +2,9 @@ package com.zt.plat.module.system.service.integration.iwork;
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkFullSyncReqVO;
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkFullSyncRespVO;
+import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkSyncByIdReqVO;
+import jakarta.validation.Valid;
+import java.util.List;
/**
* iWork 组织/人员同步服务
@@ -28,4 +31,8 @@ public interface IWorkSyncService {
*/
IWorkFullSyncRespVO fullSyncUsers(IWorkFullSyncReqVO reqVO);
+ /**
+ * 手动同步
+ */
+ IWorkFullSyncRespVO manuallySyncData(@Valid IWorkSyncByIdReqVO list);
}
diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkSyncProcessorImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkSyncProcessorImpl.java
index abf94ac4..0d44b790 100644
--- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkSyncProcessorImpl.java
+++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkSyncProcessorImpl.java
@@ -52,7 +52,7 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor {
public BatchResult syncSubcompanies(List data, SyncOptions options) {
return syncSubcompanies(data, options, null);
}
-
+ //1、同步子公司
@Override
public BatchResult syncSubcompanies(List data,
SyncOptions options,
@@ -154,7 +154,7 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor {
}
return result;
}
-
+ //2、同步部门
@Override
public BatchResult syncDepartments(List data, SyncOptions options) {
return syncDepartments(data, options, null);
@@ -272,7 +272,7 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor {
result.merge(syncDepartmentsInternal(Collections.emptyList(), options, context, true));
return result;
}
-
+ //TODO 3、同步岗位
@Override
public BatchResult syncJobTitles(List data, SyncOptions options) {
List records = CollUtil.emptyIfNull(data);
@@ -310,7 +310,7 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor {
}
return result;
}
-
+ //TODO 4、同步用户
@Override
public BatchResult syncUsers(List data, SyncOptions options) {
List records = CollUtil.emptyIfNull(data);
@@ -377,7 +377,7 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor {
}
return result;
}
-
+ //TODO
private DeptSyncOutcome upsertDept(Long deptId,
DeptSaveReqVO desired,
boolean disabled,
diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkSyncServiceImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkSyncServiceImpl.java
index b06959d6..95ac0cdc 100644
--- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkSyncServiceImpl.java
+++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkSyncServiceImpl.java
@@ -12,6 +12,7 @@ import com.zt.plat.module.system.service.integration.iwork.IWorkSyncService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.EnumSet;
@@ -34,32 +35,59 @@ public class IWorkSyncServiceImpl implements IWorkSyncService {
private final IWorkSyncProcessor syncProcessor;
private final DeptService deptService;
+ /**
+ * 同步部门
+ */
@Override
public IWorkFullSyncRespVO fullSyncDepartments(IWorkFullSyncReqVO reqVO) {
return runFullSync(reqVO, EnumSet.of(IWorkSyncEntityTypeEnum.DEPARTMENT));
}
+ /**
+ * 仅同步分部
+ */
@Override
public IWorkFullSyncRespVO fullSyncSubcompanies(IWorkFullSyncReqVO reqVO) {
return runFullSync(reqVO, EnumSet.of(IWorkSyncEntityTypeEnum.SUBCOMPANY));
}
+ /**
+ * 仅同步岗位
+ */
@Override
public IWorkFullSyncRespVO fullSyncJobTitles(IWorkFullSyncReqVO reqVO) {
return runFullSync(reqVO, EnumSet.of(IWorkSyncEntityTypeEnum.JOB_TITLE));
}
+ /**
+ * 仅同步人员(会自动包含依赖的分部、部门)
+ */
@Override
public IWorkFullSyncRespVO fullSyncUsers(IWorkFullSyncReqVO reqVO) {
return runFullSync(reqVO, EnumSet.of(IWorkSyncEntityTypeEnum.USER));
}
+ /**
+ * 手动同步
+ */
+ @Transactional
+ @Override
+ public IWorkFullSyncRespVO manuallySyncData(IWorkSyncByIdReqVO reqVO) {
+ return manuallySyncData(reqVO, EnumSet.of(IWorkSyncEntityTypeEnum.USER));
+ }
+
+ /**
+ * 全量同步方法
+ * @param reqVO 请求参数
+ * @param scopes 同步范围
+ * @return 响应对象
+ */
private IWorkFullSyncRespVO runFullSync(IWorkFullSyncReqVO reqVO, Set scopes) {
- IWorkFullSyncRespVO respVO = new IWorkFullSyncRespVO();
+ IWorkFullSyncRespVO respVO = new IWorkFullSyncRespVO();//1 初始化响应对象:创建响应VO并设置分页大小和批次统计列表
respVO.setPageSize(reqVO.getPageSize());
List batchStats = new ArrayList<>();
respVO.setBatches(batchStats);
-
+ //2 解析同步范围:根据传入的scopes确定需要同步的实体类型(用户、部门、分部、岗位)
boolean syncUsers = scopes.contains(IWorkSyncEntityTypeEnum.USER);
boolean syncDepartments = scopes.contains(IWorkSyncEntityTypeEnum.DEPARTMENT);
boolean syncSubcompanies = scopes.contains(IWorkSyncEntityTypeEnum.SUBCOMPANY);
@@ -69,18 +97,20 @@ public class IWorkSyncServiceImpl implements IWorkSyncService {
IWorkSyncProcessor.DeptSyncContext deptSyncContext = (syncDepartments || syncSubcompanies)
? new IWorkSyncProcessor.DeptSyncContext()
: null;
- if (syncSubcompanies) {
+ //3 按类型执行同步:依次执行分部、部门、岗位、用户的分页同步操作
+ if (syncSubcompanies) { //公司
processedPages += executeSubcompanyFullSync(reqVO, options, respVO.getSubcompanyStat(), batchStats, deptSyncContext);
}
- if (syncDepartments) {
+ if (syncDepartments) { //部门
processedPages += executeDepartmentFullSync(reqVO, options, respVO.getDepartmentStat(), batchStats, deptSyncContext);
}
- if (syncJobTitle) {
+ if (syncJobTitle) { // 岗位
processedPages += executeJobTitleFullSync(reqVO, options, respVO.getJobTitleStat(), batchStats);
}
- if (syncUsers) {
+ if (syncUsers) { //人员
processedPages += executeUserFullSync(reqVO, options, respVO.getUserStat(), batchStats);
}
+ //4、处理部门上下文:对部门和分部同步进行特殊处理,包括刷新待处理数据和补全部门编码
if (deptSyncContext != null) {
IWorkSyncProcessor.BatchResult flushResult = syncProcessor.flushDeptPending(deptSyncContext, options);
updateStat(respVO.getDepartmentStat(), flushResult, 0);
@@ -92,6 +122,9 @@ public class IWorkSyncServiceImpl implements IWorkSyncService {
return respVO;
}
+ /**
+ * 全量执行子公司全量同步
+ */
private int executeSubcompanyFullSync(IWorkFullSyncReqVO reqVO,
IWorkSyncProcessor.SyncOptions options,
IWorkSyncEntityStatVO stat,
@@ -103,7 +136,7 @@ public class IWorkSyncServiceImpl implements IWorkSyncService {
query.setPagesize(pageSize);
applyQueryConditions(query, reqVO);
IWorkHrSubcompanyPageRespVO pageResp = orgRestService.listSubcompanies(query);
- ensureIWorkSuccess("拉取分部", pageResp.isSuccess(), pageResp.getMessage());
+ ensureIWorkSuccess("拉取分部", pageResp.isSuccess(), pageResp.getMessage());// IWork执行成功
List dataList = CollUtil.emptyIfNull(pageResp.getDataList());
IWorkSyncProcessor.BatchResult result = syncProcessor.syncSubcompanies(dataList, options, context);
updateStat(stat, result, dataList.size());
@@ -111,6 +144,9 @@ public class IWorkSyncServiceImpl implements IWorkSyncService {
});
}
+ /**
+ * 执行部门全量同步
+ */
private int executeDepartmentFullSync(IWorkFullSyncReqVO reqVO,
IWorkSyncProcessor.SyncOptions options,
IWorkSyncEntityStatVO stat,
@@ -130,6 +166,9 @@ public class IWorkSyncServiceImpl implements IWorkSyncService {
});
}
+ /**
+ * 执行岗位全量同步
+ */
private int executeJobTitleFullSync(IWorkFullSyncReqVO reqVO,
IWorkSyncProcessor.SyncOptions options,
IWorkSyncEntityStatVO stat,
@@ -148,6 +187,9 @@ public class IWorkSyncServiceImpl implements IWorkSyncService {
});
}
+ /**
+ * 执行用户全量同步
+ */
private int executeUserFullSync(IWorkFullSyncReqVO reqVO,
IWorkSyncProcessor.SyncOptions options,
IWorkSyncEntityStatVO stat,
@@ -193,6 +235,12 @@ public class IWorkSyncServiceImpl implements IWorkSyncService {
return processedPages;
}
+ /**
+ * 更新统计
+ * @param stat 统计
+ * @param result 结果
+ * @param pulled 拉取数量
+ */
private void updateStat(IWorkSyncEntityStatVO stat, IWorkSyncProcessor.BatchResult result, int pulled) {
stat.incrementPulled(pulled);
stat.incrementCreated(result.getCreated());
@@ -201,6 +249,11 @@ public class IWorkSyncServiceImpl implements IWorkSyncService {
stat.incrementFailed(result.getFailed());
}
+ /**
+ * 查询条件
+ * @param query 查询条件
+ * @param reqVO 请求
+ */
private void applyQueryConditions(IWorkOrgBaseQueryReqVO query, IWorkFullSyncReqVO reqVO) {
if (query == null || reqVO == null) {
return;
@@ -216,6 +269,9 @@ public class IWorkSyncServiceImpl implements IWorkSyncService {
params.put("id", reqVO.getId());
}
+ /**
+ * 全量同步
+ */
private IWorkSyncProcessor.SyncOptions buildFullSyncOptions(IWorkFullSyncReqVO reqVO) {
boolean includeCanceled = Boolean.TRUE.equals(reqVO.getIncludeCanceled());
boolean allowUpdate = Boolean.TRUE.equals(reqVO.getAllowUpdate());
@@ -237,4 +293,197 @@ public class IWorkSyncServiceImpl implements IWorkSyncService {
private record BatchExecution(IWorkSyncProcessor.BatchResult result, int totalPulled) {
}
+
+
+ /**
+ * 根据ID同步
+ */
+ private IWorkFullSyncRespVO manuallySyncData(IWorkSyncByIdReqVO reqVO, Set scopes) {
+
+ //1 初始化响应对象:创建响应VO并设置分页大小和批次统计列表
+ IWorkFullSyncRespVO respVO = new IWorkFullSyncRespVO();
+ List batchStats = new ArrayList<>();
+ respVO.setBatches(batchStats);
+ //2 解析同步范围:根据传入的scopes确定需要同步的实体类型(用户、部门、分部、岗位)
+ boolean syncUsers = scopes.contains(IWorkSyncEntityTypeEnum.USER);
+ boolean syncDepartments = scopes.contains(IWorkSyncEntityTypeEnum.DEPARTMENT);
+ boolean syncSubcompanies = scopes.contains(IWorkSyncEntityTypeEnum.SUBCOMPANY);
+ boolean syncJobTitle = scopes.contains(IWorkSyncEntityTypeEnum.JOB_TITLE);
+ int processedPages = 0;
+ IWorkSyncProcessor.SyncOptions options = manualSync(reqVO);
+ IWorkSyncProcessor.DeptSyncContext deptSyncContext = (syncDepartments || syncSubcompanies)
+ ? new IWorkSyncProcessor.DeptSyncContext()
+ : null;
+ //3 按类型执行同步:依次执行分部、部门、岗位、用户的分页同步操作
+ if (syncSubcompanies) {
+ processedPages += runManualFullSyncForSubsidiaries(reqVO, options, respVO.getSubcompanyStat(), batchStats, deptSyncContext);//分部统计信息
+ }
+ if (syncDepartments) {
+ processedPages += departments(reqVO, options, respVO.getDepartmentStat(), batchStats, deptSyncContext);//部门统计信息
+ }
+ if (syncJobTitle) {
+ processedPages += syncPositionsManually(reqVO, options, respVO.getJobTitleStat(), batchStats);//岗位统计信息
+ }
+ if (syncUsers) {
+ processedPages += syncUsers(reqVO, options, respVO.getUserStat(), batchStats);//用户统计信息
+ }
+ //4、处理部门上下文:对部门和分部同步进行特殊处理,包括刷新待处理数据和补全部门编码
+ if (deptSyncContext != null) {
+ IWorkSyncProcessor.BatchResult flushResult = syncProcessor.flushDeptPending(deptSyncContext, options);
+ updateStat(respVO.getDepartmentStat(), flushResult, 0);//部门统计信息
+ if (CollUtil.isNotEmpty(deptSyncContext.getPlaceholderDeptIds())) {
+ deptService.backfillMissingCodesWithoutEvent(deptSyncContext.getPlaceholderDeptIds());
+ }
+ }
+ respVO.setProcessedPages(processedPages);
+ return respVO;
+ }
+
+
+ /**
+ * 手动同步
+ */
+ private IWorkSyncProcessor.SyncOptions manualSync(IWorkSyncByIdReqVO reqVO) {
+ boolean includeCanceled = Boolean.TRUE.equals(reqVO.getIncludeCanceled());
+ boolean allowUpdate = Boolean.TRUE.equals(reqVO.getAllowUpdate());
+ return IWorkSyncProcessor.SyncOptions.custom(includeCanceled, allowUpdate, true);
+ }
+
+
+ /**
+ * 手动执行子公司全量同步f
+ */
+ private int runManualFullSyncForSubsidiaries(IWorkSyncByIdReqVO reqVO,
+ IWorkSyncProcessor.SyncOptions options,
+ IWorkSyncEntityStatVO stat,
+ List batches,
+ IWorkSyncProcessor.DeptSyncContext context) {
+ return paged(reqVO, IWorkSyncEntityTypeEnum.SUBCOMPANY, batches, (page, pageSize) -> {
+ IWorkSubcompanyQueryReqVO query = new IWorkSubcompanyQueryReqVO();
+ query.setCurpage(page);
+ query.setPagesize(pageSize);
+ applyQuery(query, reqVO);//查询条件
+ IWorkHrSubcompanyPageRespVO pageResp = orgRestService.listSubcompanies(query);
+ ensureIWorkSuccess("拉取分部", pageResp.isSuccess(), pageResp.getMessage());// IWork执行成功
+ List dataList = CollUtil.emptyIfNull(pageResp.getDataList());
+ IWorkSyncProcessor.BatchResult result = syncProcessor.syncSubcompanies(dataList, options, context);
+ updateStat(stat, result, dataList.size());
+ return new BatchExecution(result, dataList.size());
+ });
+ }
+
+
+ /**
+ * 查询条件
+ * @param query 查询条件
+ * @param reqVO 请求
+ */
+ private void applyQuery(IWorkOrgBaseQueryReqVO query, IWorkSyncByIdReqVO reqVO) {
+ if (query == null || reqVO == null) {
+ return;
+ }
+ if (StrUtil.isBlank(reqVO.getId())) {
+ return;
+ }
+ Map params = query.getParams();
+ if (params == null) {
+ params = new HashMap<>();
+ query.setParams(params);
+ }
+ params.put("id", reqVO.getId());
+ }
+
+
+ private int paged(IWorkSyncByIdReqVO reqVO,
+ IWorkSyncEntityTypeEnum type,
+ List batches,
+ PageExecutor executor) {
+ int startPage = reqVO.getStartPage() == null ? 1 : reqVO.getStartPage();
+ int pageSize = reqVO.getPageSize() == null ? 100 : reqVO.getPageSize();
+ int pagesLimit = reqVO.getMaxPages() == null ? Integer.MAX_VALUE : reqVO.getMaxPages();
+ int processedPages = 0;
+ for (int page = startPage; processedPages < pagesLimit; page++) {
+ BatchExecution execution = executor.execute(page, pageSize);
+ if (execution == null || execution.totalPulled == 0) {
+ break;
+ }
+ processedPages++;
+ IWorkSyncBatchStatVO batchStat = new IWorkSyncBatchStatVO();
+ batchStat.setEntityType(type);
+ batchStat.setPageNumber(page);
+ batchStat.setPulled(execution.totalPulled);
+ batchStat.setCreated(execution.result.getCreated());
+ batchStat.setSkippedExisting(execution.result.getSkipped());
+ batchStat.setDisabled(execution.result.getDisabled());
+ batchStat.setFailed(execution.result.getFailed());
+ batches.add(batchStat);
+ }
+ return processedPages;
+ }
+
+ /**
+ * 手动执行部门同步
+ */
+ private int departments(IWorkSyncByIdReqVO reqVO,
+ IWorkSyncProcessor.SyncOptions options,
+ IWorkSyncEntityStatVO stat,
+ List batches,
+ IWorkSyncProcessor.DeptSyncContext context) {
+ return paged(reqVO, IWorkSyncEntityTypeEnum.DEPARTMENT, batches, (page, pageSize) -> {
+ IWorkDepartmentQueryReqVO query = new IWorkDepartmentQueryReqVO();
+ query.setCurpage(page);
+ query.setPagesize(pageSize);
+ applyQuery(query, reqVO);
+ IWorkHrDepartmentPageRespVO pageResp = orgRestService.listDepartments(query);
+ ensureIWorkSuccess("拉取部门", pageResp.isSuccess(), pageResp.getMessage());
+ List dataList = CollUtil.emptyIfNull(pageResp.getDataList());
+ IWorkSyncProcessor.BatchResult result = syncProcessor.syncDepartments(dataList, options, context);
+ updateStat(stat, result, dataList.size());
+ return new BatchExecution(result, dataList.size());
+ });
+ }
+
+
+ /**
+ * 手动执行岗位同步
+ */
+ private int syncPositionsManually(IWorkSyncByIdReqVO reqVO,
+ IWorkSyncProcessor.SyncOptions options,
+ IWorkSyncEntityStatVO stat,
+ List batches) {
+ return paged(reqVO, IWorkSyncEntityTypeEnum.JOB_TITLE, batches, (page, pageSize) -> {
+ IWorkJobTitleQueryReqVO query = new IWorkJobTitleQueryReqVO();
+ query.setCurpage(page);
+ query.setPagesize(pageSize);
+ applyQuery(query, reqVO);
+ IWorkHrJobTitlePageRespVO pageResp = orgRestService.listJobTitles(query);
+ ensureIWorkSuccess("拉取岗位", pageResp.isSuccess(), pageResp.getMessage());
+ List dataList = CollUtil.emptyIfNull(pageResp.getDataList());
+ IWorkSyncProcessor.BatchResult result = syncProcessor.syncJobTitles(dataList, options);
+ updateStat(stat, result, dataList.size());
+ return new BatchExecution(result, dataList.size());
+ });
+ }
+
+ /**
+ * 手动执行用户同步
+ */
+ private int syncUsers(IWorkSyncByIdReqVO reqVO,
+ IWorkSyncProcessor.SyncOptions options,
+ IWorkSyncEntityStatVO stat,
+ List batches) {
+ return paged(reqVO, IWorkSyncEntityTypeEnum.USER, batches, (page, pageSize) -> {
+ IWorkUserQueryReqVO query = new IWorkUserQueryReqVO();
+ query.setCurpage(page);
+ query.setPagesize(pageSize);
+ applyQuery(query, reqVO);
+ IWorkHrUserPageRespVO pageResp = orgRestService.listUsers(query);
+ ensureIWorkSuccess("拉取人员", pageResp.isSuccess(), pageResp.getMessage());
+ List dataList = CollUtil.emptyIfNull(pageResp.getDataList());
+ IWorkSyncProcessor.BatchResult result = syncProcessor.syncUsers(dataList, options);
+ updateStat(stat, result, dataList.size());
+ return new BatchExecution(result, dataList.size());
+ });
+ }
+
}