feat:设备配置信息、运行状态、设备画像接口

This commit is contained in:
waner
2026-03-13 17:30:57 +08:00
parent bf8ec7054c
commit 3b4f549dab
24 changed files with 1378 additions and 39 deletions
@@ -201,15 +201,15 @@ public class JnaPcieCryptoService implements PcieCryptoService {
}
@Override
public DeviceStatusResult getDeviceStatus(int statusBufferSize) {
int size = requirePositive("statusBufferSize", statusBufferSize);
public DeviceStatusResult getDeviceStatus() {
int size = 8;
return sessionTemplate.withSession("SDFE_DeviceStatusGet", (lib, deviceHandle, sessionHandle) -> {
Memory statusMemory = new Memory(size);
Memory statusMemory = new Memory(8);
sessionTemplate.ensureSuccess("SDFE_DeviceStatusGet", lib.SDFE_DeviceStatusGet(sessionHandle, statusMemory));
DeviceStatusResult result = new DeviceStatusResult();
result.setLength(size);
// result.setLength(size);
result.setStatusData(statusMemory.getByteArray(0, size));
return result;
});
@@ -106,8 +106,8 @@ public class MockPcieCryptoService implements PcieCryptoService {
}
@Override
public DeviceStatusResult getDeviceStatus(int statusBufferSize) {
int size = requirePositive("statusBufferSize", statusBufferSize);
public DeviceStatusResult getDeviceStatus() {
int size = 8;
DeviceStatusResult result = new DeviceStatusResult();
result.setLength(size);
result.setStatusData(new byte[size]);
@@ -73,7 +73,7 @@ public interface PcieCryptoService {
void deviceSelfTest();
DeviceStatusResult getDeviceStatus(int statusBufferSize);
DeviceStatusResult getDeviceStatus();
void deviceFactoryReset();
@@ -3,6 +3,10 @@ package com.cisd.tms.modules.device.controller;
import com.cisd.tms.common.api.ApiResponse;
import com.cisd.tms.modules.device.dto.DeviceActionResponse;
import com.cisd.tms.modules.device.dto.DeviceInfoResponse;
import com.cisd.tms.modules.device.dto.DeviceProfileResponse;
import com.cisd.tms.modules.device.dto.DeviceRuntimeStatusResponse;
import com.cisd.tms.modules.device.service.DeviceProfileService;
import com.cisd.tms.modules.device.service.DeviceRuntimeStatusService;
import com.cisd.tms.modules.device.service.DeviceService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
@@ -19,9 +23,17 @@ import org.springframework.web.bind.annotation.RestController;
public class DeviceController {
private final DeviceService deviceService;
private final DeviceProfileService deviceProfileService;
private final DeviceRuntimeStatusService deviceRuntimeStatusService;
public DeviceController(DeviceService deviceService) {
public DeviceController(
DeviceService deviceService,
DeviceProfileService deviceProfileService,
DeviceRuntimeStatusService deviceRuntimeStatusService
) {
this.deviceService = deviceService;
this.deviceProfileService = deviceProfileService;
this.deviceRuntimeStatusService = deviceRuntimeStatusService;
}
@GetMapping("/info")
@@ -31,11 +43,23 @@ public class DeviceController {
}
@GetMapping("/status")
@Operation(summary = "查询设备状态", description = "返回当前设备运行状态与版本快照。")
@Operation(summary = "查询设备状态", description = "返回当前设备运行状态、初始化状态和版本快照。")
public ApiResponse<DeviceInfoResponse> status() {
return ApiResponse.success(deviceService.info());
}
@GetMapping("/profile")
@Operation(summary = "查询设备画像", description = "返回设备静态信息,包括设备信息、硬件信息和按初始化方案裁剪后的软件版本信息。")
public ApiResponse<DeviceProfileResponse> profile() {
return ApiResponse.success(deviceProfileService.profile());
}
@GetMapping("/runtime-status")
@Operation(summary = "查询设备运行状态", description = "返回硬件实时资源使用情况、加密卡状态,以及按初始化配置裁剪后的软件运行状态。")
public ApiResponse<DeviceRuntimeStatusResponse> runtimeStatus() {
return ApiResponse.success(deviceRuntimeStatusService.loadRuntimeStatus());
}
@PostMapping("/restart")
@Operation(summary = "重启设备服务", description = "受理设备重启请求,并记录触发操作的内部用户。")
public ApiResponse<DeviceActionResponse> restart(
@@ -17,6 +17,8 @@ public class DeviceInfoResponse {
private String presetVersion;
@Schema(description = "预置来源", example = "application.yml")
private String presetSource;
@Schema(description = "设备初始化状态", example = "INITIALIZED")
private String initState;
public String getDeviceId() {
return deviceId;
@@ -65,4 +67,12 @@ public class DeviceInfoResponse {
public void setPresetSource(String presetSource) {
this.presetSource = presetSource;
}
public String getInitState() {
return initState;
}
public void setInitState(String initState) {
this.initState = initState;
}
}
@@ -5,6 +5,7 @@ import com.cisd.tms.modules.device.dto.DeviceActionResponse;
import com.cisd.tms.modules.device.dto.DeviceInfoResponse;
import com.cisd.tms.modules.device.entity.DeviceNodeEntity;
import com.cisd.tms.modules.device.repository.DeviceNodeRepository;
import com.cisd.tms.modules.init.service.InitService;
import java.time.OffsetDateTime;
import org.springframework.stereotype.Service;
@@ -13,10 +14,16 @@ public class DeviceService {
private final DeviceNodeRepository deviceNodeRepository;
private final CisdPresetProperties cisdPresetProperties;
private final InitService initService;
public DeviceService(DeviceNodeRepository deviceNodeRepository, CisdPresetProperties cisdPresetProperties) {
public DeviceService(
DeviceNodeRepository deviceNodeRepository,
CisdPresetProperties cisdPresetProperties,
InitService initService
) {
this.deviceNodeRepository = deviceNodeRepository;
this.cisdPresetProperties = cisdPresetProperties;
this.initService = initService;
}
public DeviceInfoResponse info() {
@@ -36,6 +43,8 @@ public class DeviceService {
response.setPresetProductType(cisdPresetProperties.getProductType());
response.setPresetVersion(cisdPresetProperties.getVersion());
response.setPresetSource(cisdPresetProperties.getSource());
InitService.DeviceInitStateSnapshot snapshot = initService.getDeviceInitStateSnapshot();
response.setInitState(snapshot.initState());
return response;
}
@@ -50,8 +50,14 @@ public class InitExecutorProperties {
private String standardRunUser = DEFAULT_STANDARD_RUN_USER;
// 标准版 CMSP/CMTP 启动命令
private String standardAppStartCommand = "";
// 标准版 CMSP/CMTP 停止命令
private String standardAppStopCommand = "";
// 标准版 nginx 启动命令
private String standardNginxStartCommand = "";
// 标准版 nginx 停止命令
private String standardNginxStopCommand = "";
// 标准版 RabbitMQ 停止命令
private String standardRabbitStopCommand = "";
// 标准版启动后检查命令
private String standardPostCheckCommand = "";
// 直参版 cfgZipFileId 对应目录下的 cpconfig 文件名
@@ -288,6 +294,14 @@ public class InitExecutorProperties {
this.standardAppStartCommand = standardAppStartCommand;
}
public String getStandardAppStopCommand() {
return standardAppStopCommand;
}
public void setStandardAppStopCommand(String standardAppStopCommand) {
this.standardAppStopCommand = standardAppStopCommand;
}
public String getStandardNginxStartCommand() {
return standardNginxStartCommand;
}
@@ -296,6 +310,22 @@ public class InitExecutorProperties {
this.standardNginxStartCommand = standardNginxStartCommand;
}
public String getStandardNginxStopCommand() {
return standardNginxStopCommand;
}
public void setStandardNginxStopCommand(String standardNginxStopCommand) {
this.standardNginxStopCommand = standardNginxStopCommand;
}
public String getStandardRabbitStopCommand() {
return standardRabbitStopCommand;
}
public void setStandardRabbitStopCommand(String standardRabbitStopCommand) {
this.standardRabbitStopCommand = standardRabbitStopCommand;
}
public String getStandardPostCheckCommand() {
return standardPostCheckCommand;
}
@@ -2,6 +2,7 @@ package com.cisd.tms.modules.init.controller;
import com.cisd.tms.common.api.ApiResponse;
import com.cisd.tms.modules.init.dto.InitCreateTaskResponse;
import com.cisd.tms.modules.init.dto.CurrentInitConfigResponse;
import com.cisd.tms.modules.init.dto.InitPlanTemplateResponse;
import com.cisd.tms.modules.init.dto.InitPreviewRequest;
import com.cisd.tms.modules.init.dto.InitPreviewResponse;
@@ -9,6 +10,8 @@ import com.cisd.tms.modules.init.dto.InitTaskDetailResponse;
import com.cisd.tms.modules.init.dto.InitTaskExecuteResponse;
import com.cisd.tms.modules.init.dto.InitTaskStepLogResponse;
import com.cisd.tms.modules.init.dto.InitTaskStepResponse;
import com.cisd.tms.modules.init.dto.ResetCreateTaskResponse;
import com.cisd.tms.modules.init.dto.ResetPreviewResponse;
import com.cisd.tms.modules.init.service.InitService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
@@ -65,6 +68,12 @@ public class InitController {
return ApiResponse.success(initService.createTask(request));
}
@GetMapping("/config")
@Operation(summary = "查询当前初始化配置", description = "从最新一次成功初始化任务快照中返回当前已初始化的 CISD 核心展示字段,不用于编辑。")
public ApiResponse<CurrentInitConfigResponse> currentConfig() {
return ApiResponse.success(initService.loadCurrentInitConfig());
}
@GetMapping("/tasks/{taskId}")
@Operation(summary = "查询任务详情", description = "返回初始化任务的主状态、产品类型和原始计划快照。")
public ApiResponse<InitTaskDetailResponse> taskDetail(
@@ -109,4 +118,54 @@ public class InitController {
) {
return ApiResponse.success(initService.executeTask(taskId));
}
@PostMapping("/reset/preview")
@Operation(summary = "预检重置请求", description = "自动读取当前设备有效初始化快照,返回重置任务的产品类型与预估步骤数量,不需要前端再次输入机构和 MQ 参数。")
public ApiResponse<ResetPreviewResponse> previewReset() {
return ApiResponse.success(initService.previewReset());
}
@PostMapping("/reset/tasks")
@Operation(summary = "创建重置任务", description = "自动读取当前设备有效初始化快照生成重置任务和步骤记录,任务初始状态为 PENDING。")
public ApiResponse<ResetCreateTaskResponse> createResetTask() {
return ApiResponse.success(initService.createResetTask());
}
@PostMapping("/reset/tasks/{taskId}/execute")
@Operation(summary = "执行重置任务", description = "异步受理重置任务执行请求,立即返回当前任务快照,实际步骤在后台继续运行。")
public ApiResponse<InitTaskExecuteResponse> executeResetTask(
@Parameter(description = "重置任务唯一标识")
@PathVariable String taskId
) {
return ApiResponse.success(initService.executeResetTask(taskId));
}
@GetMapping("/reset/tasks/{taskId}")
@Operation(summary = "查询重置任务详情", description = "返回重置任务的主状态、产品类型和原始计划快照。")
public ApiResponse<InitTaskDetailResponse> resetTaskDetail(
@Parameter(description = "重置任务唯一标识")
@PathVariable String taskId
) {
return ApiResponse.success(initService.getTask(taskId));
}
@GetMapping("/reset/tasks/{taskId}/steps")
@Operation(summary = "查询重置任务步骤", description = "返回重置任务下全部步骤的执行状态和命令信息。")
public ApiResponse<List<InitTaskStepResponse>> resetTaskSteps(
@Parameter(description = "重置任务唯一标识")
@PathVariable String taskId
) {
return ApiResponse.success(initService.getTaskSteps(taskId));
}
@GetMapping("/reset/tasks/{taskId}/steps/{stepNo}/log")
@Operation(summary = "查询重置步骤日志", description = "返回指定重置任务步骤的日志路径和日志内容,用于定位失败原因。")
public ApiResponse<InitTaskStepLogResponse> resetTaskStepLog(
@Parameter(description = "重置任务唯一标识")
@PathVariable String taskId,
@Parameter(description = "任务步骤序号,从 1 开始")
@PathVariable int stepNo
) {
return ApiResponse.success(initService.getTaskStepLog(taskId, stepNo));
}
}
@@ -9,6 +9,8 @@ public class InitCreateTaskResponse {
private String taskId;
@Schema(description = "任务状态", example = "PENDING")
private String status;
@Schema(description = "任务类型", example = "INIT")
private String taskType;
@Schema(description = "解析后的产品类型", example = "STANDARD")
private String resolvedProductType;
@Schema(description = "命中的预制版本", example = "6.6.4")
@@ -30,6 +32,14 @@ public class InitCreateTaskResponse {
this.status = status;
}
public String getTaskType() {
return taskType;
}
public void setTaskType(String taskType) {
this.taskType = taskType;
}
public String getResolvedProductType() {
return resolvedProductType;
}
@@ -9,6 +9,8 @@ public class InitTaskDetailResponse {
private String taskId;
@Schema(description = "任务状态", example = "RUNNING")
private String status;
@Schema(description = "任务类型", example = "INIT")
private String taskType;
@Schema(description = "解析后的产品类型", example = "STANDARD")
private String resolvedProductType;
@Schema(description = "命中的预制版本", example = "6.6.4")
@@ -38,6 +40,14 @@ public class InitTaskDetailResponse {
this.status = status;
}
public String getTaskType() {
return taskType;
}
public void setTaskType(String taskType) {
this.taskType = taskType;
}
public String getResolvedProductType() {
return resolvedProductType;
}
@@ -7,6 +7,7 @@ import com.cisd.tms.infrastructure.persistence.entity.BaseEntity;
public class InitTaskEntity extends BaseEntity {
private String taskId;
private String taskType;
private String version;
private String status;
private String productType;
@@ -23,6 +24,14 @@ public class InitTaskEntity extends BaseEntity {
this.taskId = taskId;
}
public String getTaskType() {
return taskType;
}
public void setTaskType(String taskType) {
this.taskType = taskType;
}
public String getVersion() {
return version;
}
@@ -8,6 +8,8 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.SocketException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
@@ -31,19 +33,34 @@ import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class ConfigurableInitStepExecutor implements InitStepExecutor {
private static final String INTERNAL_PREFIX = "internal::";
private static final String DEFAULT_DB_HOST = "127.0.0.1";
private static final String DEFAULT_DB_PORT = "4000";
private final InitExecutorProperties properties;
private final ObjectMapper objectMapper;
private final String springDatasourceUrl;
public ConfigurableInitStepExecutor(InitExecutorProperties properties, ObjectMapper objectMapper) {
this(properties, objectMapper, "");
}
@Autowired
public ConfigurableInitStepExecutor(
InitExecutorProperties properties,
ObjectMapper objectMapper,
@Value("${spring.datasource.url:}") String springDatasourceUrl
) {
this.properties = properties;
this.objectMapper = objectMapper;
this.springDatasourceUrl = normalize(springDatasourceUrl);
}
@Override
@@ -121,6 +138,15 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor {
case "start_cmsp_cmtp" -> executeStandardStartCommand(task, step, "start_cmsp_cmtp", properties.getStandardAppStartCommand());
case "start_nginx" -> executeStandardStartCommand(task, step, "start_nginx", properties.getStandardNginxStartCommand());
case "post_check" -> executeStandardStartCommand(task, step, "post_check", properties.getStandardPostCheckCommand());
case "stop_apps" -> executeStandardStartCommand(task, step, "stop_apps", properties.getStandardAppStopCommand());
case "stop_nginx" -> executeStandardStartCommand(task, step, "stop_nginx", properties.getStandardNginxStopCommand());
case "db_drop_cmep" -> executeResetDropDatabase(task, step, "CMEP");
case "db_drop_org" -> executeResetDropDatabase(task, step, null);
case "mq_cleanup" -> executeResetMqCleanup(task, step);
case "stop_rabbitmq" -> executeResetStopRabbitmq(task, step);
case "tlq_license_cleanup" -> executeResetTlqLicenseCleanup(task, step);
case "clean_staging" -> executeResetCleanStaging(task, step);
case "verify_reset" -> executeVerifyReset(task, step);
case "direct_db_init" -> executeDirectDbInit(task, step);
case "tlq_init_cfg" -> executeTlqInitCfg(task, step);
case "cfmq_apply" -> executeCfmqApply(task, step);
@@ -906,7 +932,7 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor {
Arrays.asList("/bin/zsh", "-lc", applyCommand),
false,
loadDir,
buildStandardDbApplyEnvironment(loadDir)
buildStandardDbApplyEnvironment(loadDir, context)
);
if (code != 0) {
return InitStepExecutionResult.failure("db_apply failed", code, logFile.toString());
@@ -1052,6 +1078,151 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor {
}
}
private InitStepExecutionResult executeResetDropDatabase(
InitTaskEntity task,
InitTaskStepEntity step,
String fixedDatabaseName
) throws IOException {
PlanContext context = loadPlanContext(task);
if ("DIRECT".equals(context.productType())) {
String logPath = writeInternalLog(task, step, "reset db drop skipped for direct product type");
return InitStepExecutionResult.success("reset db drop skipped", 0, logPath);
}
String databaseName = isBlank(fixedDatabaseName) ? normalize(context.orgCode()) : fixedDatabaseName;
String identifier = requireMysqlIdentifier(databaseName);
if (properties.getMode() == InitExecutorProperties.Mode.SIMULATE) {
String logPath = writeInternalLog(task, step, "reset db drop simulated, database=" + identifier);
return InitStepExecutionResult.success("reset db drop simulated", 0, logPath);
}
Path logFile = prepareLogPath(task, step);
String command = "DB_HOST=\"${DB_HOST:-127.0.0.1}\" DB_PORT=\"${DB_PORT:-4000}\" "
+ "mysql -h\"${DB_HOST}\" -P\"${DB_PORT}\" -u\"${DB_USER}\" -p\"${DB_PASSWORD}\" "
+ "-e \"DROP DATABASE IF EXISTS \\`" + identifier + "\\`;\"";
try {
int code = runCommand(
logFile,
Arrays.asList("/bin/zsh", "-lc", command),
false,
null,
buildStandardDbApplyEnvironment(null, context)
);
if (code != 0) {
return InitStepExecutionResult.failure("reset db drop failed", code, logFile.toString());
}
return InitStepExecutionResult.success("reset db dropped: " + identifier, 0, logFile.toString());
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
return InitStepExecutionResult.failure("reset db drop interrupted", -1, logFile.toString());
}
}
private InitStepExecutionResult executeResetMqCleanup(InitTaskEntity task, InitTaskStepEntity step) throws IOException {
PlanContext context = loadPlanContext(task);
if (!shouldApplyStandardRabbitmq(context)) {
String logPath = writeInternalLog(task, step, "mq_cleanup skipped for current mode");
return InitStepExecutionResult.success("mq_cleanup skipped", 0, logPath);
}
if (properties.getMode() == InitExecutorProperties.Mode.SIMULATE) {
String logPath = writeInternalLog(task, step, "mq_cleanup simulated");
return InitStepExecutionResult.success("mq_cleanup simulated", 0, logPath);
}
if (isBlank(context.channelUsername())) {
String logPath = writeInternalLog(task, step, "mq_cleanup failed: channel username is blank");
return InitStepExecutionResult.failure("mq_cleanup failed: channel username is blank", -1, logPath);
}
Path logFile = prepareLogPath(task, step);
String ctl = resolveRabbitmqCtlCommand();
String vhost = firstNonBlank(properties.getRabbitVhost(), "/RQ");
String user = normalize(context.channelUsername());
try {
if (!waitRabbitmqReady(logFile, ctl)) {
appendLog(logFile, "rabbitmqctl status check failed, continue with cleanup flow");
}
for (String queue : buildStandardCustomerDefaultVhostQueues(normalize(context.orgCode()))) {
runCommand(logFile, Arrays.asList(ctl, "delete_queue", queue), true);
}
for (String queue : buildStandardRqVhostQueues(normalize(context.orgCode()))) {
runCommand(logFile, Arrays.asList(ctl, "delete_queue", "-p", vhost, queue), true);
}
runCommand(logFile, Arrays.asList(ctl, "clear_permissions", "-p", vhost, user), true);
runCommand(logFile, Arrays.asList(ctl, "delete_user", user), true);
return InitStepExecutionResult.success("mq cleanup applied", 0, logFile.toString());
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
return InitStepExecutionResult.failure("mq cleanup interrupted", -1, logFile.toString());
}
}
private InitStepExecutionResult executeResetStopRabbitmq(InitTaskEntity task, InitTaskStepEntity step) throws IOException {
PlanContext context = loadPlanContext(task);
if (!shouldApplyStandardRabbitmq(context)) {
String logPath = writeInternalLog(task, step, "stop_rabbitmq skipped for current mode");
return InitStepExecutionResult.success("stop_rabbitmq skipped", 0, logPath);
}
return executeStandardStartCommand(task, step, "stop_rabbitmq", properties.getStandardRabbitStopCommand());
}
private InitStepExecutionResult executeResetTlqLicenseCleanup(InitTaskEntity task, InitTaskStepEntity step) throws IOException {
PlanContext context = loadPlanContext(task);
if (!"TLQ".equals(context.mqType()) && !"RABBITMQ_TLQ".equals(context.mqType())) {
String logPath = writeInternalLog(task, step, "tlq_license_cleanup skipped for non-TLQ mode");
return InitStepExecutionResult.success("tlq_license_cleanup skipped", 0, logPath);
}
Path target = Paths.get(resolveTlqLicenseTargetPath(context.productType())).normalize().toAbsolutePath();
Path logFile = prepareLogPath(task, step);
if (properties.getMode() == InitExecutorProperties.Mode.SIMULATE) {
appendLog(logFile, "tlq license cleanup simulated: " + target);
return InitStepExecutionResult.success("tlq license cleanup simulated", 0, logFile.toString());
}
Files.deleteIfExists(target);
appendLog(logFile, "tlq license deleted if exists: " + target);
return InitStepExecutionResult.success("tlq license cleanup applied", 0, logFile.toString());
}
private InitStepExecutionResult executeResetCleanStaging(InitTaskEntity task, InitTaskStepEntity step) throws IOException {
Path stageDir = Paths.get(properties.getStagingRootDir()).resolve(task.getTaskId()).normalize().toAbsolutePath();
Path logFile = prepareLogPath(task, step);
if (properties.getMode() == InitExecutorProperties.Mode.SIMULATE) {
appendLog(logFile, "clean_staging simulated: " + stageDir);
return InitStepExecutionResult.success("clean_staging simulated", 0, logFile.toString());
}
deleteRecursively(stageDir);
appendLog(logFile, "clean_staging removed: " + stageDir);
return InitStepExecutionResult.success("clean_staging applied", 0, logFile.toString());
}
private InitStepExecutionResult executeVerifyReset(InitTaskEntity task, InitTaskStepEntity step) throws IOException {
PlanContext context = loadPlanContext(task);
if (properties.getMode() == InitExecutorProperties.Mode.SIMULATE) {
String logPath = writeInternalLog(task, step, "verify_reset simulated");
return InitStepExecutionResult.success("verify_reset simulated", 0, logPath);
}
Path logFile = prepareLogPath(task, step);
List<String> failures = new ArrayList<>();
verifyProcessAbsent(logFile, properties.getStandardHomeDir() + "/cmsp", "cmsp", failures);
verifyProcessAbsent(logFile, properties.getStandardHomeDir() + "/cmtp", "cmtp", failures);
if (shouldApplyStandardRabbitmq(context)) {
verifyProcessAbsent(logFile, "rabbitmq-server", "rabbitmq", failures);
}
verifyDatabaseAbsent(logFile, "CMEP", context, failures);
verifyDatabaseAbsent(logFile, normalize(context.orgCode()), context, failures);
verifyMqCleanup(logFile, context, failures);
verifyTlqLicenseCleanup(context, failures);
if (!failures.isEmpty()) {
String detail = String.join("; ", failures);
appendLog(logFile, "verify_reset failed: " + detail);
return InitStepExecutionResult.failure("verify_reset failed: " + detail, -1, logFile.toString());
}
appendLog(logFile, "verify_reset success");
return InitStepExecutionResult.success("verify_reset success", 0, logFile.toString());
}
private InitStepExecutionResult executeTlqInitCfg(InitTaskEntity task, InitTaskStepEntity step) throws IOException {
PlanContext context = loadPlanContext(task);
if (!"DIRECT".equals(context.productType()) || !"RABBITMQ_TLQ".equals(context.mqType())) {
@@ -1633,7 +1804,7 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor {
return firstNonBlank(properties.getStandardTlqLicenseTargetPath(), "/home/tlq/tlqconf.zip");
}
private List<String> buildStandardDefaultVhostQueues(String orgCode) {
private List<String> buildStandardCustomerDefaultVhostQueues(String orgCode) {
List<String> queues = new ArrayList<>();
for (String ci : List.of("CI01", "CI02")) {
queues.add("LQ_DTTRSP_IB01_" + ci + "_" + orgCode);
@@ -1646,8 +1817,6 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor {
}
queues.add("LQ_SYN_IB00_CI00_" + orgCode);
queues.add("LQ_SYNRSP_IB00_CI00_" + orgCode);
queues.add("LQ_MSGRSP_GUI");
queues.add("LQ_SYNRSP_GUI");
return queues;
}
@@ -1710,8 +1879,11 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor {
return exitCode;
}
private Map<String, String> buildStandardDbApplyEnvironment(Path loadDir) {
private Map<String, String> buildStandardDbApplyEnvironment(Path loadDir, PlanContext context) {
Map<String, String> environment = new LinkedHashMap<>();
JdbcEndpoint endpoint = resolveStandardDbEndpoint();
environment.put("DB_HOST", endpoint.host());
environment.put("DB_PORT", endpoint.port());
if (!isBlank(properties.getStandardDbUsername())) {
environment.put("DB_USER", normalize(properties.getStandardDbUsername()));
}
@@ -1721,12 +1893,39 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor {
if (loadDir != null) {
environment.put("LOAD_DIR", loadDir.toAbsolutePath().normalize().toString());
}
if (context != null && !isBlank(context.orgCode())) {
environment.put("ORG_CODE", normalize(context.orgCode()));
}
if (context != null && !isBlank(context.orgNameCn())) {
environment.put("ORG_NAME_CN", normalize(context.orgNameCn()));
}
return environment;
}
private JdbcEndpoint resolveStandardDbEndpoint() {
if (isBlank(springDatasourceUrl)) {
return new JdbcEndpoint(DEFAULT_DB_HOST, DEFAULT_DB_PORT);
}
String candidate = normalize(springDatasourceUrl);
if (candidate.startsWith("jdbc:")) {
candidate = candidate.substring("jdbc:".length());
}
try {
URI uri = new URI(candidate);
String host = normalize(uri.getHost());
int port = uri.getPort();
if (isBlank(host)) {
return new JdbcEndpoint(DEFAULT_DB_HOST, DEFAULT_DB_PORT);
}
return new JdbcEndpoint(host, port > 0 ? String.valueOf(port) : DEFAULT_DB_PORT);
} catch (URISyntaxException ex) {
return new JdbcEndpoint(DEFAULT_DB_HOST, DEFAULT_DB_PORT);
}
}
private Map<String, String> buildStandardStartEnvironment(String stepName) {
Map<String, String> environment = new LinkedHashMap<>();
if ("start_cmsp_cmtp".equals(stepName)) {
if ("start_cmsp_cmtp".equals(stepName) || "stop_apps".equals(stepName)) {
environment.put("STANDARD_HOME_DIR", normalize(properties.getStandardHomeDir()));
environment.put("STANDARD_RUN_USER", normalize(properties.getStandardRunUser()));
}
@@ -1840,12 +2039,12 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor {
return new PlanContext(
firstNonBlank(normalizeUpper(text(root, "productType")), normalizeUpper(task.getProductType())),
normalizeUpper(text(request, "orgCodeType")),
text(request, "orgCode"),
firstNonBlank(text(request, "orgCode"), text(root, "orgCode")),
text(request, "orgNameCn"),
text(request, "orgNameEn"),
normalizeUpper(text(request, "deployMode")),
normalizeUpper(text(mq, "mqType")),
text(mq, "channelUsername"),
firstNonBlank(normalizeUpper(text(mq, "mqType")), firstNonBlank(normalizeUpper(text(request, "mqType")), normalizeUpper(text(root, "mqType")))),
firstNonBlank(text(mq, "channelUsername"), text(request, "channelUsername")),
text(mq, "channelPassword"),
text(nodes, "node01Ip"),
text(nodes, "node02Ip"),
@@ -1931,6 +2130,93 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor {
return "'" + escaped + "'";
}
private void verifyProcessAbsent(Path logFile, String pattern, String label, List<String> failures) throws IOException {
try {
int code = runCommand(
logFile,
Arrays.asList("/bin/zsh", "-lc", "pgrep -f " + quoteShellValue(pattern) + " >/dev/null"),
true
);
if (code == 0) {
failures.add(label + " process still running");
}
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
failures.add(label + " process verification interrupted");
}
}
private void verifyDatabaseAbsent(Path logFile, String databaseName, PlanContext context, List<String> failures) throws IOException {
if (isBlank(databaseName)) {
return;
}
String identifier = requireMysqlIdentifier(databaseName);
String command = "DB_HOST=\"${DB_HOST:-127.0.0.1}\" DB_PORT=\"${DB_PORT:-4000}\" "
+ "mysql -h\"${DB_HOST}\" -P\"${DB_PORT}\" -u\"${DB_USER}\" -p\"${DB_PASSWORD}\" -Nse "
+ quoteShellValue("SHOW DATABASES LIKE '" + identifier + "';")
+ " | grep -q .";
try {
int code = runCommand(
logFile,
Arrays.asList("/bin/zsh", "-lc", command),
true,
null,
buildStandardDbApplyEnvironment(null, context)
);
if (code == 0) {
failures.add("database still exists: " + identifier);
}
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
failures.add("database verification interrupted: " + identifier);
}
}
private void verifyMqCleanup(Path logFile, PlanContext context, List<String> failures) throws IOException {
if (!shouldApplyStandardRabbitmq(context) || isBlank(context.channelUsername())) {
return;
}
String ctl = resolveRabbitmqCtlCommand();
String vhost = firstNonBlank(properties.getRabbitVhost(), "/RQ");
verifyRabbitObjectAbsent(logFile, ctl + " list_users | awk 'NR>1 {print $1}' | grep -Fx " + quoteShellValue(context.channelUsername()) + " >/dev/null", "rabbit user still exists", failures);
for (String queue : buildStandardCustomerDefaultVhostQueues(normalize(context.orgCode()))) {
verifyRabbitObjectAbsent(logFile, ctl + " list_queues name | awk 'NR>1 {print $1}' | grep -Fx " + quoteShellValue(queue) + " >/dev/null", "default vhost queue still exists: " + queue, failures);
}
for (String queue : buildStandardRqVhostQueues(normalize(context.orgCode()))) {
verifyRabbitObjectAbsent(logFile, ctl + " list_queues -p " + quoteShellValue(vhost) + " name | awk 'NR>1 {print $1}' | grep -Fx " + quoteShellValue(queue) + " >/dev/null", "RQ vhost queue still exists: " + queue, failures);
}
}
private void verifyRabbitObjectAbsent(Path logFile, String shellCommand, String message, List<String> failures) throws IOException {
try {
int code = runCommand(logFile, Arrays.asList("/bin/zsh", "-lc", shellCommand), true);
if (code == 0) {
failures.add(message);
}
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
failures.add(message + " (verification interrupted)");
}
}
private void verifyTlqLicenseCleanup(PlanContext context, List<String> failures) {
if (!"TLQ".equals(context.mqType()) && !"RABBITMQ_TLQ".equals(context.mqType())) {
return;
}
Path target = Paths.get(resolveTlqLicenseTargetPath(context.productType())).normalize().toAbsolutePath();
if (Files.exists(target)) {
failures.add("tlq license still exists: " + target);
}
}
private String requireMysqlIdentifier(String databaseName) {
String normalized = normalize(databaseName);
if (!normalized.matches("[A-Za-z0-9_]+")) {
throw new IllegalArgumentException("invalid database identifier: " + databaseName);
}
return normalized;
}
private static void requireNotBlank(Map<String, String> configMap, String key, List<String> failures) {
if (isBlank(unquote(configMap.get(key)))) {
failures.add(key + " is blank");
@@ -1978,6 +2264,9 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor {
) {
}
private record JdbcEndpoint(String host, String port) {
}
private record ResolvedCpconfig(Path path, List<Path> candidates) {
}
@@ -9,4 +9,10 @@ import org.apache.ibatis.annotations.Param;
public interface InitTaskMapper extends BaseMapperX<InitTaskEntity> {
InitTaskEntity selectByTaskId(@Param("taskId") String taskId);
InitTaskEntity selectLatest();
InitTaskEntity selectLatestByStatus(@Param("status") String status);
InitTaskEntity selectLatestByTaskTypeAndStatus(@Param("taskType") String taskType, @Param("status") String status);
}
@@ -7,6 +7,12 @@ public interface InitTaskRepository {
Optional<InitTaskEntity> findByTaskId(String taskId);
Optional<InitTaskEntity> findLatest();
Optional<InitTaskEntity> findLatestByStatus(String status);
Optional<InitTaskEntity> findLatestByTaskTypeAndStatus(String taskType, String status);
void save(InitTaskEntity entity);
void update(InitTaskEntity entity);
@@ -20,6 +20,21 @@ public class InitTaskRepositoryImpl implements InitTaskRepository {
return Optional.ofNullable(initTaskMapper.selectByTaskId(taskId));
}
@Override
public Optional<InitTaskEntity> findLatest() {
return Optional.ofNullable(initTaskMapper.selectLatest());
}
@Override
public Optional<InitTaskEntity> findLatestByStatus(String status) {
return Optional.ofNullable(initTaskMapper.selectLatestByStatus(status));
}
@Override
public Optional<InitTaskEntity> findLatestByTaskTypeAndStatus(String taskType, String status) {
return Optional.ofNullable(initTaskMapper.selectLatestByTaskTypeAndStatus(taskType, status));
}
@Override
public void save(InitTaskEntity entity) {
initTaskMapper.insert(entity);
@@ -6,6 +6,7 @@ import com.cisd.tms.common.exception.BizException;
import com.cisd.tms.modules.init.config.InitCommandProfileService;
import com.cisd.tms.modules.init.config.InitExecutorProperties;
import com.cisd.tms.modules.init.dto.InitCreateTaskResponse;
import com.cisd.tms.modules.init.dto.CurrentInitConfigResponse;
import com.cisd.tms.modules.init.dto.InitPlanTemplateResponse;
import com.cisd.tms.modules.init.dto.InitPreviewRequest;
import com.cisd.tms.modules.init.dto.InitPreviewResponse;
@@ -13,6 +14,8 @@ import com.cisd.tms.modules.init.dto.InitTaskDetailResponse;
import com.cisd.tms.modules.init.dto.InitTaskExecuteResponse;
import com.cisd.tms.modules.init.dto.InitTaskStepLogResponse;
import com.cisd.tms.modules.init.dto.InitTaskStepResponse;
import com.cisd.tms.modules.init.dto.ResetCreateTaskResponse;
import com.cisd.tms.modules.init.dto.ResetPreviewResponse;
import com.cisd.tms.modules.init.entity.InitTaskEntity;
import com.cisd.tms.modules.init.entity.InitTaskStepEntity;
import com.cisd.tms.modules.init.executor.InitStepExecutionResult;
@@ -20,6 +23,7 @@ import com.cisd.tms.modules.init.executor.InitStepExecutor;
import com.cisd.tms.modules.init.repository.InitTaskRepository;
import com.cisd.tms.modules.init.repository.InitTaskStepRepository;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.nio.file.Files;
@@ -27,14 +31,7 @@ import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@@ -47,6 +44,8 @@ import org.springframework.stereotype.Service;
@Service
public class InitService {
private static final String TASK_TYPE_INIT = "INIT";
private static final String TASK_TYPE_RESET = "RESET";
private static final Set<String> PRESET_PRODUCT_TYPES = Set.of("ENTERPRISE", "INDIRECT", "DIRECT");
private static final Set<String> DEPLOY_STANDARD = Set.of("SINGLE", "DUAL");
private static final Set<String> DEPLOY_DIRECT = Set.of("SINGLE", "DUAL", "QUAD");
@@ -145,6 +144,18 @@ public class InitService {
return response;
}
public ResetPreviewResponse previewReset() {
ResolvedResetContext context = resolveCurrentResetContext();
String productType = context.productType();
ResetPreviewResponse response = new ResetPreviewResponse();
response.setResolvedProductType(productType);
response.setPresetVersion(cisdPresetProperties.getVersion());
response.setStepCount(resetSteps(productType, context.mqType()).size());
response.setSummary("reset preview generated from current initialized state");
return response;
}
public InitCreateTaskResponse createTask(InitPreviewRequest request) {
if (request == null) {
throw new IllegalArgumentException("request must not be null");
@@ -154,7 +165,7 @@ public class InitService {
validateCommon(request);
validateByProductType(productType, request);
String taskId = buildTaskId();
String taskId = buildTaskId(TASK_TYPE_INIT);
String normalizedMqType = normalizeUpper(request.getMq().getMqType());
String deployMode = normalizeUpper(request.getDeployMode());
LocalDateTime now = LocalDateTime.now();
@@ -164,6 +175,7 @@ public class InitService {
InitTaskEntity entity = new InitTaskEntity();
entity.setTaskId(taskId);
entity.setTaskType(TASK_TYPE_INIT);
entity.setVersion(cisdPresetProperties.getVersion());
entity.setStatus("PENDING");
entity.setProductType(productType);
@@ -198,17 +210,98 @@ public class InitService {
InitCreateTaskResponse response = new InitCreateTaskResponse();
response.setTaskId(taskId);
response.setStatus(entity.getStatus());
response.setTaskType(entity.getTaskType());
response.setResolvedProductType(productType);
response.setPresetVersion(cisdPresetProperties.getVersion());
return response;
}
public ResetCreateTaskResponse createResetTask() {
ResolvedResetContext context = resolveCurrentResetContext();
String productType = context.productType();
String taskId = buildTaskId(TASK_TYPE_RESET);
String normalizedMqType = context.mqType();
LocalDateTime now = LocalDateTime.now();
List<String> stepCodes = resetSteps(productType, normalizedMqType);
InitTaskEntity entity = new InitTaskEntity();
entity.setTaskId(taskId);
entity.setTaskType(TASK_TYPE_RESET);
entity.setVersion(cisdPresetProperties.getVersion());
entity.setStatus("PENDING");
entity.setProductType(productType);
entity.setOrgCode(context.orgCode());
entity.setMqType(normalizedMqType);
entity.setDeployMode("");
entity.setCreateTime(now);
entity.setUpdateTime(now);
entity.setInitPlanJson(generateResetPlanJson(context, stepCodes));
initTaskRepository.save(entity);
List<InitTaskStepEntity> stepEntities = new ArrayList<>();
for (int i = 0; i < stepCodes.size(); i++) {
String stepCode = stepCodes.get(i);
InitTaskStepEntity step = new InitTaskStepEntity();
step.setTaskId(taskId);
step.setStepNo(i + 1);
step.setStepCode(stepCode);
step.setStatus("PENDING");
step.setMessage("waiting");
step.setCommandLine(initCommandProfileService.resolveCommand(productType, normalizedMqType, stepCode));
step.setCreateTime(now);
step.setUpdateTime(now);
stepEntities.add(step);
}
initTaskStepRepository.saveBatch(stepEntities);
ResetCreateTaskResponse response = new ResetCreateTaskResponse();
response.setTaskId(taskId);
response.setStatus(entity.getStatus());
response.setTaskType(entity.getTaskType());
response.setResolvedProductType(productType);
response.setPresetVersion(cisdPresetProperties.getVersion());
return response;
}
public DeviceInitStateSnapshot getDeviceInitStateSnapshot() {
Optional<InitTaskEntity> running = initTaskRepository.findLatestByStatus("RUNNING");
if (running.isPresent()) {
InitTaskEntity task = running.get();
String taskType = normalize(task.getTaskType());
if (TASK_TYPE_RESET.equals(taskType)) {
return new DeviceInitStateSnapshot("RESETTING", TASK_TYPE_RESET, task.getStatus(), null, null);
}
return new DeviceInitStateSnapshot("INITIALIZING", TASK_TYPE_INIT, task.getStatus(), null, null);
}
Optional<InitTaskEntity> latestInit = initTaskRepository.findLatestByTaskTypeAndStatus(TASK_TYPE_INIT, "SUCCESS");
Optional<InitTaskEntity> latestReset = initTaskRepository.findLatestByTaskTypeAndStatus(TASK_TYPE_RESET, "SUCCESS");
if (latestInit.isEmpty()) {
return new DeviceInitStateSnapshot("UNINITIALIZED", null, null, null, null);
}
if (latestReset.isPresent() && compareTaskRecency(latestReset.get(), latestInit.get()) >= 0) {
return new DeviceInitStateSnapshot("UNINITIALIZED", TASK_TYPE_RESET, latestReset.get().getStatus(), null, null);
}
ResolvedResetContext context = resolveResetContextFromInitTask(latestInit.get());
return new DeviceInitStateSnapshot("INITIALIZED", TASK_TYPE_INIT, latestInit.get().getStatus(), context.orgCode(), context.mqType());
}
public CurrentInitConfigResponse loadCurrentInitConfig() {
Optional<InitTaskEntity> latestInit = initTaskRepository.findLatestByTaskTypeAndStatus(TASK_TYPE_INIT, "SUCCESS");
if (latestInit.isEmpty()) {
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "device is not initialized");
}
return resolveCurrentInitConfigFromTask(latestInit.get());
}
public InitTaskDetailResponse getTask(String taskId) {
InitTaskEntity entity = requireTask(taskId);
InitTaskDetailResponse response = new InitTaskDetailResponse();
response.setTaskId(entity.getTaskId());
response.setStatus(entity.getStatus());
response.setTaskType(normalize(entity.getTaskType()).isBlank() ? TASK_TYPE_INIT : entity.getTaskType());
response.setResolvedProductType(entity.getProductType());
response.setPresetVersion(entity.getVersion());
response.setOrgCode(entity.getOrgCode());
@@ -270,6 +363,19 @@ public class InitService {
}
public InitTaskExecuteResponse executeTask(String taskId) {
return executeTaskInternal(taskId);
}
public InitTaskExecuteResponse executeResetTask(String taskId) {
InitTaskEntity task = requireTask(taskId);
String taskType = normalize(task.getTaskType());
if (!TASK_TYPE_RESET.equals(taskType)) {
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "reset task not found: " + taskId);
}
return executeTaskInternal(taskId);
}
private InitTaskExecuteResponse executeTaskInternal(String taskId) {
InitTaskEntity task = requireTask(taskId);
List<InitTaskStepEntity> steps = initTaskStepRepository.findByTaskIdOrderByStepNo(taskId);
if (steps.isEmpty()) {
@@ -403,6 +509,29 @@ public class InitService {
}
}
private String generateResetPlanJson(ResolvedResetContext context, List<String> steps) {
Map<String, Object> root = new LinkedHashMap<>();
root.put("schemaVersion", "1.0");
root.put("taskType", TASK_TYPE_RESET);
root.put("productType", context.productType());
root.put("orgCode", context.orgCode());
root.put("mqType", context.mqType());
root.put("sourceTaskId", context.sourceTaskId());
Map<String, Object> request = new LinkedHashMap<>();
request.put("orgCode", context.orgCode());
request.put("mqType", context.mqType());
request.put("channelUsername", context.channelUsername());
root.put("request", request);
root.put("steps", steps);
root.put("generatedAt", LocalDateTime.now().toString());
try {
return objectMapper.writeValueAsString(root);
} catch (JsonProcessingException ex) {
throw new BizException(ErrorCode.INTERNAL_ERROR.getCode(), "failed to serialize reset plan");
}
}
private List<String> defaultSteps(String productType, String mqType) {
// 该步骤序列只描述“逻辑编排”,具体脚本命令在 step-command-profiles.json 中定义
if ("DIRECT".equals(productType)) {
@@ -467,10 +596,27 @@ public class InitService {
);
}
private String buildTaskId() {
private List<String> resetSteps(String productType, String mqType) {
if ("DIRECT".equals(productType)) {
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "reset is not supported for current product type: " + productType);
}
return List.of(
"STOP_APPS",
"DB_DROP_CMEP",
"DB_DROP_ORG",
"MQ_CLEANUP",
"STOP_RABBITMQ",
"TLQ_LICENSE_CLEANUP",
"CLEAN_STAGING",
"VERIFY_RESET"
);
}
private String buildTaskId(String taskType) {
String ts = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
String suffix = UUID.randomUUID().toString().replace("-", "").substring(0, 6).toUpperCase(Locale.ROOT);
return "INIT-" + ts + "-" + suffix;
String prefix = TASK_TYPE_RESET.equals(taskType) ? TASK_TYPE_RESET : TASK_TYPE_INIT;
return prefix + "-" + ts + "-" + suffix;
}
private String resolvePresetProductType() {
@@ -489,6 +635,14 @@ public class InitService {
return productType;
}
private String resolveSupportedResetProductType() {
String productType = resolvePresetProductType();
if ("DIRECT".equals(productType)) {
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "reset is not supported for current product type: " + productType);
}
return productType;
}
private void validateCommon(InitPreviewRequest request) {
if (request.getNodes() == null) {
throw new IllegalArgumentException("nodes is required");
@@ -526,6 +680,100 @@ public class InitService {
validateStandard(productType, request, deployMode, mqType);
}
private ResolvedResetContext resolveCurrentResetContext() {
String productType = resolveSupportedResetProductType();
Optional<InitTaskEntity> latestInit = initTaskRepository.findLatestByTaskTypeAndStatus(TASK_TYPE_INIT, "SUCCESS");
if (latestInit.isEmpty()) {
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "device is not initialized, reset is forbidden");
}
Optional<InitTaskEntity> latestReset = initTaskRepository.findLatestByTaskTypeAndStatus(TASK_TYPE_RESET, "SUCCESS");
if (latestReset.isPresent() && compareTaskRecency(latestReset.get(), latestInit.get()) >= 0) {
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "device is not initialized, reset is forbidden");
}
ResolvedResetContext context = resolveResetContextFromInitTask(latestInit.get());
if (!productType.equals(context.productType())) {
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "device preset product type changed, reset context is invalid");
}
requireAllowed("mqType", context.mqType(), resolveStandardMqAllowed(productType));
if (("RABBITMQ".equals(context.mqType()) || "RABBITMQ_TLQ".equals(context.mqType())) && isBlank(context.channelUsername())) {
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "current init context missing rabbitmq channel username");
}
return context;
}
private ResolvedResetContext resolveResetContextFromInitTask(InitTaskEntity task) {
if (task == null) {
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "successful init task is missing");
}
if (isBlank(task.getInitPlanJson())) {
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "successful init task snapshot is missing");
}
try {
JsonNode root = objectMapper.readTree(task.getInitPlanJson());
JsonNode request = root.path("request");
JsonNode mq = request.path("mq");
String productType = firstNonBlank(normalizeUpper(text(root, "productType")), normalizeUpper(task.getProductType()));
String orgCode = firstNonBlank(text(request, "orgCode"), text(root, "orgCode"), task.getOrgCode());
String mqType = firstNonBlank(normalizeUpper(text(mq, "mqType")), normalizeUpper(text(root, "mqType")), normalizeUpper(task.getMqType()));
String channelUsername = firstNonBlank(text(mq, "channelUsername"), text(request, "channelUsername"));
if (isBlank(productType) || isBlank(orgCode) || isBlank(mqType)) {
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "successful init task snapshot is incomplete");
}
return new ResolvedResetContext(productType, normalize(orgCode), normalizeUpper(mqType), normalize(channelUsername), task.getTaskId());
} catch (JsonProcessingException ex) {
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "failed to parse successful init task snapshot");
}
}
private CurrentInitConfigResponse resolveCurrentInitConfigFromTask(InitTaskEntity task) {
if (task == null) {
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "successful init task is missing");
}
if (isBlank(task.getInitPlanJson())) {
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "successful init task snapshot is missing");
}
try {
JsonNode root = objectMapper.readTree(task.getInitPlanJson());
JsonNode request = root.path("request");
JsonNode mq = request.path("mq");
JsonNode nodes = request.path("nodes");
JsonNode signServer = request.path("signServer");
CurrentInitConfigResponse response = new CurrentInitConfigResponse();
response.setOrgCodeType(firstNonBlank(normalizeUpper(text(request, "orgCodeType")), normalizeUpper(text(root, "orgCodeType"))));
response.setOrgCode(firstNonBlank(text(request, "orgCode"), text(root, "orgCode"), task.getOrgCode()));
response.setOrgNameCn(firstNonBlank(text(request, "orgNameCn"), text(root, "orgNameCn")));
response.setOrgNameEn(firstNonBlank(text(request, "orgNameEn"), text(root, "orgNameEn")));
response.setProductType(firstNonBlank(normalizeUpper(text(root, "productType")), normalizeUpper(task.getProductType())));
response.setDeployMode(firstNonBlank(normalizeUpper(text(request, "deployMode")), normalizeUpper(task.getDeployMode())));
response.setMqType(firstNonBlank(normalizeUpper(text(mq, "mqType")), normalizeUpper(text(root, "mqType")), normalizeUpper(task.getMqType())));
response.setChannelUsername(firstNonBlank(text(mq, "channelUsername"), text(request, "channelUsername")));
response.setNode01Ip(firstNonBlank(text(nodes, "node01Ip"), text(request, "node01Ip")));
response.setNode02Ip(firstNonBlank(text(nodes, "node02Ip"), text(request, "node02Ip")));
response.setSignHost(firstNonBlank(text(signServer, "signHost"), text(request, "signHost")));
response.setSignPort(integerValue(firstNonBlank(text(signServer, "signPort"), text(request, "signPort"))));
response.setSignType(firstNonBlank(text(signServer, "signType"), text(request, "signType")));
return response;
} catch (JsonProcessingException ex) {
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "failed to parse successful init task snapshot");
}
}
private int compareTaskRecency(InitTaskEntity left, InitTaskEntity right) {
Comparator<LocalDateTime> comparator = Comparator.nullsFirst(LocalDateTime::compareTo);
int byUpdate = comparator.compare(left.getUpdateTime(), right.getUpdateTime());
if (byUpdate != 0) {
return byUpdate;
}
int byCreate = comparator.compare(left.getCreateTime(), right.getCreateTime());
if (byCreate != 0) {
return byCreate;
}
return normalize(left.getTaskId()).compareTo(normalize(right.getTaskId()));
}
private void validateStandard(String productType, InitPreviewRequest request, String deployMode, String mqType) {
requireAllowed("deployMode", deployMode, DEPLOY_STANDARD);
requireAllowed("mq.mqType", mqType, resolveStandardMqAllowed(productType));
@@ -628,6 +876,32 @@ public class InitService {
}
}
private static String firstNonBlank(String... values) {
if (values == null) {
return "";
}
for (String value : values) {
if (!isBlank(value)) {
return normalize(value);
}
}
return "";
}
private static String text(JsonNode node, String fieldName) {
if (node == null || fieldName == null || node.isMissingNode() || node.isNull()) {
return "";
}
JsonNode child = node.path(fieldName);
if (child.isMissingNode() || child.isNull()) {
return "";
}
if (child.isTextual()) {
return normalize(child.asText());
}
return normalize(child.toString());
}
private static String normalizeUpper(String value) {
return normalize(value).toUpperCase(Locale.ROOT);
}
@@ -636,7 +910,37 @@ public class InitService {
return value == null ? "" : value.trim();
}
private static Integer integerValue(String value) {
String normalized = normalize(value);
if (isBlank(normalized)) {
return null;
}
try {
return Integer.valueOf(normalized);
} catch (NumberFormatException ex) {
return null;
}
}
private static boolean isBlank(String value) {
return value == null || value.isBlank();
}
public record DeviceInitStateSnapshot(
String initState,
String currentTaskType,
String currentTaskStatus,
String initializedOrgCode,
String initializedMqType
) {
}
private record ResolvedResetContext(
String productType,
String orgCode,
String mqType,
String channelUsername,
String sourceTaskId
) {
}
}
+18
View File
@@ -84,8 +84,14 @@ tms:
standard-run-user: ${TMS_INIT_EXECUTOR_STANDARD_RUN_USER:cmep4i}
# 标准版 CMSP/CMTP 启动命令。
standard-app-start-command: ${TMS_INIT_EXECUTOR_STANDARD_APP_START_COMMAND:/home/tms/bin/start_standard_apps.sh}
# 标准版 CMSP/CMTP 停止命令。
standard-app-stop-command: ${TMS_INIT_EXECUTOR_STANDARD_APP_STOP_COMMAND:/home/tms/bin/stop_standard_apps.sh}
# 标准版 nginx 启动命令。
standard-nginx-start-command: ${TMS_INIT_EXECUTOR_STANDARD_NGINX_START_COMMAND:/home/tms/bin/start_standard_nginx.sh}
# 标准版 nginx 停止命令。
standard-nginx-stop-command: ${TMS_INIT_EXECUTOR_STANDARD_NGINX_STOP_COMMAND:/home/tms/bin/stop_standard_nginx.sh}
# 标准版 RabbitMQ 停止命令。
standard-rabbit-stop-command: ${TMS_INIT_EXECUTOR_STANDARD_RABBIT_STOP_COMMAND:/home/tms/bin/stop_standard_rabbitmq.sh}
# 标准版启动后检查命令。
standard-post-check-command: ${TMS_INIT_EXECUTOR_STANDARD_POST_CHECK_COMMAND:/home/tms/bin/check_standard_runtime.sh}
# 直参版 cpconfig 文件名(按 upload-base-dir/<cfgZipFileId>/<fileName> 解析)。
@@ -149,6 +155,18 @@ tms:
version: ${TMS_CISD_PRESET_VERSION:V1.0.0}
# 预制信息来源标识(CONFIG/DB 等自定义值)。
source: ${TMS_CISD_PRESET_SOURCE:CONFIG}
device:
profile:
# 设备型号展示值。
model: ${TMS_DEVICE_PROFILE_MODEL:SYD7108}
# 设备名称展示值。
name: ${TMS_DEVICE_PROFILE_NAME:跨境支付终端一体机}
# CPU 型号默认展示值;命令读取失败或为空时回退。
cpu-model-default: ${TMS_DEVICE_PROFILE_CPU_MODEL_DEFAULT:Hygon C86-3G (OPN:3350)}
# 内存总量默认展示值;命令读取失败或为空时回退。
memory-total-default: ${TMS_DEVICE_PROFILE_MEMORY_TOTAL_DEFAULT:16GB}
# 磁盘总量默认展示值;命令读取失败或为空时回退。
disk-total-default: ${TMS_DEVICE_PROFILE_DISK_TOTAL_DEFAULT:256G}
security:
# 内部接口鉴权 token(前后端内部调用需一致)。
internal-token: ${TMS_INTERNAL_TOKEN:change-me-internal-token}
@@ -7,6 +7,7 @@
<resultMap id="InitTaskResultMap" type="com.cisd.tms.modules.init.entity.InitTaskEntity">
<id property="id" column="id"/>
<result property="taskId" column="task_id"/>
<result property="taskType" column="task_type"/>
<result property="version" column="version"/>
<result property="status" column="status"/>
<result property="productType" column="product_type"/>
@@ -19,10 +20,34 @@
</resultMap>
<select id="selectByTaskId" parameterType="string" resultMap="InitTaskResultMap">
SELECT id, task_id, version, status, product_type, org_code, mq_type, deploy_mode, init_plan_json, create_time, update_time
SELECT id, task_id, task_type, version, status, product_type, org_code, mq_type, deploy_mode, init_plan_json, create_time, update_time
FROM tms_init_task
WHERE task_id = #{taskId}
LIMIT 1
</select>
<select id="selectLatest" resultMap="InitTaskResultMap">
SELECT id, task_id, task_type, version, status, product_type, org_code, mq_type, deploy_mode, init_plan_json, create_time, update_time
FROM tms_init_task
ORDER BY create_time DESC, id DESC
LIMIT 1
</select>
<select id="selectLatestByStatus" parameterType="string" resultMap="InitTaskResultMap">
SELECT id, task_id, task_type, version, status, product_type, org_code, mq_type, deploy_mode, init_plan_json, create_time, update_time
FROM tms_init_task
WHERE status = #{status}
ORDER BY create_time DESC, id DESC
LIMIT 1
</select>
<select id="selectLatestByTaskTypeAndStatus" resultMap="InitTaskResultMap">
SELECT id, task_id, task_type, version, status, product_type, org_code, mq_type, deploy_mode, init_plan_json, create_time, update_time
FROM tms_init_task
WHERE task_type = #{taskType}
AND status = #{status}
ORDER BY create_time DESC, id DESC
LIMIT 1
</select>
</mapper>