diff --git a/zt-module-databus/zt-module-databus-server/src/test/java/com/zt/plat/module/databus/framework/integration/gateway/sample/DatabusApiInvocationExample.java b/zt-module-databus/zt-module-databus-server/src/test/java/com/zt/plat/module/databus/framework/integration/gateway/sample/DatabusApiInvocationExample.java index d0bd9492..5bd323d3 100644 --- a/zt-module-databus/zt-module-databus-server/src/test/java/com/zt/plat/module/databus/framework/integration/gateway/sample/DatabusApiInvocationExample.java +++ b/zt-module-databus/zt-module-databus-server/src/test/java/com/zt/plat/module/databus/framework/integration/gateway/sample/DatabusApiInvocationExample.java @@ -16,11 +16,17 @@ import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.cert.X509Certificate; import java.time.Duration; import java.util.LinkedHashMap; import java.util.Map; import java.util.TreeMap; 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。 @@ -37,12 +43,12 @@ public final class DatabusApiInvocationExample { // 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 = "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/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 HttpClient HTTP_CLIENT = HttpClient.newBuilder() - .connectTimeout(Duration.ofSeconds(5)) - .build(); + // ⚠️ 仅用于联调:信任所有证书 + 关闭主机名校验,生产环境请改为受信 CA 或自定义 truststore。 + private static final HttpClient HTTP_CLIENT = buildUnsafeHttpClient(); private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); private static final PrintStream OUT = buildConsolePrintStream(); public static final String ZT_APP_ID = "ZT-App-Id"; @@ -55,6 +61,45 @@ public final class DatabusApiInvocationExample { private DatabusApiInvocationExample() { } + /** + * 仅用于联调:信任所有证书并关闭主机名校验,生产环境请使用受信 CA 或自定义 truststore。 + */ + private static HttpClient buildUnsafeHttpClient() { + try { + TrustManager[] trustAll = new TrustManager[]{ + new X509TrustManager() { + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) { + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) { + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + } + }; + + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, trustAll, new SecureRandom()); + + SSLParameters sslParameters = new SSLParameters(); + // 关闭主机名校验 + sslParameters.setEndpointIdentificationAlgorithm(""); + + return HttpClient.newBuilder() + .sslContext(sslContext) + .sslParameters(sslParameters) + .connectTimeout(Duration.ofSeconds(5)) + .build(); + } catch (Exception ex) { + throw new IllegalStateException("Failed to build unsafe HttpClient", ex); + } + } + public static void main(String[] args) throws Exception { OUT.println("=== GET 请求示例 ==="); // executeGetExample(); diff --git a/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/dept/dto/DeptSaveReqDTO.java b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/dept/dto/DeptSaveReqDTO.java index 59418d5c..fbb311cc 100644 --- a/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/dept/dto/DeptSaveReqDTO.java +++ b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/dept/dto/DeptSaveReqDTO.java @@ -36,4 +36,13 @@ public class DeptSaveReqDTO { @Schema(description = "状态,见 CommonStatusEnum 枚举0 开启 1 关闭", example = "0") private Integer status; + @Schema(description = "外部系统标识,用于建立编码映射", example = "ERP") + private String externalSystemCode; + + @Schema(description = "外部系统组织编码,用于建立映射", example = "ERP-001") + private String externalDeptCode; + + @Schema(description = "外部系统组织名称", example = "ERP总部") + private String externalDeptName; + } \ No newline at end of file diff --git a/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/enums/DictTypeConstants.java b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/enums/DictTypeConstants.java index e8b5038f..a913df2b 100644 --- a/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/enums/DictTypeConstants.java +++ b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/enums/DictTypeConstants.java @@ -23,4 +23,6 @@ public interface DictTypeConstants { String SMS_SEND_STATUS = "system_sms_send_status"; // 短信发送状态 String SMS_RECEIVE_STATUS = "system_sms_receive_status"; // 短信接收状态 + String DEPT_EXTERNAL_SYSTEM = "system_dept_external_system"; // 部门外部系统标识 + } diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/dept/vo/dept/DeptSaveReqVO.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/dept/vo/dept/DeptSaveReqVO.java index 94a755e8..a850f311 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/dept/vo/dept/DeptSaveReqVO.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/dept/vo/dept/DeptSaveReqVO.java @@ -23,6 +23,15 @@ public class DeptSaveReqVO { @Size(max = 50, message = "部门编码长度不能超过 50 个字符") private String code; + @Schema(description = "外部系统标识,用于建立编码映射", example = "ERP") + private String externalSystemCode; + + @Schema(description = "外部系统组织编码,用于建立映射", example = "ERP-001") + private String externalDeptCode; + + @Schema(description = "外部系统组织名称", example = "ERP总部") + private String externalDeptName; + @Schema(description = "部门名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "ZT") @NotBlank(message = "部门名称不能为空") @Size(max = 30, message = "部门名称长度不能超过 30 个字符") diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/dept/DeptExternalCodeMapper.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/dept/DeptExternalCodeMapper.java index 0a9dbd57..910857f9 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/dept/DeptExternalCodeMapper.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/dept/DeptExternalCodeMapper.java @@ -37,6 +37,10 @@ public interface DeptExternalCodeMapper extends BaseMapperX return selectList(DeptExternalCodeDO::getDeptId, deptId); } + default int deleteByDeptId(Long deptId) { + return delete(DeptExternalCodeDO::getDeptId, deptId); + } + default List selectListBySystemCode(String systemCode) { return selectList(DeptExternalCodeDO::getSystemCode, systemCode); } diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/redis/RedisKeyConstants.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/redis/RedisKeyConstants.java index 339e07f3..13c4fc39 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/redis/RedisKeyConstants.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/redis/RedisKeyConstants.java @@ -17,6 +17,14 @@ public interface RedisKeyConstants { */ String DEPT_CHILDREN_ID_LIST = "dept_children_ids"; + /** + * 指定部门的外部组织编码映射列表缓存 + *

+ * KEY 格式:dept_external_code_list:{deptId} + * VALUE 数据类型:String 映射列表 + */ + String DEPT_EXTERNAL_CODE_LIST = "dept_external_code_list"; + /** * 角色的缓存 *

diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/DeptExternalCodeService.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/DeptExternalCodeService.java index 43a434be..43fc3a86 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/DeptExternalCodeService.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/DeptExternalCodeService.java @@ -49,6 +49,26 @@ public interface DeptExternalCodeService { */ List getDeptExternalCodeListByDeptId(Long deptId); + /** + * 根据部门与外部系统保存/更新映射(存在则更新,不存在则创建) + * + * @param deptId 本系统部门 ID + * @param systemCode 外部系统标识 + * @param externalDeptCode 外部系统组织编码 + * @param externalDeptName 外部系统组织名称(可选) + * @param status 状态,默认启用 + * @return 映射记录 ID + */ + Long saveOrUpdateDeptExternalCode(Long deptId, String systemCode, String externalDeptCode, String externalDeptName, + Integer status); + + /** + * 根据部门删除全部外部编码映射 + * + * @param deptId 部门编号 + */ + void deleteDeptExternalCodesByDeptId(Long deptId); + /** * 根据外部系统与外部组织编码查询映射 */ diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/DeptExternalCodeServiceImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/DeptExternalCodeServiceImpl.java index 5ee5384f..2084a5c4 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/DeptExternalCodeServiceImpl.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/DeptExternalCodeServiceImpl.java @@ -9,7 +9,12 @@ import com.zt.plat.module.system.controller.admin.dept.vo.depexternalcode.DeptEx 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.mysql.dept.DeptExternalCodeMapper; +import com.zt.plat.module.system.dal.mysql.dept.DeptMapper; +import com.zt.plat.module.system.dal.redis.RedisKeyConstants; import jakarta.annotation.Resource; +import org.springframework.cache.CacheManager; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; @@ -28,9 +33,12 @@ public class DeptExternalCodeServiceImpl implements DeptExternalCodeService { @Resource private DeptExternalCodeMapper deptExternalCodeMapper; @Resource - private DeptService deptService; + private DeptMapper deptMapper; + @Resource + private CacheManager cacheManager; @Override + @CacheEvict(cacheNames = RedisKeyConstants.DEPT_EXTERNAL_CODE_LIST, key = "#createReqVO.deptId", beforeInvocation = false) public Long createDeptExternalCode(DeptExternalCodeSaveReqVO createReqVO) { normalizeRequest(createReqVO); validateForCreateOrUpdate(null, createReqVO.getDeptId(), createReqVO.getSystemCode(), @@ -57,12 +65,15 @@ public class DeptExternalCodeServiceImpl implements DeptExternalCodeService { updateObj.setStatus(exists.getStatus() == null ? CommonStatusEnum.ENABLE.getStatus() : exists.getStatus()); } deptExternalCodeMapper.updateById(updateObj); + evictCacheSafely(exists.getDeptId()); + evictCacheSafely(updateObj.getDeptId()); } @Override public void deleteDeptExternalCode(Long id) { - validateExists(id); + DeptExternalCodeDO exists = validateExists(id); deptExternalCodeMapper.deleteById(id); + evictCacheSafely(exists.getDeptId()); } @Override @@ -76,6 +87,7 @@ public class DeptExternalCodeServiceImpl implements DeptExternalCodeService { } @Override + @Cacheable(cacheNames = RedisKeyConstants.DEPT_EXTERNAL_CODE_LIST, key = "#deptId") public List getDeptExternalCodeListByDeptId(Long deptId) { return deptExternalCodeMapper.selectListByDeptId(deptId); } @@ -96,6 +108,48 @@ public class DeptExternalCodeServiceImpl implements DeptExternalCodeService { return deptExternalCodeMapper.selectBySystemCodeAndDeptId(systemCode.trim(), deptId); } + @Override + public Long saveOrUpdateDeptExternalCode(Long deptId, String systemCode, String externalDeptCode, + String externalDeptName, Integer status) { + if (StrUtil.hasEmpty(systemCode, externalDeptCode) || deptId == null) { + return null; + } + String normalizedSystemCode = systemCode.trim(); + String normalizedExternalCode = externalDeptCode.trim(); + String normalizedExternalName = StrUtil.blankToDefault(StrUtil.trimToNull(externalDeptName), null); + + // 如果存在则更新,否则创建 + DeptExternalCodeDO exists = deptExternalCodeMapper.selectBySystemCodeAndDeptId(normalizedSystemCode, deptId); + if (exists != null) { + DeptExternalCodeSaveReqVO updateReqVO = new DeptExternalCodeSaveReqVO(); + updateReqVO.setId(exists.getId()); + updateReqVO.setDeptId(deptId); + updateReqVO.setSystemCode(normalizedSystemCode); + updateReqVO.setExternalDeptCode(normalizedExternalCode); + updateReqVO.setExternalDeptName(normalizedExternalName); + updateReqVO.setStatus(status == null ? exists.getStatus() : status); + updateDeptExternalCode(updateReqVO); + return exists.getId(); + } + + DeptExternalCodeSaveReqVO createReqVO = new DeptExternalCodeSaveReqVO(); + createReqVO.setDeptId(deptId); + createReqVO.setSystemCode(normalizedSystemCode); + createReqVO.setExternalDeptCode(normalizedExternalCode); + createReqVO.setExternalDeptName(normalizedExternalName); + createReqVO.setStatus(status == null ? CommonStatusEnum.ENABLE.getStatus() : status); + return createDeptExternalCode(createReqVO); + } + + @Override + public void deleteDeptExternalCodesByDeptId(Long deptId) { + if (deptId == null) { + return; + } + deptExternalCodeMapper.deleteByDeptId(deptId); + evictCacheSafely(deptId); + } + private DeptExternalCodeDO validateExists(Long id) { if (id == null) { throw exception(DEPT_EXTERNAL_RELATION_NOT_EXISTS); @@ -109,7 +163,7 @@ public class DeptExternalCodeServiceImpl implements DeptExternalCodeService { private void validateForCreateOrUpdate(Long id, Long deptId, String systemCode, String externalDeptCode) { // 校验部门存在 - DeptDO dept = deptService.getDept(deptId); + DeptDO dept = deptMapper.selectById(deptId); if (dept == null) { throw exception(DEPT_NOT_FOUND); } @@ -148,4 +202,17 @@ public class DeptExternalCodeServiceImpl implements DeptExternalCodeService { reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus()); } } + + private void evictCacheSafely(Long deptId) { + if (deptId == null || cacheManager == null) { + return; + } + try { + if (cacheManager.getCache(RedisKeyConstants.DEPT_EXTERNAL_CODE_LIST) != null) { + cacheManager.getCache(RedisKeyConstants.DEPT_EXTERNAL_CODE_LIST).evict(deptId); + } + } catch (Exception ignore) { + // 缓存失效失败不影响主流程 + } + } } diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/DeptServiceImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/DeptServiceImpl.java index ae4d35d0..2c2e8e2c 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/DeptServiceImpl.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/DeptServiceImpl.java @@ -11,12 +11,18 @@ import com.zt.plat.framework.datapermission.core.annotation.DataPermission; 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.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.dict.DictTypeDO; 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.userdept.UserDeptMapper; 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.DictTypeConstants; +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 jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; @@ -52,6 +58,12 @@ public class DeptServiceImpl implements DeptService { private UserDeptMapper userDeptMapper; @Resource private com.zt.plat.module.system.mq.producer.databus.DatabusChangeProducer databusChangeProducer; + @Resource + private DeptExternalCodeService deptExternalCodeService; + @Resource + private DictTypeService dictTypeService; + @Resource + private DictDataService dictDataService; private static final String ROOT_CODE_PREFIX = "ZT"; private static final int CODE_SEGMENT_LENGTH = 3; @@ -75,23 +87,10 @@ public class DeptServiceImpl implements DeptService { // 校验部门名的唯一性 validateDeptNameUnique(null, createReqVO.getParentId(), createReqVO.getName()); // 生成并校验部门编码 - boolean isIWorkSource = Objects.equals(createReqVO.getDeptSource(), DeptSourceEnum.IWORK.getSource()); - if (isIWorkSource) { - // iWork 来源直接使用提供的编码,不再生成 - String providedCode = StrUtil.blankToDefault(createReqVO.getCode(), null); - createReqVO.setCode(providedCode); - } else { - Long effectiveParentId = normalizeParentId(createReqVO.getParentId()); - boolean isTopLevel = Objects.equals(effectiveParentId, DeptDO.PARENT_ID_ROOT); - String resolvedCode; - if (isTopLevel) { - resolvedCode = resolveTopLevelCode(null, createReqVO.getCode()); - } else { - resolvedCode = generateDeptCode(effectiveParentId); - validateDeptCodeUnique(null, resolvedCode); - } - createReqVO.setCode(resolvedCode); - } + Long effectiveParentId = normalizeParentId(createReqVO.getParentId()); + String resolvedCode = generateDeptCode(effectiveParentId); + validateDeptCodeUnique(null, resolvedCode); + createReqVO.setCode(resolvedCode); // 插入部门 DeptDO dept = BeanUtils.toBean(createReqVO, DeptDO.class); @@ -101,6 +100,9 @@ public class DeptServiceImpl implements DeptService { } deptMapper.insert(dept); + // 维护外部系统编码映射(若有传入) + upsertExternalCodeMapping(createReqVO, dept.getId()); + // 发布部门创建事件 databusChangeProducer.sendDeptCreatedMessage(dept); @@ -121,37 +123,15 @@ public class DeptServiceImpl implements DeptService { // 校验部门名的唯一性 validateDeptNameUnique(updateReqVO.getId(), updateReqVO.getParentId(), updateReqVO.getName()); // 如果上级发生变化,需要重新生成编码并同步子级 - boolean isIWorkSource = Objects.equals(originalDept.getDeptSource(), DeptSourceEnum.IWORK.getSource()); Long newParentId = normalizeParentId(updateReqVO.getParentId()); Long oldParentId = normalizeParentId(originalDept.getParentId()); boolean parentChanged = !Objects.equals(newParentId, oldParentId); - if (isIWorkSource) { - // iWork 来源直接使用提供的编码,不再生成 - String providedCode = StrUtil.blankToDefault(updateReqVO.getCode(), null); - updateReqVO.setCode(providedCode); - } else { - if (parentChanged) { - String newCode; - if (Objects.equals(newParentId, DeptDO.PARENT_ID_ROOT)) { - newCode = resolveTopLevelCode(updateReqVO.getId(), updateReqVO.getCode()); - } else { - newCode = generateDeptCode(updateReqVO.getParentId()); - validateDeptCodeUnique(updateReqVO.getId(), newCode); - } - updateReqVO.setCode(newCode); - } else { - if (Objects.equals(newParentId, DeptDO.PARENT_ID_ROOT)) { - String requestedCode = updateReqVO.getCode(); - if (StrUtil.isNotBlank(requestedCode) && !StrUtil.equals(requestedCode.trim(), originalDept.getCode())) { - updateReqVO.setCode(resolveTopLevelCode(updateReqVO.getId(), requestedCode)); - } else { - updateReqVO.setCode(originalDept.getCode()); - } - } else { - updateReqVO.setCode(originalDept.getCode()); - } - } + String resolvedCode = originalDept.getCode(); + if (parentChanged || StrUtil.isBlank(resolvedCode)) { + resolvedCode = generateDeptCode(newParentId); + validateDeptCodeUnique(updateReqVO.getId(), resolvedCode); } + updateReqVO.setCode(resolvedCode); // 更新部门 DeptDO updateObj = BeanUtils.toBean(updateReqVO, DeptDO.class); @@ -166,6 +146,9 @@ public class DeptServiceImpl implements DeptService { if (parentChanged) { refreshChildCodesRecursively(updateObj.getId(), updateReqVO.getCode()); } + + // 维护外部系统编码映射(若有传入) + upsertExternalCodeMapping(updateReqVO, updateReqVO.getId()); } @Override @@ -183,6 +166,9 @@ public class DeptServiceImpl implements DeptService { DeptDO dept = deptMapper.selectById(id); Long tenantId = (dept != null) ? dept.getTenantId() : null; + // 级联删除外部编码映射并清理缓存 + deptExternalCodeService.deleteDeptExternalCodesByDeptId(id); + // 删除部门 deptMapper.deleteById(id); @@ -754,4 +740,65 @@ public class DeptServiceImpl implements DeptService { return dept; } + private void upsertExternalCodeMapping(DeptSaveReqVO reqVO, Long deptId) { + if (reqVO == null || deptId == null) { + return; + } + String systemCode = StrUtil.trimToNull(reqVO.getExternalSystemCode()); + String externalCode = StrUtil.trimToNull(reqVO.getExternalDeptCode()); + if (StrUtil.isBlank(systemCode) || StrUtil.isBlank(externalCode)) { + return; + } + // 缺失的外部系统字典类型或数据会自动补齐 + ensureExternalSystemDict(systemCode); + deptExternalCodeService.saveOrUpdateDeptExternalCode( + deptId, + systemCode, + externalCode, + reqVO.getExternalDeptName(), + CommonStatusEnum.ENABLE.getStatus()); + } + + /** + * 确保外部系统字典存在(含字典类型与对应值),若缺失则自动创建 + */ + private void ensureExternalSystemDict(String systemCode) { + String normalizedCode = StrUtil.trimToNull(systemCode); + if (normalizedCode == null) { + 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) { + DictDataSaveReqVO dataReq = new DictDataSaveReqVO(); + dataReq.setDictType(DictTypeConstants.DEPT_EXTERNAL_SYSTEM); + dataReq.setLabel(normalizedCode); + dataReq.setValue(normalizedCode); + dataReq.setSort(0); + dataReq.setStatus(CommonStatusEnum.ENABLE.getStatus()); + dataReq.setRemark("外部组织同步自动创建"); + dictDataService.createDictData(dataReq); + } + } catch (Exception ex) { + log.warn("[Dept] Ensure external system dict failed, systemCode={}", normalizedCode, ex); + } + } + } diff --git a/zt-module-system/zt-module-system-server/src/test/java/com/zt/plat/module/system/service/dept/DeptExternalCodeServiceImplTest.java b/zt-module-system/zt-module-system-server/src/test/java/com/zt/plat/module/system/service/dept/DeptExternalCodeServiceImplTest.java new file mode 100644 index 00000000..1c275951 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/test/java/com/zt/plat/module/system/service/dept/DeptExternalCodeServiceImplTest.java @@ -0,0 +1,105 @@ +package com.zt.plat.module.system.service.dept; + +import com.zt.plat.framework.common.enums.CommonStatusEnum; +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.dal.dataobject.dept.DeptDO; +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.redis.RedisKeyConstants; +import jakarta.annotation.Resource; +import org.junit.jupiter.api.Test; +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.context.annotation.Bean; +import org.springframework.context.annotation.Import; + +import java.util.List; + +import static com.zt.plat.module.system.dal.redis.RedisKeyConstants.DEPT_EXTERNAL_CODE_LIST; +import static org.junit.jupiter.api.Assertions.*; + +@Import({DeptExternalCodeServiceImpl.class, DeptExternalCodeServiceImplTest.CacheConfig.class}) +class DeptExternalCodeServiceImplTest extends BaseDbUnitTest { + + @Resource + private DeptExternalCodeServiceImpl deptExternalCodeService; + @Resource + private DeptExternalCodeMapper deptExternalCodeMapper; + @Resource + private CacheManager cacheManager; + + @Resource + private DeptMapper deptMapper; + + @TestConfiguration + @EnableCaching + static class CacheConfig { + @Bean + public CacheManager cacheManager() { + return new ConcurrentMapCacheManager(RedisKeyConstants.DEPT_EXTERNAL_CODE_LIST); + } + } + + @Test + void testCacheEvictOnCreateAndUpdate() { + Long deptId = 100L; + DeptDO dept = new DeptDO(); + dept.setId(deptId); + dept.setName("总部"); + dept.setCode("ZT001"); + dept.setStatus(CommonStatusEnum.ENABLE.getStatus()); + deptMapper.insert(dept); + + // 预热缓存(空结果) + List firstCall = deptExternalCodeService.getDeptExternalCodeListByDeptId(deptId); + assertTrue(firstCall.isEmpty()); + assertNotNull(cacheManager.getCache(DEPT_EXTERNAL_CODE_LIST).get(deptId)); + + // 创建映射应触发缓存失效 + DeptExternalCodeSaveReqVO createReq = new DeptExternalCodeSaveReqVO(); + createReq.setDeptId(deptId); + createReq.setSystemCode("ERP"); + createReq.setExternalDeptCode("ERP-001"); + deptExternalCodeService.createDeptExternalCode(createReq); + + List refreshed = deptExternalCodeService.getDeptExternalCodeListByDeptId(deptId); + assertEquals(1, refreshed.size()); + assertNotNull(cacheManager.getCache(DEPT_EXTERNAL_CODE_LIST).get(deptId)); + + // 更新映射也会清理缓存 + DeptExternalCodeSaveReqVO updateReq = new DeptExternalCodeSaveReqVO(); + Long id = deptExternalCodeMapper.selectListByDeptId(deptId).get(0).getId(); + updateReq.setId(id); + updateReq.setDeptId(deptId); + updateReq.setSystemCode("ERP"); + updateReq.setExternalDeptCode("ERP-002"); + updateReq.setStatus(CommonStatusEnum.ENABLE.getStatus()); + deptExternalCodeService.updateDeptExternalCode(updateReq); + + List refreshedAfterUpdate = deptExternalCodeService.getDeptExternalCodeListByDeptId(deptId); + assertEquals(1, refreshedAfterUpdate.size()); + assertEquals("ERP-002", deptExternalCodeMapper.selectById(id).getExternalDeptCode()); + } + + @Test + void testSaveOrUpdateDeptExternalCodeUpsert() { + Long deptId = 101L; + DeptDO dept = new DeptDO(); + dept.setId(deptId); + dept.setName("事业部"); + dept.setCode("ZT002"); + dept.setStatus(CommonStatusEnum.ENABLE.getStatus()); + deptMapper.insert(dept); + + Long firstId = deptExternalCodeService.saveOrUpdateDeptExternalCode(deptId, "OA", "OA-001", "OA-总部", null); + assertNotNull(firstId); + + // upsert: 同一 system + dept 更新编码 + Long secondId = deptExternalCodeService.saveOrUpdateDeptExternalCode(deptId, "OA", "OA-002", "OA-新编码", CommonStatusEnum.ENABLE.getStatus()); + assertEquals(firstId, secondId); + assertEquals("OA-002", deptExternalCodeMapper.selectById(firstId).getExternalDeptCode()); + } +} diff --git a/zt-module-system/zt-module-system-server/src/test/java/com/zt/plat/module/system/service/dept/DeptServiceImplTest.java b/zt-module-system/zt-module-system-server/src/test/java/com/zt/plat/module/system/service/dept/DeptServiceImplTest.java index 6520d2fb..5d322eac 100644 --- a/zt-module-system/zt-module-system-server/src/test/java/com/zt/plat/module/system/service/dept/DeptServiceImplTest.java +++ b/zt-module-system/zt-module-system-server/src/test/java/com/zt/plat/module/system/service/dept/DeptServiceImplTest.java @@ -5,11 +5,20 @@ import com.zt.plat.framework.common.util.object.ObjectUtils; import com.zt.plat.framework.test.core.ut.BaseDbUnitTest; 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.depexternalcode.DeptExternalCodeSaveReqVO; import com.zt.plat.module.system.dal.dataobject.dept.DeptDO; +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.service.dept.DeptExternalCodeServiceImpl; +import com.zt.plat.module.system.dal.redis.RedisKeyConstants; import jakarta.annotation.Resource; 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.context.annotation.Bean; import java.util.Arrays; import java.util.List; @@ -27,13 +36,31 @@ import static org.junit.jupiter.api.Assertions.*; * * @author niudehua */ -@Import(DeptServiceImpl.class) +@Import({DeptServiceImpl.class, DeptExternalCodeServiceImpl.class, DeptServiceImplTest.CacheConfig.class}) public class DeptServiceImplTest extends BaseDbUnitTest { @Resource private DeptServiceImpl deptService; @Resource private DeptMapper deptMapper; + @Resource + private DeptExternalCodeServiceImpl deptExternalCodeService; + @Resource + private DeptExternalCodeMapper deptExternalCodeMapper; + @Resource + private CacheManager cacheManager; + + @TestConfiguration + @EnableCaching + static class CacheConfig { + @Bean + public CacheManager cacheManager() { + return new ConcurrentMapCacheManager( + RedisKeyConstants.DEPT_CHILDREN_ID_LIST, + RedisKeyConstants.DEPT_EXTERNAL_CODE_LIST + ); + } + } private Long createDept(Long parentId, String name, int sort) { DeptSaveReqVO reqVO = new DeptSaveReqVO(); @@ -108,7 +135,7 @@ public class DeptServiceImplTest extends BaseDbUnitTest { } @Test - public void testCreateDept_topLevelRespectCustomCode() { + public void testCreateDept_topLevelAutoCode_ignoreCustomInput() { String customCode = "ROOT-001"; DeptSaveReqVO topLevelReq = new DeptSaveReqVO(); topLevelReq.setParentId(DeptDO.PARENT_ID_ROOT); @@ -120,7 +147,7 @@ public class DeptServiceImplTest extends BaseDbUnitTest { Long deptId = deptService.createDept(topLevelReq); DeptDO created = deptMapper.selectById(deptId); - assertEquals(customCode, created.getCode()); + assertEquals("ZT001", created.getCode()); } @Test @@ -187,6 +214,29 @@ public class DeptServiceImplTest extends BaseDbUnitTest { assertNull(deptMapper.selectById(id)); } + @Test + public void testDeleteDept_cascadeExternalCodesAndEvictCache() { + Long deptId = createDept(DeptDO.PARENT_ID_ROOT, "总部", 1); + + // 创建映射并预热缓存 + DeptExternalCodeSaveReqVO createReq = new DeptExternalCodeSaveReqVO(); + createReq.setDeptId(deptId); + createReq.setSystemCode("ERP"); + createReq.setExternalDeptCode("ERP-001"); + deptExternalCodeService.createDeptExternalCode(createReq); + deptExternalCodeService.getDeptExternalCodeListByDeptId(deptId); + assertNotNull(cacheManager.getCache(com.zt.plat.module.system.dal.redis.RedisKeyConstants.DEPT_EXTERNAL_CODE_LIST) + .get(deptId)); + + // 删除部门 + deptService.deleteDept(deptId); + + // 校验映射被删除且缓存被清理 + assertTrue(deptExternalCodeMapper.selectListByDeptId(deptId).isEmpty()); + assertNull(cacheManager.getCache(com.zt.plat.module.system.dal.redis.RedisKeyConstants.DEPT_EXTERNAL_CODE_LIST) + .get(deptId)); + } + @Test public void testDeleteDept_exitsChildren() { // mock 数据 diff --git a/zt-module-system/zt-module-system-server/src/test/resources/sql/clean.sql b/zt-module-system/zt-module-system-server/src/test/resources/sql/clean.sql index e7946a10..78fe1b13 100644 --- a/zt-module-system/zt-module-system-server/src/test/resources/sql/clean.sql +++ b/zt-module-system/zt-module-system-server/src/test/resources/sql/clean.sql @@ -1,4 +1,5 @@ DELETE FROM "system_dept"; +DELETE FROM "system_dept_external_code"; DELETE FROM "system_dict_data"; DELETE FROM "system_role"; DELETE FROM "system_role_menu"; diff --git a/zt-module-system/zt-module-system-server/src/test/resources/sql/create_tables.sql b/zt-module-system/zt-module-system-server/src/test/resources/sql/create_tables.sql index e27aa063..fd2321a0 100644 --- a/zt-module-system/zt-module-system-server/src/test/resources/sql/create_tables.sql +++ b/zt-module-system/zt-module-system-server/src/test/resources/sql/create_tables.sql @@ -34,6 +34,27 @@ CREATE TABLE IF NOT EXISTS "system_dept" ( PRIMARY KEY ("id") ) COMMENT '部门表'; +CREATE TABLE IF NOT EXISTS "system_dept_external_code" ( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "dept_id" bigint NOT NULL, + "system_code" varchar(64) NOT NULL, + "external_dept_code" varchar(128) NOT NULL, + "external_dept_name" varchar(255), + "status" tinyint DEFAULT 0 NOT NULL, + "remark" varchar(512), + "tenant_id" bigint DEFAULT 0, + "creator" varchar(64), + "create_time" timestamp DEFAULT CURRENT_TIMESTAMP, + "updater" varchar(64), + "update_time" timestamp DEFAULT CURRENT_TIMESTAMP, + "deleted" tinyint DEFAULT 0 NOT NULL, + PRIMARY KEY ("id") +) 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 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", "dept_id"); + CREATE TABLE IF NOT EXISTS "system_dict_data" ( "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, "sort" int NOT NULL DEFAULT '0',