fix:设备信息
This commit is contained in:
@@ -297,7 +297,7 @@ Open:
|
||||
- `db/TMS.sql`
|
||||
- `db/CMEP.sql`(包内存在时)
|
||||
- `db/<orgCode>.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 真正启动完成
|
||||
|
||||
|
||||
@@ -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 库备份。
|
||||
- 服务端路径类字段不展示给普通用户,只用于运维排查。
|
||||
|
||||
@@ -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,8 +69,48 @@ 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 /");
|
||||
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) {
|
||||
throw new IllegalStateException("物理磁盘探测未返回数据");
|
||||
}
|
||||
return totalBytes / 1024D / 1024D / 1024D;
|
||||
} catch (IOException 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("磁盘探测未返回数据");
|
||||
}
|
||||
@@ -61,12 +118,15 @@ public class DefaultHardwareStatusProbe implements HardwareStatusProbe {
|
||||
if (parts.length < 5) {
|
||||
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));
|
||||
} catch (IOException ex) {
|
||||
throw new IllegalStateException("disk probe 执行失败", ex);
|
||||
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() {
|
||||
@@ -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 两包。
|
||||
|
||||
@@ -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());
|
||||
|
||||
+7
-7
@@ -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) {
|
||||
|
||||
+1
@@ -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"));
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
+20
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<String> 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<String> 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
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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<String> 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());
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user