From a5c22222e99fc5e56d41d2fa1901f6d1dd4885fc Mon Sep 17 00:00:00 2001 From: waner Date: Fri, 17 Apr 2026 15:29:20 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E5=AE=8C=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../impl/DeviceRuntimeStatusServiceImpl.java | 19 +- .../support/DefaultSoftwareRuntimeProbe.java | 104 ++++- .../init/config/InitExecutorProperties.java | 62 ++- .../init/dto/CurrentInitConfigResponse.java | 32 ++ .../modules/init/dto/InitPreviewRequest.java | 89 +--- .../ConfigurableInitStepExecutor.java | 416 +++++++++++------- .../init/service/impl/InitServiceImpl.java | 109 +++-- .../upgrade/controller/UpgradeController.java | 8 +- .../upgrade/dto/UpgradeCreateTaskRequest.java | 10 + .../upgrade/mapper/UpgradeTaskMapper.java | 6 +- .../repository/UpgradeTaskRepository.java | 11 +- .../impl/UpgradeTaskRepositoryImpl.java | 15 +- .../upgrade/service/UpgradeService.java | 38 +- .../mapper/upgrade/UpgradeTaskMapper.xml | 12 + .../service/DeviceProfileServiceTest.java | 16 + .../DeviceRuntimeStatusServiceTest.java | 54 +++ .../device/service/DeviceServiceTest.java | 16 + .../DefaultSoftwareRuntimeProbeTest.java | 57 +++ .../ConfigurableInitStepExecutorTest.java | 252 ++++++----- .../modules/init/service/InitServiceTest.java | 233 +++++++--- .../upgrade/UpgradeDtoContractTest.java | 4 +- .../controller/UpgradeControllerTest.java | 9 +- .../executor/UpgradeTaskRunnerTest.java | 11 +- .../repository/UpgradeTaskRepositoryTest.java | 10 +- .../upgrade/service/UpgradeServiceTest.java | 83 +++- 25 files changed, 1234 insertions(+), 442 deletions(-) diff --git a/src/main/java/com/cisd/tms/modules/device/service/impl/DeviceRuntimeStatusServiceImpl.java b/src/main/java/com/cisd/tms/modules/device/service/impl/DeviceRuntimeStatusServiceImpl.java index 3a130db..7ee6f58 100644 --- a/src/main/java/com/cisd/tms/modules/device/service/impl/DeviceRuntimeStatusServiceImpl.java +++ b/src/main/java/com/cisd/tms/modules/device/service/impl/DeviceRuntimeStatusServiceImpl.java @@ -14,6 +14,7 @@ import com.cisd.tms.modules.device.support.HardwareStatusProbe; import com.cisd.tms.modules.device.support.SoftwareRuntimeProbe; import com.cisd.tms.modules.device.support.SystemSoftwareVersionProbe; import com.cisd.tms.modules.device.support.UsageSnapshot; +import com.cisd.tms.modules.init.dto.CurrentInitConfigResponse; import com.cisd.tms.modules.init.service.InitService; import java.time.OffsetDateTime; import java.util.ArrayList; @@ -177,10 +178,26 @@ public class DeviceRuntimeStatusServiceImpl implements DeviceRuntimeStatusServic } private BooleanSupplier receiverProbe() { - String productType = normalize(cisdPresetProperties.getProductType()); + String productType = resolveReceiverProductType(); return "DIRECT".equals(productType) ? softwareRuntimeProbe::isDirectAppRunning : softwareRuntimeProbe::isStandardAppRunning; } + private String resolveReceiverProductType() { + try { + String initState = normalize(initService.getDeviceInitStateSnapshot().initState()); + if ("INITIALIZED".equals(initState)) { + CurrentInitConfigResponse current = initService.loadCurrentInitConfig(); + String currentProductType = normalize(current == null ? null : current.getProductType()); + if (!currentProductType.isBlank()) { + return currentProductType; + } + } + } catch (RuntimeException ignored) { + // Fallback to the preset product type when runtime init context is unavailable. + } + return normalize(cisdPresetProperties.getProductType()); + } + @FunctionalInterface private interface UsageSupplier { UsageSnapshot get(); diff --git a/src/main/java/com/cisd/tms/modules/device/support/DefaultSoftwareRuntimeProbe.java b/src/main/java/com/cisd/tms/modules/device/support/DefaultSoftwareRuntimeProbe.java index 85dd92e..9647d1b 100644 --- a/src/main/java/com/cisd/tms/modules/device/support/DefaultSoftwareRuntimeProbe.java +++ b/src/main/java/com/cisd/tms/modules/device/support/DefaultSoftwareRuntimeProbe.java @@ -18,67 +18,128 @@ public class DefaultSoftwareRuntimeProbe implements SoftwareRuntimeProbe { private final Supplier> processSnapshotLoader; private final Duration cacheTtl; private final LongSupplier nanoTimeSupplier; + private final CommandExitCodeSupplier commandExitCodeSupplier; private final Object lock = new Object(); private volatile CachedProcessSnapshot cachedSnapshot; public DefaultSoftwareRuntimeProbe() { - this(DefaultSoftwareRuntimeProbe::loadProcessSnapshot, DEFAULT_CACHE_TTL, System::nanoTime); + this(DefaultSoftwareRuntimeProbe::loadProcessSnapshot, DEFAULT_CACHE_TTL, System::nanoTime, DefaultSoftwareRuntimeProbe::runCommand); } DefaultSoftwareRuntimeProbe( Supplier> processSnapshotLoader, Duration cacheTtl, LongSupplier nanoTimeSupplier + ) { + this(processSnapshotLoader, cacheTtl, nanoTimeSupplier, DefaultSoftwareRuntimeProbe::runCommand); + } + + DefaultSoftwareRuntimeProbe( + Supplier> processSnapshotLoader, + Duration cacheTtl, + LongSupplier nanoTimeSupplier, + CommandExitCodeSupplier commandExitCodeSupplier ) { this.processSnapshotLoader = Objects.requireNonNull(processSnapshotLoader, "processSnapshotLoader"); this.cacheTtl = Objects.requireNonNull(cacheTtl, "cacheTtl"); this.nanoTimeSupplier = Objects.requireNonNull(nanoTimeSupplier, "nanoTimeSupplier"); + this.commandExitCodeSupplier = Objects.requireNonNull(commandExitCodeSupplier, "commandExitCodeSupplier"); } @Override public boolean isRabbitmqRunning() { - return isRunning("rabbitmq-server"); + return commandSucceeds("bash", "-lc", "rabbitmqctl status >/dev/null 2>&1") + || commandSucceeds("bash", "-lc", "systemctl is-active --quiet rabbitmq.service") + || isRunningAny("beam.smp", "rabbitmq-server", "epmd"); } @Override public boolean isTlqRunning() { - return isRunning("tonglink|TLQ"); + return isRunningAny("tonglink", "tlq", "tlqremote"); } @Override public boolean isTidbRunning() { - return isRunning("tidb-server"); + return isRunningAny("tidb-server"); } @Override public boolean isNginxRunning() { - return isRunning("nginx"); + return isRunningAny("nginx"); } @Override public boolean isStandardAppRunning() { - return isRunning("cmsp") && isRunning("cmtp"); + return (commandSucceeds("bash", "-lc", "systemctl is-active --quiet cmsp.service") || isRunningAny("cmsp")) + && (commandSucceeds("bash", "-lc", "systemctl is-active --quiet cmtp.service") || isRunningAny("cmtp")); } @Override public boolean isDirectAppRunning() { - return isRunning("cmep4d|CAE"); + boolean pmts = commandSucceeds("bash", "-lc", "systemctl is-active --quiet pmts.service") + || isRunningAny("pmts", "pmtsstat"); + boolean cmcp = commandSucceeds("bash", "-lc", "systemctl is-active --quiet cmcp.service") + || isRunningAny("cmcp"); + boolean cmsp = commandSucceeds("bash", "-lc", "systemctl is-active --quiet cmsp.service") + || isRunningAny("cmsp"); + boolean cmtp = commandSucceeds("bash", "-lc", "systemctl is-active --quiet cmtp.service") + || isRunningAny("cmtp"); + boolean cmtp4cae = commandSucceeds("bash", "-lc", "systemctl is-active --quiet cmtp4cae.service") + || isRunningAny("cmtp4cae"); + return pmts && cmcp && cmsp && cmtp && cmtp4cae; } @Override public boolean isTmsRunning() { - return isRunning("tms-framework.jar"); + return isRunningAny("tms-framework.jar"); } - private boolean isRunning(String pattern) { + private boolean isRunningAll(String... tokens) { + for (String token : tokens) { + if (!isRunningAny(token)) { + return false; + } + } + return true; + } + + private boolean isRunningAny(String... tokens) { try { - Pattern compiled = Pattern.compile(pattern); - return processSnapshot().stream().anyMatch(commandLine -> compiled.matcher(commandLine).find()); + List patterns = compilePatterns(tokens); + return processSnapshot().stream().anyMatch(commandLine -> matchesAny(commandLine, patterns)); } catch (PatternSyntaxException ex) { - throw new IllegalStateException("invalid process probe pattern: " + pattern, ex); + throw new IllegalStateException("invalid process probe tokens: " + String.join(",", tokens), ex); } catch (RuntimeException ex) { - throw new IllegalStateException("process probe failed: " + pattern, ex); + throw new IllegalStateException("process probe failed: " + String.join(",", tokens), ex); + } + } + + private List compilePatterns(String... tokens) { + return List.of(tokens).stream() + .filter(token -> token != null && !token.isBlank()) + .map(this::compileTokenPattern) + .toList(); + } + + private Pattern compileTokenPattern(String token) { + return Pattern.compile("(?i)(? patterns) { + for (Pattern pattern : patterns) { + if (pattern.matcher(commandLine).find()) { + return true; + } + } + return false; + } + + private boolean commandSucceeds(String... command) { + try { + return commandExitCodeSupplier.get(command) == 0; + } catch (RuntimeException ex) { + return false; } } @@ -130,6 +191,23 @@ public class DefaultSoftwareRuntimeProbe implements SoftwareRuntimeProbe { } } + private static int runCommand(String... command) { + try { + Process process = new ProcessBuilder(command).start(); + return process.waitFor(); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + throw new IllegalStateException("command interrupted", ex); + } catch (IOException ex) { + throw new IllegalStateException("command failed", ex); + } + } + + @FunctionalInterface + interface CommandExitCodeSupplier { + int get(String... command); + } + private record CachedProcessSnapshot(List commandLines, long loadedAtNanos) { } } diff --git a/src/main/java/com/cisd/tms/modules/init/config/InitExecutorProperties.java b/src/main/java/com/cisd/tms/modules/init/config/InitExecutorProperties.java index 63667fc..80f56a0 100644 --- a/src/main/java/com/cisd/tms/modules/init/config/InitExecutorProperties.java +++ b/src/main/java/com/cisd/tms/modules/init/config/InitExecutorProperties.java @@ -82,7 +82,19 @@ public class InitExecutorProperties { private String directDbScriptDir = "/opt/CAE_Install_Base_Resource/DB"; // 直参版 TLQ init_cfg.sh 脚本目录 private String directTlqInitCfgDir = "/opt/CAE_Install_App_Resource/CAE"; - // CFMQ 配置落地目录(为空表示只做文件存在校验,不做拷贝) + // 直参版 CAE 客户化配置目标目录 + private String directCaeTargetDir = "/pmts/cfg"; + // 直参版 CAE 更新脚本目录 + private String directCaeUpdateScriptDir = "/opt/CAE_Install_App_Resource/CAE"; + // 直参版 CAE 更新命令 + private String directCaeUpdateCommand = "bash ./cae_update.sh"; + // TLQ 网关脚本目标目录 + private String directTlqGatewayScriptTargetDir = "/home/tlq/bin"; + // TLQ 重启命令 + private String directTlqRestartCommand = "systemctl restart tonglinkmq.service"; + // CFMQ 运行目录 + private String directCfmqInstallPath = "/home/cfmq/dev/cfmq"; + // CFMQ 配置落地目录(兼容旧实现) private String cfmqTargetDir = "/pmts/cfg"; // RabbitMQ 管理员账号(预制) private String rabbitAdminUsername = "admin"; @@ -392,6 +404,54 @@ public class InitExecutorProperties { this.directTlqLicenseTargetPath = directTlqLicenseTargetPath; } + public String getDirectCaeTargetDir() { + return directCaeTargetDir; + } + + public void setDirectCaeTargetDir(String directCaeTargetDir) { + this.directCaeTargetDir = directCaeTargetDir; + } + + public String getDirectCaeUpdateScriptDir() { + return directCaeUpdateScriptDir; + } + + public void setDirectCaeUpdateScriptDir(String directCaeUpdateScriptDir) { + this.directCaeUpdateScriptDir = directCaeUpdateScriptDir; + } + + public String getDirectCaeUpdateCommand() { + return directCaeUpdateCommand; + } + + public void setDirectCaeUpdateCommand(String directCaeUpdateCommand) { + this.directCaeUpdateCommand = directCaeUpdateCommand; + } + + public String getDirectTlqGatewayScriptTargetDir() { + return directTlqGatewayScriptTargetDir; + } + + public void setDirectTlqGatewayScriptTargetDir(String directTlqGatewayScriptTargetDir) { + this.directTlqGatewayScriptTargetDir = directTlqGatewayScriptTargetDir; + } + + public String getDirectTlqRestartCommand() { + return directTlqRestartCommand; + } + + public void setDirectTlqRestartCommand(String directTlqRestartCommand) { + this.directTlqRestartCommand = directTlqRestartCommand; + } + + public String getDirectCfmqInstallPath() { + return directCfmqInstallPath; + } + + public void setDirectCfmqInstallPath(String directCfmqInstallPath) { + this.directCfmqInstallPath = directCfmqInstallPath; + } + public String getCfmqTargetDir() { return cfmqTargetDir; } diff --git a/src/main/java/com/cisd/tms/modules/init/dto/CurrentInitConfigResponse.java b/src/main/java/com/cisd/tms/modules/init/dto/CurrentInitConfigResponse.java index 4c2a53b..1eefe7e 100644 --- a/src/main/java/com/cisd/tms/modules/init/dto/CurrentInitConfigResponse.java +++ b/src/main/java/com/cisd/tms/modules/init/dto/CurrentInitConfigResponse.java @@ -1,6 +1,8 @@ package com.cisd.tms.modules.init.dto; import io.swagger.v3.oas.annotations.media.Schema; +import java.util.ArrayList; +import java.util.List; @Schema(description = "当前已初始化的 CISD 核心配置响应") public class CurrentInitConfigResponse { @@ -27,6 +29,12 @@ public class CurrentInitConfigResponse { private String node02Ip; @Schema(description = "CIPS 中心网关地址", example = "192.168.10.20") private String cipsGatewayHost; + @Schema(description = "TLQ 相关文件名列表,包含 TLQ License、TLQ 配置包等") + private List tlqFileNames = new ArrayList<>(); + @Schema(description = "CFMQ 相关文件名列表,包含 CFMQ 配置包、CFMQ 配置脚本等") + private List cfmqFileNames = new ArrayList<>(); + @Schema(description = "收发器 LICENSE 文件名", example = "receiver-license.dat") + private String receiverLicenseFileName; public String getOrgCodeType() { return orgCodeType; @@ -115,4 +123,28 @@ public class CurrentInitConfigResponse { public void setCipsGatewayHost(String cipsGatewayHost) { this.cipsGatewayHost = cipsGatewayHost; } + + public List getTlqFileNames() { + return tlqFileNames; + } + + public void setTlqFileNames(List tlqFileNames) { + this.tlqFileNames = tlqFileNames; + } + + public List getCfmqFileNames() { + return cfmqFileNames; + } + + public void setCfmqFileNames(List cfmqFileNames) { + this.cfmqFileNames = cfmqFileNames; + } + + public String getReceiverLicenseFileName() { + return receiverLicenseFileName; + } + + public void setReceiverLicenseFileName(String receiverLicenseFileName) { + this.receiverLicenseFileName = receiverLicenseFileName; + } } diff --git a/src/main/java/com/cisd/tms/modules/init/dto/InitPreviewRequest.java b/src/main/java/com/cisd/tms/modules/init/dto/InitPreviewRequest.java index bf172f5..ebec32e 100644 --- a/src/main/java/com/cisd/tms/modules/init/dto/InitPreviewRequest.java +++ b/src/main/java/com/cisd/tms/modules/init/dto/InitPreviewRequest.java @@ -2,8 +2,6 @@ package com.cisd.tms.modules.init.dto; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.Valid; -import jakarta.validation.constraints.Max; -import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Pattern; @@ -51,14 +49,9 @@ public class InitPreviewRequest { private Mq mq; @Valid - @NotNull(message = "licenses is required") @Schema(description = "初始化许可证与配置包引用") private Licenses licenses; - @Valid - @Schema(description = "直连 TLQ 配置,直参版按需填写") - private DirectTlqConfig directTlq; - public String getOrgCodeType() { return orgCodeType; } @@ -123,14 +116,6 @@ public class InitPreviewRequest { this.licenses = licenses; } - public DirectTlqConfig getDirectTlq() { - return directTlq; - } - - public void setDirectTlq(DirectTlqConfig directTlq) { - this.directTlq = directTlq; - } - @Schema(description = "节点 IP 配置") public static class Nodes { @@ -193,28 +178,34 @@ public class InitPreviewRequest { @NotBlank(message = "mq.channelUsername is required") @Size(max = 32, message = "mq.channelUsername length must be <= 32") - @Schema(description = "通道用户名", example = "tms") + @Schema(description = "RABBITMQ通道用户名", example = "tms") private String channelUsername; @NotBlank(message = "mq.channelPassword is required") @Size(max = 128, message = "mq.channelPassword length must be <= 128") - @Schema(description = "通道密码") + @Schema(description = "RABBITMQ通道密码") private String channelPassword; @Size(max = 128, message = "mq.tlqLicenseFileId length must be <= 128") @Pattern(regexp = FILE_ID_REGEX, message = "mq.tlqLicenseFileId must be a fileId") - @Schema(description = "TLQ 许可证文件 fileId", example = "FILE-20260310-000001") + @Schema(description = "TLQ 许可证文件 fileId。作用:用于 TLQ 相关模式的许可证下发。使用条件:`TLQ` 或 `RABBITMQ_TLQ` 场景必填", example = "FILE-20260310-000001") private String tlqLicenseFileId; + @Size(max = 64, message = "mq.cipsGatewayHost length must be <= 64") + @Schema(description = "CIPS 中心网关地址。作用:回写到 TLQ 网关脚本中的中心网关主机地址。使用条件:仅 `DIRECT + RABBITMQ_TLQ` 必填", example = "192.168.10.20") + private String cipsGatewayHost; + + @Size(max = 128, message = "mq.cfgZipFileId length must be <= 128") + @Pattern(regexp = FILE_ID_REGEX, message = "mq.cfgZipFileId must be a fileId") + @Schema(description = "直参 PMTS-CAE 客户化配置包 fileId。使用条件:仅 `DIRECT` 使用;`RABBITMQ_TLQ`必填", example = "FILE-20260310-000201") + private String cfgZipFileId; + + @Size(max = 128, message = "mq.cfmqConfigFileId length must be <= 128") @Pattern(regexp = FILE_ID_REGEX, message = "mq.cfmqConfigFileId must be a fileId") - @Schema(description = "CFMQ 配置文件 fileId", example = "FILE-20260310-000002") + @Schema(description = "直参 CFMQ 客户化脚本/配置 fileId。使用条件:仅 `DIRECT + RABBITMQ_CFMQ` 必填", example = "FILE-20260310-000002") private String cfmqConfigFileId; - @Size(max = 64, message = "mq.cipsGatewayHost length must be <= 64") - @Schema(description = "CIPS 中心网关地址,仅 DIRECT + RABBITMQ_TLQ 使用", example = "192.168.10.20") - private String cipsGatewayHost; - public String getMqType() { return mqType; } @@ -262,6 +253,15 @@ public class InitPreviewRequest { public void setCipsGatewayHost(String cipsGatewayHost) { this.cipsGatewayHost = cipsGatewayHost; } + + public String getCfgZipFileId() { + return cfgZipFileId; + } + + public void setCfgZipFileId(String cfgZipFileId) { + this.cfgZipFileId = cfgZipFileId; + } + } @Schema(description = "许可证和配置包引用") @@ -275,7 +275,7 @@ public class InitPreviewRequest { @Size(max = 128, message = "licenses.cfgZipFileId length must be <= 128") @Pattern(regexp = FILE_ID_REGEX, message = "licenses.cfgZipFileId must be a fileId") - @Schema(description = "标准版配置包 zip 文件 fileId", example = "FILE-20260310-000004") + @Schema(description = "标准版配置包 zip 文件 fileId。作用:用于标准版/间参版初始化时引用配置包。使用条件:`ENTERPRISE`、`INDIRECT` 场景按对应流程使用,`DIRECT` 不使用此字段", example = "FILE-20260310-000004") private String cfgZipFileId; public String getReceiverLicenseFileId() { @@ -294,45 +294,4 @@ public class InitPreviewRequest { this.cfgZipFileId = cfgZipFileId; } } - - @Schema(description = "直连 TLQ 配置") - public static class DirectTlqConfig { - - @Pattern(regexp = "A|B", message = "directTlq.localNodeSeq must be A/B") - @Schema(description = "本节点序号", example = "A") - private String localNodeSeq; - - @Size(max = 256, message = "directTlq.upstreamInstitutions length must be <= 256") - @Schema(description = "上游机构列表", example = "BANKA,BANKB") - private String upstreamInstitutions; - - @Min(value = 1, message = "directTlq.upstreamCocCount must be >= 1") - @Max(value = 2, message = "directTlq.upstreamCocCount must be <= 2") - @Schema(description = "上游 COC 数量", example = "2") - private Integer upstreamCocCount; - - public String getLocalNodeSeq() { - return localNodeSeq; - } - - public void setLocalNodeSeq(String localNodeSeq) { - this.localNodeSeq = localNodeSeq; - } - - public String getUpstreamInstitutions() { - return upstreamInstitutions; - } - - public void setUpstreamInstitutions(String upstreamInstitutions) { - this.upstreamInstitutions = upstreamInstitutions; - } - - public Integer getUpstreamCocCount() { - return upstreamCocCount; - } - - public void setUpstreamCocCount(Integer upstreamCocCount) { - this.upstreamCocCount = upstreamCocCount; - } - } } diff --git a/src/main/java/com/cisd/tms/modules/init/executor/ConfigurableInitStepExecutor.java b/src/main/java/com/cisd/tms/modules/init/executor/ConfigurableInitStepExecutor.java index 71aab26..596c462 100644 --- a/src/main/java/com/cisd/tms/modules/init/executor/ConfigurableInitStepExecutor.java +++ b/src/main/java/com/cisd/tms/modules/init/executor/ConfigurableInitStepExecutor.java @@ -149,7 +149,12 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { case "verify_reset" -> executeVerifyReset(task, step); case "direct_db_init" -> executeDirectDbInit(task, step); case "tlq_init_cfg" -> executeTlqInitCfg(task, step); - case "cfmq_apply" -> executeCfmqApply(task, step); + case "cae_package_apply" -> executeCaePackageApply(task, step); + case "cae_update" -> executeCaeUpdate(task, step); + case "tlq_gateway_apply" -> executeTlqGatewayApply(task, step); + case "restart_tlq" -> executeRestartTlq(task, step); + case "cfmq_link_apply", "cfmq_apply" -> executeCfmqLinkApply(task, step); + case "cfmq_route_apply" -> executeCfmqRouteApply(task, step); case "verify" -> executeVerify(task, step); default -> { String logPath = writeInternalLog(task, step, "skip unsupported internal step: " + internalCode); @@ -177,6 +182,18 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { : buildStandardReplacements(context); int changed = replaceConfigValues(cpconfigPath, replacements); String message = "cpconfig rendered, path=" + cpconfigPath + ", changedKeys=" + changed; + if ("DIRECT".equals(context.productType())) { + Path targetCpconfigPath = Paths.get(firstNonBlank(properties.getDirectCpconfigTargetPath(), "/opt/cpconfig.cfg")) + .normalize() + .toAbsolutePath(); + if (targetCpconfigPath.getParent() != null) { + Files.createDirectories(targetCpconfigPath.getParent()); + } + if (!cpconfigPath.equals(targetCpconfigPath)) { + Files.copy(cpconfigPath, targetCpconfigPath, StandardCopyOption.REPLACE_EXISTING); + message = message + ", syncedTarget=" + targetCpconfigPath; + } + } String logPath = writeInternalLog(task, step, message); return InitStepExecutionResult.success(message, 0, logPath); } @@ -794,57 +811,8 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { String logPath = writeInternalLog(task, step, "direct_db_init skipped for non-direct product type"); return InitStepExecutionResult.success("direct_db_init skipped", 0, logPath); } - if (properties.getMode() == InitExecutorProperties.Mode.SIMULATE) { - String logPath = writeInternalLog(task, step, "direct_db_init simulated"); - return InitStepExecutionResult.success("direct_db_init simulated", 0, logPath); - } - - Path scriptDir = Paths.get(firstNonBlank(properties.getDirectDbScriptDir(), "/opt/CAE_Install_Base_Resource/DB")) - .normalize() - .toAbsolutePath(); - if (!Files.isDirectory(scriptDir)) { - String logPath = writeInternalLog(task, step, "direct_db_init failed: script dir not found: " + scriptDir); - return InitStepExecutionResult.failure("direct_db_init failed: script dir not found", -1, logPath); - } - - ResolvedCpconfig resolvedCpconfig = resolveCpconfigPath(task, context); - Path cpconfigPath = resolvedCpconfig.path(); - if (!Files.exists(cpconfigPath) || !Files.isRegularFile(cpconfigPath)) { - String logPath = writeInternalLog( - task, - step, - "direct_db_init failed: cpconfig not found: " + cpconfigPath - + ", candidates=" + formatPathList(resolvedCpconfig.candidates()) - ); - return InitStepExecutionResult.failure("direct_db_init failed: cpconfig not found", -1, logPath); - } - Path targetCpconfigPath = Paths.get(firstNonBlank(properties.getDirectCpconfigTargetPath(), "/opt/cpconfig.cfg")) - .normalize() - .toAbsolutePath(); - - try { - Path logFile = prepareLogPath(task, step); - if (targetCpconfigPath.getParent() != null) { - Files.createDirectories(targetCpconfigPath.getParent()); - } - Files.copy(cpconfigPath, targetCpconfigPath, StandardCopyOption.REPLACE_EXISTING); - appendLog(logFile, "cpconfig prepared: " + cpconfigPath + " -> " + targetCpconfigPath); - - int code = runCommand( - logFile, - Arrays.asList("bash", "./setsql.sh"), - false, - scriptDir - ); - if (code != 0) { - return InitStepExecutionResult.failure("direct_db_init failed", code, logFile.toString()); - } - return InitStepExecutionResult.success("direct db init applied", 0, logFile.toString()); - } catch (InterruptedException ex) { - Thread.currentThread().interrupt(); - String logPath = writeInternalLog(task, step, "direct_db_init interrupted"); - return InitStepExecutionResult.failure("direct_db_init interrupted", -1, logPath); - } + String logPath = writeInternalLog(task, step, "direct_db_init skipped: out of direct customer-config scope"); + return InitStepExecutionResult.success("direct_db_init skipped", 0, logPath); } private InitStepExecutionResult executeStandardDbRender(InitTaskEntity task, InitTaskStepEntity step) throws IOException { @@ -1229,83 +1197,197 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { String logPath = writeInternalLog(task, step, "tlq_init_cfg skipped for non-direct TLQ mode"); return InitStepExecutionResult.success("tlq_init_cfg skipped", 0, logPath); } - if (properties.getMode() == InitExecutorProperties.Mode.SIMULATE) { - String logPath = writeInternalLog(task, step, "tlq_init_cfg simulated"); - return InitStepExecutionResult.success("tlq_init_cfg simulated", 0, logPath); + String logPath = writeInternalLog(task, step, "tlq_init_cfg skipped: no longer part of direct customer-config scope"); + return InitStepExecutionResult.success("tlq_init_cfg skipped", 0, logPath); + } + + private InitStepExecutionResult executeCaePackageApply(InitTaskEntity task, InitTaskStepEntity step) throws IOException { + PlanContext context = loadPlanContext(task); + if (!"DIRECT".equals(context.productType())) { + String logPath = writeInternalLog(task, step, "cae_package_apply skipped for non-direct product type"); + return InitStepExecutionResult.success("cae_package_apply skipped", 0, logPath); } - String localNodeSeq = normalizeUpper(context.directTlqLocalNodeSeq()); - String upstreamInstitutions = normalize(context.directTlqUpstreamInstitutions()); - Integer upstreamCocCount = context.directTlqUpstreamCocCount(); - if (!"A".equals(localNodeSeq) && !"B".equals(localNodeSeq)) { - String logPath = writeInternalLog(task, step, "tlq_init_cfg failed: localNodeSeq must be A/B"); - return InitStepExecutionResult.failure("tlq_init_cfg failed: invalid localNodeSeq", -1, logPath); - } - if (isBlank(upstreamInstitutions)) { - String logPath = writeInternalLog(task, step, "tlq_init_cfg failed: upstreamInstitutions is blank"); - return InitStepExecutionResult.failure("tlq_init_cfg failed: upstreamInstitutions is blank", -1, logPath); - } - if (upstreamCocCount == null || upstreamCocCount < 1 || upstreamCocCount > 2) { - String logPath = writeInternalLog(task, step, "tlq_init_cfg failed: upstreamCocCount must be in [1,2]"); - return InitStepExecutionResult.failure("tlq_init_cfg failed: invalid upstreamCocCount", -1, logPath); - } + Path zipFile = resolveAndStageInputFile(task, context.cfgZipFileId(), "cae_package", null); + Path unpackDir = resolveDirectStageDir(task, "cae/unpacked"); + recreateDirectory(unpackDir); + unzipToDirectory(zipFile, unpackDir); - Path scriptDir = Paths.get(firstNonBlank(properties.getDirectTlqInitCfgDir(), "/opt/CAE_Install_App_Resource/CAE")) + Path targetDir = Paths.get(firstNonBlank(properties.getDirectCaeTargetDir(), properties.getCfmqTargetDir())) .normalize() .toAbsolutePath(); - if (!Files.isDirectory(scriptDir)) { - String logPath = writeInternalLog(task, step, "tlq_init_cfg failed: script dir not found: " + scriptDir); - return InitStepExecutionResult.failure("tlq_init_cfg failed: script dir not found", -1, logPath); + Files.createDirectories(targetDir); + copyDirectoryContents(unpackDir, targetDir); + + String message = "cae package applied: " + zipFile + " -> " + targetDir; + String logPath = writeInternalLog(task, step, message); + return InitStepExecutionResult.success(message, 0, logPath); + } + + private InitStepExecutionResult executeCaeUpdate(InitTaskEntity task, InitTaskStepEntity step) throws IOException { + PlanContext context = loadPlanContext(task); + if (!"DIRECT".equals(context.productType())) { + String logPath = writeInternalLog(task, step, "cae_update skipped for non-direct product type"); + return InitStepExecutionResult.success("cae_update skipped", 0, logPath); + } + if (properties.getMode() == InitExecutorProperties.Mode.SIMULATE) { + String logPath = writeInternalLog(task, step, "cae_update simulated"); + return InitStepExecutionResult.success("cae_update simulated", 0, logPath); } + String command = normalize(properties.getDirectCaeUpdateCommand()); + if (isBlank(command)) { + String logPath = writeInternalLog(task, step, "cae_update validated only: command is blank"); + return InitStepExecutionResult.success("cae_update validated", 0, logPath); + } + + Path scriptDir = Paths.get(firstNonBlank(properties.getDirectCaeUpdateScriptDir(), "/opt/CAE_Install_App_Resource/CAE")) + .normalize() + .toAbsolutePath(); + Path logFile = prepareLogPath(task, step); try { - Path logFile = prepareLogPath(task, step); - int code = runCommand( - logFile, - Arrays.asList( - "bash", - "./init_cfg.sh", - normalize(context.orgCode()), - localNodeSeq, - "TLQ", - upstreamInstitutions, - String.valueOf(upstreamCocCount) - ), - false, - scriptDir - ); + int code = runCommand(logFile, Arrays.asList("/bin/zsh", "-lc", command), false, scriptDir); if (code != 0) { - return InitStepExecutionResult.failure("tlq_init_cfg failed", code, logFile.toString()); + return InitStepExecutionResult.failure("cae_update failed", code, logFile.toString()); } - return InitStepExecutionResult.success("tlq_init_cfg applied", 0, logFile.toString()); + return InitStepExecutionResult.success("cae_update applied", 0, logFile.toString()); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); - String logPath = writeInternalLog(task, step, "tlq_init_cfg interrupted"); - return InitStepExecutionResult.failure("tlq_init_cfg interrupted", -1, logPath); + return InitStepExecutionResult.failure("cae_update interrupted", -1, logFile.toString()); } } - private InitStepExecutionResult executeCfmqApply(InitTaskEntity task, InitTaskStepEntity step) throws IOException { + private InitStepExecutionResult executeTlqGatewayApply(InitTaskEntity task, InitTaskStepEntity step) throws IOException { + PlanContext context = loadPlanContext(task); + if (!"DIRECT".equals(context.productType()) || !"RABBITMQ_TLQ".equals(context.mqType())) { + String logPath = writeInternalLog(task, step, "tlq_gateway_apply skipped for non-direct TLQ mode"); + return InitStepExecutionResult.success("tlq_gateway_apply skipped", 0, logPath); + } + + Path sourceScript = resolveTlqGatewaySourceScript(task); + if (!Files.exists(sourceScript) || !Files.isRegularFile(sourceScript)) { + String logPath = writeInternalLog(task, step, "tlq_gateway_apply failed: source script not found: " + sourceScript); + return InitStepExecutionResult.failure("tlq_gateway_apply failed: source script not found", -1, logPath); + } + + String rewritten = rewriteGatewayHost(Files.readString(sourceScript), context.cipsGatewayHost()); + Path targetDir = Paths.get(firstNonBlank(properties.getDirectTlqGatewayScriptTargetDir(), "/home/tlq/bin")) + .normalize() + .toAbsolutePath(); + Files.createDirectories(targetDir); + Path targetScript = targetDir.resolve(sourceScript.getFileName()); + Files.writeString(targetScript, rewritten, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); + + if (properties.getMode() == InitExecutorProperties.Mode.SIMULATE) { + String logPath = writeInternalLog(task, step, "tlq_gateway_apply simulated: " + targetScript); + return InitStepExecutionResult.success("tlq_gateway_apply simulated", 0, logPath); + } + + Path logFile = prepareLogPath(task, step); + try { + int code = runCommand(logFile, Arrays.asList("bash", targetScript.toString()), false); + if (code != 0) { + return InitStepExecutionResult.failure("tlq_gateway_apply failed", code, logFile.toString()); + } + return InitStepExecutionResult.success("tlq gateway applied: " + targetScript, 0, logFile.toString()); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + return InitStepExecutionResult.failure("tlq_gateway_apply interrupted", -1, logFile.toString()); + } + } + + private InitStepExecutionResult executeRestartTlq(InitTaskEntity task, InitTaskStepEntity step) throws IOException { + PlanContext context = loadPlanContext(task); + if (!"DIRECT".equals(context.productType()) || !"RABBITMQ_TLQ".equals(context.mqType())) { + String logPath = writeInternalLog(task, step, "restart_tlq skipped for non-direct TLQ mode"); + return InitStepExecutionResult.success("restart_tlq skipped", 0, logPath); + } + if (properties.getMode() == InitExecutorProperties.Mode.SIMULATE) { + String logPath = writeInternalLog(task, step, "restart_tlq simulated"); + return InitStepExecutionResult.success("restart_tlq simulated", 0, logPath); + } + + String command = normalize(properties.getDirectTlqRestartCommand()); + if (isBlank(command)) { + String logPath = writeInternalLog(task, step, "restart_tlq validated only: command is blank"); + return InitStepExecutionResult.success("restart_tlq validated", 0, logPath); + } + + Path logFile = prepareLogPath(task, step); + try { + int code = runCommand(logFile, Arrays.asList("/bin/zsh", "-lc", command), false); + if (code != 0) { + return InitStepExecutionResult.failure("restart_tlq failed", code, logFile.toString()); + } + return InitStepExecutionResult.success("restart_tlq applied", 0, logFile.toString()); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + return InitStepExecutionResult.failure("restart_tlq interrupted", -1, logFile.toString()); + } + } + + private InitStepExecutionResult executeCfmqLinkApply(InitTaskEntity task, InitTaskStepEntity step) throws IOException { PlanContext context = loadPlanContext(task); if (!"DIRECT".equals(context.productType()) || !"RABBITMQ_CFMQ".equals(context.mqType())) { - String logPath = writeInternalLog(task, step, "mq is not RABBITMQ_CFMQ, cfmq_apply skipped"); - return InitStepExecutionResult.success("cfmq_apply skipped", 0, logPath); + String logPath = writeInternalLog(task, step, "cfmq_link_apply skipped for non-direct CFMQ mode"); + return InitStepExecutionResult.success("cfmq_link_apply skipped", 0, logPath); } - Path source = resolveAndStageInputFile(task, context.cfmqConfigFileId(), "cfmq_config", null); - String targetDirValue = normalize(properties.getCfmqTargetDir()); - if (isBlank(targetDirValue)) { - String logPath = writeInternalLog(task, step, "cfmq config validated only: " + source); - return InitStepExecutionResult.success("cfmq config validated", 0, logPath); + Path sourceScript = resolveAndStageInputFile(task, context.cfmqConfigFileId(), "cfmq_link_script", null); + String rewritten = Files.readString(sourceScript) + .replace("/cfmq/cfmq-server", normalize(properties.getDirectCfmqInstallPath())); + Path stagedScript = resolveDirectStageDir(task, "cfmq/link").resolve(sourceScript.getFileName()); + Files.createDirectories(stagedScript.getParent()); + Files.writeString(stagedScript, rewritten, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); + + if (properties.getMode() == InitExecutorProperties.Mode.SIMULATE) { + String logPath = writeInternalLog(task, step, "cfmq_link_apply simulated: " + stagedScript); + return InitStepExecutionResult.success("cfmq_link_apply simulated", 0, logPath); } - Path targetDir = Paths.get(targetDirValue).normalize().toAbsolutePath(); - Files.createDirectories(targetDir); - Path target = targetDir.resolve(source.getFileName()); - Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING); - String message = "cfmq config applied: " + source + " -> " + target; - String logPath = writeInternalLog(task, step, message); - return InitStepExecutionResult.success(message, 0, logPath); + Path logFile = prepareLogPath(task, step); + try { + int code = runCommand(logFile, Arrays.asList("bash", stagedScript.toString()), false); + if (code != 0) { + return InitStepExecutionResult.failure("cfmq_link_apply failed", code, logFile.toString()); + } + return InitStepExecutionResult.success("cfmq_link_apply applied", 0, logFile.toString()); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + return InitStepExecutionResult.failure("cfmq_link_apply interrupted", -1, logFile.toString()); + } + } + + private InitStepExecutionResult executeCfmqRouteApply(InitTaskEntity task, InitTaskStepEntity step) throws IOException { + PlanContext context = loadPlanContext(task); + if (!"DIRECT".equals(context.productType()) || !"RABBITMQ_CFMQ".equals(context.mqType())) { + String logPath = writeInternalLog(task, step, "cfmq_route_apply skipped for non-direct CFMQ mode"); + return InitStepExecutionResult.success("cfmq_route_apply skipped", 0, logPath); + } + + Path targetDir = Paths.get(firstNonBlank(properties.getDirectCaeTargetDir(), properties.getCfmqTargetDir())) + .normalize() + .toAbsolutePath(); + Path logFile = prepareLogPath(task, step); + int routeChanged = 0; + try (Stream stream = Files.walk(targetDir)) { + for (Path item : stream.filter(Files::isRegularFile).toList()) { + String fileName = item.getFileName() == null ? "" : item.getFileName().toString(); + if (fileName.startsWith("route_") && fileName.endsWith(".ini")) { + String content = Files.readString(item); + String rewritten = content.replace("MSGMB_1", "MSGTOMB_1"); + if (!rewritten.equals(content)) { + Files.writeString(item, rewritten, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE); + routeChanged++; + } + } + } + } + + Path pmtsMsgHdl = targetDir.resolve("pmtsmsghdl.cfg"); + Files.deleteIfExists(pmtsMsgHdl); + appendLog(logFile, "cfmq route files changed=" + routeChanged + ", pmtsmsghdl deleted if exists=" + pmtsMsgHdl); + return InitStepExecutionResult.success("cfmq_route_apply applied", 0, logFile.toString()); } private InitStepExecutionResult executeVerify(InitTaskEntity task, InitTaskStepEntity step) throws IOException { @@ -1363,12 +1445,6 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { private void verifyDirect(Map configMap, PlanContext context, List failures) { requireNotBlank(configMap, "MQ_TYPE", failures); requireNotBlank(configMap, "node_01_ip", failures); - requireNotBlank(configMap, "db_ip", failures); - requireNotBlank(configMap, "sign_host", failures); - requireNotBlank(configMap, "sign_port", failures); - requireNotBlank(configMap, "sign_type", failures); - requireNotBlank(configMap, "sign_cert_dn", failures); - requireNotBlank(configMap, "sign_cert_sn", failures); if ("DUAL".equals(context.deployMode()) || "QUAD".equals(context.deployMode())) { requireNotBlank(configMap, "node_02_ip", failures); @@ -1432,13 +1508,6 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { replacements.put("node_02_ip", node02); replacements.put("node_03_ip", node03); replacements.put("node_04_ip", node04); - replacements.put("db_ip", node01); - replacements.put("sign_host", context.signHost()); - replacements.put("sign_port", context.signPort() == null ? "" : String.valueOf(context.signPort())); - replacements.put("sign_password", quoteShellValue(context.signPassword())); - replacements.put("sign_type", context.signType()); - replacements.put("sign_cert_dn", quoteShellValue(context.signCertDn())); - replacements.put("sign_cert_sn", context.signCertSn()); fillOrgCodeMappings(context, replacements); return replacements; } @@ -1531,6 +1600,12 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { } private ResolvedCpconfig resolveCpconfigPath(InitTaskEntity task, PlanContext context) throws IOException { + if ("DIRECT".equals(context.productType())) { + Path stagedCpconfig = resolveDirectStageCpconfigPath(task); + if (Files.exists(stagedCpconfig) && Files.isRegularFile(stagedCpconfig)) { + return new ResolvedCpconfig(stagedCpconfig, List.of(stagedCpconfig)); + } + } List candidates = buildCpconfigCandidates(context); if (candidates.isEmpty()) { Path uploadBase = Paths.get(firstNonBlank(properties.getUploadBaseDir(), "/home/tms/uploads")) @@ -1541,7 +1616,7 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { for (Path candidate : candidates) { if (Files.exists(candidate) && Files.isRegularFile(candidate)) { Path cpconfig = candidate; - if ("DIRECT".equals(context.productType()) && !isBlank(context.cfgZipFileId())) { + if ("DIRECT".equals(context.productType())) { cpconfig = stageFile(task, "cpconfig", firstNonBlank(properties.getDirectCpconfigFileName(), "cpconfig.cfg"), candidate); } return new ResolvedCpconfig(cpconfig, candidates); @@ -1550,10 +1625,27 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { return new ResolvedCpconfig(candidates.get(0), candidates); } + private Path resolveDirectStageCpconfigPath(InitTaskEntity task) { + Path stageRoot = Paths.get(firstNonBlank(properties.getStagingRootDir(), "/tmp/tms-init-staging")) + .normalize() + .toAbsolutePath(); + return stageRoot.resolve(task.getTaskId()) + .resolve("inputs") + .resolve("cpconfig") + .resolve(firstNonBlank(properties.getDirectCpconfigFileName(), "cpconfig.cfg")) + .normalize() + .toAbsolutePath(); + } + private List buildCpconfigCandidates(PlanContext context) { String cpconfigFileName = firstNonBlank(properties.getDirectCpconfigFileName(), "cpconfig.cfg"); if ("DIRECT".equals(context.productType())) { - return buildFileIdCandidates(context.cfgZipFileId(), cpconfigFileName); + List candidates = new ArrayList<>(); + addAbsoluteCandidate(candidates, properties.getDirectCpconfigTargetPath()); + addAbsoluteCandidate(candidates, Paths.get(firstNonBlank(properties.getDirectCpconfigTargetPath(), "/opt/cpconfig.cfg")) + .resolveSibling(cpconfigFileName) + .toString()); + return deduplicatePaths(candidates); } List candidates = new ArrayList<>(); @@ -2024,18 +2116,6 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { JsonNode nodes = request.path("nodes"); JsonNode mq = request.path("mq"); JsonNode licenses = request.path("licenses"); - JsonNode signServer = request.path("signServer"); - JsonNode directTlq = request.path("directTlq"); - - Integer signPort = null; - if (!signServer.path("signPort").isMissingNode() && !signServer.path("signPort").isNull()) { - signPort = signServer.path("signPort").asInt(); - } - Integer directTlqUpstreamCocCount = null; - if (!directTlq.path("upstreamCocCount").isMissingNode() && !directTlq.path("upstreamCocCount").isNull()) { - directTlqUpstreamCocCount = directTlq.path("upstreamCocCount").asInt(); - } - return new PlanContext( firstNonBlank(normalizeUpper(text(root, "productType")), normalizeUpper(task.getProductType())), normalizeUpper(text(request, "orgCodeType")), @@ -2051,18 +2131,10 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { text(nodes, "node03Ip"), text(nodes, "node04Ip"), text(mq, "tlqLicenseFileId"), - text(mq, "cfmqConfigFileId"), text(licenses, "receiverLicenseFileId"), - text(licenses, "cfgZipFileId"), - text(signServer, "signHost"), - signPort, - text(signServer, "signType"), - text(signServer, "signCertDn"), - text(signServer, "signCertSn"), - text(signServer, "signPassword"), - text(directTlq, "localNodeSeq"), - text(directTlq, "upstreamInstitutions"), - directTlqUpstreamCocCount + text(mq, "cipsGatewayHost"), + text(mq, "cfgZipFileId"), + text(mq, "cfmqConfigFileId") ); } @@ -2130,6 +2202,46 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { return "'" + escaped + "'"; } + private Path resolveDirectStageDir(InitTaskEntity task, String relative) { + Path stageRoot = Paths.get(firstNonBlank(properties.getStagingRootDir(), "/tmp/tms-init-staging")) + .normalize() + .toAbsolutePath(); + return stageRoot.resolve(task.getTaskId()).resolve("direct").resolve(relative).normalize().toAbsolutePath(); + } + + private Path resolveTlqGatewaySourceScript(InitTaskEntity task) throws IOException { + Path caeTargetDir = Paths.get(firstNonBlank(properties.getDirectCaeTargetDir(), properties.getCfmqTargetDir())) + .normalize() + .toAbsolutePath(); + Path preferred = caeTargetDir.resolve("bin").resolve("tlq_gateway.sh").normalize().toAbsolutePath(); + if (Files.exists(preferred) && Files.isRegularFile(preferred)) { + return preferred; + } + try (Stream stream = Files.walk(caeTargetDir)) { + for (Path item : stream.filter(Files::isRegularFile).toList()) { + String name = item.getFileName() == null ? "" : item.getFileName().toString().toLowerCase(Locale.ROOT); + if (name.contains("gateway") && name.endsWith(".sh")) { + return item; + } + } + } + return preferred; + } + + private String rewriteGatewayHost(String content, String gatewayHost) { + String normalizedHost = normalize(gatewayHost); + String rewritten = normalize(content) + .replaceAll("(?m)^(\\s*gateway\\s*=\\s*).*$", "$1" + normalizedHost) + .replaceAll("(?m)^(\\s*host\\s*=\\s*).*$", "$1" + normalizedHost); + if (rewritten.equals(normalize(content))) { + rewritten = normalize(content).replace("127.0.0.1", normalizedHost); + } + if (!rewritten.endsWith(System.lineSeparator())) { + rewritten = rewritten + System.lineSeparator(); + } + return rewritten; + } + private void verifyProcessAbsent(Path logFile, String pattern, String label, List failures) throws IOException { try { int code = runCommand( @@ -2249,18 +2361,10 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { String node03Ip, String node04Ip, String tlqLicenseFileId, - String cfmqConfigFileId, String receiverLicenseFileId, + String cipsGatewayHost, String cfgZipFileId, - String signHost, - Integer signPort, - String signType, - String signCertDn, - String signCertSn, - String signPassword, - String directTlqLocalNodeSeq, - String directTlqUpstreamInstitutions, - Integer directTlqUpstreamCocCount + String cfmqConfigFileId ) { } diff --git a/src/main/java/com/cisd/tms/modules/init/service/impl/InitServiceImpl.java b/src/main/java/com/cisd/tms/modules/init/service/impl/InitServiceImpl.java index 4607042..a95aa0c 100644 --- a/src/main/java/com/cisd/tms/modules/init/service/impl/InitServiceImpl.java +++ b/src/main/java/com/cisd/tms/modules/init/service/impl/InitServiceImpl.java @@ -3,6 +3,7 @@ package com.cisd.tms.modules.init.service.impl; import com.cisd.tms.common.config.properties.CisdPresetProperties; import com.cisd.tms.common.enums.ErrorCode; import com.cisd.tms.common.exception.BizException; +import com.cisd.tms.modules.file.repository.FileRecordRepository; import com.cisd.tms.modules.init.config.InitCommandProfileService; import com.cisd.tms.modules.init.config.InitExecutorProperties; import com.cisd.tms.modules.init.dto.InitCreateTaskResponse; @@ -59,6 +60,7 @@ public class InitServiceImpl implements InitService { private final InitTaskRepository initTaskRepository; private final InitTaskStepRepository initTaskStepRepository; + private final FileRecordRepository fileRecordRepository; private final CisdPresetProperties cisdPresetProperties; private final ObjectMapper objectMapper; private final InitStepExecutor initStepExecutor; @@ -507,9 +509,25 @@ public class InitServiceImpl implements InitService { // 该步骤序列只描述“逻辑编排”,具体脚本命令在 step-command-profiles.json 中定义 if ("DIRECT".equals(productType)) { if ("RABBITMQ_CFMQ".equals(mqType)) { - return List.of("RENDER_CONFIG", "FILE_PREPARE", "MQ_USER_APPLY", "DB_INIT", "CFMQ_APPLY", "VERIFY"); + return List.of( + "RENDER_CONFIG", + "MQ_USER_APPLY", + "CFMQ_LINK_APPLY", + "CAE_PACKAGE_APPLY", + "CFMQ_ROUTE_APPLY", + "CAE_UPDATE", + "VERIFY" + ); } - return List.of("RENDER_CONFIG", "FILE_PREPARE", "MQ_USER_APPLY", "DB_INIT", "TLQ_INIT_CFG", "VERIFY"); + return List.of( + "RENDER_CONFIG", + "MQ_USER_APPLY", + "CAE_PACKAGE_APPLY", + "TLQ_GATEWAY_APPLY", + "CAE_UPDATE", + "RESTART_TLQ", + "VERIFY" + ); } if ("ENTERPRISE".equals(productType) && "TLQ".equals(mqType)) { return List.of( @@ -621,9 +639,6 @@ public class InitServiceImpl implements InitService { if (request.getMq() == null) { throw new IllegalArgumentException("mq is required"); } - if (request.getLicenses() == null) { - throw new IllegalArgumentException("licenses is required"); - } requireNotBlank("orgCode", request.getOrgCode()); requireNotBlank("deployMode", request.getDeployMode()); @@ -631,12 +646,14 @@ public class InitServiceImpl implements InitService { requireNotBlank("mq.mqType", request.getMq().getMqType()); requireNotBlank("mq.channelUsername", request.getMq().getChannelUsername()); requireNotBlank("mq.channelPassword", request.getMq().getChannelPassword()); - requireNotBlank("licenses.receiverLicenseFileId", request.getLicenses().getReceiverLicenseFileId()); - requireFileId("licenses.receiverLicenseFileId", request.getLicenses().getReceiverLicenseFileId()); + if (request.getLicenses() != null) { + requireOptionalFileId("licenses.receiverLicenseFileId", request.getLicenses().getReceiverLicenseFileId()); + requireOptionalFileId("licenses.cfgZipFileId", request.getLicenses().getCfgZipFileId()); + } requireOptionalFileId("mq.tlqLicenseFileId", request.getMq().getTlqLicenseFileId()); requireOptionalFileId("mq.cfmqConfigFileId", request.getMq().getCfmqConfigFileId()); - requireOptionalFileId("licenses.cfgZipFileId", request.getLicenses().getCfgZipFileId()); + requireOptionalFileId("mq.cfgZipFileId", request.getMq().getCfgZipFileId()); } private void validateByProductType(String productType, InitPreviewRequest request) { @@ -709,8 +726,8 @@ public class InitServiceImpl implements InitService { JsonNode root = objectMapper.readTree(task.getInitPlanJson()); JsonNode request = root.path("request"); JsonNode mq = request.path("mq"); + JsonNode licenses = request.path("licenses"); JsonNode nodes = request.path("nodes"); - JsonNode directTlq = request.path("directTlq"); CurrentInitConfigResponse response = new CurrentInitConfigResponse(); response.setOrgCodeType(firstNonBlank(normalizeUpper(text(request, "orgCodeType")), normalizeUpper(text(root, "orgCodeType")))); @@ -724,12 +741,60 @@ public class InitServiceImpl implements InitService { response.setNode01Ip(firstNonBlank(text(nodes, "node01Ip"), text(request, "node01Ip"))); response.setNode02Ip(firstNonBlank(text(nodes, "node02Ip"), text(request, "node02Ip"))); response.setCipsGatewayHost(firstNonBlank(text(mq, "cipsGatewayHost"), text(request, "cipsGatewayHost"))); + response.setReceiverLicenseFileName(resolveUploadedFileName(text(licenses, "receiverLicenseFileId"))); + String mqType = firstNonBlank(normalizeUpper(text(mq, "mqType")), normalizeUpper(text(root, "mqType")), normalizeUpper(task.getMqType())); + response.setTlqFileNames(buildTlqFileNames(mq, mqType)); + response.setCfmqFileNames(buildCfmqFileNames(mq, mqType)); return response; } catch (JsonProcessingException ex) { throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "failed to parse successful init task snapshot"); } } + private List buildTlqFileNames( + JsonNode mq, + String mqType + ) { + List fileNames = new ArrayList<>(); + if (!Set.of("TLQ", "RABBITMQ_TLQ").contains(normalizeUpper(mqType))) { + return fileNames; + } + addUploadedFileName(fileNames, text(mq, "tlqLicenseFileId")); + addUploadedFileName(fileNames, text(mq, "cfgZipFileId")); + return fileNames; + } + + private List buildCfmqFileNames( + JsonNode mq, + String mqType + ) { + List fileNames = new ArrayList<>(); + if (!"RABBITMQ_CFMQ".equals(normalizeUpper(mqType))) { + return fileNames; + } + addUploadedFileName(fileNames, text(mq, "cfgZipFileId")); + addUploadedFileName(fileNames, text(mq, "cfmqConfigFileId")); + return fileNames; + } + + private void addUploadedFileName(List fileNames, String fileId) { + String fileName = resolveUploadedFileName(fileId); + if (isBlank(fileName)) { + return; + } + fileNames.add(fileName); + } + + private String resolveUploadedFileName(String fileId) { + if (isBlank(fileId)) { + return null; + } + return fileRecordRepository.findByFileId(normalize(fileId)) + .map(record -> normalize(record.getOriginalFilename())) + .filter(name -> !name.isEmpty()) + .orElse(null); + } + private int compareTaskRecency(InitTaskEntity left, InitTaskEntity right) { Comparator comparator = Comparator.nullsFirst(LocalDateTime::compareTo); int byUpdate = comparator.compare(left.getUpdateTime(), right.getUpdateTime()); @@ -746,6 +811,11 @@ public class InitServiceImpl implements InitService { private void validateStandard(String productType, InitPreviewRequest request, String deployMode, String mqType) { requireAllowed("deployMode", deployMode, DEPLOY_STANDARD); requireAllowed("mq.mqType", mqType, resolveStandardMqAllowed(productType)); + if (request.getLicenses() == null) { + throw new IllegalArgumentException("licenses is required"); + } + requireNotBlank("licenses.receiverLicenseFileId", request.getLicenses().getReceiverLicenseFileId()); + requireFileId("licenses.receiverLicenseFileId", request.getLicenses().getReceiverLicenseFileId()); if ("DUAL".equals(deployMode)) { requireNotBlank("nodes.node02Ip", request.getNodes().getNode02Ip()); @@ -779,26 +849,14 @@ public class InitServiceImpl implements InitService { requireNotBlank("nodes.node04Ip", request.getNodes().getNode04Ip()); } - requireNotBlank("licenses.cfgZipFileId", request.getLicenses().getCfgZipFileId()); - requireFileId("licenses.cfgZipFileId", request.getLicenses().getCfgZipFileId()); - if ("RABBITMQ_TLQ".equals(mqType)) { - requireNotBlank("mq.tlqLicenseFileId", request.getMq().getTlqLicenseFileId()); - requireFileId("mq.tlqLicenseFileId", request.getMq().getTlqLicenseFileId()); - InitPreviewRequest.DirectTlqConfig directTlq = request.getDirectTlq(); - if (directTlq == null) { - throw new IllegalArgumentException("directTlq is required for RABBITMQ_TLQ"); - } - requireNotBlank("directTlq.localNodeSeq", directTlq.getLocalNodeSeq()); - requireNotBlank("directTlq.upstreamInstitutions", directTlq.getUpstreamInstitutions()); + requireNotBlank("mq.cfgZipFileId", request.getMq().getCfgZipFileId()); + requireFileId("mq.cfgZipFileId", request.getMq().getCfgZipFileId()); requireNotBlank("mq.cipsGatewayHost", request.getMq().getCipsGatewayHost()); - if (directTlq.getUpstreamCocCount() == null - || directTlq.getUpstreamCocCount() < 1 - || directTlq.getUpstreamCocCount() > 2) { - throw new IllegalArgumentException("directTlq.upstreamCocCount must be in [1,2]"); - } } if ("RABBITMQ_CFMQ".equals(mqType)) { + requireNotBlank("mq.cfgZipFileId", request.getMq().getCfgZipFileId()); + requireFileId("mq.cfgZipFileId", request.getMq().getCfgZipFileId()); requireNotBlank("mq.cfmqConfigFileId", request.getMq().getCfmqConfigFileId()); requireFileId("mq.cfmqConfigFileId", request.getMq().getCfmqConfigFileId()); } @@ -859,6 +917,7 @@ public class InitServiceImpl implements InitService { return normalize(child.toString()); } + private static String normalizeUpper(String value) { return normalize(value).toUpperCase(Locale.ROOT); } diff --git a/src/main/java/com/cisd/tms/modules/upgrade/controller/UpgradeController.java b/src/main/java/com/cisd/tms/modules/upgrade/controller/UpgradeController.java index cadf431..33f6d53 100644 --- a/src/main/java/com/cisd/tms/modules/upgrade/controller/UpgradeController.java +++ b/src/main/java/com/cisd/tms/modules/upgrade/controller/UpgradeController.java @@ -73,14 +73,16 @@ public class UpgradeController { } @GetMapping("/upgrades") - @Operation(summary = "查询升级记录列表", description = "分页查询升级任务列表,支持按升级对象和状态筛选。") + @Operation(summary = "查询升级记录列表", description = "分页查询升级任务列表,支持按升级对象、状态和升级日期筛选。") public ApiResponse list( @RequestParam(defaultValue = "1") int page, @RequestParam(defaultValue = "10") int pageSize, @RequestParam(required = false) String taskType, - @RequestParam(required = false) String status + @RequestParam(required = false) String status, + @Parameter(description = "升级日期,格式 yyyy-MM-dd,按 startedAt 所在自然日筛选") + @RequestParam(required = false) String upgradeDate ) { - return ApiResponse.success(upgradeService.listTasks(page, pageSize, taskType, status)); + return ApiResponse.success(upgradeService.listTasks(page, pageSize, taskType, status, upgradeDate)); } @GetMapping("/upgrades/{taskId}") diff --git a/src/main/java/com/cisd/tms/modules/upgrade/dto/UpgradeCreateTaskRequest.java b/src/main/java/com/cisd/tms/modules/upgrade/dto/UpgradeCreateTaskRequest.java index e9f1939..0d13ef5 100644 --- a/src/main/java/com/cisd/tms/modules/upgrade/dto/UpgradeCreateTaskRequest.java +++ b/src/main/java/com/cisd/tms/modules/upgrade/dto/UpgradeCreateTaskRequest.java @@ -7,6 +7,8 @@ public class UpgradeCreateTaskRequest { @Schema(description = "升级包文件标识", example = "FILE-20260319-000001", requiredMode = Schema.RequiredMode.REQUIRED) private String fileId; + @Schema(description = "用户选择的升级任务类型,必须与升级包解析结果一致", example = "TMS", requiredMode = Schema.RequiredMode.REQUIRED) + private String taskType; public String getFileId() { return fileId; @@ -15,4 +17,12 @@ public class UpgradeCreateTaskRequest { public void setFileId(String fileId) { this.fileId = fileId; } + + public String getTaskType() { + return taskType; + } + + public void setTaskType(String taskType) { + this.taskType = taskType; + } } diff --git a/src/main/java/com/cisd/tms/modules/upgrade/mapper/UpgradeTaskMapper.java b/src/main/java/com/cisd/tms/modules/upgrade/mapper/UpgradeTaskMapper.java index 7f395ef..2dfce58 100644 --- a/src/main/java/com/cisd/tms/modules/upgrade/mapper/UpgradeTaskMapper.java +++ b/src/main/java/com/cisd/tms/modules/upgrade/mapper/UpgradeTaskMapper.java @@ -17,13 +17,17 @@ public interface UpgradeTaskMapper extends BaseMapperX { List selectPage( @Param("taskType") String taskType, @Param("status") String status, + @Param("startedAtBegin") LocalDateTime startedAtBegin, + @Param("startedAtEndExclusive") LocalDateTime startedAtEndExclusive, @Param("offset") int offset, @Param("limit") int limit ); long countPage( @Param("taskType") String taskType, - @Param("status") String status + @Param("status") String status, + @Param("startedAtBegin") LocalDateTime startedAtBegin, + @Param("startedAtEndExclusive") LocalDateTime startedAtEndExclusive ); int updateStatusByTaskId( diff --git a/src/main/java/com/cisd/tms/modules/upgrade/repository/UpgradeTaskRepository.java b/src/main/java/com/cisd/tms/modules/upgrade/repository/UpgradeTaskRepository.java index fa13a56..7a78053 100644 --- a/src/main/java/com/cisd/tms/modules/upgrade/repository/UpgradeTaskRepository.java +++ b/src/main/java/com/cisd/tms/modules/upgrade/repository/UpgradeTaskRepository.java @@ -13,9 +13,16 @@ public interface UpgradeTaskRepository { Optional findRunningTask(); - List findPage(String taskType, String status, int offset, int limit); + List findPage( + String taskType, + String status, + LocalDateTime startedAtBegin, + LocalDateTime startedAtEndExclusive, + int offset, + int limit + ); - long countPage(String taskType, String status); + long countPage(String taskType, String status, LocalDateTime startedAtBegin, LocalDateTime startedAtEndExclusive); void updateStatus( String taskId, diff --git a/src/main/java/com/cisd/tms/modules/upgrade/repository/impl/UpgradeTaskRepositoryImpl.java b/src/main/java/com/cisd/tms/modules/upgrade/repository/impl/UpgradeTaskRepositoryImpl.java index d0f956b..680fbbb 100644 --- a/src/main/java/com/cisd/tms/modules/upgrade/repository/impl/UpgradeTaskRepositoryImpl.java +++ b/src/main/java/com/cisd/tms/modules/upgrade/repository/impl/UpgradeTaskRepositoryImpl.java @@ -33,13 +33,20 @@ public class UpgradeTaskRepositoryImpl implements UpgradeTaskRepository { } @Override - public List findPage(String taskType, String status, int offset, int limit) { - return upgradeTaskMapper.selectPage(taskType, status, offset, limit); + public List findPage( + String taskType, + String status, + LocalDateTime startedAtBegin, + LocalDateTime startedAtEndExclusive, + int offset, + int limit + ) { + return upgradeTaskMapper.selectPage(taskType, status, startedAtBegin, startedAtEndExclusive, offset, limit); } @Override - public long countPage(String taskType, String status) { - return upgradeTaskMapper.countPage(taskType, status); + public long countPage(String taskType, String status, LocalDateTime startedAtBegin, LocalDateTime startedAtEndExclusive) { + return upgradeTaskMapper.countPage(taskType, status, startedAtBegin, startedAtEndExclusive); } @Override diff --git a/src/main/java/com/cisd/tms/modules/upgrade/service/UpgradeService.java b/src/main/java/com/cisd/tms/modules/upgrade/service/UpgradeService.java index bd2451c..38e04e2 100644 --- a/src/main/java/com/cisd/tms/modules/upgrade/service/UpgradeService.java +++ b/src/main/java/com/cisd/tms/modules/upgrade/service/UpgradeService.java @@ -19,8 +19,10 @@ import com.cisd.tms.modules.upgrade.repository.UpgradeTaskRepository; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; import java.util.List; import java.util.UUID; import java.util.concurrent.CompletableFuture; @@ -36,6 +38,7 @@ import org.springframework.stereotype.Service; public class UpgradeService { private static final DateTimeFormatter TASK_ID_TS = DateTimeFormatter.ofPattern("yyyyMMddHHmmss"); + private static final DateTimeFormatter UPGRADE_DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd"); private final UpgradeTaskRepository upgradeTaskRepository; private final FileRecordRepository fileRecordRepository; @@ -97,11 +100,17 @@ public class UpgradeService { if (request == null || request.getFileId() == null || request.getFileId().isBlank()) { throw new IllegalArgumentException("fileId must not be blank"); } + if (request.getTaskType() == null || request.getTaskType().isBlank()) { + throw new IllegalArgumentException("taskType must not be blank"); + } // 创建任务前重新预检一次,避免用户上传后替换文件或设备版本状态发生变化。 UpgradePreviewResponse preview = upgradePackageService.preview(request.getFileId()); if (!preview.isAllowed()) { throw new BizException(ErrorCode.BIZ_ERROR.getCode(), blankAs(preview.getBlockReason(), "upgrade preview not allowed")); } + if (!trim(request.getTaskType()).equals(trim(preview.getTaskType()))) { + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "selected taskType does not match package taskType"); + } FileRecordEntity fileRecord = fileRecordRepository.findByFileId(request.getFileId().trim()) .orElseThrow(() -> new BizException(ErrorCode.BIZ_ERROR.getCode(), "upgrade package file not found: " + request.getFileId().trim())); @@ -186,12 +195,27 @@ public class UpgradeService { return toResponse(loadTask(taskId)); } - public UpgradeTaskPageResponse listTasks(int page, int pageSize, String taskType, String status) { + public UpgradeTaskPageResponse listTasks(int page, int pageSize, String taskType, String status, String upgradeDate) { int normalizedPage = Math.max(1, page); int normalizedPageSize = Math.max(1, pageSize); int offset = (normalizedPage - 1) * normalizedPageSize; - List entities = upgradeTaskRepository.findPage(trim(taskType), trim(status), offset, normalizedPageSize); - long total = upgradeTaskRepository.countPage(trim(taskType), trim(status)); + LocalDateTime startedAtBegin = null; + LocalDateTime startedAtEndExclusive = null; + String normalizedUpgradeDate = trim(upgradeDate); + if (!normalizedUpgradeDate.isEmpty()) { + LocalDate filterDate = parseUpgradeDate(normalizedUpgradeDate); + startedAtBegin = filterDate.atStartOfDay(); + startedAtEndExclusive = filterDate.plusDays(1).atStartOfDay(); + } + List entities = upgradeTaskRepository.findPage( + trim(taskType), + trim(status), + startedAtBegin, + startedAtEndExclusive, + offset, + normalizedPageSize + ); + long total = upgradeTaskRepository.countPage(trim(taskType), trim(status), startedAtBegin, startedAtEndExclusive); UpgradeTaskPageResponse response = new UpgradeTaskPageResponse(); response.setPage(normalizedPage); @@ -267,4 +291,12 @@ public class UpgradeService { private String blankAs(String value, String fallback) { return trim(value).isEmpty() ? fallback : trim(value); } + + private LocalDate parseUpgradeDate(String upgradeDate) { + try { + return LocalDate.parse(upgradeDate, UPGRADE_DATE_FORMAT); + } catch (DateTimeParseException ex) { + throw new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "upgradeDate must be yyyy-MM-dd"); + } + } } diff --git a/src/main/resources/mapper/upgrade/UpgradeTaskMapper.xml b/src/main/resources/mapper/upgrade/UpgradeTaskMapper.xml index bec30a0..feaaf6e 100644 --- a/src/main/resources/mapper/upgrade/UpgradeTaskMapper.xml +++ b/src/main/resources/mapper/upgrade/UpgradeTaskMapper.xml @@ -59,6 +59,12 @@ AND status = #{status} + + AND started_at = ]]> #{startedAtBegin} + + + AND started_at #{startedAtEndExclusive} + ORDER BY create_time DESC, id DESC LIMIT #{limit} OFFSET #{offset} @@ -74,6 +80,12 @@ AND status = #{status} + + AND started_at = ]]> #{startedAtBegin} + + + AND started_at #{startedAtEndExclusive} + diff --git a/src/test/java/com/cisd/tms/modules/device/service/DeviceProfileServiceTest.java b/src/test/java/com/cisd/tms/modules/device/service/DeviceProfileServiceTest.java index bbc06be..320b1f2 100644 --- a/src/test/java/com/cisd/tms/modules/device/service/DeviceProfileServiceTest.java +++ b/src/test/java/com/cisd/tms/modules/device/service/DeviceProfileServiceTest.java @@ -3,6 +3,8 @@ package com.cisd.tms.modules.device.service; import com.cisd.tms.common.config.properties.CisdPresetProperties; import com.cisd.tms.integration.crypto.pcie.model.DeviceInfoResult; import com.cisd.tms.integration.crypto.pcie.service.MockPcieCryptoService; +import com.cisd.tms.modules.file.entity.FileRecordEntity; +import com.cisd.tms.modules.file.repository.FileRecordRepository; import com.cisd.tms.modules.device.config.DeviceProfileProperties; import com.cisd.tms.modules.device.dto.DeviceProfileResponse; import com.cisd.tms.modules.device.dto.DeviceProfileSoftwareItem; @@ -313,6 +315,7 @@ class DeviceProfileServiceTest { return new InitServiceImpl( new InMemoryInitTaskRepository(), new InMemoryInitTaskStepRepository(), + emptyFileRecordRepository(), preset(productType), objectMapper, successExecutor(), @@ -329,6 +332,19 @@ class DeviceProfileServiceTest { return properties; } + private static FileRecordRepository emptyFileRecordRepository() { + return new FileRecordRepository() { + @Override + public Optional findByFileId(String fileId) { + return Optional.empty(); + } + + @Override + public void save(FileRecordEntity entity) { + } + }; + } + private static InitStepExecutor successExecutor() { return (task, step) -> InitStepExecutionResult.success("ok", 0, null); } diff --git a/src/test/java/com/cisd/tms/modules/device/service/DeviceRuntimeStatusServiceTest.java b/src/test/java/com/cisd/tms/modules/device/service/DeviceRuntimeStatusServiceTest.java index 944813e..2652ae8 100644 --- a/src/test/java/com/cisd/tms/modules/device/service/DeviceRuntimeStatusServiceTest.java +++ b/src/test/java/com/cisd/tms/modules/device/service/DeviceRuntimeStatusServiceTest.java @@ -3,6 +3,8 @@ package com.cisd.tms.modules.device.service; import com.cisd.tms.common.config.properties.CisdPresetProperties; import com.cisd.tms.integration.crypto.pcie.model.DeviceInfoResult; import com.cisd.tms.integration.crypto.pcie.service.MockPcieCryptoService; +import com.cisd.tms.modules.file.entity.FileRecordEntity; +import com.cisd.tms.modules.file.repository.FileRecordRepository; import com.cisd.tms.modules.device.dto.DeviceRuntimeStatusResponse; import com.cisd.tms.modules.device.dto.SoftwareRuntimeStatusItem; import com.cisd.tms.modules.device.entity.DeviceSoftwareVersionEntity; @@ -146,6 +148,31 @@ class DeviceRuntimeStatusServiceTest { assertSoftwareAbsent(response.getSoftware(), "TLQ"); } + @Test + void shouldUseCurrentInitProductTypeBeforePresetWhenChoosingReceiverProbe() { + InitService initService = newInitService("DIRECT"); + createSuccessfulInitTask(initService, validDirectInitRequest("RABBITMQ_TLQ")); + InMemoryDeviceSoftwareVersionRepository versions = new InMemoryDeviceSoftwareVersionRepository(); + versions.save(component("RECEIVER", "直参轻量化终端版", "V1.3.2")); + + DeviceRuntimeStatusService service = new DeviceRuntimeStatusServiceImpl( + initService, + cryptoService(), + hardwareProbe(10.0, 8.0, 16.0, 100.0, 256.0), + softwareVersionProbe("SYD TMS V1.00"), + softwareRuntimeProbe(Map.of( + "APP_STANDARD", false, + "APP_DIRECT", true + )), + versions, + preset("ENTERPRISE") + ); + + DeviceRuntimeStatusResponse response = service.loadRuntimeStatus(); + + assertSoftwareStatus(response.getSoftware(), "RECEIVER", "NORMAL"); + } + private static void assertSoftwareStatus(List items, String code, String expectedStatus) { SoftwareRuntimeStatusItem item = items.stream() .filter(candidate -> code.equals(candidate.getComponentCode())) @@ -358,6 +385,7 @@ class DeviceRuntimeStatusServiceTest { return new InitServiceImpl( new InMemoryInitTaskRepository(), new InMemoryInitTaskStepRepository(), + emptyFileRecordRepository(), preset(productType), objectMapper, successExecutor(), @@ -374,6 +402,19 @@ class DeviceRuntimeStatusServiceTest { return properties; } + private static FileRecordRepository emptyFileRecordRepository() { + return new FileRecordRepository() { + @Override + public Optional findByFileId(String fileId) { + return Optional.empty(); + } + + @Override + public void save(FileRecordEntity entity) { + } + }; + } + private static InitStepExecutor successExecutor() { return (task, step) -> InitStepExecutionResult.success("ok", 0, null); } @@ -405,6 +446,19 @@ class DeviceRuntimeStatusServiceTest { return request; } + private static InitPreviewRequest validDirectInitRequest(String mqType) { + InitPreviewRequest request = validInitRequest(mqType); + InitPreviewRequest.Mq mq = request.getMq(); + mq.setCfgZipFileId("file-direct-cfg-001"); + if ("RABBITMQ_TLQ".equals(mqType)) { + mq.setCipsGatewayHost("192.168.10.20"); + } + if ("RABBITMQ_CFMQ".equals(mqType)) { + mq.setCfmqConfigFileId("file-direct-cfmq-001"); + } + return request; + } + private static void createSuccessfulInitTask(InitService service, InitPreviewRequest request) { InitCreateTaskResponse created = service.createTask(request); service.executeTask(created.getTaskId()); diff --git a/src/test/java/com/cisd/tms/modules/device/service/DeviceServiceTest.java b/src/test/java/com/cisd/tms/modules/device/service/DeviceServiceTest.java index 4616e2a..45f756d 100644 --- a/src/test/java/com/cisd/tms/modules/device/service/DeviceServiceTest.java +++ b/src/test/java/com/cisd/tms/modules/device/service/DeviceServiceTest.java @@ -4,6 +4,8 @@ import com.cisd.tms.common.config.properties.CisdPresetProperties; import com.cisd.tms.integration.crypto.pcie.model.DeviceInfoResult; import com.cisd.tms.integration.crypto.pcie.model.DeviceStatusResult; import com.cisd.tms.integration.crypto.pcie.service.PcieCryptoService; +import com.cisd.tms.modules.file.entity.FileRecordEntity; +import com.cisd.tms.modules.file.repository.FileRecordRepository; import com.cisd.tms.modules.device.config.DeviceProfileProperties; import com.cisd.tms.modules.device.dto.DeviceInfoResponse; import com.cisd.tms.modules.device.entity.DeviceSoftwareVersionEntity; @@ -100,6 +102,7 @@ class DeviceServiceTest { return new InitServiceImpl( taskRepository, stepRepository, + emptyFileRecordRepository(), preset(productType), objectMapper, successExecutor(), @@ -116,6 +119,19 @@ class DeviceServiceTest { return properties; } + private static FileRecordRepository emptyFileRecordRepository() { + return new FileRecordRepository() { + @Override + public Optional findByFileId(String fileId) { + return Optional.empty(); + } + + @Override + public void save(FileRecordEntity entity) { + } + }; + } + private static InitStepExecutor successExecutor() { return (task, step) -> InitStepExecutionResult.success("ok", 0, null); } diff --git a/src/test/java/com/cisd/tms/modules/device/support/DefaultSoftwareRuntimeProbeTest.java b/src/test/java/com/cisd/tms/modules/device/support/DefaultSoftwareRuntimeProbeTest.java index c61a102..873ec9c 100644 --- a/src/test/java/com/cisd/tms/modules/device/support/DefaultSoftwareRuntimeProbeTest.java +++ b/src/test/java/com/cisd/tms/modules/device/support/DefaultSoftwareRuntimeProbeTest.java @@ -66,4 +66,61 @@ class DefaultSoftwareRuntimeProbeTest { Assertions.assertFalse(probe.isStandardAppRunning()); } + + @Test + void shouldTreatRabbitmqAsRunningWhenRabbitmqctlSucceedsWithoutProcessNameMatch() { + DefaultSoftwareRuntimeProbe probe = new DefaultSoftwareRuntimeProbe( + List::of, + Duration.ofSeconds(1), + System::nanoTime, + command -> String.join(" ", command).contains("rabbitmqctl status") ? 0 : 1 + ); + + Assertions.assertTrue(probe.isRabbitmqRunning()); + } + + @Test + void shouldTreatStandardReceiverAsRunningForUppercaseJarProcesses() { + DefaultSoftwareRuntimeProbe probe = new DefaultSoftwareRuntimeProbe( + () -> List.of( + "java -jar /home/cmep4i/cmsp/CMEP-CMSP-INDIRECT.jar", + "java -jar /home/cmep4i/cmtp/CMEP-CMTP.jar" + ), + Duration.ofSeconds(1), + System::nanoTime + ); + + Assertions.assertTrue(probe.isStandardAppRunning()); + } + + @Test + void shouldNotTreatCmtp4caeAsStandardCmtpProcess() { + DefaultSoftwareRuntimeProbe probe = new DefaultSoftwareRuntimeProbe( + () -> List.of( + "/home/cmep4d/cmsp/start_cmsp.sh", + "/home/cmep4d/cmtp4cae/bin/startup.sh" + ), + Duration.ofSeconds(1), + System::nanoTime + ); + + Assertions.assertFalse(probe.isStandardAppRunning()); + } + + @Test + void shouldTreatDirectReceiverAsRunningWhenAllManualServicesAreAlive() { + DefaultSoftwareRuntimeProbe probe = new DefaultSoftwareRuntimeProbe( + () -> List.of( + "pmtsstat start cold", + "cmcp daemon", + "java -jar CMEP-CMSP-INDIRECT.jar", + "java -jar CMEP-CMTP.jar", + "cmtp4cae worker" + ), + Duration.ofSeconds(1), + System::nanoTime + ); + + Assertions.assertTrue(probe.isDirectAppRunning()); + } } diff --git a/src/test/java/com/cisd/tms/modules/init/executor/ConfigurableInitStepExecutorTest.java b/src/test/java/com/cisd/tms/modules/init/executor/ConfigurableInitStepExecutorTest.java index dfef904..708fb3c 100644 --- a/src/test/java/com/cisd/tms/modules/init/executor/ConfigurableInitStepExecutorTest.java +++ b/src/test/java/com/cisd/tms/modules/init/executor/ConfigurableInitStepExecutorTest.java @@ -74,22 +74,17 @@ class ConfigurableInitStepExecutorTest { } @Test - void shouldRenderDirectCpconfigFromUploadBaseDir() throws IOException { + void shouldRenderDirectCpconfigWithoutSignServerInput() throws IOException { Path tempRoot = Files.createTempDirectory("init-executor-render-fileid-test"); Path uploadDir = tempRoot.resolve("uploads"); - Path cfgDir = uploadDir.resolve("65"); - Files.createDirectories(cfgDir); - Path cpconfig = cfgDir.resolve("cpconfig.cfg"); + Files.createDirectories(uploadDir); + Path cpconfig = tempRoot.resolve("cpconfig.cfg"); Files.writeString(cpconfig, """ MY_CIPSID_OR_BIC=OLD MQ_TYPE=1 node_01_ip=127.0.0.1 db_ip=127.0.0.1 - sign_host=127.0.0.1 - sign_port=50006 - sign_type=Infosec - sign_cert_dn='old' - sign_cert_sn=old + db_port=4000 """); InitExecutorProperties properties = new InitExecutorProperties(); @@ -97,6 +92,7 @@ class ConfigurableInitStepExecutorTest { properties.setLogDir(tempRoot.resolve("logs").toString()); properties.setUploadBaseDir(uploadDir.toString()); properties.setDirectCpconfigFileName("cpconfig.cfg"); + properties.setDirectCpconfigTargetPath(cpconfig.toString()); ConfigurableInitStepExecutor executor = new ConfigurableInitStepExecutor(properties, new ObjectMapper()); InitTaskEntity task = new InitTaskEntity(); @@ -111,21 +107,7 @@ class ConfigurableInitStepExecutorTest { "orgNameEn", "TEST BANK", "deployMode", "SINGLE", "nodes", Map.of("node01Ip", "10.0.1.1"), - "mq", Map.of("mqType", "RABBITMQ_TLQ", "tlqLicenseFileId", "file-tlq-001"), - "licenses", Map.of("cfgZipFileId", "65"), - "signServer", Map.of( - "signHost", "172.30.1.101", - "signPort", 50006, - "signType", "Infosec", - "signCertDn", "CN=TEST,OU=CIPS", - "signCertSn", "123456", - "signPassword", "secret" - ), - "directTlq", Map.of( - "localNodeSeq", "A", - "upstreamInstitutions", "CIPSCNSHXXX", - "upstreamCocCount", 1 - ) + "mq", Map.of("mqType", "RABBITMQ_TLQ", "cipsGatewayHost", "172.30.1.10", "cfgZipFileId", "cae-001") ) )); @@ -140,15 +122,15 @@ class ConfigurableInitStepExecutorTest { String rendered = Files.readString(renderedPath); Assertions.assertTrue(rendered.contains("MY_CIPSID_OR_BIC=AAAABBBBXXX")); Assertions.assertTrue(rendered.contains("node_01_ip=10.0.1.1")); + Assertions.assertTrue(rendered.contains("db_ip=127.0.0.1")); } @Test - void shouldRenderDirectCpconfigFromCfgDirectory() throws IOException { + void shouldVerifyDirectConfigWithoutSignFields() throws IOException { Path tempRoot = Files.createTempDirectory("init-executor-render-test"); Path uploadDir = tempRoot.resolve("uploads"); - Path cfgDir = uploadDir.resolve("cfg-001"); - Files.createDirectories(cfgDir); - Path cpconfig = cfgDir.resolve("cpconfig.cfg"); + Files.createDirectories(uploadDir); + Path cpconfig = tempRoot.resolve("cpconfig.cfg"); Files.writeString(cpconfig, """ MY_CIPSID_OR_BIC=OLD MQ_TYPE=1 @@ -159,23 +141,18 @@ class ConfigurableInitStepExecutorTest { node_03_ip=127.0.0.1 node_04_ip=127.0.0.1 db_ip=127.0.0.1 - sign_host=127.0.0.1 - sign_port=50006 - sign_password=old - sign_type=Infosec - sign_cert_dn='old' - sign_cert_sn=old + db_port=4000 MY_BIC_CODE=OLD MY_CIPS_ID= MY_LEI_CODE= """); - Files.writeString(uploadDir.resolve("cfmq-001"), "cfmq.host=127.0.0.1"); InitExecutorProperties properties = new InitExecutorProperties(); properties.setMode(InitExecutorProperties.Mode.SIMULATE); properties.setLogDir(tempRoot.resolve("logs").toString()); properties.setUploadBaseDir(uploadDir.toString()); properties.setDirectCpconfigFileName("cpconfig.cfg"); + properties.setDirectCpconfigTargetPath(cpconfig.toString()); ConfigurableInitStepExecutor executor = new ConfigurableInitStepExecutor(properties, new ObjectMapper()); InitTaskEntity task = new InitTaskEntity(); @@ -190,51 +167,49 @@ class ConfigurableInitStepExecutorTest { "orgNameEn", "TEST BANK", "deployMode", "DUAL", "nodes", Map.of("node01Ip", "10.0.1.1", "node02Ip", "10.0.1.2"), - "mq", Map.of("mqType", "RABBITMQ_CFMQ", "cfmqConfigFileId", "cfmq-001"), - "licenses", Map.of("cfgZipFileId", "cfg-001"), - "signServer", Map.of( - "signHost", "172.30.1.101", - "signPort", 50006, - "signType", "Infosec", - "signCertDn", "CN=TEST,OU=CIPS", - "signCertSn", "123456", - "signPassword", "secret" - ) + "mq", Map.of("mqType", "RABBITMQ_CFMQ", "cfgZipFileId", "cae-001", "cfmqConfigFileId", "cfmq-001") ) )); - InitTaskStepEntity step = new InitTaskStepEntity(); - step.setStepNo(1); - step.setStepCode("RENDER_CONFIG"); - step.setCommandLine("internal::render_config"); + InitTaskStepEntity renderStep = new InitTaskStepEntity(); + renderStep.setStepNo(1); + renderStep.setStepCode("RENDER_CONFIG"); + renderStep.setCommandLine("internal::render_config"); + InitStepExecutionResult renderResult = executor.execute(task, renderStep); - InitStepExecutionResult result = executor.execute(task, step); - - Assertions.assertTrue(result.isSuccess()); - Assertions.assertNotNull(result.getLogPath()); - Path renderedPath = parseRenderedCpconfigPath(result.getMessage()); + Assertions.assertTrue(renderResult.isSuccess()); + Path renderedPath = parseRenderedCpconfigPath(renderResult.getMessage()); String rendered = Files.readString(renderedPath); Assertions.assertTrue(rendered.contains("MY_CIPSID_OR_BIC=AAAABBBBXXX")); Assertions.assertTrue(rendered.contains("MQ_TYPE=3")); Assertions.assertTrue(rendered.contains("node_01_ip=10.0.1.1")); Assertions.assertTrue(rendered.contains("node_02_ip=10.0.1.2")); - Assertions.assertTrue(rendered.contains("sign_host=172.30.1.101")); + Assertions.assertTrue(rendered.contains("db_ip=127.0.0.1")); + + InitTaskStepEntity verifyStep = new InitTaskStepEntity(); + verifyStep.setStepNo(2); + verifyStep.setStepCode("VERIFY"); + verifyStep.setCommandLine("internal::verify"); + InitStepExecutionResult verifyResult = executor.execute(task, verifyStep); + Assertions.assertTrue(verifyResult.isSuccess()); } @Test - void shouldApplyCfmqConfigToTargetDirectory() throws IOException { - Path tempRoot = Files.createTempDirectory("init-executor-cfmq-test"); + void shouldApplyDirectCfmqLinkScript() throws IOException { + Path tempRoot = Files.createTempDirectory("init-executor-cfmq-link-test"); Path uploadDir = tempRoot.resolve("uploads"); Files.createDirectories(uploadDir); Path cfmqSource = uploadDir.resolve("cfmq-001"); - Files.writeString(cfmqSource, "cfmq.host=127.0.0.1"); + Path marker = tempRoot.resolve("cfmq-link.marker"); + Files.writeString(cfmqSource, "printf '%s' /cfmq/cfmq-server > '" + marker + "'\n"); - Path targetDir = tempRoot.resolve("target"); + Path targetDir = tempRoot.resolve("cfmq-install"); InitExecutorProperties properties = new InitExecutorProperties(); - properties.setMode(InitExecutorProperties.Mode.SIMULATE); + properties.setMode(InitExecutorProperties.Mode.LOCAL); properties.setLogDir(tempRoot.resolve("logs").toString()); properties.setUploadBaseDir(uploadDir.toString()); - properties.setCfmqTargetDir(targetDir.toString()); + properties.setStagingRootDir(tempRoot.resolve("staging").toString()); + properties.setDirectCfmqInstallPath(targetDir.toString()); ConfigurableInitStepExecutor executor = new ConfigurableInitStepExecutor(properties, new ObjectMapper()); InitTaskEntity task = new InitTaskEntity(); @@ -249,45 +224,45 @@ class ConfigurableInitStepExecutorTest { "orgNameEn", "TEST BANK", "deployMode", "SINGLE", "nodes", Map.of("node01Ip", "10.0.1.1"), - "mq", Map.of("mqType", "RABBITMQ_CFMQ", "cfmqConfigFileId", "cfmq-001"), - "licenses", Map.of("cfgZipFileId", "cfg-001"), - "signServer", Map.of( - "signHost", "172.30.1.101", - "signPort", 50006, - "signType", "Infosec", - "signCertDn", "CN=TEST,OU=CIPS", - "signCertSn", "123456", - "signPassword", "secret" - ) + "mq", Map.of("mqType", "RABBITMQ_CFMQ", "cfgZipFileId", "cae-001", "cfmqConfigFileId", "cfmq-001") ) )); InitTaskStepEntity step = new InitTaskStepEntity(); step.setStepNo(1); - step.setStepCode("CFMQ_APPLY"); - step.setCommandLine("internal::cfmq_apply"); + step.setStepCode("CFMQ_LINK_APPLY"); + step.setCommandLine("internal::cfmq_link_apply"); InitStepExecutionResult result = executor.execute(task, step); Assertions.assertTrue(result.isSuccess()); - Path copied = targetDir.resolve(cfmqSource.getFileName()); - Assertions.assertTrue(Files.exists(copied)); - Assertions.assertEquals("cfmq.host=127.0.0.1", Files.readString(copied)); + Assertions.assertTrue(Files.exists(marker)); + Assertions.assertEquals(targetDir.toString(), Files.readString(marker)); } @Test - void shouldSimulateDirectTlqInitCfgStep() throws IOException { - Path tempRoot = Files.createTempDirectory("init-executor-tlq-init-test"); + void shouldApplyDirectCaePackageToTargetDirectory() throws IOException { + Path tempRoot = Files.createTempDirectory("init-executor-cae-package-test"); Path uploadDir = tempRoot.resolve("uploads"); Files.createDirectories(uploadDir); + Path caeZip = uploadDir.resolve("cae-001"); + writeZip(caeZip, Map.of( + "route/route_demo.ini", "NAME=MSGMB_1\n", + "pmtsmsghdl.cfg", "handler=old\n", + "bin/tlq_gateway.sh", "gateway=127.0.0.1\n" + )); + + Path targetDir = tempRoot.resolve("pmts/cfg"); InitExecutorProperties properties = new InitExecutorProperties(); - properties.setMode(InitExecutorProperties.Mode.SIMULATE); + properties.setMode(InitExecutorProperties.Mode.LOCAL); properties.setLogDir(tempRoot.resolve("logs").toString()); properties.setUploadBaseDir(uploadDir.toString()); + properties.setStagingRootDir(tempRoot.resolve("staging").toString()); + properties.setDirectCaeTargetDir(targetDir.toString()); ConfigurableInitStepExecutor executor = new ConfigurableInitStepExecutor(properties, new ObjectMapper()); InitTaskEntity task = new InitTaskEntity(); - task.setTaskId("TASK-TLQ-INIT-1"); + task.setTaskId("TASK-CAE-PACKAGE-1"); task.setProductType("DIRECT"); task.setInitPlanJson(buildInitPlanJson( "DIRECT", @@ -296,33 +271,112 @@ class ConfigurableInitStepExecutorTest { "orgCode", "AAAABBBBXXX", "orgNameCn", "测试机构", "orgNameEn", "TEST BANK", - "deployMode", "DUAL", - "nodes", Map.of("node01Ip", "10.0.1.1", "node02Ip", "10.0.1.2"), - "mq", Map.of("mqType", "RABBITMQ_TLQ", "tlqLicenseFileId", "file-tlq-001"), - "licenses", Map.of("cfgZipFileId", "cfg-001"), - "signServer", Map.of( - "signHost", "172.30.1.101", - "signPort", 50006, - "signType", "Infosec", - "signCertDn", "CN=TEST,OU=CIPS", - "signCertSn", "123456", - "signPassword", "secret" - ), - "directTlq", Map.of( - "localNodeSeq", "A", - "upstreamInstitutions", "CIPSCNSHXXX;CIPSCNWXXXX", - "upstreamCocCount", 2 - ) + "deployMode", "SINGLE", + "nodes", Map.of("node01Ip", "10.0.1.1"), + "mq", Map.of("mqType", "RABBITMQ_TLQ", "cipsGatewayHost", "172.30.1.10", "cfgZipFileId", "cae-001") ) )); InitTaskStepEntity step = new InitTaskStepEntity(); step.setStepNo(1); - step.setStepCode("TLQ_INIT_CFG"); - step.setCommandLine("internal::tlq_init_cfg"); + step.setStepCode("CAE_PACKAGE_APPLY"); + step.setCommandLine("internal::cae_package_apply"); InitStepExecutionResult result = executor.execute(task, step); + Assertions.assertTrue(result.isSuccess()); + Assertions.assertTrue(Files.exists(targetDir.resolve("route/route_demo.ini"))); + Assertions.assertTrue(Files.exists(targetDir.resolve("pmtsmsghdl.cfg"))); + Assertions.assertTrue(Files.exists(targetDir.resolve("bin/tlq_gateway.sh"))); + } + + @Test + void shouldApplyDirectTlqGatewayScript() throws IOException { + Path tempRoot = Files.createTempDirectory("init-executor-tlq-gateway-test"); + Path caeTargetDir = tempRoot.resolve("pmts/cfg"); + Path gatewaySource = caeTargetDir.resolve("bin/tlq_gateway.sh"); + Files.createDirectories(gatewaySource.getParent()); + Files.writeString(gatewaySource, "gateway=127.0.0.1\n"); + + Path tlqTargetDir = tempRoot.resolve("tlq/bin"); + InitExecutorProperties properties = new InitExecutorProperties(); + properties.setMode(InitExecutorProperties.Mode.LOCAL); + properties.setLogDir(tempRoot.resolve("logs").toString()); + properties.setDirectCaeTargetDir(caeTargetDir.toString()); + properties.setDirectTlqGatewayScriptTargetDir(tlqTargetDir.toString()); + ConfigurableInitStepExecutor executor = new ConfigurableInitStepExecutor(properties, new ObjectMapper()); + + InitTaskEntity task = new InitTaskEntity(); + task.setTaskId("TASK-TLQ-GATEWAY-1"); + task.setProductType("DIRECT"); + task.setInitPlanJson(buildInitPlanJson( + "DIRECT", + Map.of( + "orgCodeType", "BIC", + "orgCode", "AAAABBBBXXX", + "orgNameCn", "测试机构", + "orgNameEn", "TEST BANK", + "deployMode", "SINGLE", + "nodes", Map.of("node01Ip", "10.0.1.1"), + "mq", Map.of("mqType", "RABBITMQ_TLQ", "cipsGatewayHost", "172.30.1.10", "cfgZipFileId", "cae-001") + ) + )); + + InitTaskStepEntity step = new InitTaskStepEntity(); + step.setStepNo(1); + step.setStepCode("TLQ_GATEWAY_APPLY"); + step.setCommandLine("internal::tlq_gateway_apply"); + + InitStepExecutionResult result = executor.execute(task, step); + + Assertions.assertTrue(result.isSuccess()); + Path copied = tlqTargetDir.resolve("tlq_gateway.sh"); + Assertions.assertTrue(Files.exists(copied)); + Assertions.assertTrue(Files.readString(copied).contains("172.30.1.10")); + } + + @Test + void shouldRewriteDirectCfmqRouteConfig() throws IOException { + Path tempRoot = Files.createTempDirectory("init-executor-cfmq-route-test"); + Path caeTargetDir = tempRoot.resolve("pmts/cfg"); + Path routeFile = caeTargetDir.resolve("route/route_demo.ini"); + Path pmtsMsgHdl = caeTargetDir.resolve("pmtsmsghdl.cfg"); + Files.createDirectories(routeFile.getParent()); + Files.writeString(routeFile, "route.target=MSGMB_1\n"); + Files.writeString(pmtsMsgHdl, "handler=old\n"); + + InitExecutorProperties properties = new InitExecutorProperties(); + properties.setMode(InitExecutorProperties.Mode.LOCAL); + properties.setLogDir(tempRoot.resolve("logs").toString()); + properties.setDirectCaeTargetDir(caeTargetDir.toString()); + ConfigurableInitStepExecutor executor = new ConfigurableInitStepExecutor(properties, new ObjectMapper()); + + InitTaskEntity task = new InitTaskEntity(); + task.setTaskId("TASK-CFMQ-ROUTE-1"); + task.setProductType("DIRECT"); + task.setInitPlanJson(buildInitPlanJson( + "DIRECT", + Map.of( + "orgCodeType", "BIC", + "orgCode", "AAAABBBBXXX", + "orgNameCn", "测试机构", + "orgNameEn", "TEST BANK", + "deployMode", "SINGLE", + "nodes", Map.of("node01Ip", "10.0.1.1"), + "mq", Map.of("mqType", "RABBITMQ_CFMQ", "cfgZipFileId", "cae-001", "cfmqConfigFileId", "cfmq-001") + ) + )); + + InitTaskStepEntity step = new InitTaskStepEntity(); + step.setStepNo(1); + step.setStepCode("CFMQ_ROUTE_APPLY"); + step.setCommandLine("internal::cfmq_route_apply"); + + InitStepExecutionResult result = executor.execute(task, step); + + Assertions.assertTrue(result.isSuccess()); + Assertions.assertTrue(Files.readString(routeFile).contains("MSGTOMB_1")); + Assertions.assertFalse(Files.exists(pmtsMsgHdl)); } @Test diff --git a/src/test/java/com/cisd/tms/modules/init/service/InitServiceTest.java b/src/test/java/com/cisd/tms/modules/init/service/InitServiceTest.java index f45956f..917212b 100644 --- a/src/test/java/com/cisd/tms/modules/init/service/InitServiceTest.java +++ b/src/test/java/com/cisd/tms/modules/init/service/InitServiceTest.java @@ -2,6 +2,8 @@ package com.cisd.tms.modules.init.service; import com.cisd.tms.common.config.properties.CisdPresetProperties; import com.cisd.tms.common.exception.BizException; +import com.cisd.tms.modules.file.entity.FileRecordEntity; +import com.cisd.tms.modules.file.repository.FileRecordRepository; import com.cisd.tms.modules.init.config.InitCommandProfileService; import com.cisd.tms.modules.init.config.InitExecutorProperties; import com.cisd.tms.modules.init.dto.InitCreateTaskResponse; @@ -35,6 +37,7 @@ import java.util.Optional; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import org.junit.jupiter.api.Assertions; @@ -106,29 +109,34 @@ class InitServiceTest { } @Test - void shouldRequireDirectSpecificFieldsForCfmqMode() { - InitService service = newService(emptyRepository(), emptyStepRepository(), preset("DIRECT", "V1.3.2", "CONFIG"), successExecutor(), "/tmp/tms-init-logs"); - InitPreviewRequest request = validDirectRequest(); - request.getMq().setMqType("RABBITMQ_CFMQ"); - request.getMq().setCfmqConfigFileId(null); - request.getLicenses().setCfgZipFileId(null); - - IllegalArgumentException ex = Assertions.assertThrows(IllegalArgumentException.class, () -> service.preview(request)); - Assertions.assertTrue( - ex.getMessage().contains("licenses.cfgZipFileId") || ex.getMessage().contains("mq.cfmqConfigFileId") - ); - } - - @Test - void shouldRequireDirectTlqConfigForTlqMode() { + void shouldRequireMqCfgZipForDirectTlqMode() { InitService service = newService(emptyRepository(), emptyStepRepository(), preset("DIRECT", "V1.3.2", "CONFIG"), successExecutor(), "/tmp/tms-init-logs"); InitPreviewRequest request = validDirectRequest(); request.getMq().setMqType("RABBITMQ_TLQ"); - request.getMq().setTlqLicenseFileId("file-tlq-001"); - request.setDirectTlq(null); + request.getMq().setCfgZipFileId(null); IllegalArgumentException ex = Assertions.assertThrows(IllegalArgumentException.class, () -> service.preview(request)); - Assertions.assertTrue(ex.getMessage().contains("directTlq")); + Assertions.assertTrue(ex.getMessage().contains("mq.cfgZipFileId")); + } + + @Test + void shouldRequireMqCfmqConfigFileIdForDirectCfmqMode() { + InitService service = newService(emptyRepository(), emptyStepRepository(), preset("DIRECT", "V1.3.2", "CONFIG"), successExecutor(), "/tmp/tms-init-logs"); + InitPreviewRequest request = validDirectRequest(); + request.getMq().setCfmqConfigFileId(null); + + IllegalArgumentException ex = Assertions.assertThrows(IllegalArgumentException.class, () -> service.preview(request)); + Assertions.assertTrue(ex.getMessage().contains("mq.cfmqConfigFileId")); + } + + @Test + void shouldRequireMqCfgZipForDirectCfmqMode() { + InitService service = newService(emptyRepository(), emptyStepRepository(), preset("DIRECT", "V1.3.2", "CONFIG"), successExecutor(), "/tmp/tms-init-logs"); + InitPreviewRequest request = validDirectRequest(); + request.getMq().setCfgZipFileId(null); + + IllegalArgumentException ex = Assertions.assertThrows(IllegalArgumentException.class, () -> service.preview(request)); + Assertions.assertTrue(ex.getMessage().contains("mq.cfgZipFileId")); } @Test @@ -136,33 +144,20 @@ class InitServiceTest { InitService service = newService(emptyRepository(), emptyStepRepository(), preset("DIRECT", "V1.3.2", "CONFIG"), successExecutor(), "/tmp/tms-init-logs"); InitPreviewRequest request = validDirectRequest(); request.getMq().setMqType("RABBITMQ_TLQ"); - request.getMq().setTlqLicenseFileId("file-tlq-001"); setMqCipsGatewayHost(request.getMq(), "192.168.10.20"); - request.setDirectTlq(directTlqConfig()); + request.getMq().setCfgZipFileId("file-direct-tlq-cfg-001"); InitPreviewResponse response = service.preview(request); Assertions.assertEquals("DIRECT", response.getResolvedProductType()); Assertions.assertTrue(response.getStepCount() >= 1); } - @Test - void shouldAcceptDirectCfmqModeWithoutSignServer() { - InitService service = newService(emptyRepository(), emptyStepRepository(), preset("DIRECT", "V1.3.2", "CONFIG"), successExecutor(), "/tmp/tms-init-logs"); - InitPreviewRequest request = validDirectRequest(); - - InitPreviewResponse response = service.preview(request); - - Assertions.assertEquals("DIRECT", response.getResolvedProductType()); - Assertions.assertTrue(response.getStepCount() >= 1); - } - @Test void shouldRequireCipsGatewayHostForDirectTlqMode() { InitService service = newService(emptyRepository(), emptyStepRepository(), preset("DIRECT", "V1.3.2", "CONFIG"), successExecutor(), "/tmp/tms-init-logs"); InitPreviewRequest request = validDirectRequest(); request.getMq().setMqType("RABBITMQ_TLQ"); - request.getMq().setTlqLicenseFileId("file-tlq-001"); - request.setDirectTlq(directTlqConfig()); + request.getMq().setCfgZipFileId("file-direct-tlq-cfg-001"); IllegalArgumentException ex = Assertions.assertThrows(IllegalArgumentException.class, () -> service.preview(request)); Assertions.assertTrue(ex.getMessage().contains("mq.cipsGatewayHost")); @@ -173,9 +168,8 @@ class InitServiceTest { InitService service = newService(emptyRepository(), emptyStepRepository(), preset("DIRECT", "V1.3.2", "CONFIG"), successExecutor(), "/tmp/tms-init-logs"); InitPreviewRequest request = validDirectRequest(); request.getMq().setMqType("RABBITMQ_TLQ"); - request.getMq().setTlqLicenseFileId("file-tlq-001"); setMqCipsGatewayHost(request.getMq(), "192.168.10.20"); - request.setDirectTlq(directTlqConfig()); + request.getMq().setCfgZipFileId("file-direct-tlq-cfg-001"); InitPreviewResponse response = service.preview(request); @@ -245,7 +239,16 @@ class InitServiceTest { void shouldLoadCurrentInitConfigFromLatestSuccessfulInitTask() { InMemoryInitTaskRepository repository = new InMemoryInitTaskRepository(); InMemoryInitTaskStepRepository stepRepository = new InMemoryInitTaskStepRepository(); - InitService service = newService(repository, stepRepository, preset("ENTERPRISE", "V1.3.2", "CONFIG"), successExecutor(), "/tmp/tms-init-logs"); + InMemoryFileRecordRepository fileRepository = new InMemoryFileRecordRepository(); + fileRepository.save(record("file-receiver-001", "receiver-license.dat")); + InitService service = newService( + repository, + stepRepository, + fileRepository, + preset("ENTERPRISE", "V1.3.2", "CONFIG"), + successExecutor(), + "/tmp/tms-init-logs" + ); InitPreviewRequest request = validEnterpriseRequest(); request.setOrgNameEn("Test Organization"); request.getNodes().setNode02Ip("10.0.0.2"); @@ -263,6 +266,9 @@ class InitServiceTest { Assertions.assertEquals("cisd_channel_user", response.getChannelUsername()); Assertions.assertEquals("10.0.0.1", response.getNode01Ip()); Assertions.assertEquals("10.0.0.2", response.getNode02Ip()); + Assertions.assertEquals("receiver-license.dat", response.getReceiverLicenseFileName()); + Assertions.assertTrue(response.getTlqFileNames().isEmpty()); + Assertions.assertTrue(response.getCfmqFileNames().isEmpty()); } @Test @@ -298,12 +304,22 @@ class InitServiceTest { void shouldLoadCurrentInitConfigWithDirectTlqGatewayHost() { InMemoryInitTaskRepository repository = new InMemoryInitTaskRepository(); InMemoryInitTaskStepRepository stepRepository = new InMemoryInitTaskStepRepository(); - InitService service = newService(repository, stepRepository, preset("DIRECT", "V1.3.2", "CONFIG"), successExecutor(), "/tmp/tms-init-logs"); + InMemoryFileRecordRepository fileRepository = new InMemoryFileRecordRepository(); + fileRepository.save(record("file-direct-tlq-license-001", "tlq-license.dat")); + fileRepository.save(record("file-direct-tlq-cfg-001", "CFG_AAAABBBBXXX_01COCATEST_CAEA.zip")); + InitService service = newService( + repository, + stepRepository, + fileRepository, + preset("DIRECT", "V1.3.2", "CONFIG"), + successExecutor(), + "/tmp/tms-init-logs" + ); InitPreviewRequest request = validDirectRequest(); request.getMq().setMqType("RABBITMQ_TLQ"); - request.getMq().setTlqLicenseFileId("file-tlq-001"); setMqCipsGatewayHost(request.getMq(), "192.168.10.20"); - request.setDirectTlq(directTlqConfig()); + request.getMq().setTlqLicenseFileId("file-direct-tlq-license-001"); + request.getMq().setCfgZipFileId("file-direct-tlq-cfg-001"); createSuccessfulInitTask(service, request); CurrentInitConfigResponse response = service.loadCurrentInitConfig(); @@ -311,6 +327,44 @@ class InitServiceTest { Assertions.assertEquals("DIRECT", response.getProductType()); Assertions.assertEquals("RABBITMQ_TLQ", response.getMqType()); Assertions.assertEquals("192.168.10.20", readCurrentInitConfigCipsGatewayHost(response)); + assertGetterMissing(response, "getDbHost"); + assertGetterMissing(response, "getDbPort"); + Assertions.assertNull(response.getReceiverLicenseFileName()); + Assertions.assertEquals( + List.of("tlq-license.dat", "CFG_AAAABBBBXXX_01COCATEST_CAEA.zip"), + response.getTlqFileNames() + ); + Assertions.assertTrue(response.getCfmqFileNames().isEmpty()); + } + + @Test + void shouldLoadCurrentInitConfigWithDirectCfmqFilesSeparated() { + InMemoryInitTaskRepository repository = new InMemoryInitTaskRepository(); + InMemoryInitTaskStepRepository stepRepository = new InMemoryInitTaskStepRepository(); + InMemoryFileRecordRepository fileRepository = new InMemoryFileRecordRepository(); + fileRepository.save(record("file-direct-cfmq-cfg-001", "CFG_AAAABBBBXXX_01COCATEST_CAEA.zip")); + fileRepository.save(record("file-direct-cfmq-addlink-001", "addlink_CFQMAAAABBBBXXXA.sh")); + InitService service = newService( + repository, + stepRepository, + fileRepository, + preset("DIRECT", "V1.3.2", "CONFIG"), + successExecutor(), + "/tmp/tms-init-logs" + ); + InitPreviewRequest request = validDirectRequest(); + createSuccessfulInitTask(service, request); + + CurrentInitConfigResponse response = service.loadCurrentInitConfig(); + + Assertions.assertEquals("DIRECT", response.getProductType()); + Assertions.assertEquals("RABBITMQ_CFMQ", response.getMqType()); + Assertions.assertNull(response.getReceiverLicenseFileName()); + Assertions.assertTrue(response.getTlqFileNames().isEmpty()); + Assertions.assertEquals( + List.of("CFG_AAAABBBBXXX_01COCATEST_CAEA.zip", "addlink_CFQMAAAABBBBXXXA.sh"), + response.getCfmqFileNames() + ); } @Test @@ -448,21 +502,51 @@ class InitServiceTest { } @Test - void shouldNotPlanStandardPostDeployStepsForDirectMode() { + void shouldPlanDirectTlqSteps() { InMemoryInitTaskRepository repository = new InMemoryInitTaskRepository(); InMemoryInitTaskStepRepository stepRepository = new InMemoryInitTaskStepRepository(); InitService service = newService(repository, stepRepository, preset("DIRECT", "V1.3.2", "CONFIG"), successExecutor(), "/tmp/tms-init-logs"); InitPreviewRequest request = validDirectRequest(); request.getMq().setMqType("RABBITMQ_TLQ"); - request.getMq().setTlqLicenseFileId("file-tlq-001"); setMqCipsGatewayHost(request.getMq(), "192.168.10.20"); - request.setDirectTlq(directTlqConfig()); + request.getMq().setCfgZipFileId("file-direct-tlq-cfg-001"); InitCreateTaskResponse created = service.createTask(request); List steps = service.getTaskSteps(created.getTaskId()); List stepCodes = steps.stream().map(InitTaskStepResponse::getStepCode).toList(); + Assertions.assertEquals(List.of( + "RENDER_CONFIG", + "MQ_USER_APPLY", + "CAE_PACKAGE_APPLY", + "TLQ_GATEWAY_APPLY", + "CAE_UPDATE", + "RESTART_TLQ", + "VERIFY" + ), stepCodes); + assertNotContainsStandardPostDeploySteps(stepCodes); + } + + @Test + void shouldPlanDirectCfmqSteps() { + InMemoryInitTaskRepository repository = new InMemoryInitTaskRepository(); + InMemoryInitTaskStepRepository stepRepository = new InMemoryInitTaskStepRepository(); + InitService service = newService(repository, stepRepository, preset("DIRECT", "V1.3.2", "CONFIG"), successExecutor(), "/tmp/tms-init-logs"); + + InitCreateTaskResponse created = service.createTask(validDirectRequest()); + List steps = service.getTaskSteps(created.getTaskId()); + List stepCodes = steps.stream().map(InitTaskStepResponse::getStepCode).toList(); + + Assertions.assertEquals(List.of( + "RENDER_CONFIG", + "MQ_USER_APPLY", + "CFMQ_LINK_APPLY", + "CAE_PACKAGE_APPLY", + "CFMQ_ROUTE_APPLY", + "CAE_UPDATE", + "VERIFY" + ), stepCodes); assertNotContainsStandardPostDeploySteps(stepCodes); } @@ -689,13 +773,33 @@ class InitServiceTest { CisdPresetProperties preset, InitStepExecutor executor, String logDir + ) { + return newService(taskRepository, stepRepository, new InMemoryFileRecordRepository(), preset, executor, logDir); + } + + private static InitService newService( + InitTaskRepository taskRepository, + InitTaskStepRepository stepRepository, + FileRecordRepository fileRecordRepository, + CisdPresetProperties preset, + InitStepExecutor executor, + String logDir ) { InitExecutorProperties properties = new InitExecutorProperties(); properties.setLogDir(logDir); properties.setMode(InitExecutorProperties.Mode.SIMULATE); ObjectMapper objectMapper = new ObjectMapper(); InitCommandProfileService commandProfileService = new InitCommandProfileService(objectMapper, properties); - return new InitServiceImpl(taskRepository, stepRepository, preset, objectMapper, executor, commandProfileService, properties); + return new InitServiceImpl( + taskRepository, + stepRepository, + fileRecordRepository, + preset, + objectMapper, + executor, + commandProfileService, + properties + ); } private static InitTaskRepository emptyRepository() { @@ -786,13 +890,12 @@ class InitServiceTest { InitPreviewRequest request = validEnterpriseRequest(); request.setDeployMode("DUAL"); request.getNodes().setNode02Ip("10.0.0.2"); + request.setLicenses(null); InitPreviewRequest.Mq mq = request.getMq(); mq.setMqType("RABBITMQ_CFMQ"); - mq.setCfmqConfigFileId("file-cfmq-001"); - - InitPreviewRequest.Licenses licenses = request.getLicenses(); - licenses.setCfgZipFileId("file-cfg-001"); + mq.setCfgZipFileId("file-direct-cfmq-cfg-001"); + mq.setCfmqConfigFileId("file-direct-cfmq-addlink-001"); return request; } @@ -802,14 +905,6 @@ class InitServiceTest { awaitTaskStatus(service, created.getTaskId(), "SUCCESS"); } - private static InitPreviewRequest.DirectTlqConfig directTlqConfig() { - InitPreviewRequest.DirectTlqConfig config = new InitPreviewRequest.DirectTlqConfig(); - config.setLocalNodeSeq("A"); - config.setUpstreamInstitutions("CIPSCNSHXXX;CIPSCNWXXXX"); - config.setUpstreamCocCount(2); - return config; - } - private static void setMqCipsGatewayHost(InitPreviewRequest.Mq mq, String value) { try { mq.getClass().getMethod("setCipsGatewayHost", String.class).invoke(mq, value); @@ -826,6 +921,34 @@ class InitServiceTest { } } + private static void assertGetterMissing(Object target, String getterName) { + Assertions.assertThrows(NoSuchMethodException.class, () -> target.getClass().getMethod(getterName)); + } + + private static FileRecordEntity record(String fileId, String originalFilename) { + FileRecordEntity entity = new FileRecordEntity(); + entity.setFileId(fileId); + entity.setOriginalFilename(originalFilename); + entity.setStoragePath("/tmp/uploads/" + fileId + "/" + originalFilename); + entity.setStatus("ACTIVE"); + return entity; + } + + private static class InMemoryFileRecordRepository implements FileRecordRepository { + + private final Map store = new ConcurrentHashMap<>(); + + @Override + public Optional findByFileId(String fileId) { + return Optional.ofNullable(store.get(fileId)); + } + + @Override + public void save(FileRecordEntity entity) { + store.put(entity.getFileId(), entity); + } + } + private static void assertContainsStandardPostDeploySteps(List stepCodes) { Assertions.assertTrue(stepCodes.contains("DB_RENDER")); Assertions.assertTrue(stepCodes.contains("DB_APPLY")); diff --git a/src/test/java/com/cisd/tms/modules/upgrade/UpgradeDtoContractTest.java b/src/test/java/com/cisd/tms/modules/upgrade/UpgradeDtoContractTest.java index 4c7c0ba..acccea3 100644 --- a/src/test/java/com/cisd/tms/modules/upgrade/UpgradeDtoContractTest.java +++ b/src/test/java/com/cisd/tms/modules/upgrade/UpgradeDtoContractTest.java @@ -38,11 +38,13 @@ class UpgradeDtoContractTest { } @Test - void shouldAcceptFileIdForCreateTaskRequest() { + void shouldAcceptFileIdAndTaskTypeForCreateTaskRequest() { UpgradeCreateTaskRequest request = new UpgradeCreateTaskRequest(); request.setFileId("FILE-20260319-000001"); + request.setTaskType("TMS"); Assertions.assertEquals("FILE-20260319-000001", request.getFileId()); + Assertions.assertEquals("TMS", request.getTaskType()); } @Test diff --git a/src/test/java/com/cisd/tms/modules/upgrade/controller/UpgradeControllerTest.java b/src/test/java/com/cisd/tms/modules/upgrade/controller/UpgradeControllerTest.java index 28013e4..27af2de 100644 --- a/src/test/java/com/cisd/tms/modules/upgrade/controller/UpgradeControllerTest.java +++ b/src/test/java/com/cisd/tms/modules/upgrade/controller/UpgradeControllerTest.java @@ -77,7 +77,7 @@ class UpgradeControllerTest { page.setPageSize(10); page.setTotal(1L); page.getItems().add(task); - Mockito.when(upgradeService.listTasks(1, 10, "", "")).thenReturn(page); + Mockito.when(upgradeService.listTasks(1, 10, "", "", "2026-04-16")).thenReturn(page); MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new UpgradeController(upgradeService, fileService)).build(); @@ -90,7 +90,7 @@ class UpgradeControllerTest { mockMvc.perform(post("/api/v1/upgrades") .contentType(MediaType.APPLICATION_JSON) - .content("{\"fileId\":\"FILE-001\"}")) + .content("{\"fileId\":\"FILE-001\",\"taskType\":\"RECEIVER\"}")) .andExpect(status().isOk()) .andExpect(content().string(org.hamcrest.Matchers.containsString("\"taskId\":\"UPG-001\""))); @@ -106,10 +106,13 @@ class UpgradeControllerTest { .param("page", "1") .param("pageSize", "10") .param("taskType", "") - .param("status", "")) + .param("status", "") + .param("upgradeDate", "2026-04-16")) .andExpect(status().isOk()) .andExpect(content().string(org.hamcrest.Matchers.containsString("\"total\":1"))); + Mockito.verify(upgradeService).listTasks(1, 10, "", "", "2026-04-16"); + mockMvc.perform(get("/api/v1/upgrades/UPG-001")) .andExpect(status().isOk()) .andExpect(content().string(org.hamcrest.Matchers.containsString("\"taskId\":\"UPG-001\""))); diff --git a/src/test/java/com/cisd/tms/modules/upgrade/executor/UpgradeTaskRunnerTest.java b/src/test/java/com/cisd/tms/modules/upgrade/executor/UpgradeTaskRunnerTest.java index 2d274d8..23a9590 100644 --- a/src/test/java/com/cisd/tms/modules/upgrade/executor/UpgradeTaskRunnerTest.java +++ b/src/test/java/com/cisd/tms/modules/upgrade/executor/UpgradeTaskRunnerTest.java @@ -280,12 +280,19 @@ class UpgradeTaskRunnerTest { } @Override - public java.util.List findPage(String taskType, String status, int offset, int limit) { + public java.util.List findPage( + String taskType, + String status, + LocalDateTime startedAtBegin, + LocalDateTime startedAtEndExclusive, + int offset, + int limit + ) { return java.util.List.of(); } @Override - public long countPage(String taskType, String status) { + public long countPage(String taskType, String status, LocalDateTime startedAtBegin, LocalDateTime startedAtEndExclusive) { return 0; } diff --git a/src/test/java/com/cisd/tms/modules/upgrade/repository/UpgradeTaskRepositoryTest.java b/src/test/java/com/cisd/tms/modules/upgrade/repository/UpgradeTaskRepositoryTest.java index 1c7a148..dafe41f 100644 --- a/src/test/java/com/cisd/tms/modules/upgrade/repository/UpgradeTaskRepositoryTest.java +++ b/src/test/java/com/cisd/tms/modules/upgrade/repository/UpgradeTaskRepositoryTest.java @@ -47,12 +47,14 @@ class UpgradeTaskRepositoryTest { UpgradeTaskMapper mapper = Mockito.mock(UpgradeTaskMapper.class); UpgradeTaskRepository repository = new UpgradeTaskRepositoryImpl(mapper); UpgradeTaskEntity entity = entity("UPG-003", "SUCCESS"); + LocalDateTime startedAtBegin = LocalDateTime.of(2026, 4, 16, 0, 0, 0); + LocalDateTime startedAtEndExclusive = LocalDateTime.of(2026, 4, 17, 0, 0, 0); - Mockito.when(mapper.selectPage("TMS", "SUCCESS", 0, 10)).thenReturn(List.of(entity)); - Mockito.when(mapper.countPage("TMS", "SUCCESS")).thenReturn(1L); + Mockito.when(mapper.selectPage("TMS", "SUCCESS", startedAtBegin, startedAtEndExclusive, 0, 10)).thenReturn(List.of(entity)); + Mockito.when(mapper.countPage("TMS", "SUCCESS", startedAtBegin, startedAtEndExclusive)).thenReturn(1L); - List page = repository.findPage("TMS", "SUCCESS", 0, 10); - long total = repository.countPage("TMS", "SUCCESS"); + List page = repository.findPage("TMS", "SUCCESS", startedAtBegin, startedAtEndExclusive, 0, 10); + long total = repository.countPage("TMS", "SUCCESS", startedAtBegin, startedAtEndExclusive); Assertions.assertEquals(1, page.size()); Assertions.assertEquals("UPG-003", page.get(0).getTaskId()); diff --git a/src/test/java/com/cisd/tms/modules/upgrade/service/UpgradeServiceTest.java b/src/test/java/com/cisd/tms/modules/upgrade/service/UpgradeServiceTest.java index bbc31af..626ac34 100644 --- a/src/test/java/com/cisd/tms/modules/upgrade/service/UpgradeServiceTest.java +++ b/src/test/java/com/cisd/tms/modules/upgrade/service/UpgradeServiceTest.java @@ -62,6 +62,7 @@ class UpgradeServiceTest { UpgradeCreateTaskRequest request = new UpgradeCreateTaskRequest(); request.setFileId("FILE-001"); + request.setTaskType("RECEIVER"); UpgradeCreateTaskResponse response = service.createTask(request); @@ -73,6 +74,42 @@ class UpgradeServiceTest { Assertions.assertTrue(response.getTaskId().startsWith("UPG-")); } + @Test + void shouldRejectCreateTaskWhenSelectedTaskTypeDoesNotMatchPreview() { + UpgradeTaskRepository repository = new InMemoryUpgradeTaskRepository(); + FileRecordRepository fileRecordRepository = Mockito.mock(FileRecordRepository.class); + UpgradePackageService packageService = Mockito.mock(UpgradePackageService.class); + UpgradeTaskRunner runner = Mockito.mock(UpgradeTaskRunner.class); + + UpgradePreviewResponse preview = new UpgradePreviewResponse(); + preview.setTaskType("RECEIVER"); + preview.setProductType("DIRECT"); + preview.setCurrentVersion("V1.3.1"); + preview.setTargetVersion("V1.3.2"); + preview.setAllowed(true); + preview.setVerifyPassed(true); + preview.setVerifyMessage("校验通过"); + Mockito.when(packageService.preview("FILE-001")).thenReturn(preview); + + UpgradeService service = new UpgradeService( + repository, + fileRecordRepository, + packageService, + runner, + properties(), + Runnable::run, + new ConcurrentHashMap<>() + ); + + UpgradeCreateTaskRequest request = new UpgradeCreateTaskRequest(); + request.setFileId("FILE-001"); + request.setTaskType("TMS"); + + BizException exception = Assertions.assertThrows(BizException.class, () -> service.createTask(request)); + + Assertions.assertEquals("selected taskType does not match package taskType", exception.getMessage()); + } + @Test void shouldRejectExecuteWhenAnotherTaskIsRunning() { InMemoryUpgradeTaskRepository repository = new InMemoryUpgradeTaskRepository(); @@ -110,12 +147,39 @@ class UpgradeServiceTest { new ConcurrentHashMap<>() ); - UpgradeTaskPageResponse page = service.listTasks(1, 10, "", ""); + UpgradeTaskPageResponse page = service.listTasks(1, 10, "", "", null); Assertions.assertEquals(2L, page.getTotal()); Assertions.assertEquals(2, page.getItems().size()); } + @Test + void shouldFilterUpgradeTasksByStartedAtDate() { + InMemoryUpgradeTaskRepository repository = new InMemoryUpgradeTaskRepository(); + UpgradeTaskEntity sameDay = entity("UPG-001", "SUCCESS"); + sameDay.setStartedAt(LocalDateTime.of(2026, 4, 16, 9, 30, 0)); + repository.save(sameDay); + UpgradeTaskEntity anotherDay = entity("UPG-002", "FAILED"); + anotherDay.setStartedAt(LocalDateTime.of(2026, 4, 15, 23, 59, 59)); + repository.save(anotherDay); + + UpgradeService service = new UpgradeService( + repository, + Mockito.mock(FileRecordRepository.class), + Mockito.mock(UpgradePackageService.class), + Mockito.mock(UpgradeTaskRunner.class), + properties(), + Runnable::run, + new ConcurrentHashMap<>() + ); + + UpgradeTaskPageResponse page = service.listTasks(1, 10, "", "", "2026-04-16"); + + Assertions.assertEquals(1L, page.getTotal()); + Assertions.assertEquals(1, page.getItems().size()); + Assertions.assertEquals("UPG-001", page.getItems().get(0).getTaskId()); + } + @Test void shouldReturnUpgradeTaskDetail() { InMemoryUpgradeTaskRepository repository = new InMemoryUpgradeTaskRepository(); @@ -214,10 +278,21 @@ class UpgradeServiceTest { } @Override - public List findPage(String taskType, String status, int offset, int limit) { + public List findPage( + String taskType, + String status, + LocalDateTime startedAtBegin, + LocalDateTime startedAtEndExclusive, + int offset, + int limit + ) { return store.values().stream() .filter(entity -> taskType == null || taskType.isBlank() || taskType.equals(entity.getTaskType())) .filter(entity -> status == null || status.isBlank() || status.equals(entity.getStatus())) + .filter(entity -> startedAtBegin == null + || (entity.getStartedAt() != null && !entity.getStartedAt().isBefore(startedAtBegin))) + .filter(entity -> startedAtEndExclusive == null + || (entity.getStartedAt() != null && entity.getStartedAt().isBefore(startedAtEndExclusive))) .sorted(Comparator.comparing(UpgradeTaskEntity::getTaskId).reversed()) .skip(offset) .limit(limit) @@ -225,8 +300,8 @@ class UpgradeServiceTest { } @Override - public long countPage(String taskType, String status) { - return findPage(taskType, status, 0, Integer.MAX_VALUE).size(); + public long countPage(String taskType, String status, LocalDateTime startedAtBegin, LocalDateTime startedAtEndExclusive) { + return findPage(taskType, status, startedAtBegin, startedAtEndExclusive, 0, Integer.MAX_VALUE).size(); } @Override