Merge remote-tracking branch 'ztcloud/main' into main-ztcloud
# Conflicts: # zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/DeptService.java
This commit is contained in:
5
pom.xml
5
pom.xml
@@ -32,7 +32,7 @@
|
|||||||
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
|
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<revision>3.0.45</revision>
|
<revision>3.0.46</revision>
|
||||||
<!-- Maven 相关 -->
|
<!-- Maven 相关 -->
|
||||||
<java.version>17</java.version>
|
<java.version>17</java.version>
|
||||||
<maven.compiler.source>${java.version}</maven.compiler.source>
|
<maven.compiler.source>${java.version}</maven.compiler.source>
|
||||||
@@ -271,7 +271,8 @@
|
|||||||
<profile>
|
<profile>
|
||||||
<id>chenbowen</id>
|
<id>chenbowen</id>
|
||||||
<properties>
|
<properties>
|
||||||
<env.name>local</env.name>
|
<!-- <env.name>local</env.name>-->
|
||||||
|
<env.name>dev</env.name>
|
||||||
<!-- <config.server-addr>localhost:8848</config.server-addr>-->
|
<!-- <config.server-addr>localhost:8848</config.server-addr>-->
|
||||||
<config.server-addr>172.16.46.63:30848</config.server-addr>
|
<config.server-addr>172.16.46.63:30848</config.server-addr>
|
||||||
<config.namespace>chenbowen</config.namespace>
|
<config.namespace>chenbowen</config.namespace>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -19,7 +19,8 @@ CREATE TABLE databus_api_definition_credential (
|
|||||||
deleted BIT DEFAULT '0' NOT NULL
|
deleted BIT DEFAULT '0' NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE UNIQUE INDEX uk_databus_api_definition_credential ON databus_api_definition_credential (api_id, credential_id, deleted);
|
-- 去掉错误的唯一索引逻辑
|
||||||
|
-- CREATE UNIQUE INDEX uk_databus_api_definition_credential ON databus_api_definition_credential (api_id, credential_id, deleted);
|
||||||
CREATE INDEX idx_databus_api_definition_credential_api ON databus_api_definition_credential (api_id);
|
CREATE INDEX idx_databus_api_definition_credential_api ON databus_api_definition_credential (api_id);
|
||||||
CREATE INDEX idx_databus_api_definition_credential_cred ON databus_api_definition_credential (credential_id);
|
CREATE INDEX idx_databus_api_definition_credential_cred ON databus_api_definition_credential (credential_id);
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
|
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<revision>3.0.45</revision>
|
<revision>3.0.46</revision>
|
||||||
<flatten-maven-plugin.version>1.6.0</flatten-maven-plugin.version>
|
<flatten-maven-plugin.version>1.6.0</flatten-maven-plugin.version>
|
||||||
<!-- 统一依赖管理 -->
|
<!-- 统一依赖管理 -->
|
||||||
<spring.boot.version>3.4.5</spring.boot.version>
|
<spring.boot.version>3.4.5</spring.boot.version>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.zt.plat.framework.common.util.security;
|
package com.zt.plat.framework.common.util.security;
|
||||||
|
|
||||||
import cn.hutool.crypto.SecureUtil;
|
import cn.hutool.crypto.SecureUtil;
|
||||||
|
import com.zt.plat.framework.common.util.json.JsonUtils;
|
||||||
|
|
||||||
import javax.crypto.Cipher;
|
import javax.crypto.Cipher;
|
||||||
import javax.crypto.KeyGenerator;
|
import javax.crypto.KeyGenerator;
|
||||||
@@ -126,7 +127,11 @@ public final class CryptoSignatureUtils {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
sb.append(key).append('=');
|
sb.append(key).append('=');
|
||||||
sb.append(value);
|
if (value instanceof String || value instanceof Number || value instanceof Boolean) {
|
||||||
|
sb.append(value);
|
||||||
|
} else {
|
||||||
|
sb.append(JsonUtils.toJsonString(value));
|
||||||
|
}
|
||||||
sb.append('&');
|
sb.append('&');
|
||||||
}
|
}
|
||||||
if (sb.length() > 0) {
|
if (sb.length() > 0) {
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import org.springframework.boot.autoconfigure.AutoConfigurationPackages;
|
|||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.core.env.Environment;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@@ -18,14 +20,12 @@ import java.util.Set;
|
|||||||
public class BusinessDataPermissionConfiguration {
|
public class BusinessDataPermissionConfiguration {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public BusinessDataPermissionEntityScanner businessDataPermissionEntityScanner(BeanFactory beanFactory, ApplicationContext applicationContext) {
|
public BusinessDataPermissionEntityScanner businessDataPermissionEntityScanner(BeanFactory beanFactory, ApplicationContext applicationContext, Environment environment) {
|
||||||
Set<String> basePackages = new LinkedHashSet<>();
|
Set<String> basePackages = new LinkedHashSet<>();
|
||||||
|
addConfiguredBasePackages(environment, basePackages);
|
||||||
if (AutoConfigurationPackages.has(beanFactory)) {
|
if (AutoConfigurationPackages.has(beanFactory)) {
|
||||||
basePackages.addAll(AutoConfigurationPackages.get(beanFactory));
|
basePackages.addAll(AutoConfigurationPackages.get(beanFactory));
|
||||||
}
|
}
|
||||||
if (basePackages.isEmpty()) {
|
|
||||||
basePackages.add("com.zt");
|
|
||||||
}
|
|
||||||
ClassLoader classLoader = applicationContext != null
|
ClassLoader classLoader = applicationContext != null
|
||||||
? applicationContext.getClassLoader()
|
? applicationContext.getClassLoader()
|
||||||
: Thread.currentThread().getContextClassLoader();
|
: Thread.currentThread().getContextClassLoader();
|
||||||
@@ -35,6 +35,21 @@ public class BusinessDataPermissionConfiguration {
|
|||||||
return new BusinessDataPermissionEntityScanner(basePackages, classLoader);
|
return new BusinessDataPermissionEntityScanner(basePackages, classLoader);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void addConfiguredBasePackages(Environment environment, Set<String> basePackages) {
|
||||||
|
if (environment == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String configured = environment.getProperty("zt.info.base-package");
|
||||||
|
if (!StringUtils.hasText(configured)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (String pkg : configured.split("[,;\\s]+")) {
|
||||||
|
if (StringUtils.hasText(pkg)) {
|
||||||
|
basePackages.add(pkg.trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public CompanyDataPermissionRuleCustomizer autoCompanyDataPermissionRuleCustomizer(BusinessDataPermissionEntityScanner scanner) {
|
public CompanyDataPermissionRuleCustomizer autoCompanyDataPermissionRuleCustomizer(BusinessDataPermissionEntityScanner scanner) {
|
||||||
return rule -> scanner.getEntityMetadata().forEach(metadata -> {
|
return rule -> scanner.getEntityMetadata().forEach(metadata -> {
|
||||||
|
|||||||
@@ -28,6 +28,14 @@ import java.util.*;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
public class BusinessDataPermissionEntityScanner {
|
public class BusinessDataPermissionEntityScanner {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 临时排除的包前缀(物流模块 DO,不参与数据权限扫描)
|
||||||
|
*/
|
||||||
|
private static final Set<String> EXCLUDED_PACKAGE_PREFIXES = Set.of(
|
||||||
|
"com.zt.plat.module.backendlogistics",
|
||||||
|
"com.zt.plat.module.erp",
|
||||||
|
"com.zt.plat.framework.mybatis.core.dataobject.BusinessBaseDO");
|
||||||
|
|
||||||
private final Set<String> basePackages;
|
private final Set<String> basePackages;
|
||||||
private final ClassLoader classLoader;
|
private final ClassLoader classLoader;
|
||||||
|
|
||||||
@@ -70,6 +78,9 @@ public class BusinessDataPermissionEntityScanner {
|
|||||||
if (!StringUtils.hasText(className)) {
|
if (!StringUtils.hasText(className)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (isExcludedPackage(className)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
Class<?> clazz = ClassUtils.forName(className, classLoader);
|
Class<?> clazz = ClassUtils.forName(className, classLoader);
|
||||||
if (clazz == BusinessBaseDO.class || !BusinessBaseDO.class.isAssignableFrom(clazz)) {
|
if (clazz == BusinessBaseDO.class || !BusinessBaseDO.class.isAssignableFrom(clazz)) {
|
||||||
@@ -92,6 +103,15 @@ public class BusinessDataPermissionEntityScanner {
|
|||||||
return new ArrayList<>(metadataMap.values());
|
return new ArrayList<>(metadataMap.values());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isExcludedPackage(String className) {
|
||||||
|
for (String prefix : EXCLUDED_PACKAGE_PREFIXES) {
|
||||||
|
if (className.startsWith(prefix)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private EntityMetadata buildMetadata(Class<? extends BusinessBaseDO> entityClass) {
|
private EntityMetadata buildMetadata(Class<? extends BusinessBaseDO> entityClass) {
|
||||||
String tableName = resolveTableName(entityClass);
|
String tableName = resolveTableName(entityClass);
|
||||||
if (!StringUtils.hasText(tableName)) {
|
if (!StringUtils.hasText(tableName)) {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.zt.plat.framework.datapermission.core.rule.dept;
|
|||||||
import cn.hutool.core.collection.CollUtil;
|
import cn.hutool.core.collection.CollUtil;
|
||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
|
||||||
import com.zt.plat.framework.common.biz.system.permission.PermissionCommonApi;
|
import com.zt.plat.framework.common.biz.system.permission.PermissionCommonApi;
|
||||||
import com.zt.plat.framework.common.biz.system.permission.dto.DeptDataPermissionRespDTO;
|
import com.zt.plat.framework.common.biz.system.permission.dto.DeptDataPermissionRespDTO;
|
||||||
import com.zt.plat.framework.common.enums.UserTypeEnum;
|
import com.zt.plat.framework.common.enums.UserTypeEnum;
|
||||||
@@ -14,7 +15,7 @@ import com.zt.plat.framework.mybatis.core.util.MyBatisUtils;
|
|||||||
import com.zt.plat.framework.security.core.LoginUser;
|
import com.zt.plat.framework.security.core.LoginUser;
|
||||||
import com.zt.plat.framework.security.core.util.SecurityFrameworkUtils;
|
import com.zt.plat.framework.security.core.util.SecurityFrameworkUtils;
|
||||||
import com.zt.plat.framework.tenant.core.context.CompanyContextHolder;
|
import com.zt.plat.framework.tenant.core.context.CompanyContextHolder;
|
||||||
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
|
import com.zt.plat.framework.tenant.core.context.DeptContextHolder;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import net.sf.jsqlparser.expression.Alias;
|
import net.sf.jsqlparser.expression.Alias;
|
||||||
@@ -108,6 +109,11 @@ public class DeptDataPermissionRule implements DataPermissionRule {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 显式忽略部门数据权限时直接放行
|
||||||
|
if (DeptContextHolder.shouldIgnore()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// 获得数据权限
|
// 获得数据权限
|
||||||
DeptDataPermissionRespDTO deptDataPermission = loginUser.getContext(CONTEXT_KEY, DeptDataPermissionRespDTO.class);
|
DeptDataPermissionRespDTO deptDataPermission = loginUser.getContext(CONTEXT_KEY, DeptDataPermissionRespDTO.class);
|
||||||
// 从上下文中拿不到,则调用逻辑进行获取
|
// 从上下文中拿不到,则调用逻辑进行获取
|
||||||
@@ -136,6 +142,20 @@ public class DeptDataPermissionRule implements DataPermissionRule {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 若存在部门上下文,优先使用上下文中的单一部门,必要时校验公司一致性
|
||||||
|
Long ctxDeptId = DeptContextHolder.getDeptId();
|
||||||
|
if (ctxDeptId != null && ctxDeptId > 0L) {
|
||||||
|
Long currentCompanyId = CompanyContextHolder.getCompanyId();
|
||||||
|
Long ctxCompanyId = DeptContextHolder.getCompanyId();
|
||||||
|
Long compareCompanyId = ctxCompanyId != null ? ctxCompanyId : currentCompanyId;
|
||||||
|
if (currentCompanyId != null && currentCompanyId > 0L
|
||||||
|
&& compareCompanyId != null && !currentCompanyId.equals(compareCompanyId)) {
|
||||||
|
log.warn("[getExpression][LoginUser({}) Table({}/{}) DeptContextHolder company mismatch: currentCompanyId={}, ctxCompanyId={}, ctxDeptId={}, source=DeptContextHolder]",
|
||||||
|
JsonUtils.toJsonString(loginUser), tableName, tableAlias == null ? null : tableAlias.getName(),
|
||||||
|
currentCompanyId, compareCompanyId, ctxDeptId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 情况一,如果是 ALL 可查看全部,则无需拼接条件
|
// 情况一,如果是 ALL 可查看全部,则无需拼接条件
|
||||||
if (deptDataPermission.getAll()) {
|
if (deptDataPermission.getAll()) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -7,10 +7,13 @@ import com.zt.plat.framework.common.enums.UserTypeEnum;
|
|||||||
import com.zt.plat.framework.common.util.collection.SetUtils;
|
import com.zt.plat.framework.common.util.collection.SetUtils;
|
||||||
import com.zt.plat.framework.security.core.LoginUser;
|
import com.zt.plat.framework.security.core.LoginUser;
|
||||||
import com.zt.plat.framework.security.core.util.SecurityFrameworkUtils;
|
import com.zt.plat.framework.security.core.util.SecurityFrameworkUtils;
|
||||||
|
import com.zt.plat.framework.tenant.core.context.CompanyContextHolder;
|
||||||
|
import com.zt.plat.framework.tenant.core.context.DeptContextHolder;
|
||||||
import com.zt.plat.framework.test.core.ut.BaseMockitoUnitTest;
|
import com.zt.plat.framework.test.core.ut.BaseMockitoUnitTest;
|
||||||
import com.zt.plat.framework.common.biz.system.permission.dto.DeptDataPermissionRespDTO;
|
import com.zt.plat.framework.common.biz.system.permission.dto.DeptDataPermissionRespDTO;
|
||||||
import net.sf.jsqlparser.expression.Alias;
|
import net.sf.jsqlparser.expression.Alias;
|
||||||
import net.sf.jsqlparser.expression.Expression;
|
import net.sf.jsqlparser.expression.Expression;
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.mockito.InjectMocks;
|
import org.mockito.InjectMocks;
|
||||||
@@ -27,6 +30,7 @@ import static org.junit.jupiter.api.Assertions.*;
|
|||||||
import static org.mockito.ArgumentMatchers.eq;
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
import static org.mockito.ArgumentMatchers.same;
|
import static org.mockito.ArgumentMatchers.same;
|
||||||
import static org.mockito.Mockito.mockStatic;
|
import static org.mockito.Mockito.mockStatic;
|
||||||
|
import static org.mockito.Mockito.verifyNoInteractions;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -48,7 +52,13 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest {
|
|||||||
// 清空 rule
|
// 清空 rule
|
||||||
rule.getTableNames().clear();
|
rule.getTableNames().clear();
|
||||||
((Map<String, String>) ReflectUtil.getFieldValue(rule, "deptColumns")).clear();
|
((Map<String, String>) ReflectUtil.getFieldValue(rule, "deptColumns")).clear();
|
||||||
((Map<String, String>) ReflectUtil.getFieldValue(rule, "deptColumns")).clear();
|
((Map<String, String>) ReflectUtil.getFieldValue(rule, "userColumns")).clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
void tearDown() {
|
||||||
|
DeptContextHolder.clear();
|
||||||
|
CompanyContextHolder.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test // 无 LoginUser
|
@Test // 无 LoginUser
|
||||||
@@ -236,4 +246,88 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test // 忽略部门数据权限,直接放行
|
||||||
|
void testGetExpression_ignoreDeptContext() {
|
||||||
|
try (MockedStatic<SecurityFrameworkUtils> secMock = mockStatic(SecurityFrameworkUtils.class);
|
||||||
|
MockedStatic<DeptContextHolder> deptCtxMock = mockStatic(DeptContextHolder.class)) {
|
||||||
|
String tableName = "t_order";
|
||||||
|
Alias alias = new Alias("o");
|
||||||
|
LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L)
|
||||||
|
.setUserType(UserTypeEnum.ADMIN.getValue()));
|
||||||
|
secMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser);
|
||||||
|
deptCtxMock.when(DeptContextHolder::shouldIgnore).thenReturn(true);
|
||||||
|
|
||||||
|
Expression expression = rule.getExpression(tableName, alias);
|
||||||
|
|
||||||
|
assertNull(expression);
|
||||||
|
verifyNoInteractions(permissionApi);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test // 上下文部门存在且公司一致时,清空原集合并覆盖为单一 deptId
|
||||||
|
void testGetExpression_deptContextOverride_companyMatch() {
|
||||||
|
try (MockedStatic<SecurityFrameworkUtils> secMock = mockStatic(SecurityFrameworkUtils.class);
|
||||||
|
MockedStatic<DeptContextHolder> deptCtxMock = mockStatic(DeptContextHolder.class);
|
||||||
|
MockedStatic<CompanyContextHolder> 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()
|
||||||
|
.setDeptIds(CollUtil.newLinkedHashSet(10L, 20L))
|
||||||
|
.setCompanyId(1L);
|
||||||
|
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());
|
||||||
|
assertEquals(CollUtil.newLinkedHashSet(99L), deptDataPermission.getDeptIds());
|
||||||
|
assertEquals(1L, deptDataPermission.getCompanyId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test // 上下文部门存在但公司不一致时,记录告警并保持原逻辑(不覆盖)
|
||||||
|
void testGetExpression_deptContextOverride_companyMismatch() {
|
||||||
|
try (MockedStatic<SecurityFrameworkUtils> secMock = mockStatic(SecurityFrameworkUtils.class);
|
||||||
|
MockedStatic<DeptContextHolder> deptCtxMock = mockStatic(DeptContextHolder.class);
|
||||||
|
MockedStatic<CompanyContextHolder> 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()
|
||||||
|
.setDeptIds(CollUtil.newLinkedHashSet(10L))
|
||||||
|
.setCompanyId(1L);
|
||||||
|
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(2L);
|
||||||
|
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 (10)", expression.toString());
|
||||||
|
assertEquals(CollUtil.newLinkedHashSet(10L), deptDataPermission.getDeptIds());
|
||||||
|
assertEquals(1L, deptDataPermission.getCompanyId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
package com.zt.plat.framework.tenant.core.context;
|
||||||
|
|
||||||
|
import com.alibaba.ttl.TransmittableThreadLocal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 部门上下文 Holder,使用 {@link TransmittableThreadLocal} 支持在线程池/异步场景下的上下文传递。
|
||||||
|
*
|
||||||
|
* 包含当前部门编号、所属公司编号以及是否忽略部门数据权限的标识。
|
||||||
|
*/
|
||||||
|
public class DeptContextHolder {
|
||||||
|
|
||||||
|
/** 当前部门编号 */
|
||||||
|
private static final ThreadLocal<Long> DEPT_ID = new TransmittableThreadLocal<>();
|
||||||
|
/** 当前部门所属公司编号(用于一致性校验) */
|
||||||
|
private static final ThreadLocal<Long> COMPANY_ID = new TransmittableThreadLocal<>();
|
||||||
|
/** 是否忽略部门数据权限 */
|
||||||
|
private static final ThreadLocal<Boolean> IGNORE = new TransmittableThreadLocal<>();
|
||||||
|
|
||||||
|
public static Long getDeptId() {
|
||||||
|
return DEPT_ID.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Long getCompanyId() {
|
||||||
|
return COMPANY_ID.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置部门与所属公司编号。
|
||||||
|
*/
|
||||||
|
public static void setContext(Long deptId, Long companyId) {
|
||||||
|
DEPT_ID.set(deptId);
|
||||||
|
COMPANY_ID.set(companyId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setDeptId(Long deptId) {
|
||||||
|
DEPT_ID.set(deptId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setCompanyId(Long companyId) {
|
||||||
|
COMPANY_ID.set(companyId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean hasDeptId() {
|
||||||
|
Long deptId = DEPT_ID.get();
|
||||||
|
return deptId != null && deptId > 0L;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setIgnore(Boolean ignore) {
|
||||||
|
IGNORE.set(ignore);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean shouldIgnore() {
|
||||||
|
return Boolean.TRUE.equals(IGNORE.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void clear() {
|
||||||
|
DEPT_ID.remove();
|
||||||
|
COMPANY_ID.remove();
|
||||||
|
IGNORE.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ package com.zt.plat.framework.tenant.core.web;
|
|||||||
import com.zt.plat.framework.security.core.LoginUser;
|
import com.zt.plat.framework.security.core.LoginUser;
|
||||||
import com.zt.plat.framework.security.core.util.SecurityFrameworkUtils;
|
import com.zt.plat.framework.security.core.util.SecurityFrameworkUtils;
|
||||||
import com.zt.plat.framework.tenant.core.context.CompanyContextHolder;
|
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.framework.web.core.util.WebFrameworkUtils;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
@@ -66,11 +67,19 @@ public class CompanyVisitContextInterceptor implements HandlerInterceptor {
|
|||||||
|
|
||||||
if (companyId == null || companyId <= 0L) {
|
if (companyId == null || companyId <= 0L) {
|
||||||
CompanyContextHolder.setIgnore(true);
|
CompanyContextHolder.setIgnore(true);
|
||||||
|
DeptContextHolder.clear();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
CompanyContextHolder.setIgnore(false);
|
CompanyContextHolder.setIgnore(false);
|
||||||
CompanyContextHolder.setCompanyId(companyId);
|
CompanyContextHolder.setCompanyId(companyId);
|
||||||
|
// 默认不忽略部门数据权限;如果有有效部门则写入上下文
|
||||||
|
DeptContextHolder.setIgnore(false);
|
||||||
|
if (deptId != null && deptId > 0L) {
|
||||||
|
DeptContextHolder.setContext(deptId, companyId);
|
||||||
|
} else {
|
||||||
|
DeptContextHolder.clear();
|
||||||
|
}
|
||||||
if (loginUser == null) {
|
if (loginUser == null) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -91,7 +100,9 @@ public class CompanyVisitContextInterceptor implements HandlerInterceptor {
|
|||||||
LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();
|
LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();
|
||||||
if (loginUser != null) {
|
if (loginUser != null) {
|
||||||
loginUser.setVisitCompanyId(0L);
|
loginUser.setVisitCompanyId(0L);
|
||||||
|
loginUser.setVisitDeptId(0L);
|
||||||
}
|
}
|
||||||
|
DeptContextHolder.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Long resolveLong(Object value) {
|
private Long resolveLong(Object value) {
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
package com.zt.plat.framework.tenant.core.web;
|
||||||
|
|
||||||
|
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 org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.web.servlet.HandlerInterceptor;
|
||||||
|
import org.springframework.mock.web.MockHttpServletRequest;
|
||||||
|
import org.springframework.mock.web.MockHttpServletResponse;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CompanyVisitContextInterceptor 单测,覆盖公司/部门上下文写入及清理。
|
||||||
|
*/
|
||||||
|
class CompanyVisitContextInterceptorTest {
|
||||||
|
|
||||||
|
private final HandlerInterceptor interceptor = new CompanyVisitContextInterceptor();
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
void tearDown() {
|
||||||
|
CompanyContextHolder.clear();
|
||||||
|
DeptContextHolder.clear();
|
||||||
|
SecurityContextHolder.clearContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test // 无公司 id:应 ignore,公司/部门上下文清空
|
||||||
|
void testPreHandle_noCompanyId_ignore() throws Exception {
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||||
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
|
||||||
|
boolean result = interceptor.preHandle(request, response, new Object());
|
||||||
|
|
||||||
|
assertTrue(result);
|
||||||
|
assertTrue(CompanyContextHolder.isIgnore());
|
||||||
|
assertNull(CompanyContextHolder.getCompanyId());
|
||||||
|
assertNull(DeptContextHolder.getDeptId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test // 有公司无部门:写入公司,部门清空
|
||||||
|
void testPreHandle_companyOnly() throws Exception {
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||||
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
LoginUser loginUser = new LoginUser();
|
||||||
|
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(loginUser, null));
|
||||||
|
request.addHeader("visit-company-id", "11");
|
||||||
|
|
||||||
|
boolean result = interceptor.preHandle(request, response, new Object());
|
||||||
|
|
||||||
|
assertTrue(result);
|
||||||
|
assertFalse(CompanyContextHolder.isIgnore());
|
||||||
|
assertEquals(11L, CompanyContextHolder.getCompanyId());
|
||||||
|
assertFalse(DeptContextHolder.shouldIgnore());
|
||||||
|
assertNull(DeptContextHolder.getDeptId());
|
||||||
|
assertEquals(11L, loginUser.getVisitCompanyId());
|
||||||
|
assertNull(loginUser.getVisitDeptId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test // 有公司+部门:写入公司、部门上下文,afterCompletion 清理 visitDeptId & holder
|
||||||
|
void testPreHandle_withCompanyAndDept_andAfterCompletionClear() throws Exception {
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||||
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
LoginUser loginUser = new LoginUser();
|
||||||
|
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(loginUser, null));
|
||||||
|
request.addHeader("visit-company-id", "22");
|
||||||
|
request.addHeader("visit-dept-id", "33");
|
||||||
|
|
||||||
|
boolean result = interceptor.preHandle(request, response, new Object());
|
||||||
|
|
||||||
|
assertTrue(result);
|
||||||
|
assertFalse(CompanyContextHolder.isIgnore());
|
||||||
|
assertEquals(22L, CompanyContextHolder.getCompanyId());
|
||||||
|
assertEquals(33L, DeptContextHolder.getDeptId());
|
||||||
|
assertEquals(22L, DeptContextHolder.getCompanyId());
|
||||||
|
assertEquals(22L, loginUser.getVisitCompanyId());
|
||||||
|
assertEquals(33L, loginUser.getVisitDeptId());
|
||||||
|
|
||||||
|
// afterCompletion: 清理 visitCompanyId/visitDeptId 与 holder
|
||||||
|
interceptor.afterCompletion(request, response, new Object(), null);
|
||||||
|
assertEquals(0L, loginUser.getVisitCompanyId());
|
||||||
|
assertEquals(0L, loginUser.getVisitDeptId());
|
||||||
|
assertNull(DeptContextHolder.getDeptId());
|
||||||
|
assertNull(DeptContextHolder.getCompanyId());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -33,6 +33,7 @@ import org.springframework.web.util.ContentCachingResponseWrapper;
|
|||||||
import org.springframework.web.util.UriComponentsBuilder;
|
import org.springframework.web.util.UriComponentsBuilder;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.net.URLDecoder;
|
||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
@@ -286,8 +287,7 @@ public class GatewaySecurityFilter extends OncePerRequestFilter {
|
|||||||
try {
|
try {
|
||||||
boolean valid = CryptoSignatureUtils.verifySignature(signaturePayload, signatureType);
|
boolean valid = CryptoSignatureUtils.verifySignature(signaturePayload, signatureType);
|
||||||
if (!valid) {
|
if (!valid) {
|
||||||
log.error("[API-PORTAL] 签名校验失败");
|
throw new SecurityValidationException(HttpStatus.UNAUTHORIZED, "签名校验失败");
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
} catch (IllegalArgumentException ex) {
|
} catch (IllegalArgumentException ex) {
|
||||||
throw new SecurityValidationException(HttpStatus.INTERNAL_SERVER_ERROR, "签名算法配置异常");
|
throw new SecurityValidationException(HttpStatus.INTERNAL_SERVER_ERROR, "签名算法配置异常");
|
||||||
@@ -305,15 +305,28 @@ public class GatewaySecurityFilter extends OncePerRequestFilter {
|
|||||||
.build()
|
.build()
|
||||||
.getQueryParams();
|
.getQueryParams();
|
||||||
params.forEach((key, values) -> {
|
params.forEach((key, values) -> {
|
||||||
if (!StringUtils.hasText(key) || "signature".equalsIgnoreCase(key)) {
|
String decodedKey = URLDecoder.decode(key, StandardCharsets.UTF_8);
|
||||||
|
if (!StringUtils.hasText(decodedKey) || "signature".equalsIgnoreCase(decodedKey)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (CollectionUtils.isEmpty(values)) {
|
if (CollectionUtils.isEmpty(values)) {
|
||||||
target.put(key, "");
|
target.put(decodedKey, "");
|
||||||
} else if (values.size() == 1) {
|
return;
|
||||||
target.put(key, values.get(0));
|
}
|
||||||
|
// 对每一个 value 做 URL 解码,确保与客户端原文签名一致
|
||||||
|
List<String> decodedValues = values.stream()
|
||||||
|
.map(val -> URLDecoder.decode(val, StandardCharsets.UTF_8))
|
||||||
|
.toList();
|
||||||
|
boolean allNullLiteral = decodedValues.stream()
|
||||||
|
.allMatch(v -> "null".equals(v));
|
||||||
|
if (allNullLiteral) {
|
||||||
|
// 过滤掉仅包含字符串 "null" 的参数
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (decodedValues.size() == 1) {
|
||||||
|
target.put(decodedKey, decodedValues.get(0));
|
||||||
} else {
|
} else {
|
||||||
target.put(key, String.join(",", values));
|
target.put(decodedKey, String.join(",", decodedValues));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (IllegalArgumentException ex) {
|
} catch (IllegalArgumentException ex) {
|
||||||
|
|||||||
@@ -5,6 +5,10 @@ import com.fasterxml.jackson.core.type.TypeReference;
|
|||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.zt.plat.framework.common.util.security.CryptoSignatureUtils;
|
import com.zt.plat.framework.common.util.security.CryptoSignatureUtils;
|
||||||
|
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
import javax.net.ssl.SSLParameters;
|
||||||
|
import javax.net.ssl.TrustManager;
|
||||||
|
import javax.net.ssl.X509TrustManager;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.PrintStream;
|
import java.io.PrintStream;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
@@ -23,10 +27,6 @@ import java.util.LinkedHashMap;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import javax.net.ssl.SSLContext;
|
|
||||||
import javax.net.ssl.SSLParameters;
|
|
||||||
import javax.net.ssl.TrustManager;
|
|
||||||
import javax.net.ssl.X509TrustManager;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 可直接运行的示例,演示如何使用 appId=test 与对应密钥调用本地 Databus API。
|
* 可直接运行的示例,演示如何使用 appId=test 与对应密钥调用本地 Databus API。
|
||||||
@@ -37,14 +37,14 @@ public final class DatabusApiInvocationExample {
|
|||||||
|
|
||||||
// private static final String APP_ID = "iwork";
|
// private static final String APP_ID = "iwork";
|
||||||
// private static final String APP_SECRET = "lpGXiNe/GMLk0vsbYGLa8eYxXq8tGhTbuu3/D4MJzIk=";
|
// private static final String APP_SECRET = "lpGXiNe/GMLk0vsbYGLa8eYxXq8tGhTbuu3/D4MJzIk=";
|
||||||
private static final String APP_ID = "ztmy";
|
private static final String APP_ID = "jwyw";
|
||||||
private static final String APP_SECRET = "zFre/nTRGi7LpoFjN7oQkKeOT09x1fWTyIswrc702QQ=";
|
private static final String APP_SECRET = "MhfCcqB59rDTnB5yGOVXWtp/5a0JXir7pSjPl5cVMJ8=";
|
||||||
private static final String ENCRYPTION_TYPE = CryptoSignatureUtils.ENCRYPT_TYPE_AES;
|
private static final String ENCRYPTION_TYPE = CryptoSignatureUtils.ENCRYPT_TYPE_AES;
|
||||||
// private static final String TARGET_API = "http://172.16.46.63:30081/admin-api/databus/api/portal/callback/v1";
|
// private static final String TARGET_API = "http://172.16.46.63:30081/admin-api/databus/api/portal/callback/v1";
|
||||||
// private static final String TARGET_API = "http://172.16.46.195:48080/admin-api/databus/api/portal/lgstOpenApi/v1";
|
// private static final String TARGET_API = "http://172.16.46.195:48080/admin-api/databus/api/portal/lgstOpenApi/v1";
|
||||||
// private static final String TARGET_API = "http://172.16.46.195:48080/admin-api/databus/api/portal/lgstOpenApi/v1";
|
// private static final String TARGET_API = "http://172.16.46.195:48080/admin-api/databus/api/portal/lgstOpenApi/v1";
|
||||||
private static final String TARGET_API = "https://jygk.chncopper.com:30078/admin-api/databus/api/portal/lgstOpenApi/v1";
|
// private static final String TARGET_API = "https://jygk.chncopper.com:30078/admin-api/databus/api/portal/lgstOpenApi/v1";
|
||||||
// private static final String TARGET_API = "http://localhost:48080/admin-api/databus/api/portal/callback/v1";
|
private static final String TARGET_API = "http://localhost:48080/admin-api/databus/api/portal/testcbw/456";
|
||||||
// private static final String TARGET_API = "http://localhost:48080/admin-api/databus/api/portal/lgstOpenApi/v1";
|
// private static final String TARGET_API = "http://localhost:48080/admin-api/databus/api/portal/lgstOpenApi/v1";
|
||||||
// private static final String TARGET_API = "http://localhost:48080/admin-api/databus/api/portal/testcbw/456";
|
// private static final String TARGET_API = "http://localhost:48080/admin-api/databus/api/portal/testcbw/456";
|
||||||
// ⚠️ 仅用于联调:信任所有证书 + 关闭主机名校验,生产环境请改为受信 CA 或自定义 truststore。
|
// ⚠️ 仅用于联调:信任所有证书 + 关闭主机名校验,生产环境请改为受信 CA 或自定义 truststore。
|
||||||
@@ -102,10 +102,16 @@ public final class DatabusApiInvocationExample {
|
|||||||
|
|
||||||
public static void main(String[] args) throws Exception {
|
public static void main(String[] args) throws Exception {
|
||||||
OUT.println("=== GET 请求示例 ===");
|
OUT.println("=== GET 请求示例 ===");
|
||||||
// executeGetExample();
|
executeGetExample();
|
||||||
// OUT.println();
|
// OUT.println();
|
||||||
// OUT.println("=== POST 请求示例 ===");
|
OUT.println("=== POST 请求示例 ===");
|
||||||
executePostExample();
|
executePostExample("""
|
||||||
|
{"operateFlag":"I","__interfaceType__":"R_MY_JY_03","data":{"endAddressName":"1","customerCompanyName":"中铜国贸","endAddressDetail":"测试地址","remark":" ","custSuppType":"1","shipperCompanyName":"中铜国贸","consigneeCorpCode":" ","consignerContactPhone":" 11","importFlag":"10","businessSupplierCode":" ","entrustMainCode":"WT3162251027027","endAddressCode":" ","specifyCarrierCorpCode":"10086689","materDetail":[{"detailStatus":"10","batchNo":"ZLTD2510ZTGM0017001","measureCodeMdm":"CU032110001","packType":" ","quantityPlanDetail":1,"deliveryOrderNo":"ZLTD2510ZTGM0017001","measureCode":"CU032110001","goodsSpecification":" ","measureUnitCode":"PAC","entrustDetailCode":"WT3162251027027001","brand":" ","soNumber":"68ecf0055502d565d22b378a"}],"operateFlag":1,"custSuppName":"上海锦生金属有限公司","startAddressCode":" ","planStartTime":1761556166000,"customerCompanyCode":0,"importMethod":"EXW","startAddressType":"10","shipperCompanyCode":"3162","deliverCondition":"20","businessSupplierName":" ","startAddressDetail":" 111","transType":"30","endAddressType":"20","planEndTime":1761556166000,"specifyCarrierCorpName":null,"custSuppFlag":"0101","businessType":"20","consigneeCorpName":" ","custSuppCode":"10086689","startAddressName":" 111","consignerContactName":" 11"},"datetime":"20251027170929","busiBillCode":"WT3162251027027","system":"BRMS","__requestId__":"f918841c-14fb-49eb-9640-c5d1b3d46bd1"}
|
||||||
|
""");
|
||||||
|
|
||||||
|
executePostExample("""
|
||||||
|
{"msgCode":"YWJYGK0003","data":"{\\"memberId\\":65352,\\"routes\\":[{\\"carrierCorpCode\\":\\"10193776\\",\\"carrierCorpName\\":\\"成都达海金属加工配送有限公司\\",\\"endAddressCode\\":\\"440000-440300\\",\\"endAddressDetail\\":\\"深圳港\\",\\"endAddressDetailDesc\\":\\"广东省深圳市盐田区深盐路\\",\\"endAddressLatitude\\":22.567426,\\"endAddressLongitude\\":114.283271,\\"endAddressName\\":\\"广东省-深圳市\\",\\"endAddressType\\":\\"port\\",\\"startAddressCode\\":\\"520000-0\\",\\"startAddressDetail\\":\\"安龙\\",\\"startAddressDetailDesc\\":\\"贵州省安龙县德卧镇坡告村\\",\\"startAddressLatitude\\":25.066532,\\"startAddressLongitude\\":105.244186,\\"startAddressName\\":\\"贵州省-null\\",\\"startAddressType\\":\\"railway-station\\",\\"taskEndTime\\":1766592000000,\\"taskStartTime\\":1766332800000,\\"transType\\":\\"10\\"},{\\"carrierCorpCode\\":\\"10193776\\",\\"carrierCorpName\\":\\"成都达海金属加工配送有限公司\\",\\"endAddressCode\\":\\"230000-230600\\",\\"endAddressDetail\\":\\"大庆东\\",\\"endAddressDetailDesc\\":\\"黑龙江省大庆市龙凤区凤一路28号\\",\\"endAddressLatitude\\":46.544097,\\"endAddressLongitude\\":125.118902,\\"endAddressName\\":\\"黑龙江省-大庆市\\",\\"endAddressType\\":\\"railway-station\\",\\"startAddressCode\\":\\"440000-440300\\",\\"startAddressDetail\\":\\"深圳港\\",\\"startAddressDetailDesc\\":\\"广东省深圳市盐田区深盐路\\",\\"startAddressLatitude\\":22.567426,\\"startAddressLongitude\\":114.283271,\\"startAddressName\\":\\"广东省-深圳市\\",\\"startAddressType\\":\\"port\\",\\"taskEndTime\\":1767110400000,\\"taskStartTime\\":1766592000000,\\"transType\\":\\"30\\"},{\\"carrierCorpCode\\":\\"10193776\\",\\"carrierCorpName\\":\\"成都达海金属加工配送有限公司\\",\\"endAddressCode\\":\\"520000-0\\",\\"endAddressDetail\\":\\"郑屯\\",\\"endAddressDetailDesc\\":\\"贵州省郑屯镇\\",\\"endAddressName\\":\\"贵州省-null\\",\\"endAddressType\\":\\"railway-station\\",\\"startAddressCode\\":\\"230000-230600\\",\\"startAddressDetail\\":\\"大庆东\\",\\"startAddressDetailDesc\\":\\"黑龙江省大庆市龙凤区凤一路28号\\",\\"startAddressLatitude\\":46.544097,\\"startAddressLongitude\\":125.118902,\\"startAddressName\\":\\"黑龙江省-大庆市\\",\\"startAddressType\\":\\"railway-station\\",\\"taskEndTime\\":1768320000000,\\"taskStartTime\\":1767110400000,\\"transType\\":\\"20\\"}],\\"taskLineNumber\\":\\"CT202512230001_001\\",\\"taskNumber\\":\\"CT202512230001\\"}"}
|
||||||
|
""");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void executeGetExample() throws Exception {
|
private static void executeGetExample() throws Exception {
|
||||||
@@ -113,9 +119,11 @@ public final class DatabusApiInvocationExample {
|
|||||||
queryParams.put("businessCode", "11");
|
queryParams.put("businessCode", "11");
|
||||||
queryParams.put("fileId", "11");
|
queryParams.put("fileId", "11");
|
||||||
queryParams.put("null", null);
|
queryParams.put("null", null);
|
||||||
|
queryParams.put("empty", "");
|
||||||
|
queryParams.put("taskTimeEnd", "2025-12-28 23:00:00");
|
||||||
String signature = generateSignature(queryParams, Map.of());
|
String signature = generateSignature(queryParams, Map.of());
|
||||||
URI requestUri = buildUri(TARGET_API, queryParams);
|
URI requestUri = buildUri(TARGET_API, queryParams);
|
||||||
String nonce = "171615676c7d4d96b9f55f3d90ad27e0";
|
String nonce = randomNonce();
|
||||||
|
|
||||||
HttpRequest request = HttpRequest.newBuilder(requestUri)
|
HttpRequest request = HttpRequest.newBuilder(requestUri)
|
||||||
.timeout(Duration.ofSeconds(10))
|
.timeout(Duration.ofSeconds(10))
|
||||||
@@ -131,16 +139,14 @@ public final class DatabusApiInvocationExample {
|
|||||||
printResponse(response);
|
printResponse(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void executePostExample() throws Exception {
|
private static void executePostExample(String json) throws Exception {
|
||||||
Map<String, Object> queryParams = new LinkedHashMap<>();
|
Map<String, Object> queryParams = new LinkedHashMap<>();
|
||||||
|
|
||||||
long extraTimestamp = 1761556157185L;
|
long extraTimestamp = 1761556157185L;
|
||||||
// String bodyJson = String.format("""
|
// String bodyJson = String.json("""
|
||||||
// {"operateFlag":"I","__interfaceType__":"R_MY_JY_03","data":{"endAddressName":"1","customerCompanyName":"中铜国贸","endAddressDetail":"测试地址","remark":" ","custSuppType":"1","shipperCompanyName":"中铜国贸","consigneeCorpCode":" ","consignerContactPhone":" 11","importFlag":"10","businessSupplierCode":" ","entrustMainCode":"WT3162251027027","endAddressCode":" ","specifyCarrierCorpCode":"10086689","materDetail":[{"detailStatus":"10","batchNo":"ZLTD2510ZTGM0017001","measureCodeMdm":"CU032110001","packType":" ","quantityPlanDetail":1,"deliveryOrderNo":"ZLTD2510ZTGM0017001","measureCode":"CU032110001","goodsSpecification":" ","measureUnitCode":"PAC","entrustDetailCode":"WT3162251027027001","brand":" ","soNumber":"68ecf0055502d565d22b378a"}],"operateFlag":1,"custSuppName":"上海锦生金属有限公司","startAddressCode":" ","planStartTime":1761556166000,"customerCompanyCode":0,"importMethod":"EXW","startAddressType":"10","shipperCompanyCode":"3162","deliverCondition":"20","businessSupplierName":" ","startAddressDetail":" 111","transType":"30","endAddressType":"20","planEndTime":1761556166000,"specifyCarrierCorpName":null,"custSuppFlag":"0101","businessType":"20","consigneeCorpName":" ","custSuppCode":"10086689","startAddressName":" 111","consignerContactName":" 11"},"datetime":"20251027170929","busiBillCode":"WT3162251027027","system":"BRMS","__requestId__":"f918841c-14fb-49eb-9640-c5d1b3d46bd1"}
|
// {"operateFlag":"I","__interfaceType__":"R_MY_JY_03","data":{"endAddressName":"1","customerCompanyName":"中铜国贸","endAddressDetail":"测试地址","remark":" ","custSuppType":"1","shipperCompanyName":"中铜国贸","consigneeCorpCode":" ","consignerContactPhone":" 11","importFlag":"10","businessSupplierCode":" ","entrustMainCode":"WT3162251027027","endAddressCode":" ","specifyCarrierCorpCode":"10086689","materDetail":[{"detailStatus":"10","batchNo":"ZLTD2510ZTGM0017001","measureCodeMdm":"CU032110001","packType":" ","quantityPlanDetail":1,"deliveryOrderNo":"ZLTD2510ZTGM0017001","measureCode":"CU032110001","goodsSpecification":" ","measureUnitCode":"PAC","entrustDetailCode":"WT3162251027027001","brand":" ","soNumber":"68ecf0055502d565d22b378a"}],"operateFlag":1,"custSuppName":"上海锦生金属有限公司","startAddressCode":" ","planStartTime":1761556166000,"customerCompanyCode":0,"importMethod":"EXW","startAddressType":"10","shipperCompanyCode":"3162","deliverCondition":"20","businessSupplierName":" ","startAddressDetail":" 111","transType":"30","endAddressType":"20","planEndTime":1761556166000,"specifyCarrierCorpName":null,"custSuppFlag":"0101","businessType":"20","consigneeCorpName":" ","custSuppCode":"10086689","startAddressName":" 111","consignerContactName":" 11"},"datetime":"20251027170929","busiBillCode":"WT3162251027027","system":"BRMS","__requestId__":"f918841c-14fb-49eb-9640-c5d1b3d46bd1"}
|
||||||
// """, extraTimestamp);
|
// """, extraTimestamp);
|
||||||
String bodyJson = String.format("""
|
String bodyJson = String.format(json, extraTimestamp);
|
||||||
{}
|
|
||||||
""", extraTimestamp);
|
|
||||||
|
|
||||||
Map<String, Object> bodyParams = parseBodyJson(bodyJson);
|
Map<String, Object> bodyParams = parseBodyJson(bodyJson);
|
||||||
String signature = generateSignature(queryParams, bodyParams);
|
String signature = generateSignature(queryParams, bodyParams);
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package com.zt.plat.module.infra.service.file;
|
|||||||
|
|
||||||
import cn.hutool.core.io.resource.ResourceUtil;
|
import cn.hutool.core.io.resource.ResourceUtil;
|
||||||
import cn.hutool.core.util.IdUtil;
|
import cn.hutool.core.util.IdUtil;
|
||||||
|
import com.google.common.cache.CacheLoader;
|
||||||
|
import com.google.common.cache.LoadingCache;
|
||||||
import com.zt.plat.framework.common.pojo.PageResult;
|
import com.zt.plat.framework.common.pojo.PageResult;
|
||||||
import com.zt.plat.framework.common.util.json.JsonUtils;
|
import com.zt.plat.framework.common.util.json.JsonUtils;
|
||||||
import com.zt.plat.framework.common.util.validation.ValidationUtils;
|
import com.zt.plat.framework.common.util.validation.ValidationUtils;
|
||||||
@@ -14,8 +16,6 @@ import com.zt.plat.module.infra.framework.file.core.client.FileClient;
|
|||||||
import com.zt.plat.module.infra.framework.file.core.client.FileClientConfig;
|
import com.zt.plat.module.infra.framework.file.core.client.FileClientConfig;
|
||||||
import com.zt.plat.module.infra.framework.file.core.client.FileClientFactory;
|
import com.zt.plat.module.infra.framework.file.core.client.FileClientFactory;
|
||||||
import com.zt.plat.module.infra.framework.file.core.enums.FileStorageEnum;
|
import com.zt.plat.module.infra.framework.file.core.enums.FileStorageEnum;
|
||||||
import com.google.common.cache.CacheLoader;
|
|
||||||
import com.google.common.cache.LoadingCache;
|
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import jakarta.validation.Validator;
|
import jakarta.validation.Validator;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
@@ -172,7 +172,7 @@ public class FileConfigServiceImpl implements FileConfigService {
|
|||||||
// 校验存在
|
// 校验存在
|
||||||
validateFileConfigExists(id);
|
validateFileConfigExists(id);
|
||||||
// 上传文件
|
// 上传文件
|
||||||
byte[] content = ResourceUtil.readBytes("file/erweima.jpg");
|
byte[] content = ResourceUtil.readBytes("file/bg1.png");
|
||||||
return getFileClient(id).upload(content, IdUtil.fastSimpleUUID() + ".jpg", "image/jpeg");
|
return getFileClient(id).upload(content, IdUtil.fastSimpleUUID() + ".jpg", "image/jpeg");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 6.4 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 18 KiB |
@@ -41,7 +41,7 @@ public class FtpFileClientTest {
|
|||||||
client.init();
|
client.init();
|
||||||
// 上传文件
|
// 上传文件
|
||||||
String path = IdUtil.fastSimpleUUID() + ".jpg";
|
String path = IdUtil.fastSimpleUUID() + ".jpg";
|
||||||
byte[] content = ResourceUtil.readBytes("file/erweima.jpg");
|
byte[] content = ResourceUtil.readBytes("file/bg1.png");
|
||||||
String fullPath = client.upload(content, path, "image/jpeg");
|
String fullPath = client.upload(content, path, "image/jpeg");
|
||||||
System.out.println("访问地址:" + fullPath);
|
System.out.println("访问地址:" + fullPath);
|
||||||
if (false) {
|
if (false) {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ public class LocalFileClientTest {
|
|||||||
client.init();
|
client.init();
|
||||||
// 上传文件
|
// 上传文件
|
||||||
String path = IdUtil.fastSimpleUUID() + ".jpg";
|
String path = IdUtil.fastSimpleUUID() + ".jpg";
|
||||||
byte[] content = ResourceUtil.readBytes("file/erweima.jpg");
|
byte[] content = ResourceUtil.readBytes("file/bg1.png");
|
||||||
String fullPath = client.upload(content, path, "image/jpeg");
|
String fullPath = client.upload(content, path, "image/jpeg");
|
||||||
System.out.println("访问地址:" + fullPath);
|
System.out.println("访问地址:" + fullPath);
|
||||||
client.delete(path);
|
client.delete(path);
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ public class S3FileClientTest {
|
|||||||
client.init();
|
client.init();
|
||||||
// 上传文件
|
// 上传文件
|
||||||
String path = IdUtil.fastSimpleUUID() + ".jpg";
|
String path = IdUtil.fastSimpleUUID() + ".jpg";
|
||||||
byte[] content = ResourceUtil.readBytes("file/erweima.jpg");
|
byte[] content = ResourceUtil.readBytes("file/bg1.png");
|
||||||
String fullPath = client.upload(content, path, "image/jpeg");
|
String fullPath = client.upload(content, path, "image/jpeg");
|
||||||
System.out.println("访问地址:" + fullPath);
|
System.out.println("访问地址:" + fullPath);
|
||||||
// 读取文件
|
// 读取文件
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ public class SftpFileClientTest {
|
|||||||
client.init();
|
client.init();
|
||||||
// 上传文件
|
// 上传文件
|
||||||
String path = IdUtil.fastSimpleUUID() + ".jpg";
|
String path = IdUtil.fastSimpleUUID() + ".jpg";
|
||||||
byte[] content = ResourceUtil.readBytes("file/erweima.jpg");
|
byte[] content = ResourceUtil.readBytes("file/bg1.png");
|
||||||
String fullPath = client.upload(content, path, "image/jpeg");
|
String fullPath = client.upload(content, path, "image/jpeg");
|
||||||
System.out.println("访问地址:" + fullPath);
|
System.out.println("访问地址:" + fullPath);
|
||||||
if (false) {
|
if (false) {
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ public class FileServiceImplTest extends BaseDbUnitTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testCreateFile_success_01() throws Exception {
|
public void testCreateFile_success_01() throws Exception {
|
||||||
// 准备参数
|
// 准备参数
|
||||||
byte[] content = ResourceUtil.readBytes("file/erweima.jpg");
|
byte[] content = ResourceUtil.readBytes("file/bg1.png");
|
||||||
String name = "单测文件名";
|
String name = "单测文件名";
|
||||||
String directory = randomString();
|
String directory = randomString();
|
||||||
String type = "image/jpeg";
|
String type = "image/jpeg";
|
||||||
@@ -122,7 +122,7 @@ public class FileServiceImplTest extends BaseDbUnitTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testCreateFile_success_02() throws Exception {
|
public void testCreateFile_success_02() throws Exception {
|
||||||
// 准备参数
|
// 准备参数
|
||||||
byte[] content = ResourceUtil.readBytes("file/erweima.jpg");
|
byte[] content = ResourceUtil.readBytes("file/bg1.png");
|
||||||
// mock Master 文件客户端
|
// mock Master 文件客户端
|
||||||
String type = "image/jpeg";
|
String type = "image/jpeg";
|
||||||
FileClient client = mock(FileClient.class);
|
FileClient client = mock(FileClient.class);
|
||||||
@@ -318,7 +318,7 @@ public class FileServiceImplTest extends BaseDbUnitTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testCreateFile_withSameHash() throws Exception {
|
public void testCreateFile_withSameHash() throws Exception {
|
||||||
// 准备参数
|
// 准备参数
|
||||||
byte[] content = ResourceUtil.readBytes("file/erweima.jpg");
|
byte[] content = ResourceUtil.readBytes("file/bg1.png");
|
||||||
String name = "单测文件名";
|
String name = "单测文件名";
|
||||||
String directory = randomString();
|
String directory = randomString();
|
||||||
String type = "image/jpeg";
|
String type = "image/jpeg";
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import com.zt.plat.framework.common.biz.system.permission.PermissionCommonApi;
|
|||||||
import com.zt.plat.framework.common.pojo.CommonResult;
|
import com.zt.plat.framework.common.pojo.CommonResult;
|
||||||
import com.zt.plat.module.system.api.permission.dto.*;
|
import com.zt.plat.module.system.api.permission.dto.*;
|
||||||
import com.zt.plat.module.system.enums.ApiConstants;
|
import com.zt.plat.module.system.enums.ApiConstants;
|
||||||
|
import com.zt.plat.module.system.enums.permission.DataScopeEnum;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
@@ -50,4 +51,9 @@ public interface PermissionApi extends PermissionCommonApi {
|
|||||||
@Parameter(name = "userId", description = "用户编号", example = "1", required = true)
|
@Parameter(name = "userId", description = "用户编号", example = "1", required = true)
|
||||||
CommonResult<Set<Long>> getUserRoleIdListByUserId(@RequestParam("userId") Long userId);
|
CommonResult<Set<Long>> getUserRoleIdListByUserId(@RequestParam("userId") Long userId);
|
||||||
|
|
||||||
|
@GetMapping(PREFIX + "/user-data-permission-level")
|
||||||
|
@Operation(summary = "获得用户的数据权限级别")
|
||||||
|
@Parameter(name = "userId", description = "用户编号", example = "1", required = true)
|
||||||
|
CommonResult<DataScopeEnum> getUserDataPermissionLevel(@RequestParam("userId") Long userId);
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -127,8 +127,8 @@ public interface ErrorCodeConstants {
|
|||||||
ErrorCode SMS_CODE_NOT_FOUND = new ErrorCode(1_002_014_000, "验证码不存在");
|
ErrorCode SMS_CODE_NOT_FOUND = new ErrorCode(1_002_014_000, "验证码不存在");
|
||||||
ErrorCode SMS_CODE_EXPIRED = new ErrorCode(1_002_014_001, "验证码已过期");
|
ErrorCode SMS_CODE_EXPIRED = new ErrorCode(1_002_014_001, "验证码已过期");
|
||||||
ErrorCode SMS_CODE_USED = new ErrorCode(1_002_014_002, "验证码已使用");
|
ErrorCode SMS_CODE_USED = new ErrorCode(1_002_014_002, "验证码已使用");
|
||||||
ErrorCode SMS_CODE_EXCEED_SEND_MAXIMUM_QUANTITY_PER_DAY = new ErrorCode(1_002_014_004, "超过每日短信发送数量");
|
ErrorCode SMS_CODE_EXCEED_SEND_MAXIMUM_QUANTITY_PER_DAY = new ErrorCode(1_002_014_004, "超过每日短信发送数量:{}次");
|
||||||
ErrorCode SMS_CODE_SEND_TOO_FAST = new ErrorCode(1_002_014_005, "短信发送过于频繁");
|
ErrorCode SMS_CODE_SEND_TOO_FAST = new ErrorCode(1_002_014_005, "短信发送过于频繁,请于{}分钟后再试");
|
||||||
|
|
||||||
// ========== 租户信息 1-002-015-000 ==========
|
// ========== 租户信息 1-002-015-000 ==========
|
||||||
ErrorCode TENANT_NOT_EXISTS = new ErrorCode(1_002_015_000, "租户不存在");
|
ErrorCode TENANT_NOT_EXISTS = new ErrorCode(1_002_015_000, "租户不存在");
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
package com.zt.plat.module.system.enums.permission;
|
package com.zt.plat.module.system.enums.permission;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonValue;
|
||||||
import com.zt.plat.framework.common.core.ArrayValuable;
|
import com.zt.plat.framework.common.core.ArrayValuable;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 数据范围枚举类
|
* 数据范围枚举类
|
||||||
@@ -33,6 +35,26 @@ public enum DataScopeEnum implements ArrayValuable<Integer> {
|
|||||||
|
|
||||||
public static final Integer[] ARRAYS = Arrays.stream(values()).map(DataScopeEnum::getScope).toArray(Integer[]::new);
|
public static final Integer[] ARRAYS = Arrays.stream(values()).map(DataScopeEnum::getScope).toArray(Integer[]::new);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Jackson 序列化时输出整数 code,兼容旧客户端
|
||||||
|
*/
|
||||||
|
@JsonValue
|
||||||
|
public Integer getScope() {
|
||||||
|
return scope;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DataScopeEnum findByScope(Integer scope) {
|
||||||
|
if (scope == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (DataScopeEnum value : values()) {
|
||||||
|
if (Objects.equals(value.scope, scope)) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Integer[] array() {
|
public Integer[] array() {
|
||||||
return ARRAYS;
|
return ARRAYS;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import com.zt.plat.framework.common.util.object.BeanUtils;
|
|||||||
import com.zt.plat.module.system.api.permission.dto.*;
|
import com.zt.plat.module.system.api.permission.dto.*;
|
||||||
import com.zt.plat.module.system.controller.admin.permission.vo.permission.PermissionAssignRoleDataScopeReqVO;
|
import com.zt.plat.module.system.controller.admin.permission.vo.permission.PermissionAssignRoleDataScopeReqVO;
|
||||||
import com.zt.plat.module.system.controller.admin.permission.vo.permission.PermissionAssignUserRoleReqVO;
|
import com.zt.plat.module.system.controller.admin.permission.vo.permission.PermissionAssignUserRoleReqVO;
|
||||||
|
import com.zt.plat.module.system.enums.permission.DataScopeEnum;
|
||||||
import com.zt.plat.module.system.service.permission.PermissionService;
|
import com.zt.plat.module.system.service.permission.PermissionService;
|
||||||
import org.springframework.context.annotation.Primary;
|
import org.springframework.context.annotation.Primary;
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
@@ -65,6 +66,11 @@ public class PermissionApiImpl implements PermissionApi {
|
|||||||
return success(permissionService.getUserRoleIdListByUserIdFromCache(userId));
|
return success(permissionService.getUserRoleIdListByUserIdFromCache(userId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CommonResult<DataScopeEnum> getUserDataPermissionLevel(Long userId) {
|
||||||
|
return success(permissionService.getUserDataPermissionLevel(userId));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CommonResult<Boolean> hasAnyPermissions(Long userId, String... permissions) {
|
public CommonResult<Boolean> hasAnyPermissions(Long userId, String... permissions) {
|
||||||
return success(permissionService.hasAnyPermissions(userId, permissions));
|
return success(permissionService.hasAnyPermissions(userId, permissions));
|
||||||
|
|||||||
@@ -76,4 +76,7 @@ public class DeptSaveReqVO {
|
|||||||
@Schema(description = "部门来源类型", example = "1")
|
@Schema(description = "部门来源类型", example = "1")
|
||||||
private Integer deptSource;
|
private Integer deptSource;
|
||||||
|
|
||||||
|
@Schema(description = "内部使用:延迟生成部门编码", hidden = true)
|
||||||
|
private Boolean delayCodeGeneration;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,8 +94,9 @@ public class UserController {
|
|||||||
|
|
||||||
@GetMapping({"/list-all-simple", "/simple-list"})
|
@GetMapping({"/list-all-simple", "/simple-list"})
|
||||||
@Operation(summary = "获取用户精简信息列表", description = "只包含被开启的用户,主要用于前端的下拉选项")
|
@Operation(summary = "获取用户精简信息列表", description = "只包含被开启的用户,主要用于前端的下拉选项")
|
||||||
public CommonResult<List<UserSimpleRespVO>> getSimpleUserList() {
|
public CommonResult<List<UserSimpleRespVO>> getSimpleUserList(
|
||||||
List<AdminUserDO> list = userService.getUserListByStatus(CommonStatusEnum.ENABLE.getStatus(), SIMPLE_LIST_LIMIT);
|
@RequestParam(value = "keyword", required = false) String keyword) {
|
||||||
|
List<AdminUserDO> list = userService.getUserListByStatus(CommonStatusEnum.ENABLE.getStatus(), SIMPLE_LIST_LIMIT, keyword);
|
||||||
return success(UserConvert.INSTANCE.convertSimpleList(list));
|
return success(UserConvert.INSTANCE.convertSimpleList(list));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,9 @@ public class UserPageReqVO extends PageParam {
|
|||||||
@Schema(description = "用户账号,模糊匹配", example = "zt")
|
@Schema(description = "用户账号,模糊匹配", example = "zt")
|
||||||
private String username;
|
private String username;
|
||||||
|
|
||||||
|
@Schema(description = "用户昵称,模糊匹配", example = "张三")
|
||||||
|
private String nickname;
|
||||||
|
|
||||||
@Schema(description = "工号,模糊匹配", example = "A00123")
|
@Schema(description = "工号,模糊匹配", example = "A00123")
|
||||||
private String workcode;
|
private String workcode;
|
||||||
|
|
||||||
|
|||||||
@@ -114,12 +114,15 @@ public interface DeptMapper extends BaseMapperX<DeptDO> {
|
|||||||
* @param parentId 父部门ID
|
* @param parentId 父部门ID
|
||||||
* @return 编码最大的子部门
|
* @return 编码最大的子部门
|
||||||
*/
|
*/
|
||||||
default DeptDO selectLastChildByCode(Long parentId) {
|
default DeptDO selectLastChildByCode(Long parentId, String prefix) {
|
||||||
return selectOne(new LambdaQueryWrapper<DeptDO>()
|
LambdaQueryWrapper<DeptDO> wrapper = new LambdaQueryWrapper<DeptDO>()
|
||||||
.eq(DeptDO::getParentId, parentId)
|
.eq(DeptDO::getParentId, parentId)
|
||||||
.isNotNull(DeptDO::getCode)
|
.isNotNull(DeptDO::getCode);
|
||||||
.orderByDesc(DeptDO::getCode)
|
if (StrUtil.isNotBlank(prefix)) {
|
||||||
.last("LIMIT 1"));
|
wrapper.likeRight(DeptDO::getCode, prefix);
|
||||||
|
}
|
||||||
|
wrapper.orderByDesc(DeptDO::getCode).last("LIMIT 1");
|
||||||
|
return selectOne(wrapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ public interface AdminUserMapper extends BaseMapperX<AdminUserDO> {
|
|||||||
MPJLambdaWrapperX<AdminUserDO> query = new MPJLambdaWrapperX<>();
|
MPJLambdaWrapperX<AdminUserDO> query = new MPJLambdaWrapperX<>();
|
||||||
query.leftJoin(UserDeptDO.class, UserDeptDO::getUserId, AdminUserDO::getId);
|
query.leftJoin(UserDeptDO.class, UserDeptDO::getUserId, AdminUserDO::getId);
|
||||||
query.likeIfPresent(AdminUserDO::getUsername, reqVO.getUsername());
|
query.likeIfPresent(AdminUserDO::getUsername, reqVO.getUsername());
|
||||||
|
query.likeIfPresent(AdminUserDO::getNickname, reqVO.getNickname());
|
||||||
query.likeIfPresent(AdminUserDO::getWorkcode, reqVO.getWorkcode());
|
query.likeIfPresent(AdminUserDO::getWorkcode, reqVO.getWorkcode());
|
||||||
query.likeIfPresent(AdminUserDO::getMobile, reqVO.getMobile());
|
query.likeIfPresent(AdminUserDO::getMobile, reqVO.getMobile());
|
||||||
query.eqIfPresent(AdminUserDO::getStatus, reqVO.getStatus());
|
query.eqIfPresent(AdminUserDO::getStatus, reqVO.getStatus());
|
||||||
@@ -70,9 +71,16 @@ public interface AdminUserMapper extends BaseMapperX<AdminUserDO> {
|
|||||||
return selectList(new LambdaQueryWrapperX<AdminUserDO>().like(AdminUserDO::getNickname, nickname));
|
return selectList(new LambdaQueryWrapperX<AdminUserDO>().like(AdminUserDO::getNickname, nickname));
|
||||||
}
|
}
|
||||||
|
|
||||||
default List<AdminUserDO> selectListByStatus(Integer status, Integer limit) {
|
default List<AdminUserDO> selectListByStatus(Integer status, Integer limit, String keyword) {
|
||||||
LambdaQueryWrapperX<AdminUserDO> query = new LambdaQueryWrapperX<AdminUserDO>()
|
LambdaQueryWrapperX<AdminUserDO> query = new LambdaQueryWrapperX<AdminUserDO>()
|
||||||
.eq(AdminUserDO::getStatus, status);
|
.eq(AdminUserDO::getStatus, status);
|
||||||
|
if (StrUtil.isNotBlank(keyword)) {
|
||||||
|
String trimmed = keyword.trim();
|
||||||
|
query.and(w -> w.like(AdminUserDO::getNickname, trimmed)
|
||||||
|
.or().like(AdminUserDO::getUsername, trimmed)
|
||||||
|
.or().like(AdminUserDO::getMobile, trimmed)
|
||||||
|
.or().like(AdminUserDO::getWorkcode, trimmed));
|
||||||
|
}
|
||||||
if (limit != null && limit > 0) {
|
if (limit != null && limit > 0) {
|
||||||
query.last("LIMIT " + limit);
|
query.last("LIMIT " + limit);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollUtil;
|
|||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
import com.zt.plat.framework.common.biz.system.permission.dto.DeptDataPermissionRespDTO;
|
||||||
import com.zt.plat.framework.common.enums.CommonStatusEnum;
|
import com.zt.plat.framework.common.enums.CommonStatusEnum;
|
||||||
import com.zt.plat.framework.common.pojo.CompanyDeptInfo;
|
import com.zt.plat.framework.common.pojo.CompanyDeptInfo;
|
||||||
import com.zt.plat.framework.common.util.object.BeanUtils;
|
import com.zt.plat.framework.common.util.object.BeanUtils;
|
||||||
@@ -11,18 +12,14 @@ import com.zt.plat.framework.datapermission.core.annotation.DataPermission;
|
|||||||
import com.zt.plat.framework.tenant.core.aop.TenantIgnore;
|
import com.zt.plat.framework.tenant.core.aop.TenantIgnore;
|
||||||
import com.zt.plat.module.system.controller.admin.dept.vo.dept.DeptListReqVO;
|
import com.zt.plat.module.system.controller.admin.dept.vo.dept.DeptListReqVO;
|
||||||
import com.zt.plat.module.system.controller.admin.dept.vo.dept.DeptSaveReqVO;
|
import com.zt.plat.module.system.controller.admin.dept.vo.dept.DeptSaveReqVO;
|
||||||
import com.zt.plat.module.system.controller.admin.dict.vo.data.DictDataSaveReqVO;
|
|
||||||
import com.zt.plat.module.system.controller.admin.dict.vo.type.DictTypeSaveReqVO;
|
|
||||||
import com.zt.plat.module.system.dal.dataobject.dept.DeptDO;
|
import com.zt.plat.module.system.dal.dataobject.dept.DeptDO;
|
||||||
import com.zt.plat.module.system.dal.dataobject.dict.DictTypeDO;
|
|
||||||
import com.zt.plat.module.system.dal.dataobject.userdept.UserDeptDO;
|
import com.zt.plat.module.system.dal.dataobject.userdept.UserDeptDO;
|
||||||
import com.zt.plat.module.system.dal.mysql.dept.DeptMapper;
|
import com.zt.plat.module.system.dal.mysql.dept.DeptMapper;
|
||||||
import com.zt.plat.module.system.dal.mysql.userdept.UserDeptMapper;
|
import com.zt.plat.module.system.dal.mysql.userdept.UserDeptMapper;
|
||||||
|
import com.zt.plat.module.system.service.dept.DeptExternalCodeService;
|
||||||
import com.zt.plat.module.system.dal.redis.RedisKeyConstants;
|
import com.zt.plat.module.system.dal.redis.RedisKeyConstants;
|
||||||
import com.zt.plat.module.system.enums.dept.DeptSourceEnum;
|
import com.zt.plat.module.system.enums.dept.DeptSourceEnum;
|
||||||
import com.zt.plat.module.system.enums.DictTypeConstants;
|
import com.zt.plat.module.system.service.permission.PermissionService;
|
||||||
import com.zt.plat.module.system.service.dict.DictDataService;
|
|
||||||
import com.zt.plat.module.system.service.dict.DictTypeService;
|
|
||||||
import org.apache.seata.spring.annotation.GlobalTransactional;
|
import org.apache.seata.spring.annotation.GlobalTransactional;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@@ -57,17 +54,17 @@ public class DeptServiceImpl implements DeptService {
|
|||||||
@Resource
|
@Resource
|
||||||
private UserDeptMapper userDeptMapper;
|
private UserDeptMapper userDeptMapper;
|
||||||
@Resource
|
@Resource
|
||||||
|
private PermissionService permissionService;
|
||||||
|
@Resource
|
||||||
private com.zt.plat.module.system.mq.producer.databus.DatabusChangeProducer databusChangeProducer;
|
private com.zt.plat.module.system.mq.producer.databus.DatabusChangeProducer databusChangeProducer;
|
||||||
@Resource
|
@Resource
|
||||||
private DeptExternalCodeService deptExternalCodeService;
|
private DeptExternalCodeService deptExternalCodeService;
|
||||||
@Resource
|
|
||||||
private DictTypeService dictTypeService;
|
|
||||||
@Resource
|
|
||||||
private DictDataService dictDataService;
|
|
||||||
|
|
||||||
private static final String ROOT_CODE_PREFIX = "ZT";
|
private static final String ROOT_CODE_PREFIX = "ZT";
|
||||||
|
private static final String EXTERNAL_CODE_PREFIX = "CU";
|
||||||
private static final int CODE_SEGMENT_LENGTH = 3;
|
private static final int CODE_SEGMENT_LENGTH = 3;
|
||||||
private static final int MAX_SEQUENCE = 999;
|
private static final int MAX_SEQUENCE = 999;
|
||||||
|
private static final int BATCH_SIZE = 1000;
|
||||||
private static final Comparator<DeptDO> DEPT_COMPARATOR = Comparator
|
private static final Comparator<DeptDO> DEPT_COMPARATOR = Comparator
|
||||||
.comparing(DeptDO::getSort, Comparator.nullsLast(Comparator.naturalOrder()))
|
.comparing(DeptDO::getSort, Comparator.nullsLast(Comparator.naturalOrder()))
|
||||||
.thenComparing(DeptDO::getId, Comparator.nullsLast(Comparator.naturalOrder()));
|
.thenComparing(DeptDO::getId, Comparator.nullsLast(Comparator.naturalOrder()));
|
||||||
@@ -82,26 +79,33 @@ public class DeptServiceImpl implements DeptService {
|
|||||||
createReqVO.setParentId(normalizeParentId(createReqVO.getParentId()));
|
createReqVO.setParentId(normalizeParentId(createReqVO.getParentId()));
|
||||||
// 创建时默认有效
|
// 创建时默认有效
|
||||||
createReqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
createReqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||||
|
// 默认部门来源:未指定时视为外部部门
|
||||||
|
if (createReqVO.getDeptSource() == null) {
|
||||||
|
createReqVO.setDeptSource(DeptSourceEnum.EXTERNAL.getSource());
|
||||||
|
}
|
||||||
// 校验父部门的有效性
|
// 校验父部门的有效性
|
||||||
validateParentDept(null, createReqVO.getParentId());
|
validateParentDept(null, createReqVO.getParentId());
|
||||||
// 校验部门名的唯一性
|
// 校验部门名的唯一性
|
||||||
validateDeptNameUnique(null, createReqVO.getParentId(), createReqVO.getName());
|
validateDeptNameUnique(null, createReqVO.getParentId(), createReqVO.getName());
|
||||||
// 生成并校验部门编码
|
// 生成并校验部门编码(所有来源统一走生成逻辑,iWork 不再豁免)
|
||||||
Long effectiveParentId = normalizeParentId(createReqVO.getParentId());
|
if (Boolean.TRUE.equals(createReqVO.getDelayCodeGeneration())) {
|
||||||
String resolvedCode = generateDeptCode(effectiveParentId);
|
createReqVO.setCode(null);
|
||||||
validateDeptCodeUnique(null, resolvedCode);
|
} else {
|
||||||
createReqVO.setCode(resolvedCode);
|
String resolvedCode = generateDeptCode(createReqVO.getParentId(), createReqVO.getDeptSource());
|
||||||
|
validateDeptCodeUnique(null, resolvedCode);
|
||||||
|
createReqVO.setCode(resolvedCode);
|
||||||
|
}
|
||||||
|
|
||||||
// 插入部门
|
// 插入部门
|
||||||
DeptDO dept = BeanUtils.toBean(createReqVO, DeptDO.class);
|
DeptDO dept = BeanUtils.toBean(createReqVO, DeptDO.class);
|
||||||
// 设置部门来源:如果未指定,默认为外部部门
|
// 设置部门来源(前置已默认化,此处兜底)
|
||||||
if (dept.getDeptSource() == null) {
|
if (dept.getDeptSource() == null) {
|
||||||
dept.setDeptSource(DeptSourceEnum.EXTERNAL.getSource());
|
dept.setDeptSource(DeptSourceEnum.EXTERNAL.getSource());
|
||||||
}
|
}
|
||||||
deptMapper.insert(dept);
|
deptMapper.insert(dept);
|
||||||
|
|
||||||
// 维护外部系统编码映射(若有传入)
|
// 外部编码映射
|
||||||
upsertExternalCodeMapping(createReqVO, dept.getId());
|
upsertExternalMappingIfPresent(dept.getId(), createReqVO);
|
||||||
|
|
||||||
// 发布部门创建事件
|
// 发布部门创建事件
|
||||||
databusChangeProducer.sendDeptCreatedMessage(dept);
|
databusChangeProducer.sendDeptCreatedMessage(dept);
|
||||||
@@ -109,6 +113,15 @@ public class DeptServiceImpl implements DeptService {
|
|||||||
return dept.getId();
|
return dept.getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void upsertExternalMappingIfPresent(Long deptId, DeptSaveReqVO reqVO) {
|
||||||
|
String systemCode = StrUtil.trimToNull(reqVO.getExternalSystemCode());
|
||||||
|
String externalCode = StrUtil.trimToNull(reqVO.getExternalDeptCode());
|
||||||
|
if (StrUtil.hasEmpty(systemCode, externalCode) || deptId == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String externalName = StrUtil.trimToNull(reqVO.getExternalDeptName());
|
||||||
|
deptExternalCodeService.saveOrUpdateDeptExternalCode(deptId, systemCode, externalCode, externalName, reqVO.getStatus());
|
||||||
|
}
|
||||||
@Override
|
@Override
|
||||||
@CacheEvict(cacheNames = RedisKeyConstants.DEPT_CHILDREN_ID_LIST,
|
@CacheEvict(cacheNames = RedisKeyConstants.DEPT_CHILDREN_ID_LIST,
|
||||||
allEntries = true) // allEntries 清空所有缓存,因为操作一个部门,涉及到多个缓存
|
allEntries = true) // allEntries 清空所有缓存,因为操作一个部门,涉及到多个缓存
|
||||||
@@ -122,34 +135,36 @@ public class DeptServiceImpl implements DeptService {
|
|||||||
validateParentDept(updateReqVO.getId(), updateReqVO.getParentId());
|
validateParentDept(updateReqVO.getId(), updateReqVO.getParentId());
|
||||||
// 校验部门名的唯一性
|
// 校验部门名的唯一性
|
||||||
validateDeptNameUnique(updateReqVO.getId(), updateReqVO.getParentId(), updateReqVO.getName());
|
validateDeptNameUnique(updateReqVO.getId(), updateReqVO.getParentId(), updateReqVO.getName());
|
||||||
Long newParentId = normalizeParentId(updateReqVO.getParentId());
|
Integer source = ObjectUtil.defaultIfNull(updateReqVO.getDeptSource(), originalDept.getDeptSource());
|
||||||
Long oldParentId = normalizeParentId(originalDept.getParentId());
|
if (source == null) {
|
||||||
boolean parentChanged = !Objects.equals(newParentId, oldParentId);
|
source = DeptSourceEnum.EXTERNAL.getSource();
|
||||||
String existingCode = originalDept.getCode();
|
}
|
||||||
boolean needRegenerateCode = StrUtil.isBlank(existingCode);
|
String existingCode = originalDept.getCode();
|
||||||
String resolvedCode = existingCode;
|
if (StrUtil.isBlank(existingCode)) {
|
||||||
if (needRegenerateCode) {
|
if (Boolean.TRUE.equals(updateReqVO.getDelayCodeGeneration())) {
|
||||||
resolvedCode = generateDeptCode(newParentId);
|
updateReqVO.setCode(null);
|
||||||
validateDeptCodeUnique(updateReqVO.getId(), resolvedCode);
|
} else {
|
||||||
|
String newCode = generateDeptCode(updateReqVO.getParentId(), source);
|
||||||
|
validateDeptCodeUnique(updateReqVO.getId(), newCode);
|
||||||
|
updateReqVO.setCode(newCode);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
updateReqVO.setCode(existingCode);
|
||||||
}
|
}
|
||||||
updateReqVO.setCode(resolvedCode);
|
|
||||||
|
|
||||||
// 更新部门
|
// 更新部门
|
||||||
DeptDO updateObj = BeanUtils.toBean(updateReqVO, DeptDO.class);
|
DeptDO updateObj = BeanUtils.toBean(updateReqVO, DeptDO.class);
|
||||||
deptMapper.updateById(updateObj);
|
deptMapper.updateById(updateObj);
|
||||||
|
|
||||||
|
// 外部编码映射
|
||||||
|
upsertExternalMappingIfPresent(updateObj.getId(), updateReqVO);
|
||||||
|
|
||||||
// 发布部门更新事件(重新查询获取完整数据)
|
// 发布部门更新事件(重新查询获取完整数据)
|
||||||
DeptDO updatedDept = deptMapper.selectById(updateObj.getId());
|
DeptDO updatedDept = deptMapper.selectById(updateObj.getId());
|
||||||
if (updatedDept != null) {
|
if (updatedDept != null) {
|
||||||
databusChangeProducer.sendDeptUpdatedMessage(updatedDept);
|
databusChangeProducer.sendDeptUpdatedMessage(updatedDept);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (needRegenerateCode) {
|
|
||||||
refreshChildCodesRecursively(updateObj.getId(), updateReqVO.getCode());
|
|
||||||
}
|
|
||||||
|
|
||||||
// 维护外部系统编码映射(若有传入)
|
|
||||||
upsertExternalCodeMapping(updateReqVO, updateReqVO.getId());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -167,7 +182,7 @@ public class DeptServiceImpl implements DeptService {
|
|||||||
DeptDO dept = deptMapper.selectById(id);
|
DeptDO dept = deptMapper.selectById(id);
|
||||||
Long tenantId = (dept != null) ? dept.getTenantId() : null;
|
Long tenantId = (dept != null) ? dept.getTenantId() : null;
|
||||||
|
|
||||||
// 级联删除外部编码映射并清理缓存
|
// 级联删除外部编码映射
|
||||||
deptExternalCodeService.deleteDeptExternalCodesByDeptId(id);
|
deptExternalCodeService.deleteDeptExternalCodesByDeptId(id);
|
||||||
|
|
||||||
// 删除部门
|
// 删除部门
|
||||||
@@ -268,26 +283,16 @@ public class DeptServiceImpl implements DeptService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String generateDeptCode(Long parentId) {
|
private String generateDeptCode(Long parentId, Integer deptSource) {
|
||||||
Long effectiveParentId = normalizeParentId(parentId);
|
Long effectiveParentId = normalizeParentId(parentId);
|
||||||
Long codeParentId = effectiveParentId;
|
String prefix = resolveCodePrefix(effectiveParentId, deptSource);
|
||||||
String prefix = ROOT_CODE_PREFIX;
|
int nextSequence = determineNextSequence(effectiveParentId, prefix);
|
||||||
if (!DeptDO.PARENT_ID_ROOT.equals(effectiveParentId)) {
|
|
||||||
DeptDO parentDept = deptMapper.selectById(effectiveParentId);
|
|
||||||
if (parentDept == null || StrUtil.isBlank(parentDept.getCode())) {
|
|
||||||
codeParentId = DeptDO.PARENT_ID_ROOT;
|
|
||||||
} else {
|
|
||||||
prefix = parentDept.getCode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int nextSequence = determineNextSequence(codeParentId, prefix);
|
|
||||||
assertSequenceRange(nextSequence);
|
assertSequenceRange(nextSequence);
|
||||||
return prefix + formatSequence(nextSequence);
|
return prefix + formatSequence(nextSequence);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int determineNextSequence(Long parentId, String prefix) {
|
private int determineNextSequence(Long parentId, String prefix) {
|
||||||
DeptDO lastChild = deptMapper.selectLastChildByCode(parentId);
|
DeptDO lastChild = deptMapper.selectLastChildByCode(parentId, prefix);
|
||||||
Integer sequence = parseSequence(lastChild != null ? lastChild.getCode() : null, prefix);
|
Integer sequence = parseSequence(lastChild != null ? lastChild.getCode() : null, prefix);
|
||||||
if (sequence != null) {
|
if (sequence != null) {
|
||||||
return sequence + 1;
|
return sequence + 1;
|
||||||
@@ -365,12 +370,36 @@ public class DeptServiceImpl implements DeptService {
|
|||||||
candidate = candidate.trim();
|
candidate = candidate.trim();
|
||||||
}
|
}
|
||||||
if (StrUtil.isBlank(candidate)) {
|
if (StrUtil.isBlank(candidate)) {
|
||||||
candidate = generateDeptCode(DeptDO.PARENT_ID_ROOT);
|
candidate = generateDeptCode(DeptDO.PARENT_ID_ROOT, DeptSourceEnum.EXTERNAL.getSource());
|
||||||
}
|
}
|
||||||
validateDeptCodeUnique(currentDeptId, candidate);
|
validateDeptCodeUnique(currentDeptId, candidate);
|
||||||
return candidate;
|
return candidate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String resolveCodePrefix(Long parentId, Integer deptSource) {
|
||||||
|
boolean isExternal = Objects.equals(deptSource, DeptSourceEnum.EXTERNAL.getSource());
|
||||||
|
if (DeptDO.PARENT_ID_ROOT.equals(parentId)) {
|
||||||
|
return isExternal ? EXTERNAL_CODE_PREFIX : ROOT_CODE_PREFIX;
|
||||||
|
}
|
||||||
|
|
||||||
|
DeptDO parentDept = deptMapper.selectById(parentId);
|
||||||
|
if (parentDept == null || StrUtil.isBlank(parentDept.getCode())) {
|
||||||
|
return isExternal ? EXTERNAL_CODE_PREFIX : ROOT_CODE_PREFIX;
|
||||||
|
}
|
||||||
|
|
||||||
|
String parentCode = parentDept.getCode();
|
||||||
|
if (isExternal) {
|
||||||
|
if (parentCode.startsWith(EXTERNAL_CODE_PREFIX)) {
|
||||||
|
return parentCode;
|
||||||
|
}
|
||||||
|
if (parentCode.startsWith(ROOT_CODE_PREFIX)) {
|
||||||
|
return EXTERNAL_CODE_PREFIX + parentCode.substring(ROOT_CODE_PREFIX.length());
|
||||||
|
}
|
||||||
|
return EXTERNAL_CODE_PREFIX;
|
||||||
|
}
|
||||||
|
return parentCode;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DeptDO getDept(Long id) {
|
public DeptDO getDept(Long id) {
|
||||||
return deptMapper.selectById(id);
|
return deptMapper.selectById(id);
|
||||||
@@ -558,37 +587,59 @@ public class DeptServiceImpl implements DeptService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<DeptDO> getTopLevelDeptList() {
|
public List<DeptDO> getTopLevelDeptList() {
|
||||||
// 获取当前用户所属的部门列表
|
Long loginUserId = getLoginUserId();
|
||||||
Set<Long> deptIds = userDeptMapper.selectValidListByUserIds(singleton(getLoginUserId()))
|
|
||||||
.stream()
|
// 当前用户所属部门
|
||||||
.map(UserDeptDO::getDeptId)
|
Set<Long> userDeptIds = Optional.ofNullable(userDeptMapper.selectValidListByUserIds(singleton(loginUserId)))
|
||||||
.collect(Collectors.toSet());
|
.orElseGet(Collections::emptyList)
|
||||||
|
.stream()
|
||||||
|
.map(UserDeptDO::getDeptId)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
|
// 数据权限部门
|
||||||
|
DeptDataPermissionRespDTO dataPerm = permissionService.getDeptDataPermission(loginUserId);
|
||||||
|
Set<Long> permDeptIds = Optional.ofNullable(dataPerm)
|
||||||
|
.map(DeptDataPermissionRespDTO::getDeptIds)
|
||||||
|
.orElse(Collections.emptySet());
|
||||||
|
|
||||||
|
// all=true 直接返回根级启用部门
|
||||||
|
if (dataPerm != null && Boolean.TRUE.equals(dataPerm.getAll())) {
|
||||||
|
List<DeptDO> roots = deptMapper.selectListByParentId(DeptDO.PARENT_ID_ROOT, CommonStatusEnum.ENABLE.getStatus());
|
||||||
|
roots.sort(DEPT_COMPARATOR);
|
||||||
|
return roots;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 合并两类部门 ID,仅在并集为空时返回空
|
||||||
|
Set<Long> deptIds = new HashSet<>();
|
||||||
|
deptIds.addAll(userDeptIds);
|
||||||
|
deptIds.addAll(Optional.ofNullable(permDeptIds).orElse(Collections.emptySet()));
|
||||||
|
|
||||||
if (CollUtil.isEmpty(deptIds)) {
|
if (CollUtil.isEmpty(deptIds)) {
|
||||||
// 如果用户没有关联任何部门,返回空列表
|
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取用户所属部门的最顶层祖先部门
|
// 缓存已加载的部门,避免重复 IO
|
||||||
Set<Long> topLevelDeptIds = new HashSet<>();
|
Map<Long, DeptDO> deptCache = new HashMap<>();
|
||||||
for (Long deptId : deptIds) {
|
|
||||||
DeptDO dept = getDept(deptId);
|
// 批量解析最顶层祖先(到 ROOT 或上级禁用即停),减少循环 IO
|
||||||
if (dept != null && CommonStatusEnum.ENABLE.getStatus().equals(dept.getStatus())) {
|
Map<Long, Long> topLevelMap = findTopLevelAncestorIdsBatch(deptIds, deptCache);
|
||||||
// 找到该部门的最顶层祖先
|
|
||||||
DeptDO topLevelDept = findTopLevelAncestor(dept);
|
// 汇总顶层部门 ID 并取实体(使用缓存避免再查)
|
||||||
if (topLevelDept != null) {
|
Set<Long> topLevelDeptIds = topLevelMap.values().stream()
|
||||||
topLevelDeptIds.add(topLevelDept.getId());
|
.filter(Objects::nonNull)
|
||||||
}
|
.collect(Collectors.toSet());
|
||||||
}
|
|
||||||
}
|
List<DeptDO> topLevelDepts = topLevelDeptIds.stream()
|
||||||
|
.map(id -> deptCache.computeIfAbsent(id, this::getDept))
|
||||||
// 根据顶层部门ID获取部门详情
|
.filter(Objects::nonNull)
|
||||||
return topLevelDeptIds.stream()
|
.filter(dept -> CommonStatusEnum.ENABLE.getStatus().equals(dept.getStatus()))
|
||||||
.map(this::getDept)
|
.distinct()
|
||||||
.filter(Objects::nonNull)
|
.collect(Collectors.toList());
|
||||||
.filter(dept -> CommonStatusEnum.ENABLE.getStatus().equals(dept.getStatus()))
|
|
||||||
.distinct()
|
// 按 sort(nullsLast)再按 id 排序
|
||||||
.collect(Collectors.toList());
|
topLevelDepts.sort(DEPT_COMPARATOR);
|
||||||
|
return topLevelDepts;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -741,64 +792,134 @@ public class DeptServiceImpl implements DeptService {
|
|||||||
return dept;
|
return dept;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void upsertExternalCodeMapping(DeptSaveReqVO reqVO, Long deptId) {
|
/**
|
||||||
if (reqVO == null || deptId == null) {
|
* 批量查找部门的最顶层祖先(到 ROOT 或遇到禁用/缺失的父部门即停止)
|
||||||
return;
|
* 使用 1000 条分片批量查询,减少循环 IO
|
||||||
|
*
|
||||||
|
* @param deptIds 待解析的部门 ID 集合
|
||||||
|
* @param deptCache 部门缓存(可复用外部缓存)
|
||||||
|
* @return 原始部门 ID -> 顶层祖先部门 ID 映射(若未找到则为 null)
|
||||||
|
*/
|
||||||
|
private Map<Long, Long> findTopLevelAncestorIdsBatch(Set<Long> deptIds, Map<Long, DeptDO> deptCache) {
|
||||||
|
Map<Long, Long> result = new HashMap<>();
|
||||||
|
if (CollUtil.isEmpty(deptIds)) {
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
String systemCode = StrUtil.trimToNull(reqVO.getExternalSystemCode());
|
|
||||||
String externalCode = StrUtil.trimToNull(reqVO.getExternalDeptCode());
|
// 当前指针:原始部门 -> 当前向上追溯的部门 ID
|
||||||
if (StrUtil.isBlank(systemCode) || StrUtil.isBlank(externalCode)) {
|
Map<Long, Long> cursorMap = new HashMap<>();
|
||||||
return;
|
for (Long id : deptIds) {
|
||||||
|
cursorMap.put(id, id);
|
||||||
}
|
}
|
||||||
// 缺失的外部系统字典类型或数据会自动补齐
|
|
||||||
ensureExternalSystemDict(systemCode);
|
// 预先加载首批部门
|
||||||
deptExternalCodeService.saveOrUpdateDeptExternalCode(
|
loadDeptBatch(cursorMap.values(), deptCache);
|
||||||
deptId,
|
|
||||||
systemCode,
|
int safety = 0;
|
||||||
externalCode,
|
while (!cursorMap.isEmpty() && safety++ < Short.MAX_VALUE) {
|
||||||
reqVO.getExternalDeptName(),
|
// 收集本轮需要加载的父部门 ID(避免重复加载)
|
||||||
CommonStatusEnum.ENABLE.getStatus());
|
Set<Long> parentIdsToLoad = new HashSet<>();
|
||||||
|
for (Long currentId : cursorMap.values()) {
|
||||||
|
DeptDO current = deptCache.get(currentId);
|
||||||
|
if (current == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Long parentId = current.getParentId();
|
||||||
|
if (parentId != null && !DeptDO.PARENT_ID_ROOT.equals(parentId) && !deptCache.containsKey(parentId)) {
|
||||||
|
parentIdsToLoad.add(parentId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loadDeptBatch(parentIdsToLoad, deptCache);
|
||||||
|
|
||||||
|
// 遍历当前指针,决定是否上卷或结束
|
||||||
|
Iterator<Map.Entry<Long, Long>> iterator = cursorMap.entrySet().iterator();
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
Map.Entry<Long, Long> entry = iterator.next();
|
||||||
|
Long originalId = entry.getKey();
|
||||||
|
Long currentId = entry.getValue();
|
||||||
|
DeptDO current = deptCache.get(currentId);
|
||||||
|
|
||||||
|
if (current == null) {
|
||||||
|
result.put(originalId, null);
|
||||||
|
iterator.remove();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Long parentId = current.getParentId();
|
||||||
|
if (parentId == null || DeptDO.PARENT_ID_ROOT.equals(parentId)) {
|
||||||
|
// 已到达 ROOT(顶层)
|
||||||
|
result.put(originalId, current.getId());
|
||||||
|
iterator.remove();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
DeptDO parent = deptCache.get(parentId);
|
||||||
|
if (parent == null || !CommonStatusEnum.ENABLE.getStatus().equals(parent.getStatus())) {
|
||||||
|
// 父部门缺失或禁用,则当前部门视为顶层
|
||||||
|
result.put(originalId, current.getId());
|
||||||
|
iterator.remove();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 向上继续追溯
|
||||||
|
entry.setValue(parentId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 确保外部系统字典存在(含字典类型与对应值),若缺失则自动创建
|
* 将给定的部门 ID 集合按批次加载到缓存
|
||||||
*/
|
*/
|
||||||
private void ensureExternalSystemDict(String systemCode) {
|
private void loadDeptBatch(Collection<Long> ids, Map<Long, DeptDO> deptCache) {
|
||||||
String normalizedCode = StrUtil.trimToNull(systemCode);
|
if (CollUtil.isEmpty(ids)) {
|
||||||
if (normalizedCode == null) {
|
return;
|
||||||
|
}
|
||||||
|
List<Long> toLoad = ids.stream()
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.filter(id -> !deptCache.containsKey(id))
|
||||||
|
.distinct()
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
if (CollUtil.isEmpty(toLoad)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
|
||||||
DictTypeDO dictType = dictTypeService.getDictType(DictTypeConstants.DEPT_EXTERNAL_SYSTEM);
|
|
||||||
if (dictType == null) {
|
|
||||||
DictTypeSaveReqVO typeReq = new DictTypeSaveReqVO();
|
|
||||||
typeReq.setName("部门外部系统标识");
|
|
||||||
typeReq.setType(DictTypeConstants.DEPT_EXTERNAL_SYSTEM);
|
|
||||||
typeReq.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
|
||||||
typeReq.setRemark("外部组织同步自动创建");
|
|
||||||
dictTypeService.createDictType(typeReq);
|
|
||||||
} else if (!CommonStatusEnum.ENABLE.getStatus().equals(dictType.getStatus())) {
|
|
||||||
DictTypeSaveReqVO updateReq = new DictTypeSaveReqVO();
|
|
||||||
updateReq.setId(dictType.getId());
|
|
||||||
updateReq.setName(dictType.getName());
|
|
||||||
updateReq.setType(dictType.getType());
|
|
||||||
updateReq.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
|
||||||
updateReq.setRemark(dictType.getRemark());
|
|
||||||
dictTypeService.updateDictType(updateReq);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dictDataService.getDictData(DictTypeConstants.DEPT_EXTERNAL_SYSTEM, normalizedCode) == null) {
|
for (int i = 0; i < toLoad.size(); i += BATCH_SIZE) {
|
||||||
DictDataSaveReqVO dataReq = new DictDataSaveReqVO();
|
int end = Math.min(i + BATCH_SIZE, toLoad.size());
|
||||||
dataReq.setDictType(DictTypeConstants.DEPT_EXTERNAL_SYSTEM);
|
List<Long> batch = toLoad.subList(i, end);
|
||||||
dataReq.setLabel(normalizedCode);
|
List<DeptDO> depts = getDeptList(batch);
|
||||||
dataReq.setValue(normalizedCode);
|
if (CollUtil.isEmpty(depts)) {
|
||||||
dataReq.setSort(0);
|
continue;
|
||||||
dataReq.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
}
|
||||||
dataReq.setRemark("外部组织同步自动创建");
|
for (DeptDO dept : depts) {
|
||||||
dictDataService.createDictData(dataReq);
|
if (dept != null && dept.getId() != null) {
|
||||||
|
deptCache.putIfAbsent(dept.getId(), dept);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
@DataPermission(enable = false)
|
||||||
|
public void backfillMissingCodesWithoutEvent(Collection<Long> deptIds) {
|
||||||
|
if (CollUtil.isEmpty(deptIds)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
List<DeptDO> targets = deptMapper.selectBatchIds(deptIds);
|
||||||
|
for (DeptDO dept : targets) {
|
||||||
|
if (dept == null || StrUtil.isNotBlank(dept.getCode())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Integer source = ObjectUtil.defaultIfNull(dept.getDeptSource(), DeptSourceEnum.EXTERNAL.getSource());
|
||||||
|
try {
|
||||||
|
String code = generateDeptCode(dept.getParentId(), source);
|
||||||
|
validateDeptCodeUnique(dept.getId(), code);
|
||||||
|
updateDeptCode(dept.getId(), code);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
log.warn("[iWork] 回填部门编码失败 id={} name={} msg={}", dept.getId(), dept.getName(), ex.getMessage());
|
||||||
}
|
}
|
||||||
} catch (Exception ex) {
|
|
||||||
log.warn("[Dept] Ensure external system dict failed, systemCode={}", normalizedCode, ex);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,23 +5,42 @@ import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkHrJo
|
|||||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkHrSubcompanyPageRespVO;
|
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkHrSubcompanyPageRespVO;
|
||||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkHrUserPageRespVO;
|
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkHrUserPageRespVO;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstraction for applying iWork entities into local persistence.
|
* Abstraction for applying iWork entities into local persistence.
|
||||||
*/
|
*/
|
||||||
public interface IWorkSyncProcessor {
|
public interface IWorkSyncProcessor {
|
||||||
|
|
||||||
BatchResult syncSubcompanies(List<IWorkHrSubcompanyPageRespVO.Subcompany> data, SyncOptions options);
|
BatchResult syncSubcompanies(List<IWorkHrSubcompanyPageRespVO.Subcompany> data,
|
||||||
|
SyncOptions options);
|
||||||
|
|
||||||
|
BatchResult syncSubcompanies(List<IWorkHrSubcompanyPageRespVO.Subcompany> data,
|
||||||
|
SyncOptions options,
|
||||||
|
DeptSyncContext context);
|
||||||
|
|
||||||
BatchResult syncDepartments(List<IWorkHrDepartmentPageRespVO.Department> data, SyncOptions options);
|
BatchResult syncDepartments(List<IWorkHrDepartmentPageRespVO.Department> data, SyncOptions options);
|
||||||
|
|
||||||
|
BatchResult syncDepartments(List<IWorkHrDepartmentPageRespVO.Department> data,
|
||||||
|
SyncOptions options,
|
||||||
|
DeptSyncContext context);
|
||||||
|
|
||||||
BatchResult syncJobTitles(List<IWorkHrJobTitlePageRespVO.JobTitle> data, SyncOptions options);
|
BatchResult syncJobTitles(List<IWorkHrJobTitlePageRespVO.JobTitle> data, SyncOptions options);
|
||||||
|
|
||||||
BatchResult syncUsers(List<IWorkHrUserPageRespVO.User> data, SyncOptions options);
|
BatchResult syncUsers(List<IWorkHrUserPageRespVO.User> data, SyncOptions options);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对当次同步累计的待处理/占位部门做最终补偿(跨页父子依赖)。
|
||||||
|
*/
|
||||||
|
default BatchResult flushDeptPending(DeptSyncContext context, SyncOptions options) {
|
||||||
|
return BatchResult.empty();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execution options shared by batch and single sync flows.
|
* Execution options shared by batch and single sync flows.
|
||||||
*/
|
*/
|
||||||
@@ -53,6 +72,32 @@ public interface IWorkSyncProcessor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 部门/分部跨页同步上下文,用于累计待处理记录与已就绪父级。
|
||||||
|
*/
|
||||||
|
final class DeptSyncContext {
|
||||||
|
private final Set<Long> readyParentIds = new HashSet<>();
|
||||||
|
private final List<IWorkHrSubcompanyPageRespVO.Subcompany> pendingSubcompanies = new ArrayList<>();
|
||||||
|
private final List<IWorkHrDepartmentPageRespVO.Department> pendingDepartments = new ArrayList<>();
|
||||||
|
private final Set<Long> placeholderDeptIds = new HashSet<>();
|
||||||
|
|
||||||
|
public Set<Long> getReadyParentIds() {
|
||||||
|
return readyParentIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<IWorkHrSubcompanyPageRespVO.Subcompany> getPendingSubcompanies() {
|
||||||
|
return pendingSubcompanies;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<IWorkHrDepartmentPageRespVO.Department> getPendingDepartments() {
|
||||||
|
return pendingDepartments;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<Long> getPlaceholderDeptIds() {
|
||||||
|
return placeholderDeptIds;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Aggregated result for a sync batch.
|
* Aggregated result for a sync batch.
|
||||||
*/
|
*/
|
||||||
@@ -170,11 +215,11 @@ public interface IWorkSyncProcessor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
default BatchResult syncSubcompany(IWorkHrSubcompanyPageRespVO.Subcompany data, SyncOptions options) {
|
default BatchResult syncSubcompany(IWorkHrSubcompanyPageRespVO.Subcompany data, SyncOptions options) {
|
||||||
return syncSubcompanies(Collections.singletonList(data), options);
|
return syncSubcompanies(Collections.singletonList(data), options, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
default BatchResult syncDepartment(IWorkHrDepartmentPageRespVO.Department data, SyncOptions options) {
|
default BatchResult syncDepartment(IWorkHrDepartmentPageRespVO.Department data, SyncOptions options) {
|
||||||
return syncDepartments(Collections.singletonList(data), options);
|
return syncDepartments(Collections.singletonList(data), options, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
default BatchResult syncJobTitle(IWorkHrJobTitlePageRespVO.JobTitle data, SyncOptions options) {
|
default BatchResult syncJobTitle(IWorkHrJobTitlePageRespVO.JobTitle data, SyncOptions options) {
|
||||||
|
|||||||
@@ -50,13 +50,34 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BatchResult syncSubcompanies(List<IWorkHrSubcompanyPageRespVO.Subcompany> data, SyncOptions options) {
|
public BatchResult syncSubcompanies(List<IWorkHrSubcompanyPageRespVO.Subcompany> data, SyncOptions options) {
|
||||||
|
return syncSubcompanies(data, options, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BatchResult syncSubcompanies(List<IWorkHrSubcompanyPageRespVO.Subcompany> data,
|
||||||
|
SyncOptions options,
|
||||||
|
DeptSyncContext context) {
|
||||||
|
return syncSubcompaniesInternal(data, options, context, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private BatchResult syncSubcompaniesInternal(List<IWorkHrSubcompanyPageRespVO.Subcompany> data,
|
||||||
|
SyncOptions options,
|
||||||
|
DeptSyncContext context,
|
||||||
|
boolean allowPlaceholderOnRemaining) {
|
||||||
List<IWorkHrSubcompanyPageRespVO.Subcompany> records = CollUtil.emptyIfNull(data);
|
List<IWorkHrSubcompanyPageRespVO.Subcompany> records = CollUtil.emptyIfNull(data);
|
||||||
BatchResult result = BatchResult.empty();
|
BatchResult result = BatchResult.empty();
|
||||||
if (records.isEmpty()) {
|
if (records.isEmpty()
|
||||||
|
&& (context == null || CollUtil.isEmpty(context.getPendingSubcompanies()))) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
result.increasePulled(records.size());
|
result.increasePulled(records.size());
|
||||||
List<IWorkHrSubcompanyPageRespVO.Subcompany> queue = new ArrayList<>(records);
|
List<IWorkHrSubcompanyPageRespVO.Subcompany> queue = new ArrayList<>();
|
||||||
|
if (context != null && CollUtil.isNotEmpty(context.getPendingSubcompanies())) {
|
||||||
|
queue.addAll(context.getPendingSubcompanies());
|
||||||
|
context.getPendingSubcompanies().clear();
|
||||||
|
}
|
||||||
|
queue.addAll(records);
|
||||||
|
Set<Long> readyParentIds = context != null ? context.getReadyParentIds() : new HashSet<>();
|
||||||
int guard = 0;
|
int guard = 0;
|
||||||
int maxPasses = Math.max(1, queue.size() * 2);
|
int maxPasses = Math.max(1, queue.size() * 2);
|
||||||
while (!queue.isEmpty() && guard++ < maxPasses) {
|
while (!queue.isEmpty() && guard++ < maxPasses) {
|
||||||
@@ -79,6 +100,9 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor {
|
|||||||
}
|
}
|
||||||
Long deptId = externalId.longValue();
|
Long deptId = externalId.longValue();
|
||||||
ParentHolder parentHolder = resolveSubcompanyParent(sub.getSupsubcomid());
|
ParentHolder parentHolder = resolveSubcompanyParent(sub.getSupsubcomid());
|
||||||
|
if (!isParentReady(parentHolder.parentId(), readyParentIds)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
boolean canceled = isCanceledFlag(sub.getCanceled());
|
boolean canceled = isCanceledFlag(sub.getCanceled());
|
||||||
DeptSaveReqVO saveReq = buildSubcompanySaveReq(sub, deptId, parentHolder.parentId(), canceled);
|
DeptSaveReqVO saveReq = buildSubcompanySaveReq(sub, deptId, parentHolder.parentId(), canceled);
|
||||||
try {
|
try {
|
||||||
@@ -87,6 +111,9 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor {
|
|||||||
canceled,
|
canceled,
|
||||||
options);
|
options);
|
||||||
applyDeptOutcome(result, outcome, "分部", sub.getSubcompanyname());
|
applyDeptOutcome(result, outcome, "分部", sub.getSubcompanyname());
|
||||||
|
if (outcome.deptId() != null) {
|
||||||
|
readyParentIds.add(outcome.deptId());
|
||||||
|
}
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
log.error("[iWork] 同步分部失败: id={} name={}", sub.getId(), sub.getSubcompanyname(), ex);
|
log.error("[iWork] 同步分部失败: id={} name={}", sub.getId(), sub.getSubcompanyname(), ex);
|
||||||
result.increaseFailed();
|
result.increaseFailed();
|
||||||
@@ -99,10 +126,30 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!queue.isEmpty()) {
|
||||||
|
if (context != null && !allowPlaceholderOnRemaining) {
|
||||||
|
context.getPendingSubcompanies().addAll(queue);
|
||||||
|
queue.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
if (!queue.isEmpty()) {
|
if (!queue.isEmpty()) {
|
||||||
for (IWorkHrSubcompanyPageRespVO.Subcompany remaining : queue) {
|
for (IWorkHrSubcompanyPageRespVO.Subcompany remaining : queue) {
|
||||||
log.warn("[iWork] 分部因父级缺失未同步: id={} name={}", remaining.getId(), remaining.getSubcompanyname());
|
log.warn("[iWork] 分部父级缺失,延迟生成编码插入占位: id={} name={}", remaining.getId(), remaining.getSubcompanyname());
|
||||||
result.increaseFailed();
|
DeptSaveReqVO saveReq = buildSubcompanySaveReq(remaining,
|
||||||
|
remaining.getId() == null ? null : remaining.getId().longValue(),
|
||||||
|
resolveSubcompanyParent(remaining.getSupsubcomid()).parentId(),
|
||||||
|
isCanceledFlag(remaining.getCanceled()));
|
||||||
|
saveReq.setDelayCodeGeneration(true);
|
||||||
|
try {
|
||||||
|
DeptSyncOutcome outcome = upsertDept(saveReq.getId(), saveReq, isCanceledFlag(remaining.getCanceled()), options);
|
||||||
|
applyDeptOutcome(result, outcome, "分部", remaining.getSubcompanyname());
|
||||||
|
if (context != null && outcome.deptId() != null) {
|
||||||
|
context.getPlaceholderDeptIds().add(outcome.deptId());
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
log.error("[iWork] 分部占位插入失败: id={} name={}", remaining.getId(), remaining.getSubcompanyname(), ex);
|
||||||
|
result.increaseFailed();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
@@ -110,13 +157,34 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BatchResult syncDepartments(List<IWorkHrDepartmentPageRespVO.Department> data, SyncOptions options) {
|
public BatchResult syncDepartments(List<IWorkHrDepartmentPageRespVO.Department> data, SyncOptions options) {
|
||||||
|
return syncDepartments(data, options, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BatchResult syncDepartments(List<IWorkHrDepartmentPageRespVO.Department> data,
|
||||||
|
SyncOptions options,
|
||||||
|
DeptSyncContext context) {
|
||||||
|
return syncDepartmentsInternal(data, options, context, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private BatchResult syncDepartmentsInternal(List<IWorkHrDepartmentPageRespVO.Department> data,
|
||||||
|
SyncOptions options,
|
||||||
|
DeptSyncContext context,
|
||||||
|
boolean allowPlaceholderOnRemaining) {
|
||||||
List<IWorkHrDepartmentPageRespVO.Department> records = CollUtil.emptyIfNull(data);
|
List<IWorkHrDepartmentPageRespVO.Department> records = CollUtil.emptyIfNull(data);
|
||||||
BatchResult result = BatchResult.empty();
|
BatchResult result = BatchResult.empty();
|
||||||
if (records.isEmpty()) {
|
if (records.isEmpty()
|
||||||
|
&& (context == null || CollUtil.isEmpty(context.getPendingDepartments()))) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
result.increasePulled(records.size());
|
result.increasePulled(records.size());
|
||||||
List<IWorkHrDepartmentPageRespVO.Department> queue = new ArrayList<>(records);
|
List<IWorkHrDepartmentPageRespVO.Department> queue = new ArrayList<>();
|
||||||
|
if (context != null && CollUtil.isNotEmpty(context.getPendingDepartments())) {
|
||||||
|
queue.addAll(context.getPendingDepartments());
|
||||||
|
context.getPendingDepartments().clear();
|
||||||
|
}
|
||||||
|
queue.addAll(records);
|
||||||
|
Set<Long> readyParentIds = context != null ? context.getReadyParentIds() : new HashSet<>();
|
||||||
int guard = 0;
|
int guard = 0;
|
||||||
int maxPasses = Math.max(1, queue.size() * 2);
|
int maxPasses = Math.max(1, queue.size() * 2);
|
||||||
while (!queue.isEmpty() && guard++ < maxPasses) {
|
while (!queue.isEmpty() && guard++ < maxPasses) {
|
||||||
@@ -139,6 +207,9 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor {
|
|||||||
}
|
}
|
||||||
Long deptId = externalId.longValue();
|
Long deptId = externalId.longValue();
|
||||||
ParentHolder parentHolder = resolveDepartmentParent(dept);
|
ParentHolder parentHolder = resolveDepartmentParent(dept);
|
||||||
|
if (!isParentReady(parentHolder.parentId(), readyParentIds)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
boolean canceled = isCanceledFlag(dept.getCanceled());
|
boolean canceled = isCanceledFlag(dept.getCanceled());
|
||||||
DeptSaveReqVO saveReq = buildDepartmentSaveReq(dept, deptId, parentHolder.parentId(), canceled);
|
DeptSaveReqVO saveReq = buildDepartmentSaveReq(dept, deptId, parentHolder.parentId(), canceled);
|
||||||
try {
|
try {
|
||||||
@@ -147,6 +218,9 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor {
|
|||||||
canceled,
|
canceled,
|
||||||
options);
|
options);
|
||||||
applyDeptOutcome(result, outcome, "部门", dept.getDepartmentname());
|
applyDeptOutcome(result, outcome, "部门", dept.getDepartmentname());
|
||||||
|
if (outcome.deptId() != null) {
|
||||||
|
readyParentIds.add(outcome.deptId());
|
||||||
|
}
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
log.error("[iWork] 同步部门失败: id={} name={}", dept.getId(), dept.getDepartmentname(), ex);
|
log.error("[iWork] 同步部门失败: id={} name={}", dept.getId(), dept.getDepartmentname(), ex);
|
||||||
result.increaseFailed();
|
result.increaseFailed();
|
||||||
@@ -160,11 +234,42 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!queue.isEmpty()) {
|
if (!queue.isEmpty()) {
|
||||||
for (IWorkHrDepartmentPageRespVO.Department remaining : queue) {
|
if (context != null && !allowPlaceholderOnRemaining) {
|
||||||
log.warn("[iWork] 部门因父级缺失未同步: id={} name={}", remaining.getId(), remaining.getDepartmentname());
|
context.getPendingDepartments().addAll(queue);
|
||||||
result.increaseFailed();
|
queue.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!queue.isEmpty()) {
|
||||||
|
for (IWorkHrDepartmentPageRespVO.Department remaining : queue) {
|
||||||
|
log.warn("[iWork] 部门父级缺失,延迟生成编码插入占位: id={} name={}", remaining.getId(), remaining.getDepartmentname());
|
||||||
|
DeptSaveReqVO saveReq = buildDepartmentSaveReq(remaining,
|
||||||
|
remaining.getId() == null ? null : remaining.getId().longValue(),
|
||||||
|
resolveDepartmentParent(remaining).parentId(),
|
||||||
|
isCanceledFlag(remaining.getCanceled()));
|
||||||
|
saveReq.setDelayCodeGeneration(true);
|
||||||
|
try {
|
||||||
|
DeptSyncOutcome outcome = upsertDept(saveReq.getId(), saveReq, isCanceledFlag(remaining.getCanceled()), options);
|
||||||
|
applyDeptOutcome(result, outcome, "部门", remaining.getDepartmentname());
|
||||||
|
if (context != null && outcome.deptId() != null) {
|
||||||
|
context.getPlaceholderDeptIds().add(outcome.deptId());
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
log.error("[iWork] 部门占位插入失败: id={} name={}", remaining.getId(), remaining.getDepartmentname(), ex);
|
||||||
|
result.increaseFailed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BatchResult flushDeptPending(DeptSyncContext context, SyncOptions options) {
|
||||||
|
BatchResult result = BatchResult.empty();
|
||||||
|
if (context == null) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
result.merge(syncSubcompaniesInternal(Collections.emptyList(), options, context, true));
|
||||||
|
result.merge(syncDepartmentsInternal(Collections.emptyList(), options, context, true));
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,7 +341,7 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor {
|
|||||||
CommonStatusEnum status = inactive ? CommonStatusEnum.DISABLE : CommonStatusEnum.ENABLE;
|
CommonStatusEnum status = inactive ? CommonStatusEnum.DISABLE : CommonStatusEnum.ENABLE;
|
||||||
// 直接沿用 iWork 原始密码,避免重复格式化造成校验偏差
|
// 直接沿用 iWork 原始密码,避免重复格式化造成校验偏差
|
||||||
String externalPassword = trimToNull(user.getPassword());
|
String externalPassword = trimToNull(user.getPassword());
|
||||||
AdminUserDO existing = adminUserMapper.selectByUsername(username);
|
AdminUserDO existing = adminUserMapper.selectById(user.getId());
|
||||||
UserSyncOutcome outcome;
|
UserSyncOutcome outcome;
|
||||||
if (existing == null) {
|
if (existing == null) {
|
||||||
if (!options.isCreateIfMissing()) {
|
if (!options.isCreateIfMissing()) {
|
||||||
@@ -408,7 +513,7 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor {
|
|||||||
req.setIsGroup(Boolean.FALSE);
|
req.setIsGroup(Boolean.FALSE);
|
||||||
req.setDeptSource(DeptSourceEnum.IWORK.getSource());
|
req.setDeptSource(DeptSourceEnum.IWORK.getSource());
|
||||||
req.setExternalSystemCode(ExternalPlatformEnum.IWORK.getCode());
|
req.setExternalSystemCode(ExternalPlatformEnum.IWORK.getCode());
|
||||||
req.setExternalDeptCode(StrUtil.blankToDefault(trimToNull(data.getSubcompanycode()), String.valueOf(data.getId())));
|
req.setExternalDeptCode(trimToNull(data.getSubcompanycode()));
|
||||||
req.setExternalDeptName(data.getSubcompanyname());
|
req.setExternalDeptName(data.getSubcompanyname());
|
||||||
return req;
|
return req;
|
||||||
}
|
}
|
||||||
@@ -428,7 +533,7 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor {
|
|||||||
req.setIsGroup(Boolean.FALSE);
|
req.setIsGroup(Boolean.FALSE);
|
||||||
req.setDeptSource(DeptSourceEnum.IWORK.getSource());
|
req.setDeptSource(DeptSourceEnum.IWORK.getSource());
|
||||||
req.setExternalSystemCode(ExternalPlatformEnum.IWORK.getCode());
|
req.setExternalSystemCode(ExternalPlatformEnum.IWORK.getCode());
|
||||||
req.setExternalDeptCode(StrUtil.blankToDefault(trimToNull(data.getDepartmentcode()), String.valueOf(data.getId())));
|
req.setExternalDeptCode(trimToNull(data.getDepartmentcode()));
|
||||||
req.setExternalDeptName(data.getDepartmentname());
|
req.setExternalDeptName(data.getDepartmentname());
|
||||||
return req;
|
return req;
|
||||||
}
|
}
|
||||||
@@ -493,6 +598,16 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor {
|
|||||||
return new ParentHolder(DeptDO.PARENT_ID_ROOT);
|
return new ParentHolder(DeptDO.PARENT_ID_ROOT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isParentReady(Long parentId, Set<Long> readyParentIds) {
|
||||||
|
if (parentId == null || DeptDO.PARENT_ID_ROOT.equals(parentId)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (readyParentIds.contains(parentId)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return deptService.getDept(parentId) != null;
|
||||||
|
}
|
||||||
|
|
||||||
private PostDO resolvePostByCode(String code) {
|
private PostDO resolvePostByCode(String code) {
|
||||||
String key = buildPostCacheKey(code);
|
String key = buildPostCacheKey(code);
|
||||||
PostDO cached = postCache.get(key);
|
PostDO cached = postCache.get(key);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import cn.hutool.core.util.StrUtil;
|
|||||||
import com.zt.plat.framework.common.exception.util.ServiceExceptionUtil;
|
import com.zt.plat.framework.common.exception.util.ServiceExceptionUtil;
|
||||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.*;
|
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.*;
|
||||||
import com.zt.plat.module.system.enums.integration.IWorkSyncEntityTypeEnum;
|
import com.zt.plat.module.system.enums.integration.IWorkSyncEntityTypeEnum;
|
||||||
|
import com.zt.plat.module.system.service.dept.DeptService;
|
||||||
import com.zt.plat.module.system.service.integration.iwork.IWorkOrgRestService;
|
import com.zt.plat.module.system.service.integration.iwork.IWorkOrgRestService;
|
||||||
import com.zt.plat.module.system.service.integration.iwork.IWorkSyncProcessor;
|
import com.zt.plat.module.system.service.integration.iwork.IWorkSyncProcessor;
|
||||||
import com.zt.plat.module.system.service.integration.iwork.IWorkSyncService;
|
import com.zt.plat.module.system.service.integration.iwork.IWorkSyncService;
|
||||||
@@ -31,6 +32,7 @@ public class IWorkSyncServiceImpl implements IWorkSyncService {
|
|||||||
|
|
||||||
private final IWorkOrgRestService orgRestService;
|
private final IWorkOrgRestService orgRestService;
|
||||||
private final IWorkSyncProcessor syncProcessor;
|
private final IWorkSyncProcessor syncProcessor;
|
||||||
|
private final DeptService deptService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IWorkFullSyncRespVO fullSyncDepartments(IWorkFullSyncReqVO reqVO) {
|
public IWorkFullSyncRespVO fullSyncDepartments(IWorkFullSyncReqVO reqVO) {
|
||||||
@@ -64,11 +66,14 @@ public class IWorkSyncServiceImpl implements IWorkSyncService {
|
|||||||
boolean syncJobTitle = scopes.contains(IWorkSyncEntityTypeEnum.JOB_TITLE);
|
boolean syncJobTitle = scopes.contains(IWorkSyncEntityTypeEnum.JOB_TITLE);
|
||||||
int processedPages = 0;
|
int processedPages = 0;
|
||||||
IWorkSyncProcessor.SyncOptions options = buildFullSyncOptions(reqVO);
|
IWorkSyncProcessor.SyncOptions options = buildFullSyncOptions(reqVO);
|
||||||
|
IWorkSyncProcessor.DeptSyncContext deptSyncContext = (syncDepartments || syncSubcompanies)
|
||||||
|
? new IWorkSyncProcessor.DeptSyncContext()
|
||||||
|
: null;
|
||||||
if (syncSubcompanies) {
|
if (syncSubcompanies) {
|
||||||
processedPages += executeSubcompanyFullSync(reqVO, options, respVO.getSubcompanyStat(), batchStats);
|
processedPages += executeSubcompanyFullSync(reqVO, options, respVO.getSubcompanyStat(), batchStats, deptSyncContext);
|
||||||
}
|
}
|
||||||
if (syncDepartments) {
|
if (syncDepartments) {
|
||||||
processedPages += executeDepartmentFullSync(reqVO, options, respVO.getDepartmentStat(), batchStats);
|
processedPages += executeDepartmentFullSync(reqVO, options, respVO.getDepartmentStat(), batchStats, deptSyncContext);
|
||||||
}
|
}
|
||||||
if (syncJobTitle) {
|
if (syncJobTitle) {
|
||||||
processedPages += executeJobTitleFullSync(reqVO, options, respVO.getJobTitleStat(), batchStats);
|
processedPages += executeJobTitleFullSync(reqVO, options, respVO.getJobTitleStat(), batchStats);
|
||||||
@@ -76,6 +81,13 @@ public class IWorkSyncServiceImpl implements IWorkSyncService {
|
|||||||
if (syncUsers) {
|
if (syncUsers) {
|
||||||
processedPages += executeUserFullSync(reqVO, options, respVO.getUserStat(), batchStats);
|
processedPages += executeUserFullSync(reqVO, options, respVO.getUserStat(), batchStats);
|
||||||
}
|
}
|
||||||
|
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);
|
respVO.setProcessedPages(processedPages);
|
||||||
return respVO;
|
return respVO;
|
||||||
}
|
}
|
||||||
@@ -83,7 +95,8 @@ public class IWorkSyncServiceImpl implements IWorkSyncService {
|
|||||||
private int executeSubcompanyFullSync(IWorkFullSyncReqVO reqVO,
|
private int executeSubcompanyFullSync(IWorkFullSyncReqVO reqVO,
|
||||||
IWorkSyncProcessor.SyncOptions options,
|
IWorkSyncProcessor.SyncOptions options,
|
||||||
IWorkSyncEntityStatVO stat,
|
IWorkSyncEntityStatVO stat,
|
||||||
List<IWorkSyncBatchStatVO> batches) {
|
List<IWorkSyncBatchStatVO> batches,
|
||||||
|
IWorkSyncProcessor.DeptSyncContext context) {
|
||||||
return executePaged(reqVO, IWorkSyncEntityTypeEnum.SUBCOMPANY, batches, (page, pageSize) -> {
|
return executePaged(reqVO, IWorkSyncEntityTypeEnum.SUBCOMPANY, batches, (page, pageSize) -> {
|
||||||
IWorkSubcompanyQueryReqVO query = new IWorkSubcompanyQueryReqVO();
|
IWorkSubcompanyQueryReqVO query = new IWorkSubcompanyQueryReqVO();
|
||||||
query.setCurpage(page);
|
query.setCurpage(page);
|
||||||
@@ -92,7 +105,7 @@ public class IWorkSyncServiceImpl implements IWorkSyncService {
|
|||||||
IWorkHrSubcompanyPageRespVO pageResp = orgRestService.listSubcompanies(query);
|
IWorkHrSubcompanyPageRespVO pageResp = orgRestService.listSubcompanies(query);
|
||||||
ensureIWorkSuccess("拉取分部", pageResp.isSuccess(), pageResp.getMessage());
|
ensureIWorkSuccess("拉取分部", pageResp.isSuccess(), pageResp.getMessage());
|
||||||
List<IWorkHrSubcompanyPageRespVO.Subcompany> dataList = CollUtil.emptyIfNull(pageResp.getDataList());
|
List<IWorkHrSubcompanyPageRespVO.Subcompany> dataList = CollUtil.emptyIfNull(pageResp.getDataList());
|
||||||
IWorkSyncProcessor.BatchResult result = syncProcessor.syncSubcompanies(dataList, options);
|
IWorkSyncProcessor.BatchResult result = syncProcessor.syncSubcompanies(dataList, options, context);
|
||||||
updateStat(stat, result, dataList.size());
|
updateStat(stat, result, dataList.size());
|
||||||
return new BatchExecution(result, dataList.size());
|
return new BatchExecution(result, dataList.size());
|
||||||
});
|
});
|
||||||
@@ -101,7 +114,8 @@ public class IWorkSyncServiceImpl implements IWorkSyncService {
|
|||||||
private int executeDepartmentFullSync(IWorkFullSyncReqVO reqVO,
|
private int executeDepartmentFullSync(IWorkFullSyncReqVO reqVO,
|
||||||
IWorkSyncProcessor.SyncOptions options,
|
IWorkSyncProcessor.SyncOptions options,
|
||||||
IWorkSyncEntityStatVO stat,
|
IWorkSyncEntityStatVO stat,
|
||||||
List<IWorkSyncBatchStatVO> batches) {
|
List<IWorkSyncBatchStatVO> batches,
|
||||||
|
IWorkSyncProcessor.DeptSyncContext context) {
|
||||||
return executePaged(reqVO, IWorkSyncEntityTypeEnum.DEPARTMENT, batches, (page, pageSize) -> {
|
return executePaged(reqVO, IWorkSyncEntityTypeEnum.DEPARTMENT, batches, (page, pageSize) -> {
|
||||||
IWorkDepartmentQueryReqVO query = new IWorkDepartmentQueryReqVO();
|
IWorkDepartmentQueryReqVO query = new IWorkDepartmentQueryReqVO();
|
||||||
query.setCurpage(page);
|
query.setCurpage(page);
|
||||||
@@ -110,7 +124,7 @@ public class IWorkSyncServiceImpl implements IWorkSyncService {
|
|||||||
IWorkHrDepartmentPageRespVO pageResp = orgRestService.listDepartments(query);
|
IWorkHrDepartmentPageRespVO pageResp = orgRestService.listDepartments(query);
|
||||||
ensureIWorkSuccess("拉取部门", pageResp.isSuccess(), pageResp.getMessage());
|
ensureIWorkSuccess("拉取部门", pageResp.isSuccess(), pageResp.getMessage());
|
||||||
List<IWorkHrDepartmentPageRespVO.Department> dataList = CollUtil.emptyIfNull(pageResp.getDataList());
|
List<IWorkHrDepartmentPageRespVO.Department> dataList = CollUtil.emptyIfNull(pageResp.getDataList());
|
||||||
IWorkSyncProcessor.BatchResult result = syncProcessor.syncDepartments(dataList, options);
|
IWorkSyncProcessor.BatchResult result = syncProcessor.syncDepartments(dataList, options, context);
|
||||||
updateStat(stat, result, dataList.size());
|
updateStat(stat, result, dataList.size());
|
||||||
return new BatchExecution(result, dataList.size());
|
return new BatchExecution(result, dataList.size());
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.zt.plat.module.system.service.permission;
|
package com.zt.plat.module.system.service.permission;
|
||||||
|
|
||||||
import com.zt.plat.framework.common.biz.system.permission.dto.DeptDataPermissionRespDTO;
|
import com.zt.plat.framework.common.biz.system.permission.dto.DeptDataPermissionRespDTO;
|
||||||
|
import com.zt.plat.module.system.enums.permission.DataScopeEnum;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@@ -143,4 +144,12 @@ public interface PermissionService {
|
|||||||
*/
|
*/
|
||||||
DeptDataPermissionRespDTO getDeptDataPermission(Long userId);
|
DeptDataPermissionRespDTO getDeptDataPermission(Long userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得用户的数据权限级别
|
||||||
|
*
|
||||||
|
* @param userId 用户编号
|
||||||
|
* @return 数据权限范围枚举
|
||||||
|
*/
|
||||||
|
DataScopeEnum getUserDataPermissionLevel(Long userId);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import com.zt.plat.module.system.enums.permission.RoleTypeEnum;
|
|||||||
import com.zt.plat.module.system.service.dept.DeptService;
|
import com.zt.plat.module.system.service.dept.DeptService;
|
||||||
import com.zt.plat.module.system.service.user.AdminUserService;
|
import com.zt.plat.module.system.service.user.AdminUserService;
|
||||||
import com.zt.plat.module.system.service.userdept.UserDeptService;
|
import com.zt.plat.module.system.service.userdept.UserDeptService;
|
||||||
|
import com.zt.plat.framework.tenant.core.aop.TenantIgnore;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@@ -57,6 +58,15 @@ import static com.zt.plat.module.system.enums.ErrorCodeConstants.ROLE_CAN_NOT_UP
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
public class PermissionServiceImpl implements PermissionService {
|
public class PermissionServiceImpl implements PermissionService {
|
||||||
|
|
||||||
|
private static final List<DataScopeEnum> DATA_SCOPE_PRIORITY = Arrays.asList(
|
||||||
|
DataScopeEnum.ALL,
|
||||||
|
DataScopeEnum.COMPANY_AND_DEPT,
|
||||||
|
DataScopeEnum.DEPT_AND_CHILD,
|
||||||
|
DataScopeEnum.DEPT_ONLY,
|
||||||
|
DataScopeEnum.DEPT_CUSTOM,
|
||||||
|
DataScopeEnum.SELF
|
||||||
|
);
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private RoleMenuMapper roleMenuMapper;
|
private RoleMenuMapper roleMenuMapper;
|
||||||
@Resource
|
@Resource
|
||||||
@@ -404,6 +414,40 @@ public class PermissionServiceImpl implements PermissionService {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@DataPermission(enable = false)
|
||||||
|
@TenantIgnore
|
||||||
|
public DataScopeEnum getUserDataPermissionLevel(Long userId) {
|
||||||
|
List<RoleDO> roles = getEnableUserRoleListByUserIdFromCache(userId);
|
||||||
|
if (CollUtil.isEmpty(roles)) {
|
||||||
|
return DataScopeEnum.SELF;
|
||||||
|
}
|
||||||
|
|
||||||
|
DataScopeEnum best = null;
|
||||||
|
for (RoleDO role : roles) {
|
||||||
|
DataScopeEnum scopeEnum = DataScopeEnum.findByScope(role.getDataScope());
|
||||||
|
if (scopeEnum == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (best == null || compareScope(scopeEnum, best) < 0) {
|
||||||
|
best = scopeEnum;
|
||||||
|
if (DataScopeEnum.ALL.equals(best)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return best != null ? best : DataScopeEnum.SELF;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int compareScope(DataScopeEnum left, DataScopeEnum right) {
|
||||||
|
return getScopePriority(left) - getScopePriority(right);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getScopePriority(DataScopeEnum scope) {
|
||||||
|
int idx = DATA_SCOPE_PRIORITY.indexOf(scope);
|
||||||
|
return idx >= 0 ? idx : Integer.MAX_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获得自身的代理对象,解决 AOP 生效问题
|
* 获得自身的代理对象,解决 AOP 生效问题
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -10,10 +10,10 @@ import com.zt.plat.module.system.dal.dataobject.sms.SmsCodeDO;
|
|||||||
import com.zt.plat.module.system.dal.mysql.sms.SmsCodeMapper;
|
import com.zt.plat.module.system.dal.mysql.sms.SmsCodeMapper;
|
||||||
import com.zt.plat.module.system.enums.sms.SmsSceneEnum;
|
import com.zt.plat.module.system.enums.sms.SmsSceneEnum;
|
||||||
import com.zt.plat.module.system.framework.sms.config.SmsCodeProperties;
|
import com.zt.plat.module.system.framework.sms.config.SmsCodeProperties;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
|
||||||
import jakarta.annotation.Resource;
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
import static cn.hutool.core.util.RandomUtil.randomInt;
|
import static cn.hutool.core.util.RandomUtil.randomInt;
|
||||||
@@ -56,11 +56,11 @@ public class SmsCodeServiceImpl implements SmsCodeService {
|
|||||||
if (lastSmsCode != null) {
|
if (lastSmsCode != null) {
|
||||||
if (LocalDateTimeUtil.between(lastSmsCode.getCreateTime(), LocalDateTime.now()).toMillis()
|
if (LocalDateTimeUtil.between(lastSmsCode.getCreateTime(), LocalDateTime.now()).toMillis()
|
||||||
< smsCodeProperties.getSendFrequency().toMillis()) { // 发送过于频繁
|
< smsCodeProperties.getSendFrequency().toMillis()) { // 发送过于频繁
|
||||||
throw exception(SMS_CODE_SEND_TOO_FAST);
|
throw exception(SMS_CODE_SEND_TOO_FAST, smsCodeProperties.getSendFrequency().toMinutes());
|
||||||
}
|
}
|
||||||
if (isToday(lastSmsCode.getCreateTime()) && // 必须是今天,才能计算超过当天的上限
|
if (isToday(lastSmsCode.getCreateTime()) && // 必须是今天,才能计算超过当天的上限
|
||||||
lastSmsCode.getTodayIndex() >= smsCodeProperties.getSendMaximumQuantityPerDay()) { // 超过当天发送的上限。
|
lastSmsCode.getTodayIndex() >= smsCodeProperties.getSendMaximumQuantityPerDay()) { // 超过当天发送的上限。
|
||||||
throw exception(SMS_CODE_EXCEED_SEND_MAXIMUM_QUANTITY_PER_DAY);
|
throw exception(SMS_CODE_EXCEED_SEND_MAXIMUM_QUANTITY_PER_DAY, smsCodeProperties.getSendMaximumQuantityPerDay());
|
||||||
}
|
}
|
||||||
// TODO ZT:提升,每个 IP 每天可发送数量
|
// TODO ZT:提升,每个 IP 每天可发送数量
|
||||||
// TODO ZT:提升,每个 IP 每小时可发送数量
|
// TODO ZT:提升,每个 IP 每小时可发送数量
|
||||||
|
|||||||
@@ -193,10 +193,14 @@ public interface AdminUserService {
|
|||||||
* @param status 状态
|
* @param status 状态
|
||||||
* @return 用户们
|
* @return 用户们
|
||||||
*/
|
*/
|
||||||
List<AdminUserDO> getUserListByStatus(Integer status, Integer limit);
|
List<AdminUserDO> getUserListByStatus(Integer status, Integer limit, String keyword);
|
||||||
|
|
||||||
|
default List<AdminUserDO> getUserListByStatus(Integer status, Integer limit) {
|
||||||
|
return getUserListByStatus(status, limit, null);
|
||||||
|
}
|
||||||
|
|
||||||
default List<AdminUserDO> getUserListByStatus(Integer status) {
|
default List<AdminUserDO> getUserListByStatus(Integer status) {
|
||||||
return getUserListByStatus(status, null);
|
return getUserListByStatus(status, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -664,8 +664,8 @@ public class AdminUserServiceImpl implements AdminUserService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<AdminUserDO> getUserListByStatus(Integer status, Integer limit) {
|
public List<AdminUserDO> getUserListByStatus(Integer status, Integer limit, String keyword) {
|
||||||
List<AdminUserDO> users = userMapper.selectListByStatus(status, limit);
|
List<AdminUserDO> users = userMapper.selectListByStatus(status, limit, keyword);
|
||||||
fillUserDeptInfo(users);
|
fillUserDeptInfo(users);
|
||||||
return users;
|
return users;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -241,8 +241,8 @@ zt:
|
|||||||
expire-times: 10m
|
expire-times: 10m
|
||||||
send-frequency: 1m
|
send-frequency: 1m
|
||||||
send-maximum-quantity-per-day: 10
|
send-maximum-quantity-per-day: 10
|
||||||
begin-code: 9999 # 这里配置 9999 的原因是,测试方便。
|
begin-code: 100000
|
||||||
end-code: 9999 # 这里配置 9999 的原因是,测试方便。
|
end-code: 999999
|
||||||
|
|
||||||
|
|
||||||
# E办OAuth2配置文件
|
# E办OAuth2配置文件
|
||||||
|
|||||||
@@ -3,23 +3,25 @@ package com.zt.plat.module.system.service.dept;
|
|||||||
import com.zt.plat.framework.common.enums.CommonStatusEnum;
|
import com.zt.plat.framework.common.enums.CommonStatusEnum;
|
||||||
import com.zt.plat.framework.common.util.object.ObjectUtils;
|
import com.zt.plat.framework.common.util.object.ObjectUtils;
|
||||||
import com.zt.plat.framework.test.core.ut.BaseDbUnitTest;
|
import com.zt.plat.framework.test.core.ut.BaseDbUnitTest;
|
||||||
|
import com.zt.plat.module.system.controller.admin.dept.vo.depexternalcode.DeptExternalCodeSaveReqVO;
|
||||||
import com.zt.plat.module.system.controller.admin.dept.vo.dept.DeptListReqVO;
|
import com.zt.plat.module.system.controller.admin.dept.vo.dept.DeptListReqVO;
|
||||||
import com.zt.plat.module.system.controller.admin.dept.vo.dept.DeptSaveReqVO;
|
import com.zt.plat.module.system.controller.admin.dept.vo.dept.DeptSaveReqVO;
|
||||||
import com.zt.plat.module.system.controller.admin.dept.vo.depexternalcode.DeptExternalCodeSaveReqVO;
|
|
||||||
import com.zt.plat.module.system.dal.dataobject.dept.DeptDO;
|
import com.zt.plat.module.system.dal.dataobject.dept.DeptDO;
|
||||||
import com.zt.plat.module.system.dal.dataobject.dept.DeptExternalCodeDO;
|
import com.zt.plat.module.system.dal.dataobject.dept.DeptExternalCodeDO;
|
||||||
import com.zt.plat.module.system.dal.mysql.dept.DeptExternalCodeMapper;
|
import com.zt.plat.module.system.dal.mysql.dept.DeptExternalCodeMapper;
|
||||||
import com.zt.plat.module.system.dal.mysql.dept.DeptMapper;
|
import com.zt.plat.module.system.dal.mysql.dept.DeptMapper;
|
||||||
import com.zt.plat.module.system.service.dept.DeptExternalCodeServiceImpl;
|
|
||||||
import com.zt.plat.module.system.dal.redis.RedisKeyConstants;
|
import com.zt.plat.module.system.dal.redis.RedisKeyConstants;
|
||||||
|
import com.zt.plat.module.system.enums.dept.DeptSourceEnum;
|
||||||
|
import com.zt.plat.module.system.service.permission.PermissionService;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.context.annotation.Import;
|
|
||||||
import org.springframework.cache.CacheManager;
|
|
||||||
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
|
|
||||||
import org.springframework.cache.annotation.EnableCaching;
|
|
||||||
import org.springframework.boot.test.context.TestConfiguration;
|
import org.springframework.boot.test.context.TestConfiguration;
|
||||||
|
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||||
|
import org.springframework.cache.CacheManager;
|
||||||
|
import org.springframework.cache.annotation.EnableCaching;
|
||||||
|
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Import;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -51,6 +53,9 @@ public class DeptServiceImplTest extends BaseDbUnitTest {
|
|||||||
@Resource
|
@Resource
|
||||||
private CacheManager cacheManager;
|
private CacheManager cacheManager;
|
||||||
|
|
||||||
|
@MockBean
|
||||||
|
private PermissionService permissionService;
|
||||||
|
|
||||||
@TestConfiguration
|
@TestConfiguration
|
||||||
@EnableCaching
|
@EnableCaching
|
||||||
static class CacheConfig {
|
static class CacheConfig {
|
||||||
@@ -69,7 +74,7 @@ public class DeptServiceImplTest extends BaseDbUnitTest {
|
|||||||
reqVO.setName(name);
|
reqVO.setName(name);
|
||||||
reqVO.setSort(sort);
|
reqVO.setSort(sort);
|
||||||
reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||||
reqVO.setDeptSource(1);
|
reqVO.setDeptSource(DeptSourceEnum.SYNC.getSource());
|
||||||
reqVO.setIsCompany(false);
|
reqVO.setIsCompany(false);
|
||||||
reqVO.setIsGroup(false);
|
reqVO.setIsGroup(false);
|
||||||
return deptService.createDept(reqVO);
|
return deptService.createDept(reqVO);
|
||||||
@@ -83,7 +88,7 @@ public class DeptServiceImplTest extends BaseDbUnitTest {
|
|||||||
o.setParentId(DeptDO.PARENT_ID_ROOT);
|
o.setParentId(DeptDO.PARENT_ID_ROOT);
|
||||||
o.setStatus(randomCommonStatus());
|
o.setStatus(randomCommonStatus());
|
||||||
o.setCode(null);
|
o.setCode(null);
|
||||||
}).setDeptSource(1);
|
}).setDeptSource(DeptSourceEnum.SYNC.getSource());
|
||||||
|
|
||||||
// 调用
|
// 调用
|
||||||
Long deptId = deptService.createDept(reqVO);
|
Long deptId = deptService.createDept(reqVO);
|
||||||
@@ -105,7 +110,7 @@ public class DeptServiceImplTest extends BaseDbUnitTest {
|
|||||||
childReq.setName("事业部");
|
childReq.setName("事业部");
|
||||||
childReq.setSort(1);
|
childReq.setSort(1);
|
||||||
childReq.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
childReq.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||||
childReq.setDeptSource(1);
|
childReq.setDeptSource(DeptSourceEnum.SYNC.getSource());
|
||||||
Long childId = deptService.createDept(childReq);
|
Long childId = deptService.createDept(childReq);
|
||||||
|
|
||||||
DeptDO childDept = deptMapper.selectById(childId);
|
DeptDO childDept = deptMapper.selectById(childId);
|
||||||
@@ -119,7 +124,7 @@ public class DeptServiceImplTest extends BaseDbUnitTest {
|
|||||||
topLevelReq.setName("总部");
|
topLevelReq.setName("总部");
|
||||||
topLevelReq.setSort(1);
|
topLevelReq.setSort(1);
|
||||||
topLevelReq.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
topLevelReq.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||||
topLevelReq.setDeptSource(1);
|
topLevelReq.setDeptSource(DeptSourceEnum.SYNC.getSource());
|
||||||
Long topLevelId = deptService.createDept(topLevelReq);
|
Long topLevelId = deptService.createDept(topLevelReq);
|
||||||
DeptDO firstTop = deptMapper.selectById(topLevelId);
|
DeptDO firstTop = deptMapper.selectById(topLevelId);
|
||||||
assertEquals("ZT001", firstTop.getCode());
|
assertEquals("ZT001", firstTop.getCode());
|
||||||
@@ -129,12 +134,185 @@ public class DeptServiceImplTest extends BaseDbUnitTest {
|
|||||||
secondTopLevelReq.setName("总部");
|
secondTopLevelReq.setName("总部");
|
||||||
secondTopLevelReq.setSort(2);
|
secondTopLevelReq.setSort(2);
|
||||||
secondTopLevelReq.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
secondTopLevelReq.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||||
secondTopLevelReq.setDeptSource(1);
|
secondTopLevelReq.setDeptSource(DeptSourceEnum.SYNC.getSource());
|
||||||
Long secondTopId = deptService.createDept(secondTopLevelReq);
|
Long secondTopId = deptService.createDept(secondTopLevelReq);
|
||||||
DeptDO secondTop = deptMapper.selectById(secondTopId);
|
DeptDO secondTop = deptMapper.selectById(secondTopId);
|
||||||
assertEquals("ZT002", secondTop.getCode());
|
assertEquals("ZT002", secondTop.getCode());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreateDept_externalUsesCuPrefixAndIndependentSequence() {
|
||||||
|
// 自建 EXTERNAL 顶级生成 CU001,且不受 ZT 序列影响
|
||||||
|
DeptSaveReqVO externalTop = new DeptSaveReqVO();
|
||||||
|
externalTop.setParentId(DeptDO.PARENT_ID_ROOT);
|
||||||
|
externalTop.setName("自建总部");
|
||||||
|
externalTop.setSort(1);
|
||||||
|
externalTop.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||||
|
externalTop.setDeptSource(DeptSourceEnum.EXTERNAL.getSource());
|
||||||
|
Long cuTopId = deptService.createDept(externalTop);
|
||||||
|
DeptDO cuTop = deptMapper.selectById(cuTopId);
|
||||||
|
assertEquals("CU001", cuTop.getCode());
|
||||||
|
|
||||||
|
// 同时创建同步来源(非 EXTERNAL),仍使用 ZT 序列
|
||||||
|
DeptSaveReqVO syncTop = new DeptSaveReqVO();
|
||||||
|
syncTop.setParentId(DeptDO.PARENT_ID_ROOT);
|
||||||
|
syncTop.setName("同步总部");
|
||||||
|
syncTop.setSort(2);
|
||||||
|
syncTop.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||||
|
syncTop.setDeptSource(DeptSourceEnum.SYNC.getSource());
|
||||||
|
Long ztTopId = deptService.createDept(syncTop);
|
||||||
|
DeptDO ztTop = deptMapper.selectById(ztTopId);
|
||||||
|
assertEquals("ZT001", ztTop.getCode());
|
||||||
|
|
||||||
|
// 再创建一个自建顶级,应独立递增为 CU002
|
||||||
|
DeptSaveReqVO externalTop2 = new DeptSaveReqVO();
|
||||||
|
externalTop2.setParentId(DeptDO.PARENT_ID_ROOT);
|
||||||
|
externalTop2.setName("自建二部");
|
||||||
|
externalTop2.setSort(3);
|
||||||
|
externalTop2.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||||
|
externalTop2.setDeptSource(DeptSourceEnum.EXTERNAL.getSource());
|
||||||
|
Long cuTop2Id = deptService.createDept(externalTop2);
|
||||||
|
DeptDO cuTop2 = deptMapper.selectById(cuTop2Id);
|
||||||
|
assertEquals("CU002", cuTop2.getCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreateDept_externalChildFollowsCuPrefix() {
|
||||||
|
DeptSaveReqVO externalTop = new DeptSaveReqVO();
|
||||||
|
externalTop.setParentId(DeptDO.PARENT_ID_ROOT);
|
||||||
|
externalTop.setName("自建根");
|
||||||
|
externalTop.setSort(1);
|
||||||
|
externalTop.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||||
|
externalTop.setDeptSource(DeptSourceEnum.EXTERNAL.getSource());
|
||||||
|
Long topId = deptService.createDept(externalTop);
|
||||||
|
DeptDO top = deptMapper.selectById(topId);
|
||||||
|
assertEquals("CU001", top.getCode());
|
||||||
|
|
||||||
|
DeptSaveReqVO childReq = new DeptSaveReqVO();
|
||||||
|
childReq.setParentId(topId);
|
||||||
|
childReq.setName("自建子");
|
||||||
|
childReq.setSort(1);
|
||||||
|
childReq.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||||
|
childReq.setDeptSource(DeptSourceEnum.EXTERNAL.getSource());
|
||||||
|
Long childId = deptService.createDept(childReq);
|
||||||
|
DeptDO child = deptMapper.selectById(childId);
|
||||||
|
assertEquals("CU001001", child.getCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreateDept_externalChildUnderSyncParentUsesCuPrefix() {
|
||||||
|
// 同步来源父级,使用 ZT 序列
|
||||||
|
DeptSaveReqVO syncTop = new DeptSaveReqVO();
|
||||||
|
syncTop.setParentId(DeptDO.PARENT_ID_ROOT);
|
||||||
|
syncTop.setName("同步父");
|
||||||
|
syncTop.setSort(1);
|
||||||
|
syncTop.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||||
|
syncTop.setDeptSource(DeptSourceEnum.SYNC.getSource());
|
||||||
|
Long syncTopId = deptService.createDept(syncTop);
|
||||||
|
DeptDO syncTopDept = deptMapper.selectById(syncTopId);
|
||||||
|
assertEquals("ZT001", syncTopDept.getCode());
|
||||||
|
|
||||||
|
// 在同步父级下新增外部子部门,前缀替换为 CU,序列与 ZT 独立
|
||||||
|
DeptSaveReqVO externalChild1 = new DeptSaveReqVO();
|
||||||
|
externalChild1.setParentId(syncTopId);
|
||||||
|
externalChild1.setName("外部子1");
|
||||||
|
externalChild1.setSort(1);
|
||||||
|
externalChild1.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||||
|
externalChild1.setDeptSource(DeptSourceEnum.EXTERNAL.getSource());
|
||||||
|
Long child1Id = deptService.createDept(externalChild1);
|
||||||
|
DeptDO child1 = deptMapper.selectById(child1Id);
|
||||||
|
assertEquals("CU001001", child1.getCode());
|
||||||
|
|
||||||
|
DeptSaveReqVO externalChild2 = new DeptSaveReqVO();
|
||||||
|
externalChild2.setParentId(syncTopId);
|
||||||
|
externalChild2.setName("外部子2");
|
||||||
|
externalChild2.setSort(2);
|
||||||
|
externalChild2.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||||
|
externalChild2.setDeptSource(DeptSourceEnum.EXTERNAL.getSource());
|
||||||
|
Long child2Id = deptService.createDept(externalChild2);
|
||||||
|
DeptDO child2 = deptMapper.selectById(child2Id);
|
||||||
|
assertEquals("CU001002", child2.getCode());
|
||||||
|
|
||||||
|
// 同步子部门仍使用 ZT 序列,不受 CU 序列影响
|
||||||
|
DeptSaveReqVO syncChild = new DeptSaveReqVO();
|
||||||
|
syncChild.setParentId(syncTopId);
|
||||||
|
syncChild.setName("同步子");
|
||||||
|
syncChild.setSort(3);
|
||||||
|
syncChild.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||||
|
syncChild.setDeptSource(DeptSourceEnum.SYNC.getSource());
|
||||||
|
Long syncChildId = deptService.createDept(syncChild);
|
||||||
|
DeptDO syncChildDept = deptMapper.selectById(syncChildId);
|
||||||
|
assertEquals("ZT001001", syncChildDept.getCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreateDept_iWorkFollowsGenerationAndIgnoresCustomCode() {
|
||||||
|
// iWork 顶级也应走 ZT 序列,忽略自定义 code
|
||||||
|
DeptSaveReqVO iworkTop = new DeptSaveReqVO();
|
||||||
|
iworkTop.setParentId(DeptDO.PARENT_ID_ROOT);
|
||||||
|
iworkTop.setName("iWork 顶级");
|
||||||
|
iworkTop.setSort(1);
|
||||||
|
iworkTop.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||||
|
iworkTop.setDeptSource(DeptSourceEnum.IWORK.getSource());
|
||||||
|
iworkTop.setCode("CUSTOM-ZT999");
|
||||||
|
Long topId = deptService.createDept(iworkTop);
|
||||||
|
DeptDO top = deptMapper.selectById(topId);
|
||||||
|
assertEquals("ZT001", top.getCode());
|
||||||
|
|
||||||
|
// 子级继承序列递增
|
||||||
|
DeptSaveReqVO childReq = new DeptSaveReqVO();
|
||||||
|
childReq.setParentId(topId);
|
||||||
|
childReq.setName("iWork 子");
|
||||||
|
childReq.setSort(1);
|
||||||
|
childReq.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||||
|
childReq.setDeptSource(DeptSourceEnum.IWORK.getSource());
|
||||||
|
Long childId = deptService.createDept(childReq);
|
||||||
|
DeptDO child = deptMapper.selectById(childId);
|
||||||
|
assertEquals("ZT001001", child.getCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUpdateDept_iWorkGeneratesOnceWhenMissingCode() {
|
||||||
|
// 先创建一个 iWork 顶级但延迟生成编码
|
||||||
|
DeptSaveReqVO createReq = new DeptSaveReqVO();
|
||||||
|
createReq.setParentId(DeptDO.PARENT_ID_ROOT);
|
||||||
|
createReq.setName("iWork 延迟");
|
||||||
|
createReq.setSort(1);
|
||||||
|
createReq.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||||
|
createReq.setDeptSource(DeptSourceEnum.IWORK.getSource());
|
||||||
|
createReq.setDelayCodeGeneration(true);
|
||||||
|
Long deptId = deptService.createDept(createReq);
|
||||||
|
DeptDO created = deptMapper.selectById(deptId);
|
||||||
|
assertNull(created.getCode());
|
||||||
|
|
||||||
|
// 更新时生成一次编码
|
||||||
|
DeptSaveReqVO updateReq = new DeptSaveReqVO();
|
||||||
|
updateReq.setId(deptId);
|
||||||
|
updateReq.setParentId(DeptDO.PARENT_ID_ROOT);
|
||||||
|
updateReq.setName("iWork 延迟");
|
||||||
|
updateReq.setSort(1);
|
||||||
|
updateReq.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||||
|
updateReq.setDeptSource(DeptSourceEnum.IWORK.getSource());
|
||||||
|
updateReq.setDelayCodeGeneration(false);
|
||||||
|
deptService.updateDept(updateReq);
|
||||||
|
|
||||||
|
DeptDO updated = deptMapper.selectById(deptId);
|
||||||
|
assertEquals("ZT001", updated.getCode());
|
||||||
|
|
||||||
|
// 再次更新(父级不变)保持编码不变
|
||||||
|
DeptSaveReqVO updateReq2 = new DeptSaveReqVO();
|
||||||
|
updateReq2.setId(deptId);
|
||||||
|
updateReq2.setParentId(DeptDO.PARENT_ID_ROOT);
|
||||||
|
updateReq2.setName("iWork 延迟2");
|
||||||
|
updateReq2.setSort(2);
|
||||||
|
updateReq2.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||||
|
updateReq2.setDeptSource(DeptSourceEnum.IWORK.getSource());
|
||||||
|
deptService.updateDept(updateReq2);
|
||||||
|
|
||||||
|
DeptDO updated2 = deptMapper.selectById(deptId);
|
||||||
|
assertEquals("ZT001", updated2.getCode());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCreateDept_topLevelAutoCode_ignoreCustomInput() {
|
public void testCreateDept_topLevelAutoCode_ignoreCustomInput() {
|
||||||
String customCode = "ROOT-001";
|
String customCode = "ROOT-001";
|
||||||
@@ -143,7 +321,7 @@ public class DeptServiceImplTest extends BaseDbUnitTest {
|
|||||||
topLevelReq.setName("集团");
|
topLevelReq.setName("集团");
|
||||||
topLevelReq.setSort(1);
|
topLevelReq.setSort(1);
|
||||||
topLevelReq.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
topLevelReq.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||||
topLevelReq.setDeptSource(1);
|
topLevelReq.setDeptSource(DeptSourceEnum.SYNC.getSource());
|
||||||
topLevelReq.setCode(customCode);
|
topLevelReq.setCode(customCode);
|
||||||
|
|
||||||
Long deptId = deptService.createDept(topLevelReq);
|
Long deptId = deptService.createDept(topLevelReq);
|
||||||
@@ -166,7 +344,7 @@ public class DeptServiceImplTest extends BaseDbUnitTest {
|
|||||||
o.setParentId(DeptDO.PARENT_ID_ROOT);
|
o.setParentId(DeptDO.PARENT_ID_ROOT);
|
||||||
o.setId(dbDeptDO.getId());
|
o.setId(dbDeptDO.getId());
|
||||||
o.setStatus(randomCommonStatus());
|
o.setStatus(randomCommonStatus());
|
||||||
}).setDeptSource(1);
|
}).setDeptSource(DeptSourceEnum.SYNC.getSource());
|
||||||
reqVO.setCode(dbDeptDO.getCode());
|
reqVO.setCode(dbDeptDO.getCode());
|
||||||
|
|
||||||
// 调用
|
// 调用
|
||||||
@@ -195,7 +373,7 @@ public class DeptServiceImplTest extends BaseDbUnitTest {
|
|||||||
updateReq.setParentId(parentBId);
|
updateReq.setParentId(parentBId);
|
||||||
updateReq.setSort(1);
|
updateReq.setSort(1);
|
||||||
updateReq.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
updateReq.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||||
updateReq.setDeptSource(1);
|
updateReq.setDeptSource(DeptSourceEnum.SYNC.getSource());
|
||||||
deptService.updateDept(updateReq);
|
deptService.updateDept(updateReq);
|
||||||
|
|
||||||
DeptDO updatedChild = deptMapper.selectById(childId);
|
DeptDO updatedChild = deptMapper.selectById(childId);
|
||||||
@@ -223,7 +401,7 @@ public class DeptServiceImplTest extends BaseDbUnitTest {
|
|||||||
updateReq1.setName("多系统部门");
|
updateReq1.setName("多系统部门");
|
||||||
updateReq1.setSort(1);
|
updateReq1.setSort(1);
|
||||||
updateReq1.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
updateReq1.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||||
updateReq1.setDeptSource(1);
|
updateReq1.setDeptSource(DeptSourceEnum.SYNC.getSource());
|
||||||
updateReq1.setExternalSystemCode("ERP");
|
updateReq1.setExternalSystemCode("ERP");
|
||||||
updateReq1.setExternalDeptCode("ERP-100");
|
updateReq1.setExternalDeptCode("ERP-100");
|
||||||
deptService.updateDept(updateReq1);
|
deptService.updateDept(updateReq1);
|
||||||
@@ -235,7 +413,7 @@ public class DeptServiceImplTest extends BaseDbUnitTest {
|
|||||||
updateReq2.setName("多系统部门");
|
updateReq2.setName("多系统部门");
|
||||||
updateReq2.setSort(1);
|
updateReq2.setSort(1);
|
||||||
updateReq2.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
updateReq2.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||||
updateReq2.setDeptSource(1);
|
updateReq2.setDeptSource(DeptSourceEnum.SYNC.getSource());
|
||||||
updateReq2.setExternalSystemCode("OA");
|
updateReq2.setExternalSystemCode("OA");
|
||||||
updateReq2.setExternalDeptCode("OA-100");
|
updateReq2.setExternalDeptCode("OA-100");
|
||||||
deptService.updateDept(updateReq2);
|
deptService.updateDept(updateReq2);
|
||||||
@@ -257,7 +435,7 @@ public class DeptServiceImplTest extends BaseDbUnitTest {
|
|||||||
createA.setName("iWork-A");
|
createA.setName("iWork-A");
|
||||||
createA.setSort(1);
|
createA.setSort(1);
|
||||||
createA.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
createA.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||||
createA.setDeptSource(1);
|
createA.setDeptSource(DeptSourceEnum.SYNC.getSource());
|
||||||
createA.setExternalSystemCode("IWORK");
|
createA.setExternalSystemCode("IWORK");
|
||||||
createA.setExternalDeptCode("IW-001");
|
createA.setExternalDeptCode("IW-001");
|
||||||
Long deptAId = deptService.createDept(createA);
|
Long deptAId = deptService.createDept(createA);
|
||||||
@@ -272,7 +450,7 @@ public class DeptServiceImplTest extends BaseDbUnitTest {
|
|||||||
createB.setName("iWork-B");
|
createB.setName("iWork-B");
|
||||||
createB.setSort(2);
|
createB.setSort(2);
|
||||||
createB.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
createB.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||||
createB.setDeptSource(1);
|
createB.setDeptSource(DeptSourceEnum.SYNC.getSource());
|
||||||
createB.setExternalSystemCode("IWORK");
|
createB.setExternalSystemCode("IWORK");
|
||||||
createB.setExternalDeptCode("IW-001");
|
createB.setExternalDeptCode("IW-001");
|
||||||
Long deptBId = deptService.createDept(createB);
|
Long deptBId = deptService.createDept(createB);
|
||||||
@@ -300,7 +478,7 @@ public class DeptServiceImplTest extends BaseDbUnitTest {
|
|||||||
updateReq.setName("子-更新");
|
updateReq.setName("子-更新");
|
||||||
updateReq.setSort(1);
|
updateReq.setSort(1);
|
||||||
updateReq.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
updateReq.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||||
updateReq.setDeptSource(1);
|
updateReq.setDeptSource(DeptSourceEnum.SYNC.getSource());
|
||||||
updateReq.setExternalSystemCode("IWORK");
|
updateReq.setExternalSystemCode("IWORK");
|
||||||
updateReq.setExternalDeptCode("IW-CHILD");
|
updateReq.setExternalDeptCode("IW-CHILD");
|
||||||
|
|
||||||
@@ -474,7 +652,7 @@ public class DeptServiceImplTest extends BaseDbUnitTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testGetDept() {
|
public void testGetDept() {
|
||||||
// mock 数据
|
// mock 数据
|
||||||
DeptDO deptDO = randomPojo(DeptDO.class).setDeptSource(1);
|
DeptDO deptDO = randomPojo(DeptDO.class).setDeptSource(DeptSourceEnum.SYNC.getSource());
|
||||||
deptMapper.insert(deptDO);
|
deptMapper.insert(deptDO);
|
||||||
// 准备参数
|
// 准备参数
|
||||||
Long id = deptDO.getId();
|
Long id = deptDO.getId();
|
||||||
@@ -488,9 +666,9 @@ public class DeptServiceImplTest extends BaseDbUnitTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testGetDeptList_ids() {
|
public void testGetDeptList_ids() {
|
||||||
// mock 数据
|
// mock 数据
|
||||||
DeptDO deptDO01 = randomPojo(DeptDO.class).setDeptSource(1);
|
DeptDO deptDO01 = randomPojo(DeptDO.class).setDeptSource(DeptSourceEnum.SYNC.getSource());
|
||||||
deptMapper.insert(deptDO01);
|
deptMapper.insert(deptDO01);
|
||||||
DeptDO deptDO02 = randomPojo(DeptDO.class).setDeptSource(1);
|
DeptDO deptDO02 = randomPojo(DeptDO.class).setDeptSource(DeptSourceEnum.SYNC.getSource());
|
||||||
deptMapper.insert(deptDO02);
|
deptMapper.insert(deptDO02);
|
||||||
// 准备参数
|
// 准备参数
|
||||||
List<Long> ids = Arrays.asList(deptDO01.getId(), deptDO02.getId());
|
List<Long> ids = Arrays.asList(deptDO01.getId(), deptDO02.getId());
|
||||||
@@ -511,7 +689,7 @@ public class DeptServiceImplTest extends BaseDbUnitTest {
|
|||||||
o.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
o.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||||
o.setParentId(DeptDO.PARENT_ID_ROOT);
|
o.setParentId(DeptDO.PARENT_ID_ROOT);
|
||||||
o.setSort(1);
|
o.setSort(1);
|
||||||
}).setDeptSource(1);
|
}).setDeptSource(DeptSourceEnum.SYNC.getSource());
|
||||||
deptMapper.insert(dept);
|
deptMapper.insert(dept);
|
||||||
// 测试 name 不匹配
|
// 测试 name 不匹配
|
||||||
deptMapper.insert(ObjectUtils.cloneIgnoreId(dept, o -> {
|
deptMapper.insert(ObjectUtils.cloneIgnoreId(dept, o -> {
|
||||||
@@ -542,14 +720,14 @@ public class DeptServiceImplTest extends BaseDbUnitTest {
|
|||||||
o.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
o.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||||
o.setParentId(DeptDO.PARENT_ID_ROOT);
|
o.setParentId(DeptDO.PARENT_ID_ROOT);
|
||||||
o.setSort(1);
|
o.setSort(1);
|
||||||
}).setDeptSource(1);
|
}).setDeptSource(DeptSourceEnum.SYNC.getSource());
|
||||||
deptMapper.insert(dept1);
|
deptMapper.insert(dept1);
|
||||||
DeptDO dept2 = randomPojo(DeptDO.class, o -> {
|
DeptDO dept2 = randomPojo(DeptDO.class, o -> {
|
||||||
o.setName("集团二部");
|
o.setName("集团二部");
|
||||||
o.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
o.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||||
o.setParentId(DeptDO.PARENT_ID_ROOT);
|
o.setParentId(DeptDO.PARENT_ID_ROOT);
|
||||||
o.setSort(2);
|
o.setSort(2);
|
||||||
}).setDeptSource(1);
|
}).setDeptSource(DeptSourceEnum.SYNC.getSource());
|
||||||
deptMapper.insert(dept2);
|
deptMapper.insert(dept2);
|
||||||
DeptDO otherDept = randomPojo(DeptDO.class, o -> {
|
DeptDO otherDept = randomPojo(DeptDO.class, o -> {
|
||||||
o.setName("其他部门");
|
o.setName("其他部门");
|
||||||
@@ -573,14 +751,14 @@ public class DeptServiceImplTest extends BaseDbUnitTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testGetChildDeptList() {
|
public void testGetChildDeptList() {
|
||||||
// mock 数据(1 级别子节点)
|
// mock 数据(1 级别子节点)
|
||||||
DeptDO dept1 = randomPojo(DeptDO.class, o -> o.setName("1")).setDeptSource(1);
|
DeptDO dept1 = randomPojo(DeptDO.class, o -> o.setName("1")).setDeptSource(DeptSourceEnum.SYNC.getSource());
|
||||||
deptMapper.insert(dept1);
|
deptMapper.insert(dept1);
|
||||||
DeptDO dept2 = randomPojo(DeptDO.class, o -> o.setName("2")).setDeptSource(1);
|
DeptDO dept2 = randomPojo(DeptDO.class, o -> o.setName("2")).setDeptSource(DeptSourceEnum.SYNC.getSource());
|
||||||
deptMapper.insert(dept2);
|
deptMapper.insert(dept2);
|
||||||
// mock 数据(2 级子节点)
|
// mock 数据(2 级子节点)
|
||||||
DeptDO dept1a = randomPojo(DeptDO.class, o -> o.setName("1-a").setParentId(dept1.getId())).setDeptSource(1);
|
DeptDO dept1a = randomPojo(DeptDO.class, o -> o.setName("1-a").setParentId(dept1.getId())).setDeptSource(DeptSourceEnum.SYNC.getSource());
|
||||||
deptMapper.insert(dept1a);
|
deptMapper.insert(dept1a);
|
||||||
DeptDO dept2a = randomPojo(DeptDO.class, o -> o.setName("2-a").setParentId(dept2.getId())).setDeptSource(1);
|
DeptDO dept2a = randomPojo(DeptDO.class, o -> o.setName("2-a").setParentId(dept2.getId())).setDeptSource(DeptSourceEnum.SYNC.getSource());
|
||||||
deptMapper.insert(dept2a);
|
deptMapper.insert(dept2a);
|
||||||
// 准备参数
|
// 准备参数
|
||||||
Long id = dept1.getParentId();
|
Long id = dept1.getParentId();
|
||||||
@@ -596,14 +774,14 @@ public class DeptServiceImplTest extends BaseDbUnitTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testGetChildDeptListFromCache() {
|
public void testGetChildDeptListFromCache() {
|
||||||
// mock 数据(1 级别子节点)
|
// mock 数据(1 级别子节点)
|
||||||
DeptDO dept1 = randomPojo(DeptDO.class, o -> o.setName("1")).setDeptSource(1);
|
DeptDO dept1 = randomPojo(DeptDO.class, o -> o.setName("1")).setDeptSource(DeptSourceEnum.SYNC.getSource());
|
||||||
deptMapper.insert(dept1);
|
deptMapper.insert(dept1);
|
||||||
DeptDO dept2 = randomPojo(DeptDO.class, o -> o.setName("2")).setDeptSource(1);
|
DeptDO dept2 = randomPojo(DeptDO.class, o -> o.setName("2")).setDeptSource(DeptSourceEnum.SYNC.getSource());
|
||||||
deptMapper.insert(dept2);
|
deptMapper.insert(dept2);
|
||||||
// mock 数据(2 级子节点)
|
// mock 数据(2 级子节点)
|
||||||
DeptDO dept1a = randomPojo(DeptDO.class, o -> o.setName("1-a").setParentId(dept1.getId())).setDeptSource(1);
|
DeptDO dept1a = randomPojo(DeptDO.class, o -> o.setName("1-a").setParentId(dept1.getId())).setDeptSource(DeptSourceEnum.SYNC.getSource());
|
||||||
deptMapper.insert(dept1a);
|
deptMapper.insert(dept1a);
|
||||||
DeptDO dept2a = randomPojo(DeptDO.class, o -> o.setName("2-a").setParentId(dept2.getId())).setDeptSource(1);
|
DeptDO dept2a = randomPojo(DeptDO.class, o -> o.setName("2-a").setParentId(dept2.getId())).setDeptSource(DeptSourceEnum.SYNC.getSource());
|
||||||
deptMapper.insert(dept2a);
|
deptMapper.insert(dept2a);
|
||||||
// 准备参数
|
// 准备参数
|
||||||
Long id = dept1.getParentId();
|
Long id = dept1.getParentId();
|
||||||
@@ -689,4 +867,48 @@ public class DeptServiceImplTest extends BaseDbUnitTest {
|
|||||||
assertEquals("ZT001002", updatedChild2.getCode());
|
assertEquals("ZT001002", updatedChild2.getCode());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreateDept_delayCodeGeneration_thenGenerateWhenParentReady() {
|
||||||
|
Long missingParentId = 900L;
|
||||||
|
|
||||||
|
DeptSaveReqVO childReq = new DeptSaveReqVO();
|
||||||
|
childReq.setParentId(missingParentId);
|
||||||
|
childReq.setName("延迟子部门");
|
||||||
|
childReq.setSort(1);
|
||||||
|
childReq.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||||
|
childReq.setDeptSource(DeptSourceEnum.SYNC.getSource());
|
||||||
|
childReq.setDelayCodeGeneration(true);
|
||||||
|
|
||||||
|
Long childId = deptService.createDept(childReq);
|
||||||
|
DeptDO child = deptMapper.selectById(childId);
|
||||||
|
assertNotNull(childId);
|
||||||
|
assertNull(child.getCode());
|
||||||
|
|
||||||
|
// 后补父级并赋予编码
|
||||||
|
DeptDO parent = new DeptDO();
|
||||||
|
parent.setId(missingParentId);
|
||||||
|
parent.setParentId(DeptDO.PARENT_ID_ROOT);
|
||||||
|
parent.setName("后补父级");
|
||||||
|
parent.setCode("ZT900");
|
||||||
|
parent.setSort(1);
|
||||||
|
parent.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||||
|
parent.setDeptSource(DeptSourceEnum.SYNC.getSource());
|
||||||
|
deptMapper.insert(parent);
|
||||||
|
|
||||||
|
// 触发子部门生成编码
|
||||||
|
DeptSaveReqVO updateReq = new DeptSaveReqVO();
|
||||||
|
updateReq.setId(childId);
|
||||||
|
updateReq.setParentId(missingParentId);
|
||||||
|
updateReq.setName("延迟子部门");
|
||||||
|
updateReq.setSort(1);
|
||||||
|
updateReq.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||||
|
updateReq.setDeptSource(DeptSourceEnum.SYNC.getSource());
|
||||||
|
updateReq.setDelayCodeGeneration(false);
|
||||||
|
|
||||||
|
deptService.updateDept(updateReq);
|
||||||
|
|
||||||
|
DeptDO updatedChild = deptMapper.selectById(childId);
|
||||||
|
assertEquals(parent.getCode() + "001", updatedChild.getCode());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,141 @@
|
|||||||
|
package com.zt.plat.module.system.service.integration.iwork.impl;
|
||||||
|
|
||||||
|
import com.zt.plat.framework.test.core.ut.BaseMockitoUnitTest;
|
||||||
|
import com.zt.plat.module.system.controller.admin.dept.vo.dept.DeptSaveReqVO;
|
||||||
|
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkHrDepartmentPageRespVO;
|
||||||
|
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkHrSubcompanyPageRespVO;
|
||||||
|
import com.zt.plat.module.system.dal.mysql.dept.PostMapper;
|
||||||
|
import com.zt.plat.module.system.dal.mysql.user.AdminUserMapper;
|
||||||
|
import com.zt.plat.module.system.service.dept.DeptService;
|
||||||
|
import com.zt.plat.module.system.service.dept.PostService;
|
||||||
|
import com.zt.plat.module.system.service.integration.iwork.IWorkSyncProcessor;
|
||||||
|
import com.zt.plat.module.system.service.user.AdminUserService;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for cross-page pending handling and placeholder backfill in IWorkSyncProcessorImpl.
|
||||||
|
*/
|
||||||
|
class IWorkSyncProcessorImplTest extends BaseMockitoUnitTest {
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private IWorkSyncProcessorImpl processor;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private DeptService deptService;
|
||||||
|
@Mock
|
||||||
|
private PostService postService;
|
||||||
|
@Mock
|
||||||
|
private PostMapper postMapper;
|
||||||
|
@Mock
|
||||||
|
private AdminUserService adminUserService;
|
||||||
|
@Mock
|
||||||
|
private AdminUserMapper adminUserMapper;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldProcessPendingChildWhenParentArrivesInLaterPage() {
|
||||||
|
IWorkSyncProcessor.DeptSyncContext context = new IWorkSyncProcessor.DeptSyncContext();
|
||||||
|
IWorkSyncProcessor.SyncOptions options = IWorkSyncProcessor.SyncOptions.custom(true, true, true);
|
||||||
|
|
||||||
|
IWorkHrDepartmentPageRespVO.Department child = new IWorkHrDepartmentPageRespVO.Department();
|
||||||
|
child.setId(200);
|
||||||
|
child.setDepartmentname("child");
|
||||||
|
child.setSupdepid(100); // parent comes later
|
||||||
|
|
||||||
|
IWorkHrDepartmentPageRespVO.Department parent = new IWorkHrDepartmentPageRespVO.Department();
|
||||||
|
parent.setId(100);
|
||||||
|
parent.setDepartmentname("parent");
|
||||||
|
parent.setSupdepid(0); // root
|
||||||
|
|
||||||
|
when(deptService.getDept(anyLong())).thenReturn(null);
|
||||||
|
when(deptService.createDept(any(DeptSaveReqVO.class))).thenReturn(100L, 200L);
|
||||||
|
|
||||||
|
processor.syncDepartments(List.of(child), options, context);
|
||||||
|
|
||||||
|
verify(deptService, never()).createDept(any());
|
||||||
|
assertEquals(1, context.getPendingDepartments().size());
|
||||||
|
|
||||||
|
processor.syncDepartments(List.of(parent), options, context);
|
||||||
|
|
||||||
|
verify(deptService, times(2)).createDept(any());
|
||||||
|
assertTrue(context.getPendingDepartments().isEmpty(), "pending should be cleared after parent processed");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldInsertPlaceholderWhenParentMissingAfterFlush() {
|
||||||
|
IWorkSyncProcessor.DeptSyncContext context = new IWorkSyncProcessor.DeptSyncContext();
|
||||||
|
IWorkSyncProcessor.SyncOptions options = IWorkSyncProcessor.SyncOptions.custom(true, true, true);
|
||||||
|
|
||||||
|
IWorkHrDepartmentPageRespVO.Department child = new IWorkHrDepartmentPageRespVO.Department();
|
||||||
|
child.setId(300);
|
||||||
|
child.setDepartmentname("orphan");
|
||||||
|
child.setSupdepid(9999); // never provided
|
||||||
|
|
||||||
|
when(deptService.getDept(anyLong())).thenReturn(null);
|
||||||
|
when(deptService.createDept(any(DeptSaveReqVO.class))).thenReturn(300L);
|
||||||
|
|
||||||
|
processor.syncDepartments(List.of(child), options, context);
|
||||||
|
assertEquals(1, context.getPendingDepartments().size());
|
||||||
|
|
||||||
|
IWorkSyncProcessor.BatchResult flushResult = processor.flushDeptPending(context, options);
|
||||||
|
assertNotNull(flushResult);
|
||||||
|
|
||||||
|
ArgumentCaptor<DeptSaveReqVO> captor = ArgumentCaptor.forClass(DeptSaveReqVO.class);
|
||||||
|
verify(deptService, times(1)).createDept(captor.capture());
|
||||||
|
DeptSaveReqVO placeholderReq = captor.getValue();
|
||||||
|
assertTrue(Boolean.TRUE.equals(placeholderReq.getDelayCodeGeneration()));
|
||||||
|
assertNull(placeholderReq.getCode());
|
||||||
|
|
||||||
|
assertTrue(context.getPendingDepartments().isEmpty(), "pending should be cleared after placeholder insert");
|
||||||
|
assertTrue(context.getPlaceholderDeptIds().contains(300L));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldKeepExternalCodeNullWhenDepartmentCodeBlank() {
|
||||||
|
IWorkSyncProcessor.SyncOptions options = IWorkSyncProcessor.SyncOptions.custom(true, true, true);
|
||||||
|
|
||||||
|
IWorkHrDepartmentPageRespVO.Department dept = new IWorkHrDepartmentPageRespVO.Department();
|
||||||
|
dept.setId(500);
|
||||||
|
dept.setDepartmentname("blank-code-dept");
|
||||||
|
dept.setDepartmentcode(" ");
|
||||||
|
dept.setSupdepid(0);
|
||||||
|
|
||||||
|
when(deptService.getDept(anyLong())).thenReturn(null);
|
||||||
|
when(deptService.createDept(any(DeptSaveReqVO.class))).thenReturn(500L);
|
||||||
|
|
||||||
|
processor.syncDepartments(List.of(dept), options, null);
|
||||||
|
|
||||||
|
ArgumentCaptor<DeptSaveReqVO> captor = ArgumentCaptor.forClass(DeptSaveReqVO.class);
|
||||||
|
verify(deptService, times(1)).createDept(captor.capture());
|
||||||
|
DeptSaveReqVO req = captor.getValue();
|
||||||
|
assertNull(req.getExternalDeptCode(), "externalDeptCode should remain null when source code is blank");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldKeepExternalCodeNullWhenSubcompanyCodeBlank() {
|
||||||
|
IWorkSyncProcessor.SyncOptions options = IWorkSyncProcessor.SyncOptions.custom(true, true, true);
|
||||||
|
|
||||||
|
IWorkHrSubcompanyPageRespVO.Subcompany subcompany = new IWorkHrSubcompanyPageRespVO.Subcompany();
|
||||||
|
subcompany.setId(600);
|
||||||
|
subcompany.setSubcompanyname("blank-code-sub");
|
||||||
|
subcompany.setSubcompanycode(null);
|
||||||
|
subcompany.setSupsubcomid(0);
|
||||||
|
|
||||||
|
when(deptService.getDept(anyLong())).thenReturn(null);
|
||||||
|
when(deptService.createDept(any(DeptSaveReqVO.class))).thenReturn(600L);
|
||||||
|
|
||||||
|
processor.syncSubcompanies(List.of(subcompany), options, null);
|
||||||
|
|
||||||
|
ArgumentCaptor<DeptSaveReqVO> captor = ArgumentCaptor.forClass(DeptSaveReqVO.class);
|
||||||
|
verify(deptService, times(1)).createDept(captor.capture());
|
||||||
|
DeptSaveReqVO req = captor.getValue();
|
||||||
|
assertNull(req.getExternalDeptCode(), "externalDeptCode should remain null when source code is null or blank");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
package com.zt.plat.module.system.service.integration.iwork.impl;
|
||||||
|
|
||||||
|
import com.zt.plat.framework.test.core.ut.BaseMockitoUnitTest;
|
||||||
|
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkFullSyncReqVO;
|
||||||
|
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkHrDepartmentPageRespVO;
|
||||||
|
import com.zt.plat.module.system.enums.integration.IWorkSyncEntityTypeEnum;
|
||||||
|
import com.zt.plat.module.system.service.dept.DeptService;
|
||||||
|
import com.zt.plat.module.system.service.integration.iwork.IWorkOrgRestService;
|
||||||
|
import com.zt.plat.module.system.service.integration.iwork.IWorkSyncProcessor;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.stubbing.Answer;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyInt;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
|
class IWorkSyncServiceImplTest extends BaseMockitoUnitTest {
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private IWorkSyncServiceImpl syncService;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private IWorkOrgRestService orgRestService;
|
||||||
|
@Mock
|
||||||
|
private IWorkSyncProcessor syncProcessor;
|
||||||
|
@Mock
|
||||||
|
private DeptService deptService;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldBackfillCodesWhenPlaceholdersExistAfterFullSync() {
|
||||||
|
IWorkFullSyncReqVO reqVO = new IWorkFullSyncReqVO();
|
||||||
|
reqVO.setPageSize(1);
|
||||||
|
reqVO.setMaxPages(1);
|
||||||
|
|
||||||
|
IWorkHrDepartmentPageRespVO pageResp = new IWorkHrDepartmentPageRespVO();
|
||||||
|
pageResp.setSuccess(true);
|
||||||
|
IWorkHrDepartmentPageRespVO.Department dept = new IWorkHrDepartmentPageRespVO.Department();
|
||||||
|
dept.setId(1);
|
||||||
|
pageResp.setDataList(List.of(dept));
|
||||||
|
when(orgRestService.listDepartments(any())).thenReturn(pageResp);
|
||||||
|
|
||||||
|
// 在部门同步时标记占位 ID
|
||||||
|
doAnswer((Answer<IWorkSyncProcessor.BatchResult>) invocation -> {
|
||||||
|
IWorkSyncProcessor.DeptSyncContext context = invocation.getArgument(2);
|
||||||
|
if (context != null) {
|
||||||
|
context.getPlaceholderDeptIds().add(500L);
|
||||||
|
}
|
||||||
|
return IWorkSyncProcessor.BatchResult.empty();
|
||||||
|
}).when(syncProcessor).syncDepartments(any(), any(), any(IWorkSyncProcessor.DeptSyncContext.class));
|
||||||
|
|
||||||
|
when(syncProcessor.flushDeptPending(any(), any())).thenReturn(IWorkSyncProcessor.BatchResult.empty());
|
||||||
|
|
||||||
|
syncService.fullSyncDepartments(reqVO);
|
||||||
|
|
||||||
|
verify(deptService, times(1)).backfillMissingCodesWithoutEvent(argThat(set -> set.contains(500L)));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
package com.zt.plat.module.system.service.permission;
|
package com.zt.plat.module.system.service.permission;
|
||||||
|
|
||||||
import com.zt.plat.framework.common.exception.ServiceException;
|
import com.zt.plat.framework.common.exception.ServiceException;
|
||||||
|
import com.zt.plat.framework.common.enums.CommonStatusEnum;
|
||||||
import com.zt.plat.framework.test.core.ut.BaseDbUnitTest;
|
import com.zt.plat.framework.test.core.ut.BaseDbUnitTest;
|
||||||
|
import com.zt.plat.framework.common.util.json.JsonUtils;
|
||||||
import com.zt.plat.module.system.controller.admin.permission.vo.role.RoleSaveReqVO;
|
import com.zt.plat.module.system.controller.admin.permission.vo.role.RoleSaveReqVO;
|
||||||
import com.zt.plat.module.system.dal.dataobject.permission.RoleDO;
|
import com.zt.plat.module.system.dal.dataobject.permission.RoleDO;
|
||||||
import com.zt.plat.module.system.dal.dataobject.permission.RoleMenuDO;
|
import com.zt.plat.module.system.dal.dataobject.permission.RoleMenuDO;
|
||||||
@@ -11,6 +13,7 @@ import com.zt.plat.module.system.dal.mysql.permission.RoleMapper;
|
|||||||
import com.zt.plat.module.system.dal.mysql.permission.RoleMenuMapper;
|
import com.zt.plat.module.system.dal.mysql.permission.RoleMenuMapper;
|
||||||
import com.zt.plat.module.system.dal.mysql.permission.UserRoleMapper;
|
import com.zt.plat.module.system.dal.mysql.permission.UserRoleMapper;
|
||||||
import com.zt.plat.module.system.dal.mysql.rolemenuexclusion.RoleMenuExclusionMapper;
|
import com.zt.plat.module.system.dal.mysql.rolemenuexclusion.RoleMenuExclusionMapper;
|
||||||
|
import com.zt.plat.module.system.enums.permission.DataScopeEnum;
|
||||||
import com.zt.plat.module.system.enums.permission.RoleTypeEnum;
|
import com.zt.plat.module.system.enums.permission.RoleTypeEnum;
|
||||||
import com.zt.plat.module.system.service.dept.DeptService;
|
import com.zt.plat.module.system.service.dept.DeptService;
|
||||||
import com.zt.plat.module.system.service.user.AdminUserService;
|
import com.zt.plat.module.system.service.user.AdminUserService;
|
||||||
@@ -408,4 +411,54 @@ public class PermissionServiceTest extends BaseDbUnitTest {
|
|||||||
assertEquals(1, exclusionDOS.size());
|
assertEquals(1, exclusionDOS.size());
|
||||||
assertEquals(101L, exclusionDOS.get(0).getMenuId());
|
assertEquals(101L, exclusionDOS.get(0).getMenuId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetUserDataPermissionLevel_noRolesReturnSelf() {
|
||||||
|
Long userId = 1000L;
|
||||||
|
|
||||||
|
DataScopeEnum result = permissionService.getUserDataPermissionLevel(userId);
|
||||||
|
|
||||||
|
assertEquals(DataScopeEnum.SELF, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetUserDataPermissionLevel_pickHighestPriority() {
|
||||||
|
Long userId = 2000L;
|
||||||
|
RoleDO roleCustom = randomPojo(RoleDO.class, o -> o
|
||||||
|
.setStatus(CommonStatusEnum.ENABLE.getStatus())
|
||||||
|
.setDataScope(DataScopeEnum.DEPT_CUSTOM.getScope())
|
||||||
|
.setId(110L)
|
||||||
|
.setTenantId(0L));
|
||||||
|
roleMapper.insert(roleCustom);
|
||||||
|
RoleDO roleCompany = randomPojo(RoleDO.class, o -> o
|
||||||
|
.setStatus(CommonStatusEnum.ENABLE.getStatus())
|
||||||
|
.setDataScope(DataScopeEnum.COMPANY_AND_DEPT.getScope())
|
||||||
|
.setId(120L)
|
||||||
|
.setTenantId(0L));
|
||||||
|
roleMapper.insert(roleCompany);
|
||||||
|
|
||||||
|
userRoleMapper.insert(randomPojo(UserRoleDO.class, o -> o.setUserId(userId).setRoleId(roleCustom.getId())));
|
||||||
|
userRoleMapper.insert(randomPojo(UserRoleDO.class, o -> o.setUserId(userId).setRoleId(roleCompany.getId())));
|
||||||
|
|
||||||
|
DataScopeEnum result = permissionService.getUserDataPermissionLevel(userId);
|
||||||
|
|
||||||
|
assertEquals(DataScopeEnum.COMPANY_AND_DEPT, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetUserDataPermissionLevel_serializeAsNumber() {
|
||||||
|
Long userId = 3000L;
|
||||||
|
RoleDO roleAll = randomPojo(RoleDO.class, o -> o
|
||||||
|
.setStatus(CommonStatusEnum.ENABLE.getStatus())
|
||||||
|
.setDataScope(DataScopeEnum.ALL.getScope())
|
||||||
|
.setId(210L)
|
||||||
|
.setTenantId(0L));
|
||||||
|
roleMapper.insert(roleAll);
|
||||||
|
userRoleMapper.insert(randomPojo(UserRoleDO.class, o -> o.setUserId(userId).setRoleId(roleAll.getId())));
|
||||||
|
|
||||||
|
DataScopeEnum result = permissionService.getUserDataPermissionLevel(userId);
|
||||||
|
|
||||||
|
assertEquals(DataScopeEnum.ALL, result);
|
||||||
|
assertEquals("1", JsonUtils.toJsonString(result));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ create table IF NOT EXISTS system_user_dept (
|
|||||||
);
|
);
|
||||||
CREATE TABLE IF NOT EXISTS "system_dept" (
|
CREATE TABLE IF NOT EXISTS "system_dept" (
|
||||||
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
|
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
|
||||||
"code" varchar(64) NOT NULL DEFAULT '',
|
"code" varchar(64) DEFAULT NULL,
|
||||||
"name" varchar(30) NOT NULL DEFAULT '',
|
"name" varchar(30) NOT NULL DEFAULT '',
|
||||||
"short_name" varchar(30) DEFAULT '',
|
"short_name" varchar(30) DEFAULT '',
|
||||||
"parent_id" bigint NOT NULL DEFAULT '0',
|
"parent_id" bigint NOT NULL DEFAULT '0',
|
||||||
@@ -51,9 +51,8 @@ CREATE TABLE IF NOT EXISTS "system_dept_external_code" (
|
|||||||
PRIMARY KEY ("id")
|
PRIMARY KEY ("id")
|
||||||
) COMMENT '部门外部组织编码映射';
|
) COMMENT '部门外部组织编码映射';
|
||||||
|
|
||||||
CREATE UNIQUE INDEX IF NOT EXISTS "uk_system_dept_external_code_ext" ON "system_dept_external_code" ("tenant_id", "system_code", "external_dept_code");
|
CREATE INDEX IF NOT EXISTS "idx_system_dept_external_code_ext" ON "system_dept_external_code" ("tenant_id", "system_code", "external_dept_code");
|
||||||
CREATE UNIQUE INDEX IF NOT EXISTS "uk_system_dept_external_code_dept" ON "system_dept_external_code" ("tenant_id", "system_code", "dept_id");
|
CREATE INDEX IF NOT EXISTS "idx_system_dept_external_code_dept" ON "system_dept_external_code" ("tenant_id", "system_code", "dept_id");
|
||||||
CREATE INDEX IF NOT EXISTS "idx_system_dept_external_code_dept" ON "system_dept_external_code" ("tenant_id", "dept_id");
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS "system_dict_data" (
|
CREATE TABLE IF NOT EXISTS "system_dict_data" (
|
||||||
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
|
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
|
||||||
|
|||||||
Reference in New Issue
Block a user