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.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.multipart.MaxUploadSizeExceededException; import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.web.multipart.MultipartException;
@RestControllerAdvice @RestControllerAdvice
public class GlobalExceptionHandler { public class GlobalExceptionHandler {
@ -53,6 +54,12 @@ public class GlobalExceptionHandler {
.withPath(request.getRequestURI()); .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) @ExceptionHandler(PcieCryptoException.class)
public ApiResponse<Void> handleCryptoCardException(PcieCryptoException ex, HttpServletRequest request) { public ApiResponse<Void> handleCryptoCardException(PcieCryptoException ex, HttpServletRequest request) {
return ApiResponse.fail(ErrorCode.CRYPTO_CARD_ERROR.getCode(), ex.getMessage()) return ApiResponse.fail(ErrorCode.CRYPTO_CARD_ERROR.getCode(), ex.getMessage())

View File

@ -61,7 +61,7 @@ import org.springframework.web.bind.annotation.RestController;
@RestController @RestController
@RequestMapping("/api/v1/device/crypto") @RequestMapping("/api/v1/device/crypto")
@RequiredArgsConstructor @RequiredArgsConstructor
@ReplayProtected //@ReplayProtected
@Tag(name = "密码卡调试", description = "密码卡设备调试、密钥操作和文件管理接口") @Tag(name = "密码卡调试", description = "密码卡设备调试、密钥操作和文件管理接口")
public class CryptoCardController { 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.infrastructure.persistence.mapper.BaseMapperX;
import com.cisd.tms.modules.device.entity.DeviceSoftwareVersionEntity; import com.cisd.tms.modules.device.entity.DeviceSoftwareVersionEntity;
import java.util.List;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
@ -10,5 +11,7 @@ public interface DeviceSoftwareVersionMapper extends BaseMapperX<DeviceSoftwareV
DeviceSoftwareVersionEntity selectByComponentCode(@Param("componentCode") String componentCode); DeviceSoftwareVersionEntity selectByComponentCode(@Param("componentCode") String componentCode);
List<DeviceSoftwareVersionEntity> selectAll();
int updateByComponentCode(DeviceSoftwareVersionEntity entity); int updateByComponentCode(DeviceSoftwareVersionEntity entity);
} }

View File

@ -1,11 +1,14 @@
package com.cisd.tms.modules.device.repository; package com.cisd.tms.modules.device.repository;
import com.cisd.tms.modules.device.entity.DeviceSoftwareVersionEntity; import com.cisd.tms.modules.device.entity.DeviceSoftwareVersionEntity;
import java.util.List;
import java.util.Optional; import java.util.Optional;
public interface DeviceSoftwareVersionRepository { public interface DeviceSoftwareVersionRepository {
Optional<DeviceSoftwareVersionEntity> findByComponentCode(String componentCode); Optional<DeviceSoftwareVersionEntity> findByComponentCode(String componentCode);
List<DeviceSoftwareVersionEntity> findAll();
void saveOrUpdate(DeviceSoftwareVersionEntity entity); 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.entity.DeviceSoftwareVersionEntity;
import com.cisd.tms.modules.device.mapper.DeviceSoftwareVersionMapper; import com.cisd.tms.modules.device.mapper.DeviceSoftwareVersionMapper;
import com.cisd.tms.modules.device.repository.DeviceSoftwareVersionRepository; import com.cisd.tms.modules.device.repository.DeviceSoftwareVersionRepository;
import java.util.List;
import java.util.Optional; import java.util.Optional;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
@ -20,6 +21,11 @@ public class DeviceSoftwareVersionRepositoryImpl implements DeviceSoftwareVersio
return Optional.ofNullable(deviceSoftwareVersionMapper.selectByComponentCode(componentCode)); return Optional.ofNullable(deviceSoftwareVersionMapper.selectByComponentCode(componentCode));
} }
@Override
public List<DeviceSoftwareVersionEntity> findAll() {
return deviceSoftwareVersionMapper.selectAll();
}
@Override @Override
public void saveOrUpdate(DeviceSoftwareVersionEntity entity) { public void saveOrUpdate(DeviceSoftwareVersionEntity entity) {
if (findByComponentCode(entity.getComponentCode()).isPresent()) { if (findByComponentCode(entity.getComponentCode()).isPresent()) {

View File

@ -1,7 +1,6 @@
package com.cisd.tms.modules.device.service.impl; package com.cisd.tms.modules.device.service.impl;
import com.cisd.tms.common.config.properties.CisdPresetProperties; import com.cisd.tms.common.config.properties.CisdPresetProperties;
import com.cisd.tms.common.exception.BizException;
import com.cisd.tms.integration.crypto.pcie.service.PcieCryptoService; import com.cisd.tms.integration.crypto.pcie.service.PcieCryptoService;
import com.cisd.tms.modules.device.dto.CryptoCardRuntimeStatus; import com.cisd.tms.modules.device.dto.CryptoCardRuntimeStatus;
import com.cisd.tms.modules.device.dto.DeviceRuntimeStatusResponse; 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.SoftwareRuntimeProbe;
import com.cisd.tms.modules.device.support.SystemSoftwareVersionProbe; import com.cisd.tms.modules.device.support.SystemSoftwareVersionProbe;
import com.cisd.tms.modules.device.support.UsageSnapshot; import com.cisd.tms.modules.device.support.UsageSnapshot;
import com.cisd.tms.modules.init.dto.CurrentInitConfigResponse;
import com.cisd.tms.modules.init.service.InitService; import com.cisd.tms.modules.init.service.InitService;
import java.time.OffsetDateTime; import java.time.OffsetDateTime;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.function.BooleanSupplier; import java.util.function.BooleanSupplier;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service; 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_NORMAL = "NORMAL";
private static final String STATUS_ABNORMAL = "ABNORMAL"; private static final String STATUS_ABNORMAL = "ABNORMAL";
private static final String STATUS_NOT_INSTALLED = "NOT_INSTALLED";
private static final String STATUS_UNKNOWN = "UNKNOWN"; private static final String STATUS_UNKNOWN = "UNKNOWN";
private final InitService initService; private final InitService initService;
@ -104,52 +100,35 @@ public class DeviceRuntimeStatusServiceImpl implements DeviceRuntimeStatusServic
} }
private List<SoftwareRuntimeStatusItem> buildSoftwareStatuses() { private List<SoftwareRuntimeStatusItem> buildSoftwareStatuses() {
CurrentInitConfigResponse config = loadCurrentInitConfigIfPresent(); List<DeviceSoftwareVersionEntity> versions = deviceSoftwareVersionRepository.findAll();
boolean initialized = config != null;
String productType = config == null ? normalize(cisdPresetProperties.getProductType()) : normalize(config.getProductType());
String mqType = config == null ? "" : normalize(config.getMqType());
List<SoftwareRuntimeStatusItem> items = new ArrayList<>(); List<SoftwareRuntimeStatusItem> items = new ArrayList<>();
items.add(buildSoftwareItem("RABBITMQ", "消息队列", cached("RABBITMQ"), initialized && isRabbitmqEnabled(mqType), softwareRuntimeProbe::isRabbitmqRunning)); for (DeviceSoftwareVersionEntity version : versions) {
items.add(buildSoftwareItem("TLQ", "消息队列", cached("TLQ"), initialized && isTlqEnabled(mqType), softwareRuntimeProbe::isTlqRunning)); items.add(buildSoftwareItem(version));
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));
return items; return items;
} }
private SoftwareRuntimeStatusItem buildReceiverItem(String productType, boolean initialized) { private SoftwareRuntimeStatusItem buildSoftwareItem(DeviceSoftwareVersionEntity version) {
boolean standard = "ENTERPRISE".equals(productType) || "INDIRECT".equals(productType); String code = normalize(version.getComponentCode());
BooleanSupplier supplier = standard ? softwareRuntimeProbe::isStandardAppRunning : softwareRuntimeProbe::isDirectAppRunning; String name = firstNonBlank(version.getComponentName(), code);
String name = "标准收发器"; String displayName = displayName(version, code, 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
) {
SoftwareRuntimeStatusItem item = new SoftwareRuntimeStatusItem(); SoftwareRuntimeStatusItem item = new SoftwareRuntimeStatusItem();
item.setComponentCode(code); item.setComponentCode(code);
item.setComponentName(name); item.setComponentName(name);
item.setDisplayName(firstNonBlank(displayName, name)); item.setDisplayName(firstNonBlank(displayName, name));
if (!enabled) { BooleanSupplier probe = probeFor(code);
item.setStatus(STATUS_NOT_INSTALLED); if (probe == null) {
item.setMessage("未安装"); item.setStatus(STATUS_UNKNOWN);
item.setMessage("未知");
return item; return item;
} }
try { try {
if (probe.getAsBoolean()) { if (probe.getAsBoolean()) {
item.setStatus(STATUS_NORMAL); item.setStatus(STATUS_NORMAL);
item.setMessage("正常"); item.setMessage("运行中");
} else { } else {
item.setStatus(STATUS_ABNORMAL); item.setStatus(STATUS_ABNORMAL);
item.setMessage("异常"); item.setMessage("未运行");
} }
} catch (RuntimeException ex) { } catch (RuntimeException ex) {
item.setStatus(STATUS_UNKNOWN); item.setStatus(STATUS_UNKNOWN);
@ -158,47 +137,6 @@ public class DeviceRuntimeStatusServiceImpl implements DeviceRuntimeStatusServic
return item; 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) { private String normalize(String value) {
return value == null ? "" : value.trim().toUpperCase(); return value == null ? "" : value.trim().toUpperCase();
} }
@ -219,6 +157,30 @@ public class DeviceRuntimeStatusServiceImpl implements DeviceRuntimeStatusServic
return ""; 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 @FunctionalInterface
private interface UsageSupplier { private interface UsageSupplier {
UsageSnapshot get(); UsageSnapshot get();

View File

@ -13,10 +13,13 @@ import com.cisd.tms.modules.init.service.InitService;
import java.time.OffsetDateTime; import java.time.OffsetDateTime;
import java.util.Optional; import java.util.Optional;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.StopWatch;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
@Slf4j
public class DeviceServiceImpl implements DeviceService { public class DeviceServiceImpl implements DeviceService {
private final DeviceProfileProperties deviceProfileProperties; private final DeviceProfileProperties deviceProfileProperties;
@ -26,14 +29,27 @@ public class DeviceServiceImpl implements DeviceService {
@Override @Override
public DeviceInfoResponse info() { public DeviceInfoResponse info() {
StopWatch stopWatch = new StopWatch();
stopWatch.start("1");
DeviceInfoResponse response = new DeviceInfoResponse(); DeviceInfoResponse response = new DeviceInfoResponse();
response.setDeviceModel(trim(deviceProfileProperties.getModel())); response.setDeviceModel(trim(deviceProfileProperties.getModel()));
response.setDeviceName(trim(deviceProfileProperties.getName())); response.setDeviceName(trim(deviceProfileProperties.getName()));
response.setVersion(loadTmsVersion()); response.setVersion(loadTmsVersion());
stopWatch.stop();
stopWatch.start("2");
response.setSerialNumber(loadDeviceSerial()); response.setSerialNumber(loadDeviceSerial());
stopWatch.stop();
stopWatch.start("3");
response.setMasterKeyStatus(loadManagementKeyReady()); response.setMasterKeyStatus(loadManagementKeyReady());
stopWatch.stop();
stopWatch.start("4");
InitService.DeviceInitStateSnapshot snapshot = initService.getDeviceInitStateSnapshot(); InitService.DeviceInitStateSnapshot snapshot = initService.getDeviceInitStateSnapshot();
response.setDeviceStatus(snapshot.initState()); response.setDeviceStatus(snapshot.initState());
stopWatch.stop();
log.info(stopWatch.prettyPrint());
return response; return response;
} }
@ -63,8 +79,9 @@ public class DeviceServiceImpl implements DeviceService {
private boolean loadManagementKeyReady() { private boolean loadManagementKeyReady() {
try { try {
DeviceStatusResult result = pcieCryptoService.getDeviceStatus(); // DeviceStatusResult result = pcieCryptoService.getDeviceStatus();
return result != null && result.getFsmState() == 0; // return result != null && result.getFsmState() == 0;
return pcieCryptoService.checkLmk();
} catch (RuntimeException ex) { } catch (RuntimeException ex) {
return false; return false;
} }

View File

@ -1,10 +1,41 @@
package com.cisd.tms.modules.device.support; 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; import org.springframework.stereotype.Component;
@Component @Component
public class DefaultSoftwareRuntimeProbe implements SoftwareRuntimeProbe { 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 @Override
public boolean isRabbitmqRunning() { public boolean isRabbitmqRunning() {
return isRunning("rabbitmq-server"); return isRunning("rabbitmq-server");
@ -41,22 +72,64 @@ public class DefaultSoftwareRuntimeProbe implements SoftwareRuntimeProbe {
} }
private boolean isRunning(String pattern) { private boolean isRunning(String pattern) {
Process process;
try { try {
process = new ProcessBuilder("bash", "-lc", "pgrep -f '" + pattern + "' >/dev/null").start(); Pattern compiled = Pattern.compile(pattern);
int exitCode = process.waitFor(); return processSnapshot().stream().anyMatch(commandLine -> compiled.matcher(commandLine).find());
if (exitCode == 0) { } catch (PatternSyntaxException ex) {
return true; throw new IllegalStateException("invalid process probe pattern: " + pattern, ex);
} } catch (RuntimeException ex) {
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) {
throw new IllegalStateException("process probe failed: " + pattern, 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 LIMIT 1
</select> </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 id="updateByComponentCode" parameterType="com.cisd.tms.modules.device.entity.DeviceSoftwareVersionEntity">
UPDATE tms_device_software_version UPDATE tms_device_software_version
SET component_name = #{componentName}, SET component_name = #{componentName},

View File

@ -6,6 +6,7 @@ import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.web.multipart.MaxUploadSizeExceededException; import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.web.multipart.MultipartException;
class GlobalExceptionHandlerTest { class GlobalExceptionHandlerTest {
@ -24,4 +25,20 @@ class GlobalExceptionHandlerTest {
Assertions.assertEquals("upload file size exceeds limit", response.getMsg()); Assertions.assertEquals("upload file size exceeds limit", response.getMsg());
Assertions.assertEquals("/api/v1/upgrade-packages", response.getPath()); 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)); return Optional.ofNullable(data.get(componentCode));
} }
@Override
public List<DeviceSoftwareVersionEntity> findAll() {
return new ArrayList<>(data.values());
}
@Override @Override
public void saveOrUpdate(DeviceSoftwareVersionEntity entity) { public void saveOrUpdate(DeviceSoftwareVersionEntity entity) {
data.put(entity.getComponentCode(), entity); data.put(entity.getComponentCode(), entity);

View File

@ -29,6 +29,7 @@ import java.time.LocalDateTime;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
@ -45,10 +46,10 @@ class DeviceRuntimeStatusServiceTest {
InMemoryDeviceSoftwareVersionRepository versions = new InMemoryDeviceSoftwareVersionRepository(); InMemoryDeviceSoftwareVersionRepository versions = new InMemoryDeviceSoftwareVersionRepository();
versions.save(component("RABBITMQ", "消息队列", "RabbitMQ V3.8.21")); 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("TIDB", "数据库", "TiDB V8.1.0"));
versions.save(component("NGINX", "Nginx", "Nginx V1.20")); versions.save(component("NGINX", "Nginx", "Nginx V1.20"));
versions.save(component("RECEIVER", "标准收发器企业版", "标准收发器企业版 V1.3.2")); versions.save(component("RECEIVER", "标准收发器企业版", "标准收发器企业版 V1.3.2"));
versions.save(component("TMS", "设备管理软件", "SYD TMS V1.00"));
DeviceRuntimeStatusService service = new DeviceRuntimeStatusServiceImpl( DeviceRuntimeStatusService service = new DeviceRuntimeStatusServiceImpl(
initService, initService,
@ -75,39 +76,50 @@ class DeviceRuntimeStatusServiceTest {
Assertions.assertEquals("NORMAL", response.getHardware().getCryptoCard().getStatus()); Assertions.assertEquals("NORMAL", response.getHardware().getCryptoCard().getStatus());
assertSoftwareStatus(response.getSoftware(), "RABBITMQ", "NORMAL"); assertSoftwareStatus(response.getSoftware(), "RABBITMQ", "NORMAL");
assertSoftwareStatus(response.getSoftware(), "TLQ", "NOT_INSTALLED");
assertSoftwareStatus(response.getSoftware(), "TIDB", "NORMAL"); assertSoftwareStatus(response.getSoftware(), "TIDB", "NORMAL");
assertSoftwareStatus(response.getSoftware(), "NGINX", "ABNORMAL"); assertSoftwareStatus(response.getSoftware(), "NGINX", "ABNORMAL");
assertSoftwareStatus(response.getSoftware(), "RECEIVER", "NORMAL"); assertSoftwareStatus(response.getSoftware(), "RECEIVER", "NORMAL");
assertSoftwareStatus(response.getSoftware(), "TMS", "NORMAL"); assertSoftwareStatus(response.getSoftware(), "TMS", "NORMAL");
assertSoftwareAbsent(response.getSoftware(), "TLQ");
} }
@Test @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( DeviceRuntimeStatusService service = new DeviceRuntimeStatusServiceImpl(
newInitService("ENTERPRISE"), newInitService("ENTERPRISE"),
cryptoService(), cryptoService(),
hardwareProbe(10.0, 8.0, 16.0, 100.0, 256.0), hardwareProbe(10.0, 8.0, 16.0, 100.0, 256.0),
softwareVersionProbe("SYD TMS V1.00"), softwareVersionProbe("SYD TMS V1.00"),
softwareRuntimeProbe(Map.of("TMS", true)), softwareRuntimeProbe(Map.of("RABBITMQ", false, "TMS", true)),
new InMemoryDeviceSoftwareVersionRepository(), versions,
preset("ENTERPRISE") preset("ENTERPRISE")
); );
DeviceRuntimeStatusResponse response = service.loadRuntimeStatus(); DeviceRuntimeStatusResponse response = service.loadRuntimeStatus();
assertSoftwareStatus(response.getSoftware(), "RABBITMQ", "NOT_INSTALLED"); Assertions.assertEquals(2, response.getSoftware().size());
assertSoftwareStatus(response.getSoftware(), "TLQ", "NOT_INSTALLED"); assertSoftwareStatus(response.getSoftware(), "RABBITMQ", "ABNORMAL");
assertSoftwareStatus(response.getSoftware(), "TIDB", "NOT_INSTALLED");
assertSoftwareStatus(response.getSoftware(), "NGINX", "NOT_INSTALLED");
assertSoftwareStatus(response.getSoftware(), "RECEIVER", "NOT_INSTALLED");
assertSoftwareStatus(response.getSoftware(), "TMS", "NORMAL"); assertSoftwareStatus(response.getSoftware(), "TMS", "NORMAL");
assertSoftwareAbsent(response.getSoftware(), "TLQ");
assertSoftwareAbsent(response.getSoftware(), "TIDB");
assertSoftwareAbsent(response.getSoftware(), "NGINX");
assertSoftwareAbsent(response.getSoftware(), "RECEIVER");
} }
@Test @Test
void shouldReturnUnknownWhenProbeFails() { void shouldReturnUnknownWhenProbeFails() {
InitService initService = newInitService("INDIRECT"); InitService initService = newInitService("INDIRECT");
createSuccessfulInitTask(initService, validInitRequest("RABBITMQ_TLQ")); 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( DeviceRuntimeStatusService service = new DeviceRuntimeStatusServiceImpl(
initService, initService,
@ -115,7 +127,7 @@ class DeviceRuntimeStatusServiceTest {
failingHardwareProbe(), failingHardwareProbe(),
softwareVersionProbe("SYD TMS V1.00"), softwareVersionProbe("SYD TMS V1.00"),
failingSoftwareRuntimeProbe(), failingSoftwareRuntimeProbe(),
new InMemoryDeviceSoftwareVersionRepository(), versions,
preset("INDIRECT") preset("INDIRECT")
); );
@ -127,11 +139,11 @@ class DeviceRuntimeStatusServiceTest {
Assertions.assertEquals("ABNORMAL", response.getHardware().getCryptoCard().getStatus()); Assertions.assertEquals("ABNORMAL", response.getHardware().getCryptoCard().getStatus());
assertSoftwareStatus(response.getSoftware(), "RABBITMQ", "UNKNOWN"); assertSoftwareStatus(response.getSoftware(), "RABBITMQ", "UNKNOWN");
assertSoftwareStatus(response.getSoftware(), "TLQ", "UNKNOWN");
assertSoftwareStatus(response.getSoftware(), "TIDB", "UNKNOWN"); assertSoftwareStatus(response.getSoftware(), "TIDB", "UNKNOWN");
assertSoftwareStatus(response.getSoftware(), "NGINX", "UNKNOWN"); assertSoftwareStatus(response.getSoftware(), "NGINX", "UNKNOWN");
assertSoftwareStatus(response.getSoftware(), "RECEIVER", "UNKNOWN"); assertSoftwareStatus(response.getSoftware(), "RECEIVER", "UNKNOWN");
assertSoftwareStatus(response.getSoftware(), "TMS", "UNKNOWN"); assertSoftwareStatus(response.getSoftware(), "TMS", "UNKNOWN");
assertSoftwareAbsent(response.getSoftware(), "TLQ");
} }
private static void assertSoftwareStatus(List<SoftwareRuntimeStatusItem> items, String code, String expectedStatus) { private static void assertSoftwareStatus(List<SoftwareRuntimeStatusItem> items, String code, String expectedStatus) {
@ -142,6 +154,13 @@ class DeviceRuntimeStatusServiceTest {
Assertions.assertEquals(expectedStatus, item.getStatus()); 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) { private static HardwareStatusProbe hardwareProbe(double cpu, double memoryUsed, double memoryTotal, double diskUsed, double diskTotal) {
return new HardwareStatusProbe() { return new HardwareStatusProbe() {
@Override @Override
@ -410,13 +429,18 @@ class DeviceRuntimeStatusServiceTest {
private static class InMemoryDeviceSoftwareVersionRepository implements DeviceSoftwareVersionRepository { private static class InMemoryDeviceSoftwareVersionRepository implements DeviceSoftwareVersionRepository {
private final Map<String, DeviceSoftwareVersionEntity> data = new HashMap<>(); private final Map<String, DeviceSoftwareVersionEntity> data = new LinkedHashMap<>();
@Override @Override
public Optional<DeviceSoftwareVersionEntity> findByComponentCode(String componentCode) { public Optional<DeviceSoftwareVersionEntity> findByComponentCode(String componentCode) {
return Optional.ofNullable(data.get(componentCode)); return Optional.ofNullable(data.get(componentCode));
} }
@Override
public List<DeviceSoftwareVersionEntity> findAll() {
return new ArrayList<>(data.values());
}
@Override @Override
public void saveOrUpdate(DeviceSoftwareVersionEntity entity) { public void saveOrUpdate(DeviceSoftwareVersionEntity entity) {
data.put(entity.getComponentCode(), entity); data.put(entity.getComponentCode(), entity);

View File

@ -179,6 +179,11 @@ class DeviceServiceTest {
return Optional.of(entity); return Optional.of(entity);
} }
@Override
public List<DeviceSoftwareVersionEntity> findAll() {
return List.of();
}
@Override @Override
public void saveOrUpdate(DeviceSoftwareVersionEntity entity) { 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.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
@ -317,6 +319,11 @@ class UpgradeTaskRunnerTest {
return Optional.ofNullable(store.get(componentCode)); return Optional.ofNullable(store.get(componentCode));
} }
@Override
public List<DeviceSoftwareVersionEntity> findAll() {
return new ArrayList<>(store.values());
}
@Override @Override
public void saveOrUpdate(DeviceSoftwareVersionEntity entity) { public void saveOrUpdate(DeviceSoftwareVersionEntity entity) {
store.put(entity.getComponentCode(), entity); store.put(entity.getComponentCode(), entity);

View File

@ -26,7 +26,9 @@ import java.security.KeyPairGenerator;
import java.security.Security; import java.security.Security;
import java.security.Signature; import java.security.Signature;
import java.security.spec.ECGenParameterSpec; import java.security.spec.ECGenParameterSpec;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
@ -473,6 +475,11 @@ class UpgradePackageServiceTest {
return Optional.ofNullable(store.get(componentCode)); return Optional.ofNullable(store.get(componentCode));
} }
@Override
public List<DeviceSoftwareVersionEntity> findAll() {
return new ArrayList<>(store.values());
}
@Override @Override
public void saveOrUpdate(DeviceSoftwareVersionEntity entity) { public void saveOrUpdate(DeviceSoftwareVersionEntity entity) {
store.put(entity.getComponentCode(), entity); store.put(entity.getComponentCode(), entity);