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