fix:设备信息
This commit is contained in:
@@ -95,12 +95,18 @@ public class WebMvcConfig implements WebMvcConfigurer {
|
||||
.addPathPatterns("/api/**")
|
||||
.excludePathPatterns(
|
||||
"/api/v1/device/status",
|
||||
"/api/v1/device/info",
|
||||
"/api/v1/masterKey/status",
|
||||
"/api/v1/masterKey/init",
|
||||
"/api/v1/masterKey/recover",
|
||||
"/api/v1/auth/password-login",
|
||||
"/api/v1/system/health",
|
||||
"/api/v1/device/time-config",
|
||||
"/api/v1/auth/ukey-login",
|
||||
"/api/v1/auth/ukey-login/randoms",
|
||||
"/api/v1/auth/captcha",
|
||||
"/api/v1/auth/super-admin/ukeys/issue-sign"
|
||||
"/api/v1/auth/super-admin/ukeys/issue-sign",
|
||||
"/api/v1/device/network/**"
|
||||
);
|
||||
|
||||
registry.addInterceptor(internalApiReplayInterceptor)
|
||||
|
||||
@@ -33,7 +33,6 @@ public class ResourceBackupProperties {
|
||||
"tms_auth_session",
|
||||
"tms_auth_challenge",
|
||||
"tms_auth_audit_log",
|
||||
"tms_operation_audit_log",
|
||||
"tms_resource_backup_task",
|
||||
"tms_resource_restore_task"
|
||||
);
|
||||
|
||||
@@ -69,7 +69,7 @@ public class ResourceRestoreController {
|
||||
}
|
||||
|
||||
@GetMapping("/latest")
|
||||
@RequireInternalAuth(anyRole = RoleCode.OPS_ADMIN, authLevel = AuthLevel.FULL)
|
||||
@RequireInternalAuth(anyRole = RoleCode.OPS_ADMIN, authLevel = AuthLevel.LIMITED)
|
||||
@Operation(summary = "查询最近资源恢复结果", description = "TMS 重启后用于恢复页面展示最近一次恢复时间、结果和失败原因。")
|
||||
public ApiResponse<ResourceRestoreLatestResponse> latestRestore() {
|
||||
return ApiResponse.success(resourceTaskQueryService.getLatestRestore());
|
||||
|
||||
+26
-1
@@ -16,6 +16,9 @@ import com.cisd.tms.modules.device.support.SystemHardwareProbe;
|
||||
import com.cisd.tms.modules.device.support.SystemSoftwareVersionProbe;
|
||||
import com.cisd.tms.modules.init.dto.CurrentInitConfigResponse;
|
||||
import com.cisd.tms.modules.init.service.InitService;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
@@ -49,7 +52,7 @@ public class DeviceProfileServiceImpl implements DeviceProfileService {
|
||||
section.setModel(deviceProfileProperties.getModel());
|
||||
section.setName(deviceProfileProperties.getName());
|
||||
section.setSerialNumber(trim(info.getDeviceSerial()));
|
||||
section.setDeviceVersion(formatVersion(info.getStandardVersion()));
|
||||
section.setDeviceVersion(firstNonBlank(info.getDeviceVersionText(), formatVersion(info.getDeviceVersion())));
|
||||
return section;
|
||||
}
|
||||
|
||||
@@ -136,12 +139,34 @@ public class DeviceProfileServiceImpl implements DeviceProfileService {
|
||||
if (version <= 0) {
|
||||
return "";
|
||||
}
|
||||
String decoded = decodeAsciiDeviceVersion(version);
|
||||
if (!decoded.isBlank()) {
|
||||
return decoded.startsWith("V") ? decoded : "V" + decoded;
|
||||
}
|
||||
if (version >= 100) {
|
||||
return "V" + (version / 100) + "." + String.format("%02d", version % 100);
|
||||
}
|
||||
return "V" + version;
|
||||
}
|
||||
|
||||
private String decodeAsciiDeviceVersion(int version) {
|
||||
byte[] bytes = ByteBuffer.allocate(Integer.BYTES)
|
||||
.order(ByteOrder.LITTLE_ENDIAN)
|
||||
.putInt(version)
|
||||
.array();
|
||||
String decoded = new String(bytes, StandardCharsets.US_ASCII)
|
||||
.replace("\u0000", "")
|
||||
.trim();
|
||||
if (!decoded.isBlank() && decoded.chars().allMatch(DeviceProfileServiceImpl::isPrintableVersionChar)) {
|
||||
return decoded;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private static boolean isPrintableVersionChar(int value) {
|
||||
return (value >= '0' && value <= '9') || value == '.' || value == 'V' || value == 'v';
|
||||
}
|
||||
|
||||
private static String firstNonBlank(String... values) {
|
||||
if (values == null) {
|
||||
return "";
|
||||
|
||||
@@ -3,12 +3,29 @@ package com.cisd.tms.modules.device.support;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class DefaultHardwareStatusProbe implements HardwareStatusProbe {
|
||||
|
||||
private final String diskUsagePath;
|
||||
private final CommandRunner commandRunner;
|
||||
|
||||
@Autowired
|
||||
public DefaultHardwareStatusProbe(@Value("${tms.device.runtime-status.disk-usage-path:/home/tms}") String diskUsagePath) {
|
||||
this(diskUsagePath, DefaultHardwareStatusProbe::runShellCommand);
|
||||
}
|
||||
|
||||
DefaultHardwareStatusProbe(String diskUsagePath, CommandRunner commandRunner) {
|
||||
this.diskUsagePath = firstNonBlank(diskUsagePath, "/home/tms");
|
||||
this.commandRunner = commandRunner == null ? DefaultHardwareStatusProbe::runShellCommand : commandRunner;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double cpuUsagePercent() {
|
||||
CpuTimes before = readCpuTimes();
|
||||
@@ -52,23 +69,66 @@ public class DefaultHardwareStatusProbe implements HardwareStatusProbe {
|
||||
|
||||
@Override
|
||||
public UsageSnapshot diskUsage() {
|
||||
double totalGb = physicalDiskTotalGb();
|
||||
double usedGb = filesystemUsedGb();
|
||||
return new UsageSnapshot(round(usedGb), round(totalGb));
|
||||
}
|
||||
|
||||
private double physicalDiskTotalGb() {
|
||||
try {
|
||||
List<String> lines = runCommand("df -k /");
|
||||
if (lines.size() < 2) {
|
||||
throw new IllegalStateException("磁盘探测未返回数据");
|
||||
List<String> lines = commandRunner.run("lsblk -b -d -n -e 7,11 -o TYPE,SIZE");
|
||||
long totalBytes = 0L;
|
||||
for (String line : lines) {
|
||||
String[] parts = line.trim().split("\\s+");
|
||||
if (parts.length >= 2 && "disk".equals(parts[0])) {
|
||||
totalBytes += Long.parseLong(parts[1]);
|
||||
}
|
||||
}
|
||||
String[] parts = lines.get(1).trim().split("\\s+");
|
||||
if (parts.length < 5) {
|
||||
throw new IllegalStateException("磁盘探测返回无效数据");
|
||||
if (totalBytes <= 0L) {
|
||||
throw new IllegalStateException("物理磁盘探测未返回数据");
|
||||
}
|
||||
double totalGb = Long.parseLong(parts[1]) / 1024D / 1024D;
|
||||
double usedGb = Long.parseLong(parts[2]) / 1024D / 1024D;
|
||||
return new UsageSnapshot(round(usedGb), round(totalGb));
|
||||
return totalBytes / 1024D / 1024D / 1024D;
|
||||
} catch (IOException ex) {
|
||||
throw new IllegalStateException("disk probe 执行失败", ex);
|
||||
throw new IllegalStateException("physical disk probe 执行失败", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private double filesystemUsedGb() {
|
||||
RuntimeException lastFailure = null;
|
||||
for (String path : diskUsagePaths()) {
|
||||
try {
|
||||
return parseFilesystemUsedGb(commandRunner.run("df -kP " + shellQuote(path)));
|
||||
} catch (IOException ex) {
|
||||
lastFailure = new IllegalStateException("disk probe 执行失败", ex);
|
||||
} catch (RuntimeException ex) {
|
||||
lastFailure = ex;
|
||||
}
|
||||
}
|
||||
if (lastFailure != null) {
|
||||
throw lastFailure;
|
||||
}
|
||||
throw new IllegalStateException("disk probe 执行失败");
|
||||
}
|
||||
|
||||
private double parseFilesystemUsedGb(List<String> lines) {
|
||||
if (lines.size() < 2) {
|
||||
throw new IllegalStateException("磁盘探测未返回数据");
|
||||
}
|
||||
String[] parts = lines.get(1).trim().split("\\s+");
|
||||
if (parts.length < 5) {
|
||||
throw new IllegalStateException("磁盘探测返回无效数据");
|
||||
}
|
||||
return Long.parseLong(parts[2]) / 1024D / 1024D;
|
||||
}
|
||||
|
||||
private List<String> diskUsagePaths() {
|
||||
LinkedHashSet<String> paths = new LinkedHashSet<>();
|
||||
paths.add(firstNonBlank(diskUsagePath, "/home/tms"));
|
||||
paths.add("/home");
|
||||
paths.add("/");
|
||||
return new ArrayList<>(paths);
|
||||
}
|
||||
|
||||
private CpuTimes readCpuTimes() {
|
||||
try {
|
||||
String line = Files.readAllLines(Path.of("/proc/stat")).get(0);
|
||||
@@ -92,7 +152,7 @@ public class DefaultHardwareStatusProbe implements HardwareStatusProbe {
|
||||
return Long.parseLong(parts[1]);
|
||||
}
|
||||
|
||||
private List<String> runCommand(String command) throws IOException {
|
||||
private static List<String> runShellCommand(String command) throws IOException {
|
||||
Process process = new ProcessBuilder("bash", "-lc", command).start();
|
||||
try {
|
||||
int exitCode = process.waitFor();
|
||||
@@ -107,10 +167,23 @@ public class DefaultHardwareStatusProbe implements HardwareStatusProbe {
|
||||
}
|
||||
}
|
||||
|
||||
private static String firstNonBlank(String value, String fallback) {
|
||||
return value == null || value.isBlank() ? fallback : value.trim();
|
||||
}
|
||||
|
||||
private static String shellQuote(String value) {
|
||||
return "'" + value.replace("'", "'\"'\"'") + "'";
|
||||
}
|
||||
|
||||
private double round(double value) {
|
||||
return Math.round(value * 100D) / 100D;
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
interface CommandRunner {
|
||||
List<String> run(String command) throws IOException;
|
||||
}
|
||||
|
||||
private record CpuTimes(long idle, long total) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,25 @@
|
||||
package com.cisd.tms.modules.device.support;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class DefaultSystemHardwareProbe implements SystemHardwareProbe {
|
||||
|
||||
private final CommandRunner commandRunner;
|
||||
|
||||
public DefaultSystemHardwareProbe() {
|
||||
this(DefaultSystemHardwareProbe::runShellCommand);
|
||||
}
|
||||
|
||||
DefaultSystemHardwareProbe(CommandRunner commandRunner) {
|
||||
this.commandRunner = commandRunner == null ? DefaultSystemHardwareProbe::runShellCommand : commandRunner;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String cpuModel() {
|
||||
return firstLine("lscpu | awk -F: '/Model name/ {print $2}'");
|
||||
@@ -39,17 +48,32 @@ public class DefaultSystemHardwareProbe implements SystemHardwareProbe {
|
||||
|
||||
@Override
|
||||
public String diskTotal() {
|
||||
return firstLine("df -h / | awk 'NR==2 {print $2}'");
|
||||
try {
|
||||
List<String> lines = commandRunner.run("lsblk -b -d -n -e 7,11 -o TYPE,SIZE");
|
||||
long totalBytes = 0L;
|
||||
for (String line : lines) {
|
||||
String[] parts = line.trim().split("\\s+");
|
||||
if (parts.length >= 2 && "disk".equals(parts[0])) {
|
||||
totalBytes += Long.parseLong(parts[1]);
|
||||
}
|
||||
}
|
||||
if (totalBytes <= 0L) {
|
||||
return "";
|
||||
}
|
||||
double gb = totalBytes / 1024.0 / 1024.0 / 1024.0;
|
||||
return String.format(Locale.ROOT, "%.2fGB", gb);
|
||||
} catch (InterruptedException ignored) {
|
||||
Thread.currentThread().interrupt();
|
||||
return "";
|
||||
} catch (IOException | NumberFormatException ignored) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private String firstLine(String command) {
|
||||
try {
|
||||
Process process = new ProcessBuilder("bash", "-lc", command).start();
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
|
||||
String line = reader.readLine();
|
||||
process.waitFor();
|
||||
return line == null ? "" : line.trim();
|
||||
}
|
||||
List<String> lines = commandRunner.run(command);
|
||||
return lines.isEmpty() ? "" : lines.get(0).trim();
|
||||
} catch (InterruptedException ignored) {
|
||||
Thread.currentThread().interrupt();
|
||||
return "";
|
||||
@@ -57,4 +81,19 @@ public class DefaultSystemHardwareProbe implements SystemHardwareProbe {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private static List<String> runShellCommand(String command) throws IOException, InterruptedException {
|
||||
Process process = new ProcessBuilder("bash", "-lc", command).start();
|
||||
int exitCode = process.waitFor();
|
||||
List<String> lines = new String(process.getInputStream().readAllBytes()).lines().toList();
|
||||
if (exitCode != 0) {
|
||||
throw new IOException("命令执行失败: " + command);
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
interface CommandRunner {
|
||||
List<String> run(String command) throws IOException, InterruptedException;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.cisd.tms.common.api.ApiResponse;
|
||||
import com.cisd.tms.modules.file.dto.FileDetailResponse;
|
||||
import com.cisd.tms.modules.file.dto.FileUploadResponse;
|
||||
import com.cisd.tms.modules.file.service.FileService;
|
||||
import com.cisd.tms.security.internal.ReplayProtected;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
@@ -26,6 +27,7 @@ public class FileController {
|
||||
|
||||
// 内部上传入口,前端先拿到 fileId,再把 fileId 传给初始化相关接口。
|
||||
@PostMapping("/upload")
|
||||
@ReplayProtected
|
||||
@Operation(summary = "上传初始化文件", description = "上传许可证、配置包等初始化依赖文件,返回 fileId 供后续任务引用。")
|
||||
public ApiResponse<FileUploadResponse> upload(
|
||||
@Parameter(description = "待上传的初始化文件")
|
||||
|
||||
@@ -37,7 +37,6 @@ public class LmkController {
|
||||
|
||||
@PostMapping("/masterKey/init")
|
||||
@Operation(summary = "初始化主密钥", description = "执行密码卡初始化识别、主密钥生成加载、内部密钥生成,并返回主密钥状态与校验值。")
|
||||
@ReplayProtected
|
||||
public ApiResponse<MasterKeyStateResult> initMasterKey() {
|
||||
return ApiResponse.success(lmkService.initMasterKey());
|
||||
}
|
||||
@@ -65,7 +64,6 @@ public class LmkController {
|
||||
*/
|
||||
@PostMapping("/masterKey/recover")
|
||||
@Operation(summary = "恢复全部密钥", description = "请求体需传入两份按 packetIndex 标识的备份包。两份备份包必须分别对应 packetIndex=1、2。服务端会依次恢复 LMK、IK 和用户密钥。")
|
||||
@ReplayProtected
|
||||
public ApiResponse<Void> recoverLmkEx(@Valid @RequestBody MasterKeyRecoverPacketsRequest request) {
|
||||
validatePacketIndexes(request.getPackets());
|
||||
lmkService.recoverKeyPackets(request.getPackets());
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.cisd.tms.modules.mk.service.impl;
|
||||
|
||||
import com.cisd.tms.common.enums.ErrorCode;
|
||||
import com.cisd.tms.common.exception.BizException;
|
||||
import com.cisd.tms.common.util.DivisionUtils;
|
||||
import com.cisd.tms.integration.crypto.pcie.Gm0018AlgorithmIds;
|
||||
import com.cisd.tms.integration.crypto.pcie.PcieUserKeyType;
|
||||
@@ -70,6 +72,9 @@ public class LmkServiceImpl implements LmkService {
|
||||
|
||||
@Override
|
||||
public MasterKeyStateResult initMasterKey() {
|
||||
if (pcieCryptoService.checkLmk()) {
|
||||
throw new BizException(ErrorCode.CONFLICT.getCode(), "主密钥已存在,拒绝重新初始化");
|
||||
}
|
||||
// 密钥体系一旦重建,之前导出的两份备份包就全部失效,必须清空当前轮次缓存。
|
||||
clearCachedBackupRound();
|
||||
byte[] oldPin = decodeConfiguredPin("tms.mk.init-identify.old-pin-base64", masterKeyInitProperties.getOldPinBase64());
|
||||
@@ -171,6 +176,9 @@ public class LmkServiceImpl implements LmkService {
|
||||
|
||||
@Override
|
||||
public MasterKeyStateResult recoverKeyPackets(List<MasterKeyBackupPacket> packets) {
|
||||
if (pcieCryptoService.checkLmk()) {
|
||||
throw new BizException(ErrorCode.CONFLICT.getCode(), "主密钥已存在,拒绝重新初始化");
|
||||
}
|
||||
// 进入恢复流程后,旧的备份轮次缓存不再可信,避免和恢复后的新密钥体系混用。
|
||||
clearCachedBackupRound();
|
||||
// 调用方可以任意顺序传入两份包,这里统一按 packetIndex 排序并校验必须是 1、2 两包。
|
||||
|
||||
Reference in New Issue
Block a user