fix:设备信息展示

This commit is contained in:
waner 2026-04-16 10:15:24 +08:00
parent 16578208bc
commit 312391052f
17 changed files with 317 additions and 147 deletions

View File

@ -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<Void> 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<Void> handleCryptoCardException(PcieCryptoException ex, HttpServletRequest request) {
return ApiResponse.fail(ErrorCode.CRYPTO_CARD_ERROR.getCode(), ex.getMessage())

View File

@ -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 {

View File

@ -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<DeviceSoftwareV
DeviceSoftwareVersionEntity selectByComponentCode(@Param("componentCode") String componentCode);
List<DeviceSoftwareVersionEntity> selectAll();
int updateByComponentCode(DeviceSoftwareVersionEntity entity);
}

View File

@ -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<DeviceSoftwareVersionEntity> findByComponentCode(String componentCode);
List<DeviceSoftwareVersionEntity> findAll();
void saveOrUpdate(DeviceSoftwareVersionEntity entity);
}

View File

@ -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<DeviceSoftwareVersionEntity> findAll() {
return deviceSoftwareVersionMapper.selectAll();
}
@Override
public void saveOrUpdate(DeviceSoftwareVersionEntity entity) {
if (findByComponentCode(entity.getComponentCode()).isPresent()) {

View File

@ -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<SoftwareRuntimeStatusItem> 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<DeviceSoftwareVersionEntity> versions = deviceSoftwareVersionRepository.findAll();
List<SoftwareRuntimeStatusItem> 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<DeviceSoftwareVersionEntity> 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();

View File

@ -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;
}

View File

@ -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<List<String>> 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<List<String>> 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<String> 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<String> 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<String> loadProcessSnapshot() {
Process process;
try {
process = new ProcessBuilder("bash", "-lc", "ps -eo args=").start();
int exitCode = process.waitFor();
List<String> 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<String> commandLines, long loadedAtNanos) {
}
}

View File

@ -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;
}
}

View File

@ -23,6 +23,12 @@
LIMIT 1
</select>
<select id="selectAll" resultMap="DeviceSoftwareVersionResultMap">
SELECT id, component_code, component_name, current_version, source_type, detected_at, remarks, create_time, update_time
FROM tms_device_software_version
ORDER BY id ASC
</select>
<update id="updateByComponentCode" parameterType="com.cisd.tms.modules.device.entity.DeviceSoftwareVersionEntity">
UPDATE tms_device_software_version
SET component_name = #{componentName},

View File

@ -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<Void> 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());
}
}

View File

@ -391,6 +391,11 @@ class DeviceProfileServiceTest {
return Optional.ofNullable(data.get(componentCode));
}
@Override
public List<DeviceSoftwareVersionEntity> findAll() {
return new ArrayList<>(data.values());
}
@Override
public void saveOrUpdate(DeviceSoftwareVersionEntity entity) {
data.put(entity.getComponentCode(), entity);

View File

@ -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<SoftwareRuntimeStatusItem> items, String code, String expectedStatus) {
@ -142,6 +154,13 @@ class DeviceRuntimeStatusServiceTest {
Assertions.assertEquals(expectedStatus, item.getStatus());
}
private static void assertSoftwareAbsent(List<SoftwareRuntimeStatusItem> 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<String, DeviceSoftwareVersionEntity> data = new HashMap<>();
private final Map<String, DeviceSoftwareVersionEntity> data = new LinkedHashMap<>();
@Override
public Optional<DeviceSoftwareVersionEntity> findByComponentCode(String componentCode) {
return Optional.ofNullable(data.get(componentCode));
}
@Override
public List<DeviceSoftwareVersionEntity> findAll() {
return new ArrayList<>(data.values());
}
@Override
public void saveOrUpdate(DeviceSoftwareVersionEntity entity) {
data.put(entity.getComponentCode(), entity);

View File

@ -179,6 +179,11 @@ class DeviceServiceTest {
return Optional.of(entity);
}
@Override
public List<DeviceSoftwareVersionEntity> findAll() {
return List.of();
}
@Override
public void saveOrUpdate(DeviceSoftwareVersionEntity entity) {
}

View File

@ -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());
}
}

View File

@ -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<DeviceSoftwareVersionEntity> findAll() {
return new ArrayList<>(store.values());
}
@Override
public void saveOrUpdate(DeviceSoftwareVersionEntity entity) {
store.put(entity.getComponentCode(), entity);

View File

@ -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<DeviceSoftwareVersionEntity> findAll() {
return new ArrayList<>(store.values());
}
@Override
public void saveOrUpdate(DeviceSoftwareVersionEntity entity) {
store.put(entity.getComponentCode(), entity);