初始化配置完善

This commit is contained in:
waner
2026-04-17 15:29:20 +08:00
parent 8989c25aed
commit a5c22222e9
25 changed files with 1234 additions and 442 deletions
@@ -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.SoftwareRuntimeProbe;
import com.cisd.tms.modules.device.support.SystemSoftwareVersionProbe; import com.cisd.tms.modules.device.support.SystemSoftwareVersionProbe;
import com.cisd.tms.modules.device.support.UsageSnapshot; 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 com.cisd.tms.modules.init.service.InitService;
import java.time.OffsetDateTime; import java.time.OffsetDateTime;
import java.util.ArrayList; import java.util.ArrayList;
@@ -177,10 +178,26 @@ public class DeviceRuntimeStatusServiceImpl implements DeviceRuntimeStatusServic
} }
private BooleanSupplier receiverProbe() { private BooleanSupplier receiverProbe() {
String productType = normalize(cisdPresetProperties.getProductType()); String productType = resolveReceiverProductType();
return "DIRECT".equals(productType) ? softwareRuntimeProbe::isDirectAppRunning : softwareRuntimeProbe::isStandardAppRunning; 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 @FunctionalInterface
private interface UsageSupplier { private interface UsageSupplier {
UsageSnapshot get(); UsageSnapshot get();
@@ -18,67 +18,128 @@ public class DefaultSoftwareRuntimeProbe implements SoftwareRuntimeProbe {
private final Supplier<List<String>> processSnapshotLoader; private final Supplier<List<String>> processSnapshotLoader;
private final Duration cacheTtl; private final Duration cacheTtl;
private final LongSupplier nanoTimeSupplier; private final LongSupplier nanoTimeSupplier;
private final CommandExitCodeSupplier commandExitCodeSupplier;
private final Object lock = new Object(); private final Object lock = new Object();
private volatile CachedProcessSnapshot cachedSnapshot; private volatile CachedProcessSnapshot cachedSnapshot;
public DefaultSoftwareRuntimeProbe() { public DefaultSoftwareRuntimeProbe() {
this(DefaultSoftwareRuntimeProbe::loadProcessSnapshot, DEFAULT_CACHE_TTL, System::nanoTime); this(DefaultSoftwareRuntimeProbe::loadProcessSnapshot, DEFAULT_CACHE_TTL, System::nanoTime, DefaultSoftwareRuntimeProbe::runCommand);
} }
DefaultSoftwareRuntimeProbe( DefaultSoftwareRuntimeProbe(
Supplier<List<String>> processSnapshotLoader, Supplier<List<String>> processSnapshotLoader,
Duration cacheTtl, Duration cacheTtl,
LongSupplier nanoTimeSupplier LongSupplier nanoTimeSupplier
) {
this(processSnapshotLoader, cacheTtl, nanoTimeSupplier, DefaultSoftwareRuntimeProbe::runCommand);
}
DefaultSoftwareRuntimeProbe(
Supplier<List<String>> processSnapshotLoader,
Duration cacheTtl,
LongSupplier nanoTimeSupplier,
CommandExitCodeSupplier commandExitCodeSupplier
) { ) {
this.processSnapshotLoader = Objects.requireNonNull(processSnapshotLoader, "processSnapshotLoader"); this.processSnapshotLoader = Objects.requireNonNull(processSnapshotLoader, "processSnapshotLoader");
this.cacheTtl = Objects.requireNonNull(cacheTtl, "cacheTtl"); this.cacheTtl = Objects.requireNonNull(cacheTtl, "cacheTtl");
this.nanoTimeSupplier = Objects.requireNonNull(nanoTimeSupplier, "nanoTimeSupplier"); this.nanoTimeSupplier = Objects.requireNonNull(nanoTimeSupplier, "nanoTimeSupplier");
this.commandExitCodeSupplier = Objects.requireNonNull(commandExitCodeSupplier, "commandExitCodeSupplier");
} }
@Override @Override
public boolean isRabbitmqRunning() { 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 @Override
public boolean isTlqRunning() { public boolean isTlqRunning() {
return isRunning("tonglink|TLQ"); return isRunningAny("tonglink", "tlq", "tlqremote");
} }
@Override @Override
public boolean isTidbRunning() { public boolean isTidbRunning() {
return isRunning("tidb-server"); return isRunningAny("tidb-server");
} }
@Override @Override
public boolean isNginxRunning() { public boolean isNginxRunning() {
return isRunning("nginx"); return isRunningAny("nginx");
} }
@Override @Override
public boolean isStandardAppRunning() { 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 @Override
public boolean isDirectAppRunning() { 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 @Override
public boolean isTmsRunning() { 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 { try {
Pattern compiled = Pattern.compile(pattern); List<Pattern> patterns = compilePatterns(tokens);
return processSnapshot().stream().anyMatch(commandLine -> compiled.matcher(commandLine).find()); return processSnapshot().stream().anyMatch(commandLine -> matchesAny(commandLine, patterns));
} catch (PatternSyntaxException ex) { } 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) { } catch (RuntimeException ex) {
throw new IllegalStateException("process probe failed: " + pattern, ex); throw new IllegalStateException("process probe failed: " + String.join(",", tokens), ex);
}
}
private List<Pattern> 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)(?<![a-z0-9])" + Pattern.quote(token.trim()) + "(?![a-z0-9])");
}
private boolean matchesAny(String commandLine, List<Pattern> 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<String> commandLines, long loadedAtNanos) { private record CachedProcessSnapshot(List<String> commandLines, long loadedAtNanos) {
} }
} }
@@ -82,7 +82,19 @@ public class InitExecutorProperties {
private String directDbScriptDir = "/opt/CAE_Install_Base_Resource/DB"; private String directDbScriptDir = "/opt/CAE_Install_Base_Resource/DB";
// 直参版 TLQ init_cfg.sh 脚本目录 // 直参版 TLQ init_cfg.sh 脚本目录
private String directTlqInitCfgDir = "/opt/CAE_Install_App_Resource/CAE"; 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"; private String cfmqTargetDir = "/pmts/cfg";
// RabbitMQ 管理员账号(预制) // RabbitMQ 管理员账号(预制)
private String rabbitAdminUsername = "admin"; private String rabbitAdminUsername = "admin";
@@ -392,6 +404,54 @@ public class InitExecutorProperties {
this.directTlqLicenseTargetPath = directTlqLicenseTargetPath; 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() { public String getCfmqTargetDir() {
return cfmqTargetDir; return cfmqTargetDir;
} }
@@ -1,6 +1,8 @@
package com.cisd.tms.modules.init.dto; package com.cisd.tms.modules.init.dto;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import java.util.ArrayList;
import java.util.List;
@Schema(description = "当前已初始化的 CISD 核心配置响应") @Schema(description = "当前已初始化的 CISD 核心配置响应")
public class CurrentInitConfigResponse { public class CurrentInitConfigResponse {
@@ -27,6 +29,12 @@ public class CurrentInitConfigResponse {
private String node02Ip; private String node02Ip;
@Schema(description = "CIPS 中心网关地址", example = "192.168.10.20") @Schema(description = "CIPS 中心网关地址", example = "192.168.10.20")
private String cipsGatewayHost; private String cipsGatewayHost;
@Schema(description = "TLQ 相关文件名列表,包含 TLQ License、TLQ 配置包等")
private List<String> tlqFileNames = new ArrayList<>();
@Schema(description = "CFMQ 相关文件名列表,包含 CFMQ 配置包、CFMQ 配置脚本等")
private List<String> cfmqFileNames = new ArrayList<>();
@Schema(description = "收发器 LICENSE 文件名", example = "receiver-license.dat")
private String receiverLicenseFileName;
public String getOrgCodeType() { public String getOrgCodeType() {
return orgCodeType; return orgCodeType;
@@ -115,4 +123,28 @@ public class CurrentInitConfigResponse {
public void setCipsGatewayHost(String cipsGatewayHost) { public void setCipsGatewayHost(String cipsGatewayHost) {
this.cipsGatewayHost = cipsGatewayHost; this.cipsGatewayHost = cipsGatewayHost;
} }
public List<String> getTlqFileNames() {
return tlqFileNames;
}
public void setTlqFileNames(List<String> tlqFileNames) {
this.tlqFileNames = tlqFileNames;
}
public List<String> getCfmqFileNames() {
return cfmqFileNames;
}
public void setCfmqFileNames(List<String> cfmqFileNames) {
this.cfmqFileNames = cfmqFileNames;
}
public String getReceiverLicenseFileName() {
return receiverLicenseFileName;
}
public void setReceiverLicenseFileName(String receiverLicenseFileName) {
this.receiverLicenseFileName = receiverLicenseFileName;
}
} }
@@ -2,8 +2,6 @@ package com.cisd.tms.modules.init.dto;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern; import jakarta.validation.constraints.Pattern;
@@ -51,14 +49,9 @@ public class InitPreviewRequest {
private Mq mq; private Mq mq;
@Valid @Valid
@NotNull(message = "licenses is required")
@Schema(description = "初始化许可证与配置包引用") @Schema(description = "初始化许可证与配置包引用")
private Licenses licenses; private Licenses licenses;
@Valid
@Schema(description = "直连 TLQ 配置,直参版按需填写")
private DirectTlqConfig directTlq;
public String getOrgCodeType() { public String getOrgCodeType() {
return orgCodeType; return orgCodeType;
} }
@@ -123,14 +116,6 @@ public class InitPreviewRequest {
this.licenses = licenses; this.licenses = licenses;
} }
public DirectTlqConfig getDirectTlq() {
return directTlq;
}
public void setDirectTlq(DirectTlqConfig directTlq) {
this.directTlq = directTlq;
}
@Schema(description = "节点 IP 配置") @Schema(description = "节点 IP 配置")
public static class Nodes { public static class Nodes {
@@ -193,28 +178,34 @@ public class InitPreviewRequest {
@NotBlank(message = "mq.channelUsername is required") @NotBlank(message = "mq.channelUsername is required")
@Size(max = 32, message = "mq.channelUsername length must be <= 32") @Size(max = 32, message = "mq.channelUsername length must be <= 32")
@Schema(description = "通道用户名", example = "tms") @Schema(description = "RABBITMQ通道用户名", example = "tms")
private String channelUsername; private String channelUsername;
@NotBlank(message = "mq.channelPassword is required") @NotBlank(message = "mq.channelPassword is required")
@Size(max = 128, message = "mq.channelPassword length must be <= 128") @Size(max = 128, message = "mq.channelPassword length must be <= 128")
@Schema(description = "通道密码") @Schema(description = "RABBITMQ通道密码")
private String channelPassword; private String channelPassword;
@Size(max = 128, message = "mq.tlqLicenseFileId length must be <= 128") @Size(max = 128, message = "mq.tlqLicenseFileId length must be <= 128")
@Pattern(regexp = FILE_ID_REGEX, message = "mq.tlqLicenseFileId must be a fileId") @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; 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") @Size(max = 128, message = "mq.cfmqConfigFileId length must be <= 128")
@Pattern(regexp = FILE_ID_REGEX, message = "mq.cfmqConfigFileId must be a fileId") @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; 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() { public String getMqType() {
return mqType; return mqType;
} }
@@ -262,6 +253,15 @@ public class InitPreviewRequest {
public void setCipsGatewayHost(String cipsGatewayHost) { public void setCipsGatewayHost(String cipsGatewayHost) {
this.cipsGatewayHost = cipsGatewayHost; this.cipsGatewayHost = cipsGatewayHost;
} }
public String getCfgZipFileId() {
return cfgZipFileId;
}
public void setCfgZipFileId(String cfgZipFileId) {
this.cfgZipFileId = cfgZipFileId;
}
} }
@Schema(description = "许可证和配置包引用") @Schema(description = "许可证和配置包引用")
@@ -275,7 +275,7 @@ public class InitPreviewRequest {
@Size(max = 128, message = "licenses.cfgZipFileId length must be <= 128") @Size(max = 128, message = "licenses.cfgZipFileId length must be <= 128")
@Pattern(regexp = FILE_ID_REGEX, message = "licenses.cfgZipFileId must be a fileId") @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; private String cfgZipFileId;
public String getReceiverLicenseFileId() { public String getReceiverLicenseFileId() {
@@ -294,45 +294,4 @@ public class InitPreviewRequest {
this.cfgZipFileId = cfgZipFileId; 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;
}
}
} }
@@ -149,7 +149,12 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor {
case "verify_reset" -> executeVerifyReset(task, step); case "verify_reset" -> executeVerifyReset(task, step);
case "direct_db_init" -> executeDirectDbInit(task, step); case "direct_db_init" -> executeDirectDbInit(task, step);
case "tlq_init_cfg" -> executeTlqInitCfg(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); case "verify" -> executeVerify(task, step);
default -> { default -> {
String logPath = writeInternalLog(task, step, "skip unsupported internal step: " + internalCode); String logPath = writeInternalLog(task, step, "skip unsupported internal step: " + internalCode);
@@ -177,6 +182,18 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor {
: buildStandardReplacements(context); : buildStandardReplacements(context);
int changed = replaceConfigValues(cpconfigPath, replacements); int changed = replaceConfigValues(cpconfigPath, replacements);
String message = "cpconfig rendered, path=" + cpconfigPath + ", changedKeys=" + changed; 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); String logPath = writeInternalLog(task, step, message);
return InitStepExecutionResult.success(message, 0, logPath); 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"); String logPath = writeInternalLog(task, step, "direct_db_init skipped for non-direct product type");
return InitStepExecutionResult.success("direct_db_init skipped", 0, logPath); return InitStepExecutionResult.success("direct_db_init skipped", 0, logPath);
} }
if (properties.getMode() == InitExecutorProperties.Mode.SIMULATE) { String logPath = writeInternalLog(task, step, "direct_db_init skipped: out of direct customer-config scope");
String logPath = writeInternalLog(task, step, "direct_db_init simulated"); return InitStepExecutionResult.success("direct_db_init skipped", 0, logPath);
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);
}
} }
private InitStepExecutionResult executeStandardDbRender(InitTaskEntity task, InitTaskStepEntity step) throws IOException { private InitStepExecutionResult executeStandardDbRender(InitTaskEntity task, InitTaskStepEntity step) throws IOException {
@@ -1229,85 +1197,199 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor {
String logPath = writeInternalLog(task, step, "tlq_init_cfg skipped for non-direct TLQ mode"); String logPath = writeInternalLog(task, step, "tlq_init_cfg skipped for non-direct TLQ mode");
return InitStepExecutionResult.success("tlq_init_cfg skipped", 0, logPath); return InitStepExecutionResult.success("tlq_init_cfg skipped", 0, logPath);
} }
if (properties.getMode() == InitExecutorProperties.Mode.SIMULATE) { String logPath = writeInternalLog(task, step, "tlq_init_cfg skipped: no longer part of direct customer-config scope");
String logPath = writeInternalLog(task, step, "tlq_init_cfg simulated"); return InitStepExecutionResult.success("tlq_init_cfg skipped", 0, logPath);
return InitStepExecutionResult.success("tlq_init_cfg simulated", 0, logPath);
} }
String localNodeSeq = normalizeUpper(context.directTlqLocalNodeSeq()); private InitStepExecutionResult executeCaePackageApply(InitTaskEntity task, InitTaskStepEntity step) throws IOException {
String upstreamInstitutions = normalize(context.directTlqUpstreamInstitutions()); PlanContext context = loadPlanContext(task);
Integer upstreamCocCount = context.directTlqUpstreamCocCount(); if (!"DIRECT".equals(context.productType())) {
if (!"A".equals(localNodeSeq) && !"B".equals(localNodeSeq)) { String logPath = writeInternalLog(task, step, "cae_package_apply skipped for non-direct product type");
String logPath = writeInternalLog(task, step, "tlq_init_cfg failed: localNodeSeq must be A/B"); return InitStepExecutionResult.success("cae_package_apply skipped", 0, logPath);
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 scriptDir = Paths.get(firstNonBlank(properties.getDirectTlqInitCfgDir(), "/opt/CAE_Install_App_Resource/CAE")) Path zipFile = resolveAndStageInputFile(task, context.cfgZipFileId(), "cae_package", null);
Path unpackDir = resolveDirectStageDir(task, "cae/unpacked");
recreateDirectory(unpackDir);
unzipToDirectory(zipFile, unpackDir);
Path targetDir = Paths.get(firstNonBlank(properties.getDirectCaeTargetDir(), properties.getCfmqTargetDir()))
.normalize() .normalize()
.toAbsolutePath(); .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);
}
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
);
if (code != 0) {
return InitStepExecutionResult.failure("tlq_init_cfg failed", code, logFile.toString());
}
return InitStepExecutionResult.success("tlq_init_cfg 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);
}
}
private InitStepExecutionResult executeCfmqApply(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);
}
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 targetDir = Paths.get(targetDirValue).normalize().toAbsolutePath();
Files.createDirectories(targetDir); Files.createDirectories(targetDir);
Path target = targetDir.resolve(source.getFileName()); copyDirectoryContents(unpackDir, targetDir);
Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
String message = "cfmq config applied: " + source + " -> " + target; String message = "cae package applied: " + zipFile + " -> " + targetDir;
String logPath = writeInternalLog(task, step, message); String logPath = writeInternalLog(task, step, message);
return InitStepExecutionResult.success(message, 0, logPath); 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 {
int code = runCommand(logFile, Arrays.asList("/bin/zsh", "-lc", command), false, scriptDir);
if (code != 0) {
return InitStepExecutionResult.failure("cae_update failed", code, logFile.toString());
}
return InitStepExecutionResult.success("cae_update applied", 0, logFile.toString());
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
return InitStepExecutionResult.failure("cae_update interrupted", -1, logFile.toString());
}
}
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, "cfmq_link_apply skipped for non-direct CFMQ mode");
return InitStepExecutionResult.success("cfmq_link_apply skipped", 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 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<Path> 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 { private InitStepExecutionResult executeVerify(InitTaskEntity task, InitTaskStepEntity step) throws IOException {
PlanContext context = loadPlanContext(task); PlanContext context = loadPlanContext(task);
ResolvedCpconfig resolvedCpconfig = resolveCpconfigPath(task, context); ResolvedCpconfig resolvedCpconfig = resolveCpconfigPath(task, context);
@@ -1363,12 +1445,6 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor {
private void verifyDirect(Map<String, String> configMap, PlanContext context, List<String> failures) { private void verifyDirect(Map<String, String> configMap, PlanContext context, List<String> failures) {
requireNotBlank(configMap, "MQ_TYPE", failures); requireNotBlank(configMap, "MQ_TYPE", failures);
requireNotBlank(configMap, "node_01_ip", 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())) { if ("DUAL".equals(context.deployMode()) || "QUAD".equals(context.deployMode())) {
requireNotBlank(configMap, "node_02_ip", failures); requireNotBlank(configMap, "node_02_ip", failures);
@@ -1432,13 +1508,6 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor {
replacements.put("node_02_ip", node02); replacements.put("node_02_ip", node02);
replacements.put("node_03_ip", node03); replacements.put("node_03_ip", node03);
replacements.put("node_04_ip", node04); 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); fillOrgCodeMappings(context, replacements);
return replacements; return replacements;
} }
@@ -1531,6 +1600,12 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor {
} }
private ResolvedCpconfig resolveCpconfigPath(InitTaskEntity task, PlanContext context) throws IOException { 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<Path> candidates = buildCpconfigCandidates(context); List<Path> candidates = buildCpconfigCandidates(context);
if (candidates.isEmpty()) { if (candidates.isEmpty()) {
Path uploadBase = Paths.get(firstNonBlank(properties.getUploadBaseDir(), "/home/tms/uploads")) Path uploadBase = Paths.get(firstNonBlank(properties.getUploadBaseDir(), "/home/tms/uploads"))
@@ -1541,7 +1616,7 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor {
for (Path candidate : candidates) { for (Path candidate : candidates) {
if (Files.exists(candidate) && Files.isRegularFile(candidate)) { if (Files.exists(candidate) && Files.isRegularFile(candidate)) {
Path cpconfig = 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); cpconfig = stageFile(task, "cpconfig", firstNonBlank(properties.getDirectCpconfigFileName(), "cpconfig.cfg"), candidate);
} }
return new ResolvedCpconfig(cpconfig, candidates); return new ResolvedCpconfig(cpconfig, candidates);
@@ -1550,10 +1625,27 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor {
return new ResolvedCpconfig(candidates.get(0), candidates); 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<Path> buildCpconfigCandidates(PlanContext context) { private List<Path> buildCpconfigCandidates(PlanContext context) {
String cpconfigFileName = firstNonBlank(properties.getDirectCpconfigFileName(), "cpconfig.cfg"); String cpconfigFileName = firstNonBlank(properties.getDirectCpconfigFileName(), "cpconfig.cfg");
if ("DIRECT".equals(context.productType())) { if ("DIRECT".equals(context.productType())) {
return buildFileIdCandidates(context.cfgZipFileId(), cpconfigFileName); List<Path> candidates = new ArrayList<>();
addAbsoluteCandidate(candidates, properties.getDirectCpconfigTargetPath());
addAbsoluteCandidate(candidates, Paths.get(firstNonBlank(properties.getDirectCpconfigTargetPath(), "/opt/cpconfig.cfg"))
.resolveSibling(cpconfigFileName)
.toString());
return deduplicatePaths(candidates);
} }
List<Path> candidates = new ArrayList<>(); List<Path> candidates = new ArrayList<>();
@@ -2024,18 +2116,6 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor {
JsonNode nodes = request.path("nodes"); JsonNode nodes = request.path("nodes");
JsonNode mq = request.path("mq"); JsonNode mq = request.path("mq");
JsonNode licenses = request.path("licenses"); 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( return new PlanContext(
firstNonBlank(normalizeUpper(text(root, "productType")), normalizeUpper(task.getProductType())), firstNonBlank(normalizeUpper(text(root, "productType")), normalizeUpper(task.getProductType())),
normalizeUpper(text(request, "orgCodeType")), normalizeUpper(text(request, "orgCodeType")),
@@ -2051,18 +2131,10 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor {
text(nodes, "node03Ip"), text(nodes, "node03Ip"),
text(nodes, "node04Ip"), text(nodes, "node04Ip"),
text(mq, "tlqLicenseFileId"), text(mq, "tlqLicenseFileId"),
text(mq, "cfmqConfigFileId"),
text(licenses, "receiverLicenseFileId"), text(licenses, "receiverLicenseFileId"),
text(licenses, "cfgZipFileId"), text(mq, "cipsGatewayHost"),
text(signServer, "signHost"), text(mq, "cfgZipFileId"),
signPort, text(mq, "cfmqConfigFileId")
text(signServer, "signType"),
text(signServer, "signCertDn"),
text(signServer, "signCertSn"),
text(signServer, "signPassword"),
text(directTlq, "localNodeSeq"),
text(directTlq, "upstreamInstitutions"),
directTlqUpstreamCocCount
); );
} }
@@ -2130,6 +2202,46 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor {
return "'" + escaped + "'"; 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<Path> 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<String> failures) throws IOException { private void verifyProcessAbsent(Path logFile, String pattern, String label, List<String> failures) throws IOException {
try { try {
int code = runCommand( int code = runCommand(
@@ -2249,18 +2361,10 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor {
String node03Ip, String node03Ip,
String node04Ip, String node04Ip,
String tlqLicenseFileId, String tlqLicenseFileId,
String cfmqConfigFileId,
String receiverLicenseFileId, String receiverLicenseFileId,
String cipsGatewayHost,
String cfgZipFileId, String cfgZipFileId,
String signHost, String cfmqConfigFileId
Integer signPort,
String signType,
String signCertDn,
String signCertSn,
String signPassword,
String directTlqLocalNodeSeq,
String directTlqUpstreamInstitutions,
Integer directTlqUpstreamCocCount
) { ) {
} }
@@ -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.config.properties.CisdPresetProperties;
import com.cisd.tms.common.enums.ErrorCode; import com.cisd.tms.common.enums.ErrorCode;
import com.cisd.tms.common.exception.BizException; 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.InitCommandProfileService;
import com.cisd.tms.modules.init.config.InitExecutorProperties; import com.cisd.tms.modules.init.config.InitExecutorProperties;
import com.cisd.tms.modules.init.dto.InitCreateTaskResponse; import com.cisd.tms.modules.init.dto.InitCreateTaskResponse;
@@ -59,6 +60,7 @@ public class InitServiceImpl implements InitService {
private final InitTaskRepository initTaskRepository; private final InitTaskRepository initTaskRepository;
private final InitTaskStepRepository initTaskStepRepository; private final InitTaskStepRepository initTaskStepRepository;
private final FileRecordRepository fileRecordRepository;
private final CisdPresetProperties cisdPresetProperties; private final CisdPresetProperties cisdPresetProperties;
private final ObjectMapper objectMapper; private final ObjectMapper objectMapper;
private final InitStepExecutor initStepExecutor; private final InitStepExecutor initStepExecutor;
@@ -507,9 +509,25 @@ public class InitServiceImpl implements InitService {
// 该步骤序列只描述“逻辑编排”,具体脚本命令在 step-command-profiles.json 中定义 // 该步骤序列只描述“逻辑编排”,具体脚本命令在 step-command-profiles.json 中定义
if ("DIRECT".equals(productType)) { if ("DIRECT".equals(productType)) {
if ("RABBITMQ_CFMQ".equals(mqType)) { 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)) { if ("ENTERPRISE".equals(productType) && "TLQ".equals(mqType)) {
return List.of( return List.of(
@@ -621,9 +639,6 @@ public class InitServiceImpl implements InitService {
if (request.getMq() == null) { if (request.getMq() == null) {
throw new IllegalArgumentException("mq is required"); throw new IllegalArgumentException("mq is required");
} }
if (request.getLicenses() == null) {
throw new IllegalArgumentException("licenses is required");
}
requireNotBlank("orgCode", request.getOrgCode()); requireNotBlank("orgCode", request.getOrgCode());
requireNotBlank("deployMode", request.getDeployMode()); requireNotBlank("deployMode", request.getDeployMode());
@@ -631,12 +646,14 @@ public class InitServiceImpl implements InitService {
requireNotBlank("mq.mqType", request.getMq().getMqType()); requireNotBlank("mq.mqType", request.getMq().getMqType());
requireNotBlank("mq.channelUsername", request.getMq().getChannelUsername()); requireNotBlank("mq.channelUsername", request.getMq().getChannelUsername());
requireNotBlank("mq.channelPassword", request.getMq().getChannelPassword()); 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.tlqLicenseFileId", request.getMq().getTlqLicenseFileId());
requireOptionalFileId("mq.cfmqConfigFileId", request.getMq().getCfmqConfigFileId()); 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) { private void validateByProductType(String productType, InitPreviewRequest request) {
@@ -709,8 +726,8 @@ public class InitServiceImpl implements InitService {
JsonNode root = objectMapper.readTree(task.getInitPlanJson()); JsonNode root = objectMapper.readTree(task.getInitPlanJson());
JsonNode request = root.path("request"); JsonNode request = root.path("request");
JsonNode mq = request.path("mq"); JsonNode mq = request.path("mq");
JsonNode licenses = request.path("licenses");
JsonNode nodes = request.path("nodes"); JsonNode nodes = request.path("nodes");
JsonNode directTlq = request.path("directTlq");
CurrentInitConfigResponse response = new CurrentInitConfigResponse(); CurrentInitConfigResponse response = new CurrentInitConfigResponse();
response.setOrgCodeType(firstNonBlank(normalizeUpper(text(request, "orgCodeType")), normalizeUpper(text(root, "orgCodeType")))); 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.setNode01Ip(firstNonBlank(text(nodes, "node01Ip"), text(request, "node01Ip")));
response.setNode02Ip(firstNonBlank(text(nodes, "node02Ip"), text(request, "node02Ip"))); response.setNode02Ip(firstNonBlank(text(nodes, "node02Ip"), text(request, "node02Ip")));
response.setCipsGatewayHost(firstNonBlank(text(mq, "cipsGatewayHost"), text(request, "cipsGatewayHost"))); 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; return response;
} catch (JsonProcessingException ex) { } catch (JsonProcessingException ex) {
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "failed to parse successful init task snapshot"); throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "failed to parse successful init task snapshot");
} }
} }
private List<String> buildTlqFileNames(
JsonNode mq,
String mqType
) {
List<String> 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<String> buildCfmqFileNames(
JsonNode mq,
String mqType
) {
List<String> 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<String> 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) { private int compareTaskRecency(InitTaskEntity left, InitTaskEntity right) {
Comparator<LocalDateTime> comparator = Comparator.nullsFirst(LocalDateTime::compareTo); Comparator<LocalDateTime> comparator = Comparator.nullsFirst(LocalDateTime::compareTo);
int byUpdate = comparator.compare(left.getUpdateTime(), right.getUpdateTime()); 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) { private void validateStandard(String productType, InitPreviewRequest request, String deployMode, String mqType) {
requireAllowed("deployMode", deployMode, DEPLOY_STANDARD); requireAllowed("deployMode", deployMode, DEPLOY_STANDARD);
requireAllowed("mq.mqType", mqType, resolveStandardMqAllowed(productType)); 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)) { if ("DUAL".equals(deployMode)) {
requireNotBlank("nodes.node02Ip", request.getNodes().getNode02Ip()); requireNotBlank("nodes.node02Ip", request.getNodes().getNode02Ip());
@@ -779,26 +849,14 @@ public class InitServiceImpl implements InitService {
requireNotBlank("nodes.node04Ip", request.getNodes().getNode04Ip()); requireNotBlank("nodes.node04Ip", request.getNodes().getNode04Ip());
} }
requireNotBlank("licenses.cfgZipFileId", request.getLicenses().getCfgZipFileId());
requireFileId("licenses.cfgZipFileId", request.getLicenses().getCfgZipFileId());
if ("RABBITMQ_TLQ".equals(mqType)) { if ("RABBITMQ_TLQ".equals(mqType)) {
requireNotBlank("mq.tlqLicenseFileId", request.getMq().getTlqLicenseFileId()); requireNotBlank("mq.cfgZipFileId", request.getMq().getCfgZipFileId());
requireFileId("mq.tlqLicenseFileId", request.getMq().getTlqLicenseFileId()); requireFileId("mq.cfgZipFileId", request.getMq().getCfgZipFileId());
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.cipsGatewayHost", request.getMq().getCipsGatewayHost()); 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)) { if ("RABBITMQ_CFMQ".equals(mqType)) {
requireNotBlank("mq.cfgZipFileId", request.getMq().getCfgZipFileId());
requireFileId("mq.cfgZipFileId", request.getMq().getCfgZipFileId());
requireNotBlank("mq.cfmqConfigFileId", request.getMq().getCfmqConfigFileId()); requireNotBlank("mq.cfmqConfigFileId", request.getMq().getCfmqConfigFileId());
requireFileId("mq.cfmqConfigFileId", request.getMq().getCfmqConfigFileId()); requireFileId("mq.cfmqConfigFileId", request.getMq().getCfmqConfigFileId());
} }
@@ -859,6 +917,7 @@ public class InitServiceImpl implements InitService {
return normalize(child.toString()); return normalize(child.toString());
} }
private static String normalizeUpper(String value) { private static String normalizeUpper(String value) {
return normalize(value).toUpperCase(Locale.ROOT); return normalize(value).toUpperCase(Locale.ROOT);
} }
@@ -73,14 +73,16 @@ public class UpgradeController {
} }
@GetMapping("/upgrades") @GetMapping("/upgrades")
@Operation(summary = "查询升级记录列表", description = "分页查询升级任务列表,支持按升级对象和状态筛选。") @Operation(summary = "查询升级记录列表", description = "分页查询升级任务列表,支持按升级对象、状态和升级日期筛选。")
public ApiResponse<UpgradeTaskPageResponse> list( public ApiResponse<UpgradeTaskPageResponse> list(
@RequestParam(defaultValue = "1") int page, @RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int pageSize, @RequestParam(defaultValue = "10") int pageSize,
@RequestParam(required = false) String taskType, @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}") @GetMapping("/upgrades/{taskId}")
@@ -7,6 +7,8 @@ public class UpgradeCreateTaskRequest {
@Schema(description = "升级包文件标识", example = "FILE-20260319-000001", requiredMode = Schema.RequiredMode.REQUIRED) @Schema(description = "升级包文件标识", example = "FILE-20260319-000001", requiredMode = Schema.RequiredMode.REQUIRED)
private String fileId; private String fileId;
@Schema(description = "用户选择的升级任务类型,必须与升级包解析结果一致", example = "TMS", requiredMode = Schema.RequiredMode.REQUIRED)
private String taskType;
public String getFileId() { public String getFileId() {
return fileId; return fileId;
@@ -15,4 +17,12 @@ public class UpgradeCreateTaskRequest {
public void setFileId(String fileId) { public void setFileId(String fileId) {
this.fileId = fileId; this.fileId = fileId;
} }
public String getTaskType() {
return taskType;
}
public void setTaskType(String taskType) {
this.taskType = taskType;
}
} }
@@ -17,13 +17,17 @@ public interface UpgradeTaskMapper extends BaseMapperX<UpgradeTaskEntity> {
List<UpgradeTaskEntity> selectPage( List<UpgradeTaskEntity> selectPage(
@Param("taskType") String taskType, @Param("taskType") String taskType,
@Param("status") String status, @Param("status") String status,
@Param("startedAtBegin") LocalDateTime startedAtBegin,
@Param("startedAtEndExclusive") LocalDateTime startedAtEndExclusive,
@Param("offset") int offset, @Param("offset") int offset,
@Param("limit") int limit @Param("limit") int limit
); );
long countPage( long countPage(
@Param("taskType") String taskType, @Param("taskType") String taskType,
@Param("status") String status @Param("status") String status,
@Param("startedAtBegin") LocalDateTime startedAtBegin,
@Param("startedAtEndExclusive") LocalDateTime startedAtEndExclusive
); );
int updateStatusByTaskId( int updateStatusByTaskId(
@@ -13,9 +13,16 @@ public interface UpgradeTaskRepository {
Optional<UpgradeTaskEntity> findRunningTask(); Optional<UpgradeTaskEntity> findRunningTask();
List<UpgradeTaskEntity> findPage(String taskType, String status, int offset, int limit); List<UpgradeTaskEntity> 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( void updateStatus(
String taskId, String taskId,
@@ -33,13 +33,20 @@ public class UpgradeTaskRepositoryImpl implements UpgradeTaskRepository {
} }
@Override @Override
public List<UpgradeTaskEntity> findPage(String taskType, String status, int offset, int limit) { public List<UpgradeTaskEntity> findPage(
return upgradeTaskMapper.selectPage(taskType, status, offset, limit); String taskType,
String status,
LocalDateTime startedAtBegin,
LocalDateTime startedAtEndExclusive,
int offset,
int limit
) {
return upgradeTaskMapper.selectPage(taskType, status, startedAtBegin, startedAtEndExclusive, offset, limit);
} }
@Override @Override
public long countPage(String taskType, String status) { public long countPage(String taskType, String status, LocalDateTime startedAtBegin, LocalDateTime startedAtEndExclusive) {
return upgradeTaskMapper.countPage(taskType, status); return upgradeTaskMapper.countPage(taskType, status, startedAtBegin, startedAtEndExclusive);
} }
@Override @Override
@@ -19,8 +19,10 @@ import com.cisd.tms.modules.upgrade.repository.UpgradeTaskRepository;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
@@ -36,6 +38,7 @@ import org.springframework.stereotype.Service;
public class UpgradeService { public class UpgradeService {
private static final DateTimeFormatter TASK_ID_TS = DateTimeFormatter.ofPattern("yyyyMMddHHmmss"); 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 UpgradeTaskRepository upgradeTaskRepository;
private final FileRecordRepository fileRecordRepository; private final FileRecordRepository fileRecordRepository;
@@ -97,11 +100,17 @@ public class UpgradeService {
if (request == null || request.getFileId() == null || request.getFileId().isBlank()) { if (request == null || request.getFileId() == null || request.getFileId().isBlank()) {
throw new IllegalArgumentException("fileId must not be blank"); 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()); UpgradePreviewResponse preview = upgradePackageService.preview(request.getFileId());
if (!preview.isAllowed()) { if (!preview.isAllowed()) {
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), blankAs(preview.getBlockReason(), "upgrade preview not allowed")); 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()) FileRecordEntity fileRecord = fileRecordRepository.findByFileId(request.getFileId().trim())
.orElseThrow(() -> new BizException(ErrorCode.BIZ_ERROR.getCode(), "upgrade package file not found: " + 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)); 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 normalizedPage = Math.max(1, page);
int normalizedPageSize = Math.max(1, pageSize); int normalizedPageSize = Math.max(1, pageSize);
int offset = (normalizedPage - 1) * normalizedPageSize; int offset = (normalizedPage - 1) * normalizedPageSize;
List<UpgradeTaskEntity> entities = upgradeTaskRepository.findPage(trim(taskType), trim(status), offset, normalizedPageSize); LocalDateTime startedAtBegin = null;
long total = upgradeTaskRepository.countPage(trim(taskType), trim(status)); LocalDateTime startedAtEndExclusive = null;
String normalizedUpgradeDate = trim(upgradeDate);
if (!normalizedUpgradeDate.isEmpty()) {
LocalDate filterDate = parseUpgradeDate(normalizedUpgradeDate);
startedAtBegin = filterDate.atStartOfDay();
startedAtEndExclusive = filterDate.plusDays(1).atStartOfDay();
}
List<UpgradeTaskEntity> 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(); UpgradeTaskPageResponse response = new UpgradeTaskPageResponse();
response.setPage(normalizedPage); response.setPage(normalizedPage);
@@ -267,4 +291,12 @@ public class UpgradeService {
private String blankAs(String value, String fallback) { private String blankAs(String value, String fallback) {
return trim(value).isEmpty() ? fallback : trim(value); 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");
}
}
} }
@@ -59,6 +59,12 @@
<if test="status != null and status != ''"> <if test="status != null and status != ''">
AND status = #{status} AND status = #{status}
</if> </if>
<if test="startedAtBegin != null">
AND started_at <![CDATA[ >= ]]> #{startedAtBegin}
</if>
<if test="startedAtEndExclusive != null">
AND started_at <![CDATA[ < ]]> #{startedAtEndExclusive}
</if>
</where> </where>
ORDER BY create_time DESC, id DESC ORDER BY create_time DESC, id DESC
LIMIT #{limit} OFFSET #{offset} LIMIT #{limit} OFFSET #{offset}
@@ -74,6 +80,12 @@
<if test="status != null and status != ''"> <if test="status != null and status != ''">
AND status = #{status} AND status = #{status}
</if> </if>
<if test="startedAtBegin != null">
AND started_at <![CDATA[ >= ]]> #{startedAtBegin}
</if>
<if test="startedAtEndExclusive != null">
AND started_at <![CDATA[ < ]]> #{startedAtEndExclusive}
</if>
</where> </where>
</select> </select>
@@ -3,6 +3,8 @@ package com.cisd.tms.modules.device.service;
import com.cisd.tms.common.config.properties.CisdPresetProperties; 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.DeviceInfoResult;
import com.cisd.tms.integration.crypto.pcie.service.MockPcieCryptoService; 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.config.DeviceProfileProperties;
import com.cisd.tms.modules.device.dto.DeviceProfileResponse; import com.cisd.tms.modules.device.dto.DeviceProfileResponse;
import com.cisd.tms.modules.device.dto.DeviceProfileSoftwareItem; import com.cisd.tms.modules.device.dto.DeviceProfileSoftwareItem;
@@ -313,6 +315,7 @@ class DeviceProfileServiceTest {
return new InitServiceImpl( return new InitServiceImpl(
new InMemoryInitTaskRepository(), new InMemoryInitTaskRepository(),
new InMemoryInitTaskStepRepository(), new InMemoryInitTaskStepRepository(),
emptyFileRecordRepository(),
preset(productType), preset(productType),
objectMapper, objectMapper,
successExecutor(), successExecutor(),
@@ -329,6 +332,19 @@ class DeviceProfileServiceTest {
return properties; return properties;
} }
private static FileRecordRepository emptyFileRecordRepository() {
return new FileRecordRepository() {
@Override
public Optional<FileRecordEntity> findByFileId(String fileId) {
return Optional.empty();
}
@Override
public void save(FileRecordEntity entity) {
}
};
}
private static InitStepExecutor successExecutor() { private static InitStepExecutor successExecutor() {
return (task, step) -> InitStepExecutionResult.success("ok", 0, null); return (task, step) -> InitStepExecutionResult.success("ok", 0, null);
} }
@@ -3,6 +3,8 @@ package com.cisd.tms.modules.device.service;
import com.cisd.tms.common.config.properties.CisdPresetProperties; 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.DeviceInfoResult;
import com.cisd.tms.integration.crypto.pcie.service.MockPcieCryptoService; 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.DeviceRuntimeStatusResponse;
import com.cisd.tms.modules.device.dto.SoftwareRuntimeStatusItem; import com.cisd.tms.modules.device.dto.SoftwareRuntimeStatusItem;
import com.cisd.tms.modules.device.entity.DeviceSoftwareVersionEntity; import com.cisd.tms.modules.device.entity.DeviceSoftwareVersionEntity;
@@ -146,6 +148,31 @@ class DeviceRuntimeStatusServiceTest {
assertSoftwareAbsent(response.getSoftware(), "TLQ"); 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<SoftwareRuntimeStatusItem> items, String code, String expectedStatus) { private static void assertSoftwareStatus(List<SoftwareRuntimeStatusItem> items, String code, String expectedStatus) {
SoftwareRuntimeStatusItem item = items.stream() SoftwareRuntimeStatusItem item = items.stream()
.filter(candidate -> code.equals(candidate.getComponentCode())) .filter(candidate -> code.equals(candidate.getComponentCode()))
@@ -358,6 +385,7 @@ class DeviceRuntimeStatusServiceTest {
return new InitServiceImpl( return new InitServiceImpl(
new InMemoryInitTaskRepository(), new InMemoryInitTaskRepository(),
new InMemoryInitTaskStepRepository(), new InMemoryInitTaskStepRepository(),
emptyFileRecordRepository(),
preset(productType), preset(productType),
objectMapper, objectMapper,
successExecutor(), successExecutor(),
@@ -374,6 +402,19 @@ class DeviceRuntimeStatusServiceTest {
return properties; return properties;
} }
private static FileRecordRepository emptyFileRecordRepository() {
return new FileRecordRepository() {
@Override
public Optional<FileRecordEntity> findByFileId(String fileId) {
return Optional.empty();
}
@Override
public void save(FileRecordEntity entity) {
}
};
}
private static InitStepExecutor successExecutor() { private static InitStepExecutor successExecutor() {
return (task, step) -> InitStepExecutionResult.success("ok", 0, null); return (task, step) -> InitStepExecutionResult.success("ok", 0, null);
} }
@@ -405,6 +446,19 @@ class DeviceRuntimeStatusServiceTest {
return request; 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) { private static void createSuccessfulInitTask(InitService service, InitPreviewRequest request) {
InitCreateTaskResponse created = service.createTask(request); InitCreateTaskResponse created = service.createTask(request);
service.executeTask(created.getTaskId()); service.executeTask(created.getTaskId());
@@ -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.DeviceInfoResult;
import com.cisd.tms.integration.crypto.pcie.model.DeviceStatusResult; import com.cisd.tms.integration.crypto.pcie.model.DeviceStatusResult;
import com.cisd.tms.integration.crypto.pcie.service.PcieCryptoService; 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.config.DeviceProfileProperties;
import com.cisd.tms.modules.device.dto.DeviceInfoResponse; import com.cisd.tms.modules.device.dto.DeviceInfoResponse;
import com.cisd.tms.modules.device.entity.DeviceSoftwareVersionEntity; import com.cisd.tms.modules.device.entity.DeviceSoftwareVersionEntity;
@@ -100,6 +102,7 @@ class DeviceServiceTest {
return new InitServiceImpl( return new InitServiceImpl(
taskRepository, taskRepository,
stepRepository, stepRepository,
emptyFileRecordRepository(),
preset(productType), preset(productType),
objectMapper, objectMapper,
successExecutor(), successExecutor(),
@@ -116,6 +119,19 @@ class DeviceServiceTest {
return properties; return properties;
} }
private static FileRecordRepository emptyFileRecordRepository() {
return new FileRecordRepository() {
@Override
public Optional<FileRecordEntity> findByFileId(String fileId) {
return Optional.empty();
}
@Override
public void save(FileRecordEntity entity) {
}
};
}
private static InitStepExecutor successExecutor() { private static InitStepExecutor successExecutor() {
return (task, step) -> InitStepExecutionResult.success("ok", 0, null); return (task, step) -> InitStepExecutionResult.success("ok", 0, null);
} }
@@ -66,4 +66,61 @@ class DefaultSoftwareRuntimeProbeTest {
Assertions.assertFalse(probe.isStandardAppRunning()); 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());
}
} }
@@ -74,22 +74,17 @@ class ConfigurableInitStepExecutorTest {
} }
@Test @Test
void shouldRenderDirectCpconfigFromUploadBaseDir() throws IOException { void shouldRenderDirectCpconfigWithoutSignServerInput() throws IOException {
Path tempRoot = Files.createTempDirectory("init-executor-render-fileid-test"); Path tempRoot = Files.createTempDirectory("init-executor-render-fileid-test");
Path uploadDir = tempRoot.resolve("uploads"); Path uploadDir = tempRoot.resolve("uploads");
Path cfgDir = uploadDir.resolve("65"); Files.createDirectories(uploadDir);
Files.createDirectories(cfgDir); Path cpconfig = tempRoot.resolve("cpconfig.cfg");
Path cpconfig = cfgDir.resolve("cpconfig.cfg");
Files.writeString(cpconfig, """ Files.writeString(cpconfig, """
MY_CIPSID_OR_BIC=OLD MY_CIPSID_OR_BIC=OLD
MQ_TYPE=1 MQ_TYPE=1
node_01_ip=127.0.0.1 node_01_ip=127.0.0.1
db_ip=127.0.0.1 db_ip=127.0.0.1
sign_host=127.0.0.1 db_port=4000
sign_port=50006
sign_type=Infosec
sign_cert_dn='old'
sign_cert_sn=old
"""); """);
InitExecutorProperties properties = new InitExecutorProperties(); InitExecutorProperties properties = new InitExecutorProperties();
@@ -97,6 +92,7 @@ class ConfigurableInitStepExecutorTest {
properties.setLogDir(tempRoot.resolve("logs").toString()); properties.setLogDir(tempRoot.resolve("logs").toString());
properties.setUploadBaseDir(uploadDir.toString()); properties.setUploadBaseDir(uploadDir.toString());
properties.setDirectCpconfigFileName("cpconfig.cfg"); properties.setDirectCpconfigFileName("cpconfig.cfg");
properties.setDirectCpconfigTargetPath(cpconfig.toString());
ConfigurableInitStepExecutor executor = new ConfigurableInitStepExecutor(properties, new ObjectMapper()); ConfigurableInitStepExecutor executor = new ConfigurableInitStepExecutor(properties, new ObjectMapper());
InitTaskEntity task = new InitTaskEntity(); InitTaskEntity task = new InitTaskEntity();
@@ -111,21 +107,7 @@ class ConfigurableInitStepExecutorTest {
"orgNameEn", "TEST BANK", "orgNameEn", "TEST BANK",
"deployMode", "SINGLE", "deployMode", "SINGLE",
"nodes", Map.of("node01Ip", "10.0.1.1"), "nodes", Map.of("node01Ip", "10.0.1.1"),
"mq", Map.of("mqType", "RABBITMQ_TLQ", "tlqLicenseFileId", "file-tlq-001"), "mq", Map.of("mqType", "RABBITMQ_TLQ", "cipsGatewayHost", "172.30.1.10", "cfgZipFileId", "cae-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
)
) )
)); ));
@@ -140,15 +122,15 @@ class ConfigurableInitStepExecutorTest {
String rendered = Files.readString(renderedPath); String rendered = Files.readString(renderedPath);
Assertions.assertTrue(rendered.contains("MY_CIPSID_OR_BIC=AAAABBBBXXX")); Assertions.assertTrue(rendered.contains("MY_CIPSID_OR_BIC=AAAABBBBXXX"));
Assertions.assertTrue(rendered.contains("node_01_ip=10.0.1.1")); Assertions.assertTrue(rendered.contains("node_01_ip=10.0.1.1"));
Assertions.assertTrue(rendered.contains("db_ip=127.0.0.1"));
} }
@Test @Test
void shouldRenderDirectCpconfigFromCfgDirectory() throws IOException { void shouldVerifyDirectConfigWithoutSignFields() throws IOException {
Path tempRoot = Files.createTempDirectory("init-executor-render-test"); Path tempRoot = Files.createTempDirectory("init-executor-render-test");
Path uploadDir = tempRoot.resolve("uploads"); Path uploadDir = tempRoot.resolve("uploads");
Path cfgDir = uploadDir.resolve("cfg-001"); Files.createDirectories(uploadDir);
Files.createDirectories(cfgDir); Path cpconfig = tempRoot.resolve("cpconfig.cfg");
Path cpconfig = cfgDir.resolve("cpconfig.cfg");
Files.writeString(cpconfig, """ Files.writeString(cpconfig, """
MY_CIPSID_OR_BIC=OLD MY_CIPSID_OR_BIC=OLD
MQ_TYPE=1 MQ_TYPE=1
@@ -159,23 +141,18 @@ class ConfigurableInitStepExecutorTest {
node_03_ip=127.0.0.1 node_03_ip=127.0.0.1
node_04_ip=127.0.0.1 node_04_ip=127.0.0.1
db_ip=127.0.0.1 db_ip=127.0.0.1
sign_host=127.0.0.1 db_port=4000
sign_port=50006
sign_password=old
sign_type=Infosec
sign_cert_dn='old'
sign_cert_sn=old
MY_BIC_CODE=OLD MY_BIC_CODE=OLD
MY_CIPS_ID= MY_CIPS_ID=
MY_LEI_CODE= MY_LEI_CODE=
"""); """);
Files.writeString(uploadDir.resolve("cfmq-001"), "cfmq.host=127.0.0.1");
InitExecutorProperties properties = new InitExecutorProperties(); InitExecutorProperties properties = new InitExecutorProperties();
properties.setMode(InitExecutorProperties.Mode.SIMULATE); properties.setMode(InitExecutorProperties.Mode.SIMULATE);
properties.setLogDir(tempRoot.resolve("logs").toString()); properties.setLogDir(tempRoot.resolve("logs").toString());
properties.setUploadBaseDir(uploadDir.toString()); properties.setUploadBaseDir(uploadDir.toString());
properties.setDirectCpconfigFileName("cpconfig.cfg"); properties.setDirectCpconfigFileName("cpconfig.cfg");
properties.setDirectCpconfigTargetPath(cpconfig.toString());
ConfigurableInitStepExecutor executor = new ConfigurableInitStepExecutor(properties, new ObjectMapper()); ConfigurableInitStepExecutor executor = new ConfigurableInitStepExecutor(properties, new ObjectMapper());
InitTaskEntity task = new InitTaskEntity(); InitTaskEntity task = new InitTaskEntity();
@@ -190,51 +167,49 @@ class ConfigurableInitStepExecutorTest {
"orgNameEn", "TEST BANK", "orgNameEn", "TEST BANK",
"deployMode", "DUAL", "deployMode", "DUAL",
"nodes", Map.of("node01Ip", "10.0.1.1", "node02Ip", "10.0.1.2"), "nodes", Map.of("node01Ip", "10.0.1.1", "node02Ip", "10.0.1.2"),
"mq", Map.of("mqType", "RABBITMQ_CFMQ", "cfmqConfigFileId", "cfmq-001"), "mq", Map.of("mqType", "RABBITMQ_CFMQ", "cfgZipFileId", "cae-001", "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"
)
) )
)); ));
InitTaskStepEntity step = new InitTaskStepEntity(); InitTaskStepEntity renderStep = new InitTaskStepEntity();
step.setStepNo(1); renderStep.setStepNo(1);
step.setStepCode("RENDER_CONFIG"); renderStep.setStepCode("RENDER_CONFIG");
step.setCommandLine("internal::render_config"); renderStep.setCommandLine("internal::render_config");
InitStepExecutionResult renderResult = executor.execute(task, renderStep);
InitStepExecutionResult result = executor.execute(task, step); Assertions.assertTrue(renderResult.isSuccess());
Path renderedPath = parseRenderedCpconfigPath(renderResult.getMessage());
Assertions.assertTrue(result.isSuccess());
Assertions.assertNotNull(result.getLogPath());
Path renderedPath = parseRenderedCpconfigPath(result.getMessage());
String rendered = Files.readString(renderedPath); String rendered = Files.readString(renderedPath);
Assertions.assertTrue(rendered.contains("MY_CIPSID_OR_BIC=AAAABBBBXXX")); Assertions.assertTrue(rendered.contains("MY_CIPSID_OR_BIC=AAAABBBBXXX"));
Assertions.assertTrue(rendered.contains("MQ_TYPE=3")); Assertions.assertTrue(rendered.contains("MQ_TYPE=3"));
Assertions.assertTrue(rendered.contains("node_01_ip=10.0.1.1")); 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("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 @Test
void shouldApplyCfmqConfigToTargetDirectory() throws IOException { void shouldApplyDirectCfmqLinkScript() throws IOException {
Path tempRoot = Files.createTempDirectory("init-executor-cfmq-test"); Path tempRoot = Files.createTempDirectory("init-executor-cfmq-link-test");
Path uploadDir = tempRoot.resolve("uploads"); Path uploadDir = tempRoot.resolve("uploads");
Files.createDirectories(uploadDir); Files.createDirectories(uploadDir);
Path cfmqSource = uploadDir.resolve("cfmq-001"); 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(); InitExecutorProperties properties = new InitExecutorProperties();
properties.setMode(InitExecutorProperties.Mode.SIMULATE); properties.setMode(InitExecutorProperties.Mode.LOCAL);
properties.setLogDir(tempRoot.resolve("logs").toString()); properties.setLogDir(tempRoot.resolve("logs").toString());
properties.setUploadBaseDir(uploadDir.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()); ConfigurableInitStepExecutor executor = new ConfigurableInitStepExecutor(properties, new ObjectMapper());
InitTaskEntity task = new InitTaskEntity(); InitTaskEntity task = new InitTaskEntity();
@@ -249,45 +224,45 @@ class ConfigurableInitStepExecutorTest {
"orgNameEn", "TEST BANK", "orgNameEn", "TEST BANK",
"deployMode", "SINGLE", "deployMode", "SINGLE",
"nodes", Map.of("node01Ip", "10.0.1.1"), "nodes", Map.of("node01Ip", "10.0.1.1"),
"mq", Map.of("mqType", "RABBITMQ_CFMQ", "cfmqConfigFileId", "cfmq-001"), "mq", Map.of("mqType", "RABBITMQ_CFMQ", "cfgZipFileId", "cae-001", "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"
)
) )
)); ));
InitTaskStepEntity step = new InitTaskStepEntity(); InitTaskStepEntity step = new InitTaskStepEntity();
step.setStepNo(1); step.setStepNo(1);
step.setStepCode("CFMQ_APPLY"); step.setStepCode("CFMQ_LINK_APPLY");
step.setCommandLine("internal::cfmq_apply"); step.setCommandLine("internal::cfmq_link_apply");
InitStepExecutionResult result = executor.execute(task, step); InitStepExecutionResult result = executor.execute(task, step);
Assertions.assertTrue(result.isSuccess()); Assertions.assertTrue(result.isSuccess());
Path copied = targetDir.resolve(cfmqSource.getFileName()); Assertions.assertTrue(Files.exists(marker));
Assertions.assertTrue(Files.exists(copied)); Assertions.assertEquals(targetDir.toString(), Files.readString(marker));
Assertions.assertEquals("cfmq.host=127.0.0.1", Files.readString(copied));
} }
@Test @Test
void shouldSimulateDirectTlqInitCfgStep() throws IOException { void shouldApplyDirectCaePackageToTargetDirectory() throws IOException {
Path tempRoot = Files.createTempDirectory("init-executor-tlq-init-test"); Path tempRoot = Files.createTempDirectory("init-executor-cae-package-test");
Path uploadDir = tempRoot.resolve("uploads"); Path uploadDir = tempRoot.resolve("uploads");
Files.createDirectories(uploadDir); 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(); InitExecutorProperties properties = new InitExecutorProperties();
properties.setMode(InitExecutorProperties.Mode.SIMULATE); properties.setMode(InitExecutorProperties.Mode.LOCAL);
properties.setLogDir(tempRoot.resolve("logs").toString()); properties.setLogDir(tempRoot.resolve("logs").toString());
properties.setUploadBaseDir(uploadDir.toString()); properties.setUploadBaseDir(uploadDir.toString());
properties.setStagingRootDir(tempRoot.resolve("staging").toString());
properties.setDirectCaeTargetDir(targetDir.toString());
ConfigurableInitStepExecutor executor = new ConfigurableInitStepExecutor(properties, new ObjectMapper()); ConfigurableInitStepExecutor executor = new ConfigurableInitStepExecutor(properties, new ObjectMapper());
InitTaskEntity task = new InitTaskEntity(); InitTaskEntity task = new InitTaskEntity();
task.setTaskId("TASK-TLQ-INIT-1"); task.setTaskId("TASK-CAE-PACKAGE-1");
task.setProductType("DIRECT"); task.setProductType("DIRECT");
task.setInitPlanJson(buildInitPlanJson( task.setInitPlanJson(buildInitPlanJson(
"DIRECT", "DIRECT",
@@ -296,33 +271,112 @@ class ConfigurableInitStepExecutorTest {
"orgCode", "AAAABBBBXXX", "orgCode", "AAAABBBBXXX",
"orgNameCn", "测试机构", "orgNameCn", "测试机构",
"orgNameEn", "TEST BANK", "orgNameEn", "TEST BANK",
"deployMode", "DUAL", "deployMode", "SINGLE",
"nodes", Map.of("node01Ip", "10.0.1.1", "node02Ip", "10.0.1.2"), "nodes", Map.of("node01Ip", "10.0.1.1"),
"mq", Map.of("mqType", "RABBITMQ_TLQ", "tlqLicenseFileId", "file-tlq-001"), "mq", Map.of("mqType", "RABBITMQ_TLQ", "cipsGatewayHost", "172.30.1.10", "cfgZipFileId", "cae-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
)
) )
)); ));
InitTaskStepEntity step = new InitTaskStepEntity(); InitTaskStepEntity step = new InitTaskStepEntity();
step.setStepNo(1); step.setStepNo(1);
step.setStepCode("TLQ_INIT_CFG"); step.setStepCode("CAE_PACKAGE_APPLY");
step.setCommandLine("internal::tlq_init_cfg"); step.setCommandLine("internal::cae_package_apply");
InitStepExecutionResult result = executor.execute(task, step); InitStepExecutionResult result = executor.execute(task, step);
Assertions.assertTrue(result.isSuccess()); 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 @Test
@@ -2,6 +2,8 @@ package com.cisd.tms.modules.init.service;
import com.cisd.tms.common.config.properties.CisdPresetProperties; import com.cisd.tms.common.config.properties.CisdPresetProperties;
import com.cisd.tms.common.exception.BizException; 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.InitCommandProfileService;
import com.cisd.tms.modules.init.config.InitExecutorProperties; import com.cisd.tms.modules.init.config.InitExecutorProperties;
import com.cisd.tms.modules.init.dto.InitCreateTaskResponse; 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.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assertions;
@@ -106,29 +109,34 @@ class InitServiceTest {
} }
@Test @Test
void shouldRequireDirectSpecificFieldsForCfmqMode() { 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_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() {
InitService service = newService(emptyRepository(), emptyStepRepository(), preset("DIRECT", "V1.3.2", "CONFIG"), successExecutor(), "/tmp/tms-init-logs"); InitService service = newService(emptyRepository(), emptyStepRepository(), preset("DIRECT", "V1.3.2", "CONFIG"), successExecutor(), "/tmp/tms-init-logs");
InitPreviewRequest request = validDirectRequest(); InitPreviewRequest request = validDirectRequest();
request.getMq().setMqType("RABBITMQ_TLQ"); request.getMq().setMqType("RABBITMQ_TLQ");
request.getMq().setTlqLicenseFileId("file-tlq-001"); request.getMq().setCfgZipFileId(null);
request.setDirectTlq(null);
IllegalArgumentException ex = Assertions.assertThrows(IllegalArgumentException.class, () -> service.preview(request)); 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 @Test
@@ -136,33 +144,20 @@ class InitServiceTest {
InitService service = newService(emptyRepository(), emptyStepRepository(), preset("DIRECT", "V1.3.2", "CONFIG"), successExecutor(), "/tmp/tms-init-logs"); InitService service = newService(emptyRepository(), emptyStepRepository(), preset("DIRECT", "V1.3.2", "CONFIG"), successExecutor(), "/tmp/tms-init-logs");
InitPreviewRequest request = validDirectRequest(); InitPreviewRequest request = validDirectRequest();
request.getMq().setMqType("RABBITMQ_TLQ"); request.getMq().setMqType("RABBITMQ_TLQ");
request.getMq().setTlqLicenseFileId("file-tlq-001");
setMqCipsGatewayHost(request.getMq(), "192.168.10.20"); setMqCipsGatewayHost(request.getMq(), "192.168.10.20");
request.setDirectTlq(directTlqConfig()); request.getMq().setCfgZipFileId("file-direct-tlq-cfg-001");
InitPreviewResponse response = service.preview(request); InitPreviewResponse response = service.preview(request);
Assertions.assertEquals("DIRECT", response.getResolvedProductType()); Assertions.assertEquals("DIRECT", response.getResolvedProductType());
Assertions.assertTrue(response.getStepCount() >= 1); 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 @Test
void shouldRequireCipsGatewayHostForDirectTlqMode() { void shouldRequireCipsGatewayHostForDirectTlqMode() {
InitService service = newService(emptyRepository(), emptyStepRepository(), preset("DIRECT", "V1.3.2", "CONFIG"), successExecutor(), "/tmp/tms-init-logs"); InitService service = newService(emptyRepository(), emptyStepRepository(), preset("DIRECT", "V1.3.2", "CONFIG"), successExecutor(), "/tmp/tms-init-logs");
InitPreviewRequest request = validDirectRequest(); InitPreviewRequest request = validDirectRequest();
request.getMq().setMqType("RABBITMQ_TLQ"); request.getMq().setMqType("RABBITMQ_TLQ");
request.getMq().setTlqLicenseFileId("file-tlq-001"); request.getMq().setCfgZipFileId("file-direct-tlq-cfg-001");
request.setDirectTlq(directTlqConfig());
IllegalArgumentException ex = Assertions.assertThrows(IllegalArgumentException.class, () -> service.preview(request)); IllegalArgumentException ex = Assertions.assertThrows(IllegalArgumentException.class, () -> service.preview(request));
Assertions.assertTrue(ex.getMessage().contains("mq.cipsGatewayHost")); 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"); InitService service = newService(emptyRepository(), emptyStepRepository(), preset("DIRECT", "V1.3.2", "CONFIG"), successExecutor(), "/tmp/tms-init-logs");
InitPreviewRequest request = validDirectRequest(); InitPreviewRequest request = validDirectRequest();
request.getMq().setMqType("RABBITMQ_TLQ"); request.getMq().setMqType("RABBITMQ_TLQ");
request.getMq().setTlqLicenseFileId("file-tlq-001");
setMqCipsGatewayHost(request.getMq(), "192.168.10.20"); setMqCipsGatewayHost(request.getMq(), "192.168.10.20");
request.setDirectTlq(directTlqConfig()); request.getMq().setCfgZipFileId("file-direct-tlq-cfg-001");
InitPreviewResponse response = service.preview(request); InitPreviewResponse response = service.preview(request);
@@ -245,7 +239,16 @@ class InitServiceTest {
void shouldLoadCurrentInitConfigFromLatestSuccessfulInitTask() { void shouldLoadCurrentInitConfigFromLatestSuccessfulInitTask() {
InMemoryInitTaskRepository repository = new InMemoryInitTaskRepository(); InMemoryInitTaskRepository repository = new InMemoryInitTaskRepository();
InMemoryInitTaskStepRepository stepRepository = new InMemoryInitTaskStepRepository(); 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(); InitPreviewRequest request = validEnterpriseRequest();
request.setOrgNameEn("Test Organization"); request.setOrgNameEn("Test Organization");
request.getNodes().setNode02Ip("10.0.0.2"); request.getNodes().setNode02Ip("10.0.0.2");
@@ -263,6 +266,9 @@ class InitServiceTest {
Assertions.assertEquals("cisd_channel_user", response.getChannelUsername()); Assertions.assertEquals("cisd_channel_user", response.getChannelUsername());
Assertions.assertEquals("10.0.0.1", response.getNode01Ip()); Assertions.assertEquals("10.0.0.1", response.getNode01Ip());
Assertions.assertEquals("10.0.0.2", response.getNode02Ip()); 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 @Test
@@ -298,12 +304,22 @@ class InitServiceTest {
void shouldLoadCurrentInitConfigWithDirectTlqGatewayHost() { void shouldLoadCurrentInitConfigWithDirectTlqGatewayHost() {
InMemoryInitTaskRepository repository = new InMemoryInitTaskRepository(); InMemoryInitTaskRepository repository = new InMemoryInitTaskRepository();
InMemoryInitTaskStepRepository stepRepository = new InMemoryInitTaskStepRepository(); 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(); InitPreviewRequest request = validDirectRequest();
request.getMq().setMqType("RABBITMQ_TLQ"); request.getMq().setMqType("RABBITMQ_TLQ");
request.getMq().setTlqLicenseFileId("file-tlq-001");
setMqCipsGatewayHost(request.getMq(), "192.168.10.20"); 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); createSuccessfulInitTask(service, request);
CurrentInitConfigResponse response = service.loadCurrentInitConfig(); CurrentInitConfigResponse response = service.loadCurrentInitConfig();
@@ -311,6 +327,44 @@ class InitServiceTest {
Assertions.assertEquals("DIRECT", response.getProductType()); Assertions.assertEquals("DIRECT", response.getProductType());
Assertions.assertEquals("RABBITMQ_TLQ", response.getMqType()); Assertions.assertEquals("RABBITMQ_TLQ", response.getMqType());
Assertions.assertEquals("192.168.10.20", readCurrentInitConfigCipsGatewayHost(response)); 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 @Test
@@ -448,21 +502,51 @@ class InitServiceTest {
} }
@Test @Test
void shouldNotPlanStandardPostDeployStepsForDirectMode() { void shouldPlanDirectTlqSteps() {
InMemoryInitTaskRepository repository = new InMemoryInitTaskRepository(); InMemoryInitTaskRepository repository = new InMemoryInitTaskRepository();
InMemoryInitTaskStepRepository stepRepository = new InMemoryInitTaskStepRepository(); InMemoryInitTaskStepRepository stepRepository = new InMemoryInitTaskStepRepository();
InitService service = newService(repository, stepRepository, preset("DIRECT", "V1.3.2", "CONFIG"), successExecutor(), "/tmp/tms-init-logs"); InitService service = newService(repository, stepRepository, preset("DIRECT", "V1.3.2", "CONFIG"), successExecutor(), "/tmp/tms-init-logs");
InitPreviewRequest request = validDirectRequest(); InitPreviewRequest request = validDirectRequest();
request.getMq().setMqType("RABBITMQ_TLQ"); request.getMq().setMqType("RABBITMQ_TLQ");
request.getMq().setTlqLicenseFileId("file-tlq-001");
setMqCipsGatewayHost(request.getMq(), "192.168.10.20"); setMqCipsGatewayHost(request.getMq(), "192.168.10.20");
request.setDirectTlq(directTlqConfig()); request.getMq().setCfgZipFileId("file-direct-tlq-cfg-001");
InitCreateTaskResponse created = service.createTask(request); InitCreateTaskResponse created = service.createTask(request);
List<InitTaskStepResponse> steps = service.getTaskSteps(created.getTaskId()); List<InitTaskStepResponse> steps = service.getTaskSteps(created.getTaskId());
List<String> stepCodes = steps.stream().map(InitTaskStepResponse::getStepCode).toList(); List<String> 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<InitTaskStepResponse> steps = service.getTaskSteps(created.getTaskId());
List<String> 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); assertNotContainsStandardPostDeploySteps(stepCodes);
} }
@@ -689,13 +773,33 @@ class InitServiceTest {
CisdPresetProperties preset, CisdPresetProperties preset,
InitStepExecutor executor, InitStepExecutor executor,
String logDir 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(); InitExecutorProperties properties = new InitExecutorProperties();
properties.setLogDir(logDir); properties.setLogDir(logDir);
properties.setMode(InitExecutorProperties.Mode.SIMULATE); properties.setMode(InitExecutorProperties.Mode.SIMULATE);
ObjectMapper objectMapper = new ObjectMapper(); ObjectMapper objectMapper = new ObjectMapper();
InitCommandProfileService commandProfileService = new InitCommandProfileService(objectMapper, properties); 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() { private static InitTaskRepository emptyRepository() {
@@ -786,13 +890,12 @@ class InitServiceTest {
InitPreviewRequest request = validEnterpriseRequest(); InitPreviewRequest request = validEnterpriseRequest();
request.setDeployMode("DUAL"); request.setDeployMode("DUAL");
request.getNodes().setNode02Ip("10.0.0.2"); request.getNodes().setNode02Ip("10.0.0.2");
request.setLicenses(null);
InitPreviewRequest.Mq mq = request.getMq(); InitPreviewRequest.Mq mq = request.getMq();
mq.setMqType("RABBITMQ_CFMQ"); mq.setMqType("RABBITMQ_CFMQ");
mq.setCfmqConfigFileId("file-cfmq-001"); mq.setCfgZipFileId("file-direct-cfmq-cfg-001");
mq.setCfmqConfigFileId("file-direct-cfmq-addlink-001");
InitPreviewRequest.Licenses licenses = request.getLicenses();
licenses.setCfgZipFileId("file-cfg-001");
return request; return request;
} }
@@ -802,14 +905,6 @@ class InitServiceTest {
awaitTaskStatus(service, created.getTaskId(), "SUCCESS"); 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) { private static void setMqCipsGatewayHost(InitPreviewRequest.Mq mq, String value) {
try { try {
mq.getClass().getMethod("setCipsGatewayHost", String.class).invoke(mq, value); 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<String, FileRecordEntity> store = new ConcurrentHashMap<>();
@Override
public Optional<FileRecordEntity> 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<String> stepCodes) { private static void assertContainsStandardPostDeploySteps(List<String> stepCodes) {
Assertions.assertTrue(stepCodes.contains("DB_RENDER")); Assertions.assertTrue(stepCodes.contains("DB_RENDER"));
Assertions.assertTrue(stepCodes.contains("DB_APPLY")); Assertions.assertTrue(stepCodes.contains("DB_APPLY"));
@@ -38,11 +38,13 @@ class UpgradeDtoContractTest {
} }
@Test @Test
void shouldAcceptFileIdForCreateTaskRequest() { void shouldAcceptFileIdAndTaskTypeForCreateTaskRequest() {
UpgradeCreateTaskRequest request = new UpgradeCreateTaskRequest(); UpgradeCreateTaskRequest request = new UpgradeCreateTaskRequest();
request.setFileId("FILE-20260319-000001"); request.setFileId("FILE-20260319-000001");
request.setTaskType("TMS");
Assertions.assertEquals("FILE-20260319-000001", request.getFileId()); Assertions.assertEquals("FILE-20260319-000001", request.getFileId());
Assertions.assertEquals("TMS", request.getTaskType());
} }
@Test @Test
@@ -77,7 +77,7 @@ class UpgradeControllerTest {
page.setPageSize(10); page.setPageSize(10);
page.setTotal(1L); page.setTotal(1L);
page.getItems().add(task); 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(); MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new UpgradeController(upgradeService, fileService)).build();
@@ -90,7 +90,7 @@ class UpgradeControllerTest {
mockMvc.perform(post("/api/v1/upgrades") mockMvc.perform(post("/api/v1/upgrades")
.contentType(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON)
.content("{\"fileId\":\"FILE-001\"}")) .content("{\"fileId\":\"FILE-001\",\"taskType\":\"RECEIVER\"}"))
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(content().string(org.hamcrest.Matchers.containsString("\"taskId\":\"UPG-001\""))); .andExpect(content().string(org.hamcrest.Matchers.containsString("\"taskId\":\"UPG-001\"")));
@@ -106,10 +106,13 @@ class UpgradeControllerTest {
.param("page", "1") .param("page", "1")
.param("pageSize", "10") .param("pageSize", "10")
.param("taskType", "") .param("taskType", "")
.param("status", "")) .param("status", "")
.param("upgradeDate", "2026-04-16"))
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(content().string(org.hamcrest.Matchers.containsString("\"total\":1"))); .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")) mockMvc.perform(get("/api/v1/upgrades/UPG-001"))
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(content().string(org.hamcrest.Matchers.containsString("\"taskId\":\"UPG-001\""))); .andExpect(content().string(org.hamcrest.Matchers.containsString("\"taskId\":\"UPG-001\"")));
@@ -280,12 +280,19 @@ class UpgradeTaskRunnerTest {
} }
@Override @Override
public java.util.List<UpgradeTaskEntity> findPage(String taskType, String status, int offset, int limit) { public java.util.List<UpgradeTaskEntity> findPage(
String taskType,
String status,
LocalDateTime startedAtBegin,
LocalDateTime startedAtEndExclusive,
int offset,
int limit
) {
return java.util.List.of(); return java.util.List.of();
} }
@Override @Override
public long countPage(String taskType, String status) { public long countPage(String taskType, String status, LocalDateTime startedAtBegin, LocalDateTime startedAtEndExclusive) {
return 0; return 0;
} }
@@ -47,12 +47,14 @@ class UpgradeTaskRepositoryTest {
UpgradeTaskMapper mapper = Mockito.mock(UpgradeTaskMapper.class); UpgradeTaskMapper mapper = Mockito.mock(UpgradeTaskMapper.class);
UpgradeTaskRepository repository = new UpgradeTaskRepositoryImpl(mapper); UpgradeTaskRepository repository = new UpgradeTaskRepositoryImpl(mapper);
UpgradeTaskEntity entity = entity("UPG-003", "SUCCESS"); 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.selectPage("TMS", "SUCCESS", startedAtBegin, startedAtEndExclusive, 0, 10)).thenReturn(List.of(entity));
Mockito.when(mapper.countPage("TMS", "SUCCESS")).thenReturn(1L); Mockito.when(mapper.countPage("TMS", "SUCCESS", startedAtBegin, startedAtEndExclusive)).thenReturn(1L);
List<UpgradeTaskEntity> page = repository.findPage("TMS", "SUCCESS", 0, 10); List<UpgradeTaskEntity> page = repository.findPage("TMS", "SUCCESS", startedAtBegin, startedAtEndExclusive, 0, 10);
long total = repository.countPage("TMS", "SUCCESS"); long total = repository.countPage("TMS", "SUCCESS", startedAtBegin, startedAtEndExclusive);
Assertions.assertEquals(1, page.size()); Assertions.assertEquals(1, page.size());
Assertions.assertEquals("UPG-003", page.get(0).getTaskId()); Assertions.assertEquals("UPG-003", page.get(0).getTaskId());
@@ -62,6 +62,7 @@ class UpgradeServiceTest {
UpgradeCreateTaskRequest request = new UpgradeCreateTaskRequest(); UpgradeCreateTaskRequest request = new UpgradeCreateTaskRequest();
request.setFileId("FILE-001"); request.setFileId("FILE-001");
request.setTaskType("RECEIVER");
UpgradeCreateTaskResponse response = service.createTask(request); UpgradeCreateTaskResponse response = service.createTask(request);
@@ -73,6 +74,42 @@ class UpgradeServiceTest {
Assertions.assertTrue(response.getTaskId().startsWith("UPG-")); 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 @Test
void shouldRejectExecuteWhenAnotherTaskIsRunning() { void shouldRejectExecuteWhenAnotherTaskIsRunning() {
InMemoryUpgradeTaskRepository repository = new InMemoryUpgradeTaskRepository(); InMemoryUpgradeTaskRepository repository = new InMemoryUpgradeTaskRepository();
@@ -110,12 +147,39 @@ class UpgradeServiceTest {
new ConcurrentHashMap<>() new ConcurrentHashMap<>()
); );
UpgradeTaskPageResponse page = service.listTasks(1, 10, "", ""); UpgradeTaskPageResponse page = service.listTasks(1, 10, "", "", null);
Assertions.assertEquals(2L, page.getTotal()); Assertions.assertEquals(2L, page.getTotal());
Assertions.assertEquals(2, page.getItems().size()); 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 @Test
void shouldReturnUpgradeTaskDetail() { void shouldReturnUpgradeTaskDetail() {
InMemoryUpgradeTaskRepository repository = new InMemoryUpgradeTaskRepository(); InMemoryUpgradeTaskRepository repository = new InMemoryUpgradeTaskRepository();
@@ -214,10 +278,21 @@ class UpgradeServiceTest {
} }
@Override @Override
public List<UpgradeTaskEntity> findPage(String taskType, String status, int offset, int limit) { public List<UpgradeTaskEntity> findPage(
String taskType,
String status,
LocalDateTime startedAtBegin,
LocalDateTime startedAtEndExclusive,
int offset,
int limit
) {
return store.values().stream() return store.values().stream()
.filter(entity -> taskType == null || taskType.isBlank() || taskType.equals(entity.getTaskType())) .filter(entity -> taskType == null || taskType.isBlank() || taskType.equals(entity.getTaskType()))
.filter(entity -> status == null || status.isBlank() || status.equals(entity.getStatus())) .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()) .sorted(Comparator.comparing(UpgradeTaskEntity::getTaskId).reversed())
.skip(offset) .skip(offset)
.limit(limit) .limit(limit)
@@ -225,8 +300,8 @@ class UpgradeServiceTest {
} }
@Override @Override
public long countPage(String taskType, String status) { public long countPage(String taskType, String status, LocalDateTime startedAtBegin, LocalDateTime startedAtEndExclusive) {
return findPage(taskType, status, 0, Integer.MAX_VALUE).size(); return findPage(taskType, status, startedAtBegin, startedAtEndExclusive, 0, Integer.MAX_VALUE).size();
} }
@Override @Override