From 312391052f43dd1ff2646a3d0aa88c3670d2bea9 Mon Sep 17 00:00:00 2001 From: waner Date: Thu, 16 Apr 2026 10:15:24 +0800 Subject: [PATCH] =?UTF-8?q?fix=EF=BC=9A=E8=AE=BE=E5=A4=87=E4=BF=A1?= =?UTF-8?q?=E6=81=AF=E5=B1=95=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/GlobalExceptionHandler.java | 7 ++ .../controller/CryptoCardController.java | 2 +- .../mapper/DeviceSoftwareVersionMapper.java | 3 + .../DeviceSoftwareVersionRepository.java | 3 + .../DeviceSoftwareVersionRepositoryImpl.java | 6 + .../impl/DeviceRuntimeStatusServiceImpl.java | 114 ++++++------------ .../service/impl/DeviceServiceImpl.java | 21 +++- .../support/DefaultSoftwareRuntimeProbe.java | 101 +++++++++++++--- .../openapi/OpenSignController.java | 41 ------- .../device/DeviceSoftwareVersionMapper.xml | 6 + .../exception/GlobalExceptionHandlerTest.java | 17 +++ .../service/DeviceProfileServiceTest.java | 5 + .../DeviceRuntimeStatusServiceTest.java | 50 ++++++-- .../device/service/DeviceServiceTest.java | 5 + .../DefaultSoftwareRuntimeProbeTest.java | 69 +++++++++++ .../executor/UpgradeTaskRunnerTest.java | 7 ++ .../service/UpgradePackageServiceTest.java | 7 ++ 17 files changed, 317 insertions(+), 147 deletions(-) delete mode 100644 src/main/java/com/cisd/tms/modules/sign/controller/openapi/OpenSignController.java create mode 100644 src/test/java/com/cisd/tms/modules/device/support/DefaultSoftwareRuntimeProbeTest.java diff --git a/src/main/java/com/cisd/tms/common/exception/GlobalExceptionHandler.java b/src/main/java/com/cisd/tms/common/exception/GlobalExceptionHandler.java index 85c1de1..fd3f87f 100644 --- a/src/main/java/com/cisd/tms/common/exception/GlobalExceptionHandler.java +++ b/src/main/java/com/cisd/tms/common/exception/GlobalExceptionHandler.java @@ -13,6 +13,7 @@ import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.multipart.MaxUploadSizeExceededException; +import org.springframework.web.multipart.MultipartException; @RestControllerAdvice public class GlobalExceptionHandler { @@ -53,6 +54,12 @@ public class GlobalExceptionHandler { .withPath(request.getRequestURI()); } + @ExceptionHandler(MultipartException.class) + public ApiResponse handleMultipartException(MultipartException ex, HttpServletRequest request) { + return ApiResponse.fail(ErrorCode.VALIDATE_FAILED.getCode(), "failed to parse multipart request") + .withPath(request.getRequestURI()); + } + @ExceptionHandler(PcieCryptoException.class) public ApiResponse handleCryptoCardException(PcieCryptoException ex, HttpServletRequest request) { return ApiResponse.fail(ErrorCode.CRYPTO_CARD_ERROR.getCode(), ex.getMessage()) diff --git a/src/main/java/com/cisd/tms/modules/device/controller/CryptoCardController.java b/src/main/java/com/cisd/tms/modules/device/controller/CryptoCardController.java index ad3e31e..e1e5b7d 100644 --- a/src/main/java/com/cisd/tms/modules/device/controller/CryptoCardController.java +++ b/src/main/java/com/cisd/tms/modules/device/controller/CryptoCardController.java @@ -61,7 +61,7 @@ import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/api/v1/device/crypto") @RequiredArgsConstructor -@ReplayProtected +//@ReplayProtected @Tag(name = "密码卡调试", description = "密码卡设备调试、密钥操作和文件管理接口") public class CryptoCardController { diff --git a/src/main/java/com/cisd/tms/modules/device/mapper/DeviceSoftwareVersionMapper.java b/src/main/java/com/cisd/tms/modules/device/mapper/DeviceSoftwareVersionMapper.java index 136ed05..eb18d6b 100644 --- a/src/main/java/com/cisd/tms/modules/device/mapper/DeviceSoftwareVersionMapper.java +++ b/src/main/java/com/cisd/tms/modules/device/mapper/DeviceSoftwareVersionMapper.java @@ -2,6 +2,7 @@ package com.cisd.tms.modules.device.mapper; import com.cisd.tms.infrastructure.persistence.mapper.BaseMapperX; import com.cisd.tms.modules.device.entity.DeviceSoftwareVersionEntity; +import java.util.List; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; @@ -10,5 +11,7 @@ public interface DeviceSoftwareVersionMapper extends BaseMapperX selectAll(); + int updateByComponentCode(DeviceSoftwareVersionEntity entity); } diff --git a/src/main/java/com/cisd/tms/modules/device/repository/DeviceSoftwareVersionRepository.java b/src/main/java/com/cisd/tms/modules/device/repository/DeviceSoftwareVersionRepository.java index 4fdcc89..8cab239 100644 --- a/src/main/java/com/cisd/tms/modules/device/repository/DeviceSoftwareVersionRepository.java +++ b/src/main/java/com/cisd/tms/modules/device/repository/DeviceSoftwareVersionRepository.java @@ -1,11 +1,14 @@ package com.cisd.tms.modules.device.repository; import com.cisd.tms.modules.device.entity.DeviceSoftwareVersionEntity; +import java.util.List; import java.util.Optional; public interface DeviceSoftwareVersionRepository { Optional findByComponentCode(String componentCode); + List findAll(); + void saveOrUpdate(DeviceSoftwareVersionEntity entity); } diff --git a/src/main/java/com/cisd/tms/modules/device/repository/impl/DeviceSoftwareVersionRepositoryImpl.java b/src/main/java/com/cisd/tms/modules/device/repository/impl/DeviceSoftwareVersionRepositoryImpl.java index 479ae6b..111e861 100644 --- a/src/main/java/com/cisd/tms/modules/device/repository/impl/DeviceSoftwareVersionRepositoryImpl.java +++ b/src/main/java/com/cisd/tms/modules/device/repository/impl/DeviceSoftwareVersionRepositoryImpl.java @@ -3,6 +3,7 @@ package com.cisd.tms.modules.device.repository.impl; import com.cisd.tms.modules.device.entity.DeviceSoftwareVersionEntity; import com.cisd.tms.modules.device.mapper.DeviceSoftwareVersionMapper; import com.cisd.tms.modules.device.repository.DeviceSoftwareVersionRepository; +import java.util.List; import java.util.Optional; import org.springframework.stereotype.Repository; @@ -20,6 +21,11 @@ public class DeviceSoftwareVersionRepositoryImpl implements DeviceSoftwareVersio return Optional.ofNullable(deviceSoftwareVersionMapper.selectByComponentCode(componentCode)); } + @Override + public List findAll() { + return deviceSoftwareVersionMapper.selectAll(); + } + @Override public void saveOrUpdate(DeviceSoftwareVersionEntity entity) { if (findByComponentCode(entity.getComponentCode()).isPresent()) { diff --git a/src/main/java/com/cisd/tms/modules/device/service/impl/DeviceRuntimeStatusServiceImpl.java b/src/main/java/com/cisd/tms/modules/device/service/impl/DeviceRuntimeStatusServiceImpl.java index 3b099da..3a130db 100644 --- a/src/main/java/com/cisd/tms/modules/device/service/impl/DeviceRuntimeStatusServiceImpl.java +++ b/src/main/java/com/cisd/tms/modules/device/service/impl/DeviceRuntimeStatusServiceImpl.java @@ -1,7 +1,6 @@ package com.cisd.tms.modules.device.service.impl; import com.cisd.tms.common.config.properties.CisdPresetProperties; -import com.cisd.tms.common.exception.BizException; import com.cisd.tms.integration.crypto.pcie.service.PcieCryptoService; import com.cisd.tms.modules.device.dto.CryptoCardRuntimeStatus; import com.cisd.tms.modules.device.dto.DeviceRuntimeStatusResponse; @@ -15,12 +14,10 @@ import com.cisd.tms.modules.device.support.HardwareStatusProbe; import com.cisd.tms.modules.device.support.SoftwareRuntimeProbe; import com.cisd.tms.modules.device.support.SystemSoftwareVersionProbe; import com.cisd.tms.modules.device.support.UsageSnapshot; -import com.cisd.tms.modules.init.dto.CurrentInitConfigResponse; import com.cisd.tms.modules.init.service.InitService; import java.time.OffsetDateTime; import java.util.ArrayList; import java.util.List; -import java.util.Optional; import java.util.function.BooleanSupplier; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -31,7 +28,6 @@ public class DeviceRuntimeStatusServiceImpl implements DeviceRuntimeStatusServic private static final String STATUS_NORMAL = "NORMAL"; private static final String STATUS_ABNORMAL = "ABNORMAL"; - private static final String STATUS_NOT_INSTALLED = "NOT_INSTALLED"; private static final String STATUS_UNKNOWN = "UNKNOWN"; private final InitService initService; @@ -104,52 +100,35 @@ public class DeviceRuntimeStatusServiceImpl implements DeviceRuntimeStatusServic } private List buildSoftwareStatuses() { - CurrentInitConfigResponse config = loadCurrentInitConfigIfPresent(); - boolean initialized = config != null; - String productType = config == null ? normalize(cisdPresetProperties.getProductType()) : normalize(config.getProductType()); - String mqType = config == null ? "" : normalize(config.getMqType()); - + List versions = deviceSoftwareVersionRepository.findAll(); List items = new ArrayList<>(); - items.add(buildSoftwareItem("RABBITMQ", "消息队列", cached("RABBITMQ"), initialized && isRabbitmqEnabled(mqType), softwareRuntimeProbe::isRabbitmqRunning)); - items.add(buildSoftwareItem("TLQ", "消息队列", cached("TLQ"), initialized && isTlqEnabled(mqType), softwareRuntimeProbe::isTlqRunning)); - items.add(buildSoftwareItem("TIDB", "数据库", cached("TIDB"), initialized && isTidbEnabled(productType), softwareRuntimeProbe::isTidbRunning)); - items.add(buildSoftwareItem("NGINX", "Nginx", cached("NGINX"), initialized && isNginxEnabled(productType), softwareRuntimeProbe::isNginxRunning)); - items.add(buildReceiverItem(productType, initialized)); - items.add(buildSoftwareItem("TMS", "设备管理软件", firstNonBlank(cached("TMS"), systemSoftwareVersionProbe.tmsVersion(), cisdPresetProperties.getVersion()), true, softwareRuntimeProbe::isTmsRunning)); + for (DeviceSoftwareVersionEntity version : versions) { + items.add(buildSoftwareItem(version)); + } return items; } - private SoftwareRuntimeStatusItem buildReceiverItem(String productType, boolean initialized) { - boolean standard = "ENTERPRISE".equals(productType) || "INDIRECT".equals(productType); - BooleanSupplier supplier = standard ? softwareRuntimeProbe::isStandardAppRunning : softwareRuntimeProbe::isDirectAppRunning; - String name = "标准收发器"; - String displayName = firstNonBlank(cached("RECEIVER"), appName(productType)); - return buildSoftwareItem("RECEIVER", name, displayName, initialized, supplier); - } - - private SoftwareRuntimeStatusItem buildSoftwareItem( - String code, - String name, - String displayName, - boolean enabled, - BooleanSupplier probe - ) { + private SoftwareRuntimeStatusItem buildSoftwareItem(DeviceSoftwareVersionEntity version) { + String code = normalize(version.getComponentCode()); + String name = firstNonBlank(version.getComponentName(), code); + String displayName = displayName(version, code, name); SoftwareRuntimeStatusItem item = new SoftwareRuntimeStatusItem(); item.setComponentCode(code); item.setComponentName(name); item.setDisplayName(firstNonBlank(displayName, name)); - if (!enabled) { - item.setStatus(STATUS_NOT_INSTALLED); - item.setMessage("未安装"); + BooleanSupplier probe = probeFor(code); + if (probe == null) { + item.setStatus(STATUS_UNKNOWN); + item.setMessage("未知"); return item; } try { if (probe.getAsBoolean()) { item.setStatus(STATUS_NORMAL); - item.setMessage("正常"); + item.setMessage("运行中"); } else { item.setStatus(STATUS_ABNORMAL); - item.setMessage("异常"); + item.setMessage("未运行"); } } catch (RuntimeException ex) { item.setStatus(STATUS_UNKNOWN); @@ -158,47 +137,6 @@ public class DeviceRuntimeStatusServiceImpl implements DeviceRuntimeStatusServic return item; } - private CurrentInitConfigResponse loadCurrentInitConfigIfPresent() { - try { - if (!"INITIALIZED".equals(initService.getDeviceInitStateSnapshot().initState())) { - return null; - } - return initService.loadCurrentInitConfig(); - } catch (BizException ex) { - return null; - } - } - - private boolean isRabbitmqEnabled(String mqType) { - return "RABBITMQ".equals(mqType) || "RABBITMQ_TLQ".equals(mqType) || "RABBITMQ_CFMQ".equals(mqType); - } - - private boolean isTlqEnabled(String mqType) { - return "TLQ".equals(mqType) || "RABBITMQ_TLQ".equals(mqType); - } - - private boolean isTidbEnabled(String productType) { - return "ENTERPRISE".equals(productType) || "INDIRECT".equals(productType); - } - - private boolean isNginxEnabled(String productType) { - return "ENTERPRISE".equals(productType) || "INDIRECT".equals(productType); - } - - private String cached(String componentCode) { - Optional entity = deviceSoftwareVersionRepository.findByComponentCode(componentCode); - return entity.map(DeviceSoftwareVersionEntity::getCurrentVersion).orElse(""); - } - - private String appName(String productType) { - return switch (productType) { - case "ENTERPRISE" -> "标准收发器企业版"; - case "INDIRECT" -> "标准收发器间参版"; - case "DIRECT" -> "直参轻量化终端版"; - default -> "应用软件"; - }; - } - private String normalize(String value) { return value == null ? "" : value.trim().toUpperCase(); } @@ -219,6 +157,30 @@ public class DeviceRuntimeStatusServiceImpl implements DeviceRuntimeStatusServic return ""; } + private String displayName(DeviceSoftwareVersionEntity version, String code, String name) { + if ("TMS".equals(code)) { + return firstNonBlank(version.getCurrentVersion(), systemSoftwareVersionProbe.tmsVersion(), cisdPresetProperties.getVersion(), name); + } + return firstNonBlank(version.getCurrentVersion(), name); + } + + private BooleanSupplier probeFor(String componentCode) { + return switch (componentCode) { + case "RABBITMQ" -> softwareRuntimeProbe::isRabbitmqRunning; + case "TLQ" -> softwareRuntimeProbe::isTlqRunning; + case "TIDB" -> softwareRuntimeProbe::isTidbRunning; + case "NGINX" -> softwareRuntimeProbe::isNginxRunning; + case "RECEIVER" -> receiverProbe(); + case "TMS" -> softwareRuntimeProbe::isTmsRunning; + default -> null; + }; + } + + private BooleanSupplier receiverProbe() { + String productType = normalize(cisdPresetProperties.getProductType()); + return "DIRECT".equals(productType) ? softwareRuntimeProbe::isDirectAppRunning : softwareRuntimeProbe::isStandardAppRunning; + } + @FunctionalInterface private interface UsageSupplier { UsageSnapshot get(); diff --git a/src/main/java/com/cisd/tms/modules/device/service/impl/DeviceServiceImpl.java b/src/main/java/com/cisd/tms/modules/device/service/impl/DeviceServiceImpl.java index 4ceeca6..f65c4a4 100644 --- a/src/main/java/com/cisd/tms/modules/device/service/impl/DeviceServiceImpl.java +++ b/src/main/java/com/cisd/tms/modules/device/service/impl/DeviceServiceImpl.java @@ -13,10 +13,13 @@ import com.cisd.tms.modules.init.service.InitService; import java.time.OffsetDateTime; import java.util.Optional; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import org.springframework.util.StopWatch; @Service @RequiredArgsConstructor +@Slf4j public class DeviceServiceImpl implements DeviceService { private final DeviceProfileProperties deviceProfileProperties; @@ -26,14 +29,27 @@ public class DeviceServiceImpl implements DeviceService { @Override public DeviceInfoResponse info() { + StopWatch stopWatch = new StopWatch(); + stopWatch.start("1"); DeviceInfoResponse response = new DeviceInfoResponse(); response.setDeviceModel(trim(deviceProfileProperties.getModel())); response.setDeviceName(trim(deviceProfileProperties.getName())); response.setVersion(loadTmsVersion()); + stopWatch.stop(); + + stopWatch.start("2"); response.setSerialNumber(loadDeviceSerial()); + stopWatch.stop(); + + stopWatch.start("3"); response.setMasterKeyStatus(loadManagementKeyReady()); + stopWatch.stop(); + + stopWatch.start("4"); InitService.DeviceInitStateSnapshot snapshot = initService.getDeviceInitStateSnapshot(); response.setDeviceStatus(snapshot.initState()); + stopWatch.stop(); + log.info(stopWatch.prettyPrint()); return response; } @@ -63,8 +79,9 @@ public class DeviceServiceImpl implements DeviceService { private boolean loadManagementKeyReady() { try { - DeviceStatusResult result = pcieCryptoService.getDeviceStatus(); - return result != null && result.getFsmState() == 0; +// DeviceStatusResult result = pcieCryptoService.getDeviceStatus(); +// return result != null && result.getFsmState() == 0; + return pcieCryptoService.checkLmk(); } catch (RuntimeException ex) { return false; } diff --git a/src/main/java/com/cisd/tms/modules/device/support/DefaultSoftwareRuntimeProbe.java b/src/main/java/com/cisd/tms/modules/device/support/DefaultSoftwareRuntimeProbe.java index 1d5c47d..85dd92e 100644 --- a/src/main/java/com/cisd/tms/modules/device/support/DefaultSoftwareRuntimeProbe.java +++ b/src/main/java/com/cisd/tms/modules/device/support/DefaultSoftwareRuntimeProbe.java @@ -1,10 +1,41 @@ package com.cisd.tms.modules.device.support; +import java.io.IOException; +import java.time.Duration; +import java.util.List; +import java.util.Objects; +import java.util.function.LongSupplier; +import java.util.function.Supplier; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; import org.springframework.stereotype.Component; @Component public class DefaultSoftwareRuntimeProbe implements SoftwareRuntimeProbe { + private static final Duration DEFAULT_CACHE_TTL = Duration.ofSeconds(1); + + private final Supplier> processSnapshotLoader; + private final Duration cacheTtl; + private final LongSupplier nanoTimeSupplier; + + private final Object lock = new Object(); + private volatile CachedProcessSnapshot cachedSnapshot; + + public DefaultSoftwareRuntimeProbe() { + this(DefaultSoftwareRuntimeProbe::loadProcessSnapshot, DEFAULT_CACHE_TTL, System::nanoTime); + } + + DefaultSoftwareRuntimeProbe( + Supplier> processSnapshotLoader, + Duration cacheTtl, + LongSupplier nanoTimeSupplier + ) { + this.processSnapshotLoader = Objects.requireNonNull(processSnapshotLoader, "processSnapshotLoader"); + this.cacheTtl = Objects.requireNonNull(cacheTtl, "cacheTtl"); + this.nanoTimeSupplier = Objects.requireNonNull(nanoTimeSupplier, "nanoTimeSupplier"); + } + @Override public boolean isRabbitmqRunning() { return isRunning("rabbitmq-server"); @@ -41,22 +72,64 @@ public class DefaultSoftwareRuntimeProbe implements SoftwareRuntimeProbe { } private boolean isRunning(String pattern) { - Process process; try { - process = new ProcessBuilder("bash", "-lc", "pgrep -f '" + pattern + "' >/dev/null").start(); - int exitCode = process.waitFor(); - if (exitCode == 0) { - return true; - } - if (exitCode == 1) { - return false; - } - throw new IllegalStateException("pgrep failed for pattern: " + pattern); - } catch (InterruptedException ex) { - Thread.currentThread().interrupt(); - throw new IllegalStateException("process probe interrupted: " + pattern, ex); - } catch (Exception ex) { + Pattern compiled = Pattern.compile(pattern); + return processSnapshot().stream().anyMatch(commandLine -> compiled.matcher(commandLine).find()); + } catch (PatternSyntaxException ex) { + throw new IllegalStateException("invalid process probe pattern: " + pattern, ex); + } catch (RuntimeException ex) { throw new IllegalStateException("process probe failed: " + pattern, ex); } } + + private List processSnapshot() { + long now = nanoTimeSupplier.getAsLong(); + CachedProcessSnapshot cached = cachedSnapshot; + if (isUsable(cached, now)) { + return cached.commandLines(); + } + + synchronized (lock) { + now = nanoTimeSupplier.getAsLong(); + cached = cachedSnapshot; + if (isUsable(cached, now)) { + return cached.commandLines(); + } + List commandLines = List.copyOf(processSnapshotLoader.get()); + cachedSnapshot = new CachedProcessSnapshot(commandLines, now); + return commandLines; + } + } + + private boolean isUsable(CachedProcessSnapshot cached, long now) { + if (cached == null) { + return false; + } + long ttlNanos = cacheTtl.toNanos(); + return ttlNanos <= 0 || now - cached.loadedAtNanos() < ttlNanos; + } + + private static List loadProcessSnapshot() { + Process process; + try { + process = new ProcessBuilder("bash", "-lc", "ps -eo args=").start(); + int exitCode = process.waitFor(); + List commandLines = new String(process.getInputStream().readAllBytes()).lines() + .map(String::trim) + .filter(line -> !line.isBlank()) + .toList(); + if (exitCode != 0) { + throw new IllegalStateException("ps command failed"); + } + return commandLines; + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + throw new IllegalStateException("process snapshot interrupted", ex); + } catch (IOException ex) { + throw new IllegalStateException("process snapshot failed", ex); + } + } + + private record CachedProcessSnapshot(List commandLines, long loadedAtNanos) { + } } diff --git a/src/main/java/com/cisd/tms/modules/sign/controller/openapi/OpenSignController.java b/src/main/java/com/cisd/tms/modules/sign/controller/openapi/OpenSignController.java deleted file mode 100644 index 3500f4f..0000000 --- a/src/main/java/com/cisd/tms/modules/sign/controller/openapi/OpenSignController.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.cisd.tms.modules.sign.controller.openapi; - -import com.cisd.tms.modules.sign.dto.openapi.OpenSignRequest; -import com.cisd.tms.modules.sign.dto.openapi.OpenSignResponse; -import com.cisd.tms.modules.sign.service.SignService; -import com.cisd.tms.modules.sign.support.SignCommand; -import com.cisd.tms.modules.sign.support.SignResult; -import jakarta.validation.Valid; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@RequestMapping("/openapi/v1/sign") -public class OpenSignController { - - private final SignService signService; - - public OpenSignController(SignService signService) { - this.signService = signService; - } - - @PostMapping("/signature") - public OpenSignResponse signature(@Valid @RequestBody OpenSignRequest request) { - SignCommand command = new SignCommand(); - command.setRequestId(request.getRequestId()); - command.setAlgorithm(request.getAlgorithm()); - command.setPlainText(request.getPayload()); - - SignResult result = signService.sign(command); - - OpenSignResponse response = new OpenSignResponse(); - response.setRequestId(result.getRequestId()); - response.setAlgorithm(result.getAlgorithm()); - response.setDigestHex(result.getDigestHex()); - response.setSignature(result.getSignature()); - response.setSignedAt(result.getSignedAt().toString()); - return response; - } -} diff --git a/src/main/resources/mapper/device/DeviceSoftwareVersionMapper.xml b/src/main/resources/mapper/device/DeviceSoftwareVersionMapper.xml index 2ed322b..977d7d7 100644 --- a/src/main/resources/mapper/device/DeviceSoftwareVersionMapper.xml +++ b/src/main/resources/mapper/device/DeviceSoftwareVersionMapper.xml @@ -23,6 +23,12 @@ LIMIT 1 + + UPDATE tms_device_software_version SET component_name = #{componentName}, diff --git a/src/test/java/com/cisd/tms/common/exception/GlobalExceptionHandlerTest.java b/src/test/java/com/cisd/tms/common/exception/GlobalExceptionHandlerTest.java index fd47f10..2179d60 100644 --- a/src/test/java/com/cisd/tms/common/exception/GlobalExceptionHandlerTest.java +++ b/src/test/java/com/cisd/tms/common/exception/GlobalExceptionHandlerTest.java @@ -6,6 +6,7 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.web.multipart.MaxUploadSizeExceededException; +import org.springframework.web.multipart.MultipartException; class GlobalExceptionHandlerTest { @@ -24,4 +25,20 @@ class GlobalExceptionHandlerTest { Assertions.assertEquals("upload file size exceeds limit", response.getMsg()); Assertions.assertEquals("/api/v1/upgrade-packages", response.getPath()); } + + @Test + void shouldHandleMultipartParseFailureAsValidationFailure() { + GlobalExceptionHandler handler = new GlobalExceptionHandler(); + MockHttpServletRequest request = new MockHttpServletRequest("POST", "/api/v1/files/upload"); + + ApiResponse response = handler.handleMultipartException( + new MultipartException("Failed to parse multipart servlet request"), + request + ); + + Assertions.assertFalse(response.isSuccess()); + Assertions.assertEquals(ErrorCode.VALIDATE_FAILED.getCode(), response.getCode()); + Assertions.assertEquals("failed to parse multipart request", response.getMsg()); + Assertions.assertEquals("/api/v1/files/upload", response.getPath()); + } } diff --git a/src/test/java/com/cisd/tms/modules/device/service/DeviceProfileServiceTest.java b/src/test/java/com/cisd/tms/modules/device/service/DeviceProfileServiceTest.java index 790219e..bbc06be 100644 --- a/src/test/java/com/cisd/tms/modules/device/service/DeviceProfileServiceTest.java +++ b/src/test/java/com/cisd/tms/modules/device/service/DeviceProfileServiceTest.java @@ -391,6 +391,11 @@ class DeviceProfileServiceTest { return Optional.ofNullable(data.get(componentCode)); } + @Override + public List findAll() { + return new ArrayList<>(data.values()); + } + @Override public void saveOrUpdate(DeviceSoftwareVersionEntity entity) { data.put(entity.getComponentCode(), entity); diff --git a/src/test/java/com/cisd/tms/modules/device/service/DeviceRuntimeStatusServiceTest.java b/src/test/java/com/cisd/tms/modules/device/service/DeviceRuntimeStatusServiceTest.java index 470aac3..944813e 100644 --- a/src/test/java/com/cisd/tms/modules/device/service/DeviceRuntimeStatusServiceTest.java +++ b/src/test/java/com/cisd/tms/modules/device/service/DeviceRuntimeStatusServiceTest.java @@ -29,6 +29,7 @@ import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Optional; @@ -45,10 +46,10 @@ class DeviceRuntimeStatusServiceTest { InMemoryDeviceSoftwareVersionRepository versions = new InMemoryDeviceSoftwareVersionRepository(); versions.save(component("RABBITMQ", "消息队列", "RabbitMQ V3.8.21")); - versions.save(component("TLQ", "消息队列", "tonglink/Q V8.1.11")); versions.save(component("TIDB", "数据库", "TiDB V8.1.0")); versions.save(component("NGINX", "Nginx", "Nginx V1.20")); versions.save(component("RECEIVER", "标准收发器企业版", "标准收发器企业版 V1.3.2")); + versions.save(component("TMS", "设备管理软件", "SYD TMS V1.00")); DeviceRuntimeStatusService service = new DeviceRuntimeStatusServiceImpl( initService, @@ -75,39 +76,50 @@ class DeviceRuntimeStatusServiceTest { Assertions.assertEquals("NORMAL", response.getHardware().getCryptoCard().getStatus()); assertSoftwareStatus(response.getSoftware(), "RABBITMQ", "NORMAL"); - assertSoftwareStatus(response.getSoftware(), "TLQ", "NOT_INSTALLED"); assertSoftwareStatus(response.getSoftware(), "TIDB", "NORMAL"); assertSoftwareStatus(response.getSoftware(), "NGINX", "ABNORMAL"); assertSoftwareStatus(response.getSoftware(), "RECEIVER", "NORMAL"); assertSoftwareStatus(response.getSoftware(), "TMS", "NORMAL"); + assertSoftwareAbsent(response.getSoftware(), "TLQ"); } @Test - void shouldMarkNonInitializedSoftwareAsNotInstalled() { + void shouldReturnOnlyComponentsThatExistInVersionTable() { + InMemoryDeviceSoftwareVersionRepository versions = new InMemoryDeviceSoftwareVersionRepository(); + versions.save(component("RABBITMQ", "消息队列", "RabbitMQ V3.8.21")); + versions.save(component("TMS", "设备管理软件", "SYD TMS V1.00")); + DeviceRuntimeStatusService service = new DeviceRuntimeStatusServiceImpl( newInitService("ENTERPRISE"), cryptoService(), hardwareProbe(10.0, 8.0, 16.0, 100.0, 256.0), softwareVersionProbe("SYD TMS V1.00"), - softwareRuntimeProbe(Map.of("TMS", true)), - new InMemoryDeviceSoftwareVersionRepository(), + softwareRuntimeProbe(Map.of("RABBITMQ", false, "TMS", true)), + versions, preset("ENTERPRISE") ); DeviceRuntimeStatusResponse response = service.loadRuntimeStatus(); - assertSoftwareStatus(response.getSoftware(), "RABBITMQ", "NOT_INSTALLED"); - assertSoftwareStatus(response.getSoftware(), "TLQ", "NOT_INSTALLED"); - assertSoftwareStatus(response.getSoftware(), "TIDB", "NOT_INSTALLED"); - assertSoftwareStatus(response.getSoftware(), "NGINX", "NOT_INSTALLED"); - assertSoftwareStatus(response.getSoftware(), "RECEIVER", "NOT_INSTALLED"); + Assertions.assertEquals(2, response.getSoftware().size()); + assertSoftwareStatus(response.getSoftware(), "RABBITMQ", "ABNORMAL"); assertSoftwareStatus(response.getSoftware(), "TMS", "NORMAL"); + assertSoftwareAbsent(response.getSoftware(), "TLQ"); + assertSoftwareAbsent(response.getSoftware(), "TIDB"); + assertSoftwareAbsent(response.getSoftware(), "NGINX"); + assertSoftwareAbsent(response.getSoftware(), "RECEIVER"); } @Test void shouldReturnUnknownWhenProbeFails() { InitService initService = newInitService("INDIRECT"); createSuccessfulInitTask(initService, validInitRequest("RABBITMQ_TLQ")); + InMemoryDeviceSoftwareVersionRepository versions = new InMemoryDeviceSoftwareVersionRepository(); + versions.save(component("RABBITMQ", "消息队列", "RabbitMQ V3.8.21")); + versions.save(component("TIDB", "数据库", "TiDB V8.1.0")); + versions.save(component("NGINX", "Nginx", "Nginx V1.20")); + versions.save(component("RECEIVER", "标准收发器间参版", "标准收发器间参版 V1.3.2")); + versions.save(component("TMS", "设备管理软件", "SYD TMS V1.00")); DeviceRuntimeStatusService service = new DeviceRuntimeStatusServiceImpl( initService, @@ -115,7 +127,7 @@ class DeviceRuntimeStatusServiceTest { failingHardwareProbe(), softwareVersionProbe("SYD TMS V1.00"), failingSoftwareRuntimeProbe(), - new InMemoryDeviceSoftwareVersionRepository(), + versions, preset("INDIRECT") ); @@ -127,11 +139,11 @@ class DeviceRuntimeStatusServiceTest { Assertions.assertEquals("ABNORMAL", response.getHardware().getCryptoCard().getStatus()); assertSoftwareStatus(response.getSoftware(), "RABBITMQ", "UNKNOWN"); - assertSoftwareStatus(response.getSoftware(), "TLQ", "UNKNOWN"); assertSoftwareStatus(response.getSoftware(), "TIDB", "UNKNOWN"); assertSoftwareStatus(response.getSoftware(), "NGINX", "UNKNOWN"); assertSoftwareStatus(response.getSoftware(), "RECEIVER", "UNKNOWN"); assertSoftwareStatus(response.getSoftware(), "TMS", "UNKNOWN"); + assertSoftwareAbsent(response.getSoftware(), "TLQ"); } private static void assertSoftwareStatus(List items, String code, String expectedStatus) { @@ -142,6 +154,13 @@ class DeviceRuntimeStatusServiceTest { Assertions.assertEquals(expectedStatus, item.getStatus()); } + private static void assertSoftwareAbsent(List items, String code) { + Assertions.assertTrue( + items.stream().noneMatch(candidate -> code.equals(candidate.getComponentCode())), + () -> "expected software item " + code + " to be absent but got " + items.stream().map(SoftwareRuntimeStatusItem::getComponentCode).toList() + ); + } + private static HardwareStatusProbe hardwareProbe(double cpu, double memoryUsed, double memoryTotal, double diskUsed, double diskTotal) { return new HardwareStatusProbe() { @Override @@ -410,13 +429,18 @@ class DeviceRuntimeStatusServiceTest { private static class InMemoryDeviceSoftwareVersionRepository implements DeviceSoftwareVersionRepository { - private final Map data = new HashMap<>(); + private final Map data = new LinkedHashMap<>(); @Override public Optional findByComponentCode(String componentCode) { return Optional.ofNullable(data.get(componentCode)); } + @Override + public List findAll() { + return new ArrayList<>(data.values()); + } + @Override public void saveOrUpdate(DeviceSoftwareVersionEntity entity) { data.put(entity.getComponentCode(), entity); diff --git a/src/test/java/com/cisd/tms/modules/device/service/DeviceServiceTest.java b/src/test/java/com/cisd/tms/modules/device/service/DeviceServiceTest.java index c8490d9..4616e2a 100644 --- a/src/test/java/com/cisd/tms/modules/device/service/DeviceServiceTest.java +++ b/src/test/java/com/cisd/tms/modules/device/service/DeviceServiceTest.java @@ -179,6 +179,11 @@ class DeviceServiceTest { return Optional.of(entity); } + @Override + public List findAll() { + return List.of(); + } + @Override public void saveOrUpdate(DeviceSoftwareVersionEntity entity) { } diff --git a/src/test/java/com/cisd/tms/modules/device/support/DefaultSoftwareRuntimeProbeTest.java b/src/test/java/com/cisd/tms/modules/device/support/DefaultSoftwareRuntimeProbeTest.java new file mode 100644 index 0000000..c61a102 --- /dev/null +++ b/src/test/java/com/cisd/tms/modules/device/support/DefaultSoftwareRuntimeProbeTest.java @@ -0,0 +1,69 @@ +package com.cisd.tms.modules.device.support; + +import java.time.Duration; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class DefaultSoftwareRuntimeProbeTest { + + @Test + void shouldReuseSingleProcessSnapshotAcrossMultipleProbeCallsWithinCacheWindow() { + AtomicInteger loads = new AtomicInteger(); + AtomicLong now = new AtomicLong(1_000_000L); + DefaultSoftwareRuntimeProbe probe = new DefaultSoftwareRuntimeProbe( + () -> { + loads.incrementAndGet(); + return List.of( + "/usr/sbin/rabbitmq-server", + "/usr/local/bin/tidb-server", + "/opt/app/cmsp", + "/opt/app/cmtp", + "java -jar tms-framework.jar" + ); + }, + Duration.ofSeconds(1), + now::get + ); + + Assertions.assertTrue(probe.isRabbitmqRunning()); + Assertions.assertTrue(probe.isTidbRunning()); + Assertions.assertTrue(probe.isStandardAppRunning()); + Assertions.assertTrue(probe.isTmsRunning()); + + Assertions.assertEquals(1, loads.get()); + } + + @Test + void shouldRefreshSnapshotAfterCacheWindowExpires() { + AtomicInteger loads = new AtomicInteger(); + AtomicLong now = new AtomicLong(1_000_000L); + DefaultSoftwareRuntimeProbe probe = new DefaultSoftwareRuntimeProbe( + () -> { + loads.incrementAndGet(); + return List.of("java -jar tms-framework.jar"); + }, + Duration.ofMillis(100), + now::get + ); + + Assertions.assertTrue(probe.isTmsRunning()); + now.addAndGet(Duration.ofMillis(150).toNanos()); + Assertions.assertTrue(probe.isTmsRunning()); + + Assertions.assertEquals(2, loads.get()); + } + + @Test + void shouldRequireBothStandardReceiverProcesses() { + DefaultSoftwareRuntimeProbe probe = new DefaultSoftwareRuntimeProbe( + () -> List.of("/opt/app/cmsp"), + Duration.ofSeconds(1), + System::nanoTime + ); + + Assertions.assertFalse(probe.isStandardAppRunning()); + } +} diff --git a/src/test/java/com/cisd/tms/modules/upgrade/executor/UpgradeTaskRunnerTest.java b/src/test/java/com/cisd/tms/modules/upgrade/executor/UpgradeTaskRunnerTest.java index 1703556..2d274d8 100644 --- a/src/test/java/com/cisd/tms/modules/upgrade/executor/UpgradeTaskRunnerTest.java +++ b/src/test/java/com/cisd/tms/modules/upgrade/executor/UpgradeTaskRunnerTest.java @@ -15,7 +15,9 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.time.LocalDateTime; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.zip.ZipEntry; @@ -317,6 +319,11 @@ class UpgradeTaskRunnerTest { return Optional.ofNullable(store.get(componentCode)); } + @Override + public List findAll() { + return new ArrayList<>(store.values()); + } + @Override public void saveOrUpdate(DeviceSoftwareVersionEntity entity) { store.put(entity.getComponentCode(), entity); diff --git a/src/test/java/com/cisd/tms/modules/upgrade/service/UpgradePackageServiceTest.java b/src/test/java/com/cisd/tms/modules/upgrade/service/UpgradePackageServiceTest.java index b3f1316..86e39fd 100644 --- a/src/test/java/com/cisd/tms/modules/upgrade/service/UpgradePackageServiceTest.java +++ b/src/test/java/com/cisd/tms/modules/upgrade/service/UpgradePackageServiceTest.java @@ -26,7 +26,9 @@ import java.security.KeyPairGenerator; import java.security.Security; import java.security.Signature; import java.security.spec.ECGenParameterSpec; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.zip.ZipEntry; @@ -473,6 +475,11 @@ class UpgradePackageServiceTest { return Optional.ofNullable(store.get(componentCode)); } + @Override + public List findAll() { + return new ArrayList<>(store.values()); + } + @Override public void saveOrUpdate(DeviceSoftwareVersionEntity entity) { store.put(entity.getComponentCode(), entity);