diff --git a/README.md b/README.md index 8424223..fbc931b 100644 --- a/README.md +++ b/README.md @@ -297,7 +297,7 @@ Open: - `db/TMS.sql` - `db/CMEP.sql`(包内存在时) - `db/.sql`(标准/间参收发器包内存在时,导回同名机构库) -- `TMS_DB` 备份默认通过 `tms.backup.tms-db-excluded-tables` 排除角色、授权、会话、审计和资源备份/恢复任务表,恢复时不会覆盖新机器安全状态,也不会把旧的 `RUNNING` 任务带到新机器 +- `TMS_DB` 备份默认通过 `tms.backup.tms-db-excluded-tables` 排除角色、授权、会话和资源备份/恢复任务表,恢复时不会覆盖新机器安全登录状态,也不会把旧的 `RUNNING` 任务带到新机器;`tms_operation_audit_log` 默认随 TMS 库一起备份 - `MQ_REPLAY` 通过 `replay-mq.sh` 消费 `mq/mq-restore-context.json`,也可用 `MQ_REPLAY_COMMAND` 委派给现场脚本 - Java 启动恢复脚本前会在工作目录生成 `restore-env.sh`,传递数据库连接、恢复脚本路径、MQ replay 脚本路径和健康检查配置;健康检查会按配置重试等待 TMS 真正启动完成 diff --git a/docs/openapi/resource-backup-restore-frontend-integration.md b/docs/openapi/resource-backup-restore-frontend-integration.md index 9cec71c..d05970e 100644 --- a/docs/openapi/resource-backup-restore-frontend-integration.md +++ b/docs/openapi/resource-backup-restore-frontend-integration.md @@ -44,7 +44,7 @@ ### 2.3 权限和防重放 -资源备份/恢复接口要求内部登录态,角色为 `OPS_ADMIN` 或 `SUPER_ADMIN`,认证等级为 `FULL`。 +资源备份/恢复接口要求内部登录态,角色为 `OPS_ADMIN`,认证等级为 `FULL`。 需要防重放头的接口: @@ -514,7 +514,7 @@ Content-Disposition: attachment; filename="RBKP-20260429-160000-ABCDEF.tmsbak" - `USER_KEY` - `MQ_REPLAY` -`TMS_DB` 默认排除 `tms.backup.tms-db-excluded-tables` 中声明的表。默认排除角色、授权、会话和审计表,避免恢复时覆盖新机器安全状态。 +`TMS_DB` 默认排除 `tms.backup.tms-db-excluded-tables` 中声明的表。默认排除角色、授权、会话和资源任务表,避免恢复时覆盖新机器安全登录状态或带回旧的资源任务状态;`tms_operation_audit_log` 默认随 TMS 库一起备份。 ## 6. TypeScript 类型建议 @@ -840,5 +840,5 @@ const latestText = computed(() => { - apply 返回后不要继续请求恢复步骤接口;恢复侧没有步骤接口。 - TMS 重启后只调用 latest 展示最近恢复结果。 - `payloadDecrypted=false` 不表示失败,它表示预检阶段没有解密完整 payload。 -- `TMS_DB` 默认不包含角色、授权、会话和审计表,因此恢复不会覆盖新机器已有登录授权状态。 +- `TMS_DB` 默认不包含角色、授权、会话和资源任务表,因此恢复不会覆盖新机器已有登录授权状态;操作审计日志默认随 TMS 库备份。 - 服务端路径类字段不展示给普通用户,只用于运维排查。 diff --git a/src/main/java/com/cisd/tms/common/config/WebMvcConfig.java b/src/main/java/com/cisd/tms/common/config/WebMvcConfig.java index d3d4917..e982049 100644 --- a/src/main/java/com/cisd/tms/common/config/WebMvcConfig.java +++ b/src/main/java/com/cisd/tms/common/config/WebMvcConfig.java @@ -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) diff --git a/src/main/java/com/cisd/tms/modules/backup/config/ResourceBackupProperties.java b/src/main/java/com/cisd/tms/modules/backup/config/ResourceBackupProperties.java index e2b2875..3ff6083 100644 --- a/src/main/java/com/cisd/tms/modules/backup/config/ResourceBackupProperties.java +++ b/src/main/java/com/cisd/tms/modules/backup/config/ResourceBackupProperties.java @@ -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" ); diff --git a/src/main/java/com/cisd/tms/modules/backup/controller/ResourceRestoreController.java b/src/main/java/com/cisd/tms/modules/backup/controller/ResourceRestoreController.java index 864d647..0e5f7d6 100644 --- a/src/main/java/com/cisd/tms/modules/backup/controller/ResourceRestoreController.java +++ b/src/main/java/com/cisd/tms/modules/backup/controller/ResourceRestoreController.java @@ -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 latestRestore() { return ApiResponse.success(resourceTaskQueryService.getLatestRestore()); diff --git a/src/main/java/com/cisd/tms/modules/device/service/impl/DeviceProfileServiceImpl.java b/src/main/java/com/cisd/tms/modules/device/service/impl/DeviceProfileServiceImpl.java index e8023e6..70cd40f 100644 --- a/src/main/java/com/cisd/tms/modules/device/service/impl/DeviceProfileServiceImpl.java +++ b/src/main/java/com/cisd/tms/modules/device/service/impl/DeviceProfileServiceImpl.java @@ -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 ""; diff --git a/src/main/java/com/cisd/tms/modules/device/support/DefaultHardwareStatusProbe.java b/src/main/java/com/cisd/tms/modules/device/support/DefaultHardwareStatusProbe.java index c4fafcd..262b728 100644 --- a/src/main/java/com/cisd/tms/modules/device/support/DefaultHardwareStatusProbe.java +++ b/src/main/java/com/cisd/tms/modules/device/support/DefaultHardwareStatusProbe.java @@ -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 lines = runCommand("df -k /"); - if (lines.size() < 2) { - throw new IllegalStateException("磁盘探测未返回数据"); + List 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 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 diskUsagePaths() { + LinkedHashSet 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 runCommand(String command) throws IOException { + private static List 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 run(String command) throws IOException; + } + private record CpuTimes(long idle, long total) { } } diff --git a/src/main/java/com/cisd/tms/modules/device/support/DefaultSystemHardwareProbe.java b/src/main/java/com/cisd/tms/modules/device/support/DefaultSystemHardwareProbe.java index 5298461..e55650e 100644 --- a/src/main/java/com/cisd/tms/modules/device/support/DefaultSystemHardwareProbe.java +++ b/src/main/java/com/cisd/tms/modules/device/support/DefaultSystemHardwareProbe.java @@ -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 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 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 runShellCommand(String command) throws IOException, InterruptedException { + Process process = new ProcessBuilder("bash", "-lc", command).start(); + int exitCode = process.waitFor(); + List lines = new String(process.getInputStream().readAllBytes()).lines().toList(); + if (exitCode != 0) { + throw new IOException("命令执行失败: " + command); + } + return lines; + } + + @FunctionalInterface + interface CommandRunner { + List run(String command) throws IOException, InterruptedException; + } } diff --git a/src/main/java/com/cisd/tms/modules/file/controller/FileController.java b/src/main/java/com/cisd/tms/modules/file/controller/FileController.java index c98fd2a..aba06ef 100644 --- a/src/main/java/com/cisd/tms/modules/file/controller/FileController.java +++ b/src/main/java/com/cisd/tms/modules/file/controller/FileController.java @@ -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 upload( @Parameter(description = "待上传的初始化文件") diff --git a/src/main/java/com/cisd/tms/modules/mk/controller/LmkController.java b/src/main/java/com/cisd/tms/modules/mk/controller/LmkController.java index 3443626..4b64d4a 100644 --- a/src/main/java/com/cisd/tms/modules/mk/controller/LmkController.java +++ b/src/main/java/com/cisd/tms/modules/mk/controller/LmkController.java @@ -37,7 +37,6 @@ public class LmkController { @PostMapping("/masterKey/init") @Operation(summary = "初始化主密钥", description = "执行密码卡初始化识别、主密钥生成加载、内部密钥生成,并返回主密钥状态与校验值。") - @ReplayProtected public ApiResponse 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 recoverLmkEx(@Valid @RequestBody MasterKeyRecoverPacketsRequest request) { validatePacketIndexes(request.getPackets()); lmkService.recoverKeyPackets(request.getPackets()); diff --git a/src/main/java/com/cisd/tms/modules/mk/service/impl/LmkServiceImpl.java b/src/main/java/com/cisd/tms/modules/mk/service/impl/LmkServiceImpl.java index 5dc56d7..058e979 100644 --- a/src/main/java/com/cisd/tms/modules/mk/service/impl/LmkServiceImpl.java +++ b/src/main/java/com/cisd/tms/modules/mk/service/impl/LmkServiceImpl.java @@ -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 packets) { + if (pcieCryptoService.checkLmk()) { + throw new BizException(ErrorCode.CONFLICT.getCode(), "主密钥已存在,拒绝重新初始化"); + } // 进入恢复流程后,旧的备份轮次缓存不再可信,避免和恢复后的新密钥体系混用。 clearCachedBackupRound(); // 调用方可以任意顺序传入两份包,这里统一按 packetIndex 排序并校验必须是 1、2 两包。 diff --git a/src/test/java/com/cisd/tms/modules/backup/ResourceBackupPropertiesTest.java b/src/test/java/com/cisd/tms/modules/backup/ResourceBackupPropertiesTest.java index 420abf0..2bf383e 100644 --- a/src/test/java/com/cisd/tms/modules/backup/ResourceBackupPropertiesTest.java +++ b/src/test/java/com/cisd/tms/modules/backup/ResourceBackupPropertiesTest.java @@ -28,6 +28,7 @@ class ResourceBackupPropertiesTest { Assertions.assertTrue(properties.getTmsDbExcludedTables().contains("tms_role_account")); Assertions.assertTrue(properties.getTmsDbExcludedTables().contains("tms_role_ukey_binding")); Assertions.assertTrue(properties.getTmsDbExcludedTables().contains("tms_auth_session")); + Assertions.assertFalse(properties.getTmsDbExcludedTables().contains("tms_operation_audit_log")); Assertions.assertTrue(properties.getTmsDbExcludedTables().contains("tms_resource_backup_task")); Assertions.assertTrue(properties.getTmsDbExcludedTables().contains("tms_resource_restore_task")); Assertions.assertEquals(300, properties.getMysqldumpTimeoutSeconds()); diff --git a/src/test/java/com/cisd/tms/modules/backup/controller/ResourceBackupAuthorizationAuditContractTest.java b/src/test/java/com/cisd/tms/modules/backup/controller/ResourceBackupAuthorizationAuditContractTest.java index 6e9512b..6e1a2df 100644 --- a/src/test/java/com/cisd/tms/modules/backup/controller/ResourceBackupAuthorizationAuditContractTest.java +++ b/src/test/java/com/cisd/tms/modules/backup/controller/ResourceBackupAuthorizationAuditContractTest.java @@ -13,17 +13,17 @@ import org.junit.jupiter.api.Test; class ResourceBackupAuthorizationAuditContractTest { @Test - void shouldRequireOpsOrSuperAdminFullForBackupAndRestoreEndpoints() throws Exception { - assertFullOpsOrSuperAdmin(ResourceBackupController.class.getMethod( + void shouldRequireOpsAdminFullForBackupAndRestoreEndpoints() throws Exception { + assertFullOpsAdmin(ResourceBackupController.class.getMethod( "createBackupTask", com.cisd.tms.modules.backup.dto.request.CreateResourceBackupRequest.class )); - assertFullOpsOrSuperAdmin(ResourceBackupController.class.getMethod("downloadPackage", String.class)); - assertFullOpsOrSuperAdmin(ResourceRestoreController.class.getMethod( + assertFullOpsAdmin(ResourceBackupController.class.getMethod("downloadPackage", String.class)); + assertFullOpsAdmin(ResourceRestoreController.class.getMethod( "precheck", com.cisd.tms.modules.backup.dto.request.ResourceRestorePrecheckRequest.class )); - assertFullOpsOrSuperAdmin(ResourceRestoreController.class.getMethod( + assertFullOpsAdmin(ResourceRestoreController.class.getMethod( "createRestoreTask", com.cisd.tms.modules.backup.dto.request.CreateResourceRestoreRequest.class )); @@ -46,11 +46,11 @@ class ResourceBackupAuthorizationAuditContractTest { ), ActionType.RECOVER); } - private static void assertFullOpsOrSuperAdmin(Method method) { + private static void assertFullOpsAdmin(Method method) { RequireInternalAuth auth = method.getAnnotation(RequireInternalAuth.class); Assertions.assertNotNull(auth, method.getName() + " should require internal auth"); Assertions.assertEquals(AuthLevel.FULL, auth.authLevel()); - Assertions.assertArrayEquals(new RoleCode[]{RoleCode.OPS_ADMIN, RoleCode.SUPER_ADMIN}, auth.anyRole()); + Assertions.assertArrayEquals(new RoleCode[]{RoleCode.OPS_ADMIN}, auth.anyRole()); } private static void assertAudit(Method method, ActionType actionType) { diff --git a/src/test/java/com/cisd/tms/modules/backup/packagex/MysqlDatabaseBackupCollectorTest.java b/src/test/java/com/cisd/tms/modules/backup/packagex/MysqlDatabaseBackupCollectorTest.java index e2874ea..e17f6fa 100644 --- a/src/test/java/com/cisd/tms/modules/backup/packagex/MysqlDatabaseBackupCollectorTest.java +++ b/src/test/java/com/cisd/tms/modules/backup/packagex/MysqlDatabaseBackupCollectorTest.java @@ -103,6 +103,7 @@ class MysqlDatabaseBackupCollectorTest { collector.collectTmsDatabaseDump(); String invocation = Files.readString(argsLog); + Assertions.assertFalse(invocation.contains("--ignore-table=TMS.tms_operation_audit_log")); Assertions.assertTrue(invocation.contains("--ignore-table=TMS.tms_resource_backup_task")); Assertions.assertTrue(invocation.contains("--ignore-table=TMS.tms_resource_restore_task")); } diff --git a/src/test/java/com/cisd/tms/modules/device/service/DeviceProfileServiceTest.java b/src/test/java/com/cisd/tms/modules/device/service/DeviceProfileServiceTest.java index 320b1f2..fe2efa5 100644 --- a/src/test/java/com/cisd/tms/modules/device/service/DeviceProfileServiceTest.java +++ b/src/test/java/com/cisd/tms/modules/device/service/DeviceProfileServiceTest.java @@ -68,8 +68,8 @@ class DeviceProfileServiceTest { Assertions.assertEquals("SYD7108", response.getDevice().getModel()); Assertions.assertEquals("跨境支付终端一体机", response.getDevice().getName()); - Assertions.assertEquals("1234567890ABCDEF", response.getDevice().getSerialNumber()); - Assertions.assertEquals("V2.50", response.getDevice().getDeviceVersion()); + Assertions.assertEquals("709230400244", response.getDevice().getSerialNumber()); + Assertions.assertEquals("V1.00", response.getDevice().getDeviceVersion()); Assertions.assertEquals("SYD1408加密卡", response.getHardware().getCryptoCardName()); Assertions.assertEquals("Hygon C86-3G (OPN:3350)", response.getHardware().getCpuModel()); Assertions.assertEquals("32GB", response.getHardware().getMemoryTotal()); @@ -290,9 +290,10 @@ class DeviceProfileServiceTest { public DeviceInfoResult getDeviceInfo() { DeviceInfoResult result = new DeviceInfoResult(); result.setDeviceName("SYD1408加密卡"); - result.setDeviceSerial("1234567890ABCDEF"); - result.setDeviceVersion(100); - result.setStandardVersion(250); + result.setDeviceSerial("709230400244"); + result.setDeviceVersion(808463921); + result.setStandardVersion(1581074); + result.setStandardVersionText("GMT 0018-2012"); return result; } }; diff --git a/src/test/java/com/cisd/tms/modules/device/support/DefaultHardwareStatusProbeSpringTest.java b/src/test/java/com/cisd/tms/modules/device/support/DefaultHardwareStatusProbeSpringTest.java new file mode 100644 index 0000000..2dba338 --- /dev/null +++ b/src/test/java/com/cisd/tms/modules/device/support/DefaultHardwareStatusProbeSpringTest.java @@ -0,0 +1,20 @@ +package com.cisd.tms.modules.device.support; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; + +class DefaultHardwareStatusProbeSpringTest { + + @Test + void shouldInstantiateDefaultHardwareStatusProbeBean() { + try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) { + context.registerBean(PropertySourcesPlaceholderConfigurer.class, PropertySourcesPlaceholderConfigurer::new); + context.register(DefaultHardwareStatusProbe.class); + context.refresh(); + + Assertions.assertNotNull(context.getBean(DefaultHardwareStatusProbe.class)); + } + } +} diff --git a/src/test/java/com/cisd/tms/modules/device/support/DefaultHardwareStatusProbeTest.java b/src/test/java/com/cisd/tms/modules/device/support/DefaultHardwareStatusProbeTest.java new file mode 100644 index 0000000..d9b04a3 --- /dev/null +++ b/src/test/java/com/cisd/tms/modules/device/support/DefaultHardwareStatusProbeTest.java @@ -0,0 +1,64 @@ +package com.cisd.tms.modules.device.support; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class DefaultHardwareStatusProbeTest { + + @Test + void shouldUsePhysicalDiskTotalForDiskUsageTotal() { + List commands = new ArrayList<>(); + DefaultHardwareStatusProbe probe = new DefaultHardwareStatusProbe("/home/tms", command -> { + commands.add(command); + if ("lsblk -b -d -n -e 7,11 -o TYPE,SIZE".equals(command)) { + return List.of( + "disk 1024000000000", + "disk 1024000000000" + ); + } + return List.of( + "Filesystem 1024-blocks Used Available Capacity Mounted on", + "/dev/mapper/data 1073741824 524288000 549453824 49% /home" + ); + }); + + UsageSnapshot snapshot = probe.diskUsage(); + + Assertions.assertEquals(500.0, snapshot.getUsedGb()); + Assertions.assertEquals(1907.35, snapshot.getTotalGb()); + Assertions.assertEquals( + List.of("lsblk -b -d -n -e 7,11 -o TYPE,SIZE", "df -kP '/home/tms'"), + commands + ); + } + + @Test + void shouldFallbackToHomeWhenConfiguredPathForUsedCapacityIsUnavailable() { + List commands = new ArrayList<>(); + DefaultHardwareStatusProbe probe = new DefaultHardwareStatusProbe("/home/tms", command -> { + commands.add(command); + if ("lsblk -b -d -n -e 7,11 -o TYPE,SIZE".equals(command)) { + return List.of("disk 966367641600"); + } + if ("df -kP '/home/tms'".equals(command)) { + throw new IOException("No such file or directory"); + } + return List.of( + "Filesystem 1024-blocks Used Available Capacity Mounted on", + "/dev/mapper/data 943718400 471859200 471859200 50% /home" + ); + }); + + UsageSnapshot snapshot = probe.diskUsage(); + + Assertions.assertEquals(450.0, snapshot.getUsedGb()); + Assertions.assertEquals(900.0, snapshot.getTotalGb()); + Assertions.assertEquals( + List.of("lsblk -b -d -n -e 7,11 -o TYPE,SIZE", "df -kP '/home/tms'", "df -kP '/home'"), + commands + ); + } +} diff --git a/src/test/java/com/cisd/tms/modules/device/support/DefaultSystemHardwareProbeTest.java b/src/test/java/com/cisd/tms/modules/device/support/DefaultSystemHardwareProbeTest.java new file mode 100644 index 0000000..be204d9 --- /dev/null +++ b/src/test/java/com/cisd/tms/modules/device/support/DefaultSystemHardwareProbeTest.java @@ -0,0 +1,37 @@ +package com.cisd.tms.modules.device.support; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class DefaultSystemHardwareProbeTest { + + @Test + void shouldReturnPhysicalDiskTotalInGbUsingBinaryUnits() { + List commands = new ArrayList<>(); + DefaultSystemHardwareProbe probe = new DefaultSystemHardwareProbe(command -> { + commands.add(command); + return List.of( + "disk 1024000000000", + "disk 1024000000000", + "rom 1048576" + ); + }); + + String total = probe.diskTotal(); + + Assertions.assertEquals("1907.35GB", total); + Assertions.assertEquals(List.of("lsblk -b -d -n -e 7,11 -o TYPE,SIZE"), commands); + } + + @Test + void shouldReturnBlankWhenPhysicalDiskProbeFails() { + DefaultSystemHardwareProbe probe = new DefaultSystemHardwareProbe(command -> { + throw new IOException("lsblk unavailable"); + }); + + Assertions.assertEquals("", probe.diskTotal()); + } +} diff --git a/src/test/java/com/cisd/tms/modules/mk/service/LmkServiceTest.java b/src/test/java/com/cisd/tms/modules/mk/service/LmkServiceTest.java index f81f471..ffb3b92 100644 --- a/src/test/java/com/cisd/tms/modules/mk/service/LmkServiceTest.java +++ b/src/test/java/com/cisd/tms/modules/mk/service/LmkServiceTest.java @@ -14,6 +14,8 @@ import com.cisd.tms.integration.crypto.pcie.model.MasterKeyRecoveryRequest; import com.cisd.tms.integration.crypto.pcie.model.MasterKeyRecoveryResult; import com.cisd.tms.integration.crypto.pcie.model.RecoverUserKeyRequest; import com.cisd.tms.integration.crypto.pcie.service.PcieCryptoService; +import com.cisd.tms.common.enums.ErrorCode; +import com.cisd.tms.common.exception.BizException; import com.cisd.tms.modules.mk.config.MasterKeyInitProperties; import com.cisd.tms.modules.mk.dto.MasterKeyBackupPacket; import com.cisd.tms.modules.mk.dto.MasterKeyPacketIkPart; @@ -69,6 +71,21 @@ class LmkServiceTest { Assertions.assertEquals("主密钥初始化失败", exception.getMessage()); } + @Test + void shouldRejectInitMasterKeyWhenMasterKeyAlreadyExists() { + PcieCryptoService pcieCryptoService = Mockito.mock(PcieCryptoService.class); + MasterKeyInitProperties properties = configuredPins("MTIzNDU2Nzg=", "ODc2NTQzMjE="); + Mockito.when(pcieCryptoService.checkLmk()).thenReturn(true); + + LmkService service = new LmkServiceImpl(pcieCryptoService, properties); + + BizException exception = Assertions.assertThrows(BizException.class, service::initMasterKey); + + Assertions.assertEquals(ErrorCode.CONFLICT.getCode(), exception.getCode()); + Assertions.assertEquals("主密钥已存在,拒绝重新初始化", exception.getMessage()); + Mockito.verify(pcieCryptoService, Mockito.never()).initializeMasterKey(Mockito.any(), Mockito.any()); + } + @Test void shouldDestroyMasterKeyAfterDeletingUserKey() { PcieCryptoService pcieCryptoService = Mockito.mock(PcieCryptoService.class);