From 61280718dc61a4a671fd2fa1c2fe48800723bb6f Mon Sep 17 00:00:00 2001 From: waner Date: Wed, 6 May 2026 09:28:51 +0800 Subject: [PATCH] =?UTF-8?q?feat:crl=E7=AE=A1=E7=90=86=E3=80=81=E9=94=99?= =?UTF-8?q?=E8=AF=AF=E4=BF=A1=E6=81=AF=E4=B8=AD=E6=96=87=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 7 +- .../com/cisd/tms/common/api/ApiResponse.java | 2 +- .../com/cisd/tms/common/enums/ErrorCode.java | 20 +- .../exception/GlobalExceptionHandler.java | 4 +- .../com/cisd/tms/common/util/HmacUtil.java | 2 +- .../tms/common/util/Sm2SignatureUtil.java | 8 +- .../crypto/pcie/PcieErrorMapper.java | 10 +- .../crypto/pcie/PcieSessionTemplate.java | 10 +- .../pcie/service/JnaPcieCryptoService.java | 28 +- .../pcie/service/MockPcieCryptoService.java | 12 +- .../auth/dto/AdminChangePasswordRequest.java | 4 +- .../auth/dto/ChangePasswordRequest.java | 4 +- .../tms/modules/auth/dto/LoginRequest.java | 4 +- .../auth/dto/PasswordLoginAccountRequest.java | 4 +- .../auth/dto/PasswordLoginRequest.java | 8 +- .../tms/modules/auth/dto/UkeyLoginProof.java | 12 +- .../auth/dto/UkeyLoginRandomRequest.java | 2 +- .../modules/auth/dto/UkeyLoginRequest.java | 4 +- .../InternalAuthorizationInterceptor.java | 8 +- .../service/impl/AuthAdminServiceImpl.java | 14 +- .../auth/service/impl/AuthServiceImpl.java | 24 +- .../service/impl/InMemoryCaptchaService.java | 6 +- .../impl/InMemoryUkeyLoginRandomService.java | 6 +- .../service/impl/Pbkdf2PasswordHasher.java | 2 +- .../service/impl/PcieCompatUkeyVerifier.java | 28 +- .../ResourceEnvelopeCryptoServiceImpl.java | 20 +- .../ResourceDiscoveryServiceImpl.java | 4 +- .../packagex/MqReplayContextBuilder.java | 4 +- .../MysqlDatabaseBackupCollector.java | 14 +- .../ResourcePackageFormatSupport.java | 2 +- .../packagex/ResourcePackageServiceImpl.java | 24 +- .../packagex/ResourcePayloadExtractor.java | 18 +- .../LightweightRestoreApplierLauncher.java | 4 +- .../ProcessBuilderRestoreCommandInvoker.java | 4 +- .../restore/RestoreStatusFileReader.java | 4 +- .../impl/ResourceBackupServiceImpl.java | 12 +- .../impl/ResourcePrecheckServiceImpl.java | 2 +- .../impl/ResourceRestoreServiceImpl.java | 16 +- .../impl/ResourceTaskQueryServiceImpl.java | 10 +- .../InMemoryResourcePrecheckStateStore.java | 2 +- .../FileSystemResourcePrecheckStateStore.java | 6 +- .../FileSystemResourceTaskStepLogService.java | 2 +- .../cert/controller/CrlController.java | 14 +- .../cert/dto/CrlImportTaskDetailRequest.java | 14 + .../cert/dto/CrlImportTaskResponse.java | 41 ++ .../CertificateCrlImportTaskEntity.java | 27 ++ .../CertificateCrlImportTaskMapper.java | 30 ++ .../CertificateCrlRevokedEntityMapper.java | 5 + .../cert/repository/CrlRepository.java | 9 + .../repository/impl/CrlRepositoryImpl.java | 65 ++- .../cert/service/CertificateService.java | 40 +- .../tms/modules/cert/service/CrlService.java | 402 ++++++++++++++++- .../modules/cert/service/EntityService.java | 105 ++++- .../cert/service/TrustedCertService.java | 26 ++ .../modules/cert/support/CertPemSupport.java | 42 ++ .../device/service/NetworkConfigService.java | 4 +- .../service/impl/TimeConfigServiceImpl.java | 28 +- .../support/DefaultHardwareStatusProbe.java | 18 +- .../support/DefaultSoftwareRuntimeProbe.java | 14 +- .../support/crypto/CryptoPayloadCodec.java | 4 +- .../file/service/impl/FileServiceImpl.java | 12 +- .../config/InitCommandProfileService.java | 4 +- .../modules/init/dto/InitPreviewRequest.java | 66 +-- .../ConfigurableInitStepExecutor.java | 406 +++++++++--------- .../init/service/impl/InitServiceImpl.java | 42 +- .../log/service/AuditBackupService.java | 10 +- .../log/service/AuditLogRestoreService.java | 20 +- .../log/service/OperationAuditService.java | 2 +- .../modules/mk/controller/LmkController.java | 4 +- .../modules/mk/dto/MasterKeyBackupPacket.java | 6 +- .../dto/MasterKeyRecoverPacketsRequest.java | 4 +- .../mk/dto/MasterKeyRecoverRequest.java | 8 +- .../cisd/tms/modules/mk/dto/UKeySignDTO.java | 6 +- .../mk/service/impl/LmkServiceImpl.java | 36 +- .../impl/ReplayProtectionServiceImpl.java | 22 +- .../internal/InternalSignPreviewRequest.java | 2 +- .../sign/dto/openapi/OpenSignRequest.java | 4 +- .../sign/support/Sha256MockSignProvider.java | 4 +- .../upgrade/executor/UpgradeTaskRunner.java | 14 +- .../service/UpgradePackageService.java | 24 +- .../upgrade/service/UpgradeService.java | 16 +- .../NoopUpgradePackageSignatureVerifier.java | 2 +- .../SoftUpgradePackageSignatureVerifier.java | 14 +- .../support/UpgradePackageStagingService.java | 2 +- .../internal/InternalApiAuthInterceptor.java | 6 +- .../InternalApiReplayInterceptor.java | 4 +- .../openapi/OpenApiSignAuthInterceptor.java | 4 +- src/main/resources/application.yml | 10 +- .../db/migration/V1__tms_schema_full.sql | 27 +- .../migration/V7__add_cert_module_tables.sql | 25 +- .../cert/CertificateCrlImportTaskMapper.xml | 65 +++ .../CertificateCrlRevokedEntityMapper.xml | 23 + .../ChineseErrorMessageContractTest.java | 67 +++ .../exception/GlobalExceptionHandlerTest.java | 2 +- .../MultipartUploadConfigurationTest.java | 4 +- .../InternalAuthorizationInterceptorTest.java | 8 +- .../modules/auth/service/AuthServiceTest.java | 2 +- .../impl/PcieCompatUkeyVerifierTest.java | 8 +- .../packagex/MqReplayContextBuilderTest.java | 2 +- .../ResourcePayloadExtractorTest.java | 4 +- .../ResourceRestoreHandoffServiceTest.java | 2 +- .../CertControllerSecurityAnnotationTest.java | 1 + .../controller/CertSwaggerAnnotationTest.java | 4 + .../impl/CertSchemaContractTest.java | 32 ++ .../impl/CrlRepositoryImplTest.java | 20 +- .../cert/service/CertificateServiceTest.java | 111 +++++ .../modules/cert/service/CrlServiceTest.java | 211 ++++++++- .../cert/service/EntityServiceTest.java | 29 ++ .../DeviceRuntimeStatusServiceTest.java | 20 +- .../tms/modules/file/FileServiceTest.java | 4 +- .../ConfigurableInitStepExecutorTest.java | 10 +- .../modules/init/service/InitServiceTest.java | 14 +- .../aspect/OperationAuditAspectLoginTest.java | 4 +- .../modules/mk/service/LmkServiceTest.java | 4 +- .../service/UpgradePackageServiceTest.java | 4 +- .../upgrade/service/UpgradeServiceTest.java | 2 +- ...ftUpgradePackageSignatureVerifierTest.java | 2 +- .../InternalApiAuthInterceptorTest.java | 4 +- .../OpenApiSignAuthInterceptorTest.java | 2 +- 119 files changed, 2055 insertions(+), 685 deletions(-) create mode 100644 src/main/java/com/cisd/tms/modules/cert/dto/CrlImportTaskDetailRequest.java create mode 100644 src/main/java/com/cisd/tms/modules/cert/dto/CrlImportTaskResponse.java create mode 100644 src/main/java/com/cisd/tms/modules/cert/entity/CertificateCrlImportTaskEntity.java create mode 100644 src/main/java/com/cisd/tms/modules/cert/mapper/CertificateCrlImportTaskMapper.java create mode 100644 src/main/resources/mapper/cert/CertificateCrlImportTaskMapper.xml create mode 100644 src/main/resources/mapper/cert/CertificateCrlRevokedEntityMapper.xml create mode 100644 src/test/java/com/cisd/tms/common/ChineseErrorMessageContractTest.java create mode 100644 src/test/java/com/cisd/tms/modules/cert/repository/impl/CertSchemaContractTest.java diff --git a/README.md b/README.md index 3ad81ae..1806be8 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,7 @@ Open: - Upgrade execute API: `POST /api/v1/upgrades/{taskId}/execute` - Upgrade list API: `GET /api/v1/upgrades` - CRL import API: `POST /api/v1/crls/import` +- CRL import task detail API: `POST /api/v1/crls/import-tasks/detail` - CRL revoked list API: `POST /api/v1/crls/list` - CRL delete API: `POST /api/v1/crls/delete` - PCIe crypto count API: `GET /api/v1/device/crypto/device-count` @@ -100,13 +101,17 @@ Open: ## Certificate CRL Management +前后端对接文档: +- [docs/openapi/certificate-frontend-integration.md](/Users/waner/Work/CISD/文档/tms-framework/docs/openapi/certificate-frontend-integration.md) + 最小调用顺序: - `POST /api/v1/crls/import` - `POST /api/v1/crls/list` - `POST /api/v1/crls/delete` 当前实现边界: -- CRL 文件支持 PEM / DER 格式,导入后保存 CRL metadata 和 CRL 内每条 revoked certificate 明细。 +- CRL 文件支持 PEM / DER 格式,导入接口会先创建后台任务并返回 `taskId`,前端通过 `POST /api/v1/crls/import-tasks/detail` 轮询 `PENDING/RUNNING/SUCCESS/FAILED` 状态。 +- 后台导入完成后保存 CRL metadata 和 CRL 内每条 revoked certificate 明细;原始 CRL 文件只作为临时文件参与解析,任务结束后删除,不作为业务数据长期保存。 - CRL issuer 通过可信 CA 的 issuer DN 候选和实际 CRL 签名验证解析。 - CRL issuer 必须是可信 CA,且 KeyUsage 必须允许 `cRLSign`。 - 重复 CRL 通过 SHA-256 fingerprint 拒绝。 diff --git a/src/main/java/com/cisd/tms/common/api/ApiResponse.java b/src/main/java/com/cisd/tms/common/api/ApiResponse.java index 21fed58..de6f85f 100644 --- a/src/main/java/com/cisd/tms/common/api/ApiResponse.java +++ b/src/main/java/com/cisd/tms/common/api/ApiResponse.java @@ -9,7 +9,7 @@ import org.slf4j.MDC; public class ApiResponse { private static final int SUCCESS_CODE = 200; - private static final String DEFAULT_SUCCESS_MSG = "success"; + private static final String DEFAULT_SUCCESS_MSG = "成功"; private boolean success; private int code; diff --git a/src/main/java/com/cisd/tms/common/enums/ErrorCode.java b/src/main/java/com/cisd/tms/common/enums/ErrorCode.java index c0b3a6f..c0f18d7 100644 --- a/src/main/java/com/cisd/tms/common/enums/ErrorCode.java +++ b/src/main/java/com/cisd/tms/common/enums/ErrorCode.java @@ -1,16 +1,16 @@ package com.cisd.tms.common.enums; public enum ErrorCode { - BAD_REQUEST(400, "bad request"), - UNAUTHORIZED(4010, "unauthorized"), - SESSION_INVALID(4011, "session invalid"), - FORBIDDEN(4030, "forbidden"), - VALIDATE_FAILED(4001, "parameter validation failed"), - CONFLICT(4090, "conflict"), - BIZ_ERROR(5001, "business exception"), - CRYPTO_CARD_ERROR(5201, "crypto card adapter error"), - SERVICE_UNAVAILABLE(5030, "service unavailable"), - INTERNAL_ERROR(5000, "internal server error"); + BAD_REQUEST(400, "请求错误"), + UNAUTHORIZED(4010, "未认证或认证失败"), + SESSION_INVALID(4011, "会话无效"), + FORBIDDEN(4030, "无权限访问"), + VALIDATE_FAILED(4001, "参数校验失败"), + CONFLICT(4090, "数据冲突"), + BIZ_ERROR(5001, "业务处理异常"), + CRYPTO_CARD_ERROR(5201, "密码卡适配异常"), + SERVICE_UNAVAILABLE(5030, "服务暂不可用"), + INTERNAL_ERROR(5000, "服务器内部错误"); private final int code; private final String message; diff --git a/src/main/java/com/cisd/tms/common/exception/GlobalExceptionHandler.java b/src/main/java/com/cisd/tms/common/exception/GlobalExceptionHandler.java index 8808e23..60026ad 100644 --- a/src/main/java/com/cisd/tms/common/exception/GlobalExceptionHandler.java +++ b/src/main/java/com/cisd/tms/common/exception/GlobalExceptionHandler.java @@ -55,7 +55,7 @@ public class GlobalExceptionHandler { @ExceptionHandler(MaxUploadSizeExceededException.class) public ResponseEntity> handleMaxUploadSizeExceeded(MaxUploadSizeExceededException ex, HttpServletRequest request) { - return response(HttpStatus.BAD_REQUEST, ApiResponse.fail(ErrorCode.VALIDATE_FAILED.getCode(), "upload file size exceeds limit") + return response(HttpStatus.BAD_REQUEST, ApiResponse.fail(ErrorCode.VALIDATE_FAILED.getCode(), "上传文件大小超过限制") .withPath(request.getRequestURI())); } @@ -73,7 +73,7 @@ public class GlobalExceptionHandler { @ExceptionHandler(ReplayProtectionException.class) public ResponseEntity> handleReplayProtectionException(ReplayProtectionException ex, HttpServletRequest request) { - log.warn("Replay protection failed", ex); + log.warn("Replay protection 执行失败", ex); return response(HttpStatus.SERVICE_UNAVAILABLE, ApiResponse.fail(ErrorCode.SERVICE_UNAVAILABLE.getCode(), "replay protection unavailable") .withPath(request.getRequestURI())); diff --git a/src/main/java/com/cisd/tms/common/util/HmacUtil.java b/src/main/java/com/cisd/tms/common/util/HmacUtil.java index 38ce923..e82e290 100644 --- a/src/main/java/com/cisd/tms/common/util/HmacUtil.java +++ b/src/main/java/com/cisd/tms/common/util/HmacUtil.java @@ -16,7 +16,7 @@ public final class HmacUtil { byte[] digest = mac.doFinal(content.getBytes(StandardCharsets.UTF_8)); return toHex(digest); } catch (Exception ex) { - throw new IllegalStateException("Cannot calculate hmac", ex); + throw new IllegalStateException("HMAC计算失败", ex); } } diff --git a/src/main/java/com/cisd/tms/common/util/Sm2SignatureUtil.java b/src/main/java/com/cisd/tms/common/util/Sm2SignatureUtil.java index 814ee20..f33ddac 100644 --- a/src/main/java/com/cisd/tms/common/util/Sm2SignatureUtil.java +++ b/src/main/java/com/cisd/tms/common/util/Sm2SignatureUtil.java @@ -26,13 +26,13 @@ public final class Sm2SignatureUtil { public static boolean verifyBase64Signature(String publicKey, String data, String signatureBase64) { if (publicKey == null || publicKey.isBlank()) { - throw new IllegalArgumentException("public key is required"); + throw new IllegalArgumentException("public key不能为空"); } if (data == null) { - throw new IllegalArgumentException("data is required"); + throw new IllegalArgumentException("data不能为空"); } if (signatureBase64 == null || signatureBase64.isBlank()) { - throw new IllegalArgumentException("signature is required"); + throw new IllegalArgumentException("signature不能为空"); } try { PublicKey key = parsePublicKey(publicKey); @@ -42,7 +42,7 @@ public final class Sm2SignatureUtil { byte[] signatureBytes = Base64.getDecoder().decode(signatureBase64.trim()); return signature.verify(signatureBytes); } catch (Exception e) { - throw new IllegalArgumentException("sm2 verify failed", e); + throw new IllegalArgumentException("SM2验签失败", e); } } diff --git a/src/main/java/com/cisd/tms/integration/crypto/pcie/PcieErrorMapper.java b/src/main/java/com/cisd/tms/integration/crypto/pcie/PcieErrorMapper.java index 9eebd57..acbe58a 100644 --- a/src/main/java/com/cisd/tms/integration/crypto/pcie/PcieErrorMapper.java +++ b/src/main/java/com/cisd/tms/integration/crypto/pcie/PcieErrorMapper.java @@ -11,9 +11,9 @@ public class PcieErrorMapper { public PcieErrorMapper() { // From PCIe custom document (SDR_BASE + 0x1F ~ 0x27) - lowByteErrorMap.put(0x1F, "ukey error"); - lowByteErrorMap.put(0x20, "generate key error"); - lowByteErrorMap.put(0x21, "state error"); + lowByteErrorMap.put(0x1F, "UKey错误"); + lowByteErrorMap.put(0x20, "生成密钥错误"); + lowByteErrorMap.put(0x21, "状态错误"); lowByteErrorMap.put(0x22, "retry exceeded"); lowByteErrorMap.put(0x23, "device busy"); lowByteErrorMap.put(0x24, "error status"); @@ -25,7 +25,7 @@ public class PcieErrorMapper { public String toMessage(int retCode) { if (retCode == 0) { - return "success"; + return "成功"; } int lowByte = retCode & 0xFF; @@ -34,6 +34,6 @@ public class PcieErrorMapper { return known + " (retCode=0x" + Integer.toHexString(retCode) + ")"; } - return "native error (retCode=0x" + Integer.toHexString(retCode) + ")"; + return "原生接口错误(retCode=0x" + Integer.toHexString(retCode) + ")"; } } diff --git a/src/main/java/com/cisd/tms/integration/crypto/pcie/PcieSessionTemplate.java b/src/main/java/com/cisd/tms/integration/crypto/pcie/PcieSessionTemplate.java index edfa613..59b6fa1 100644 --- a/src/main/java/com/cisd/tms/integration/crypto/pcie/PcieSessionTemplate.java +++ b/src/main/java/com/cisd/tms/integration/crypto/pcie/PcieSessionTemplate.java @@ -70,7 +70,7 @@ public class PcieSessionTemplate { } finally { int closeSessionRc = lib.SDF_CloseSession(session); if (closeSessionRc != 0) { - log.warn("{} close session failed: {}", apiName, errorMapper.toMessage(closeSessionRc)); + log.warn("{} close session 执行失败:{}", apiName, errorMapper.toMessage(closeSessionRc)); } } } @@ -136,7 +136,7 @@ public class PcieSessionTemplate { log.info("Loaded PCIe native library by name: {}", libName); } } catch (UnsatisfiedLinkError ex) { - throw new PcieCryptoException("Native.load", "load PCIe native library failed", ex); + throw new PcieCryptoException("Native.load", "加载PCIe原生库失败", ex); } return nativeLibrary; } @@ -161,7 +161,7 @@ public class PcieSessionTemplate { if (openSessionRc == 0) { return sessionRef.getValue(); } - log.warn("{} open session failed, will reset shared device and retry once: {}", apiName, errorMapper.toMessage(openSessionRc)); + log.warn("{} open session 执行失败, will reset shared device and retry once: {}", apiName, errorMapper.toMessage(openSessionRc)); return null; } @@ -178,7 +178,7 @@ public class PcieSessionTemplate { } int closeSessionRc = lib.SDF_CloseSession(sessionHandle); if (closeSessionRc != 0) { - log.warn("{} close session failed: {}", apiName, errorMapper.toMessage(closeSessionRc)); + log.warn("{} close session 执行失败:{}", apiName, errorMapper.toMessage(closeSessionRc)); } } @@ -195,7 +195,7 @@ public class PcieSessionTemplate { sharedDeviceHandle = null; int closeRc = lib.SDF_CloseDevice(device); if (closeRc != 0) { - log.warn("close shared device failed: {}", errorMapper.toMessage(closeRc)); + log.warn("close shared device 执行失败:{}", errorMapper.toMessage(closeRc)); } } diff --git a/src/main/java/com/cisd/tms/integration/crypto/pcie/service/JnaPcieCryptoService.java b/src/main/java/com/cisd/tms/integration/crypto/pcie/service/JnaPcieCryptoService.java index 13a2706..603c81c 100644 --- a/src/main/java/com/cisd/tms/integration/crypto/pcie/service/JnaPcieCryptoService.java +++ b/src/main/java/com/cisd/tms/integration/crypto/pcie/service/JnaPcieCryptoService.java @@ -148,7 +148,7 @@ public class JnaPcieCryptoService implements PcieCryptoService { int retCode = lib.SDFE_CheckLMK(sessionHandle); if (retCode != 0) { if ((retCode & 0xFF) == SDFE_INIT_STATUS_LOW_BYTE) { - throw new IllegalStateException("master key initialization failed"); + throw new IllegalStateException("主密钥初始化失败"); } sessionTemplate.ensureSuccess("SDFE_CheckLMK", retCode); } @@ -260,7 +260,7 @@ public class JnaPcieCryptoService implements PcieCryptoService { GenerateKeyPairRequest req = requireRequest("request", request); int keyBits = req.getKeyBits(); if (keyBits != ECC_KEY_BITS) { - throw new IllegalArgumentException("keyBits must be 256 for ECC"); + throw new IllegalArgumentException("ECC密钥位数必须为256"); } int keyIndex = requireNonNegative("keyIndex", req.getKeyIndex()); @@ -331,7 +331,7 @@ public class JnaPcieCryptoService implements PcieCryptoService { GenerateKekRequest req = requireRequest("request", request); int keyBits = req.getKeyBits(); if (keyBits != 128 && keyBits != 192 && keyBits != 256) { - throw new IllegalArgumentException("keyBits must be one of 128/192/256 for KEK"); + throw new IllegalArgumentException("KEK密钥位数必须为128/192/256之一"); } int keyIndex = requireNonNegative("keyIndex", req.getKeyIndex()); @@ -388,7 +388,7 @@ public class JnaPcieCryptoService implements PcieCryptoService { SdfeKekComponent output = new SdfeKekComponent(); int structSize = output.size(); if (componentSize < structSize) { - throw new IllegalArgumentException("componentSize must be >= " + structSize); + throw new IllegalArgumentException("componentSize必须大于等于" + structSize); } sessionTemplate.ensureSuccess( "SDFE_ExportKEKComponent", @@ -424,7 +424,7 @@ public class JnaPcieCryptoService implements PcieCryptoService { byte[] encryptedKey = requireNonEmptyBytes("encryptedKey", req.getEncryptedKey()); int storeFlag = req.getStoreFlag(); if (storeFlag != 0 && storeFlag != 1) { - throw new IllegalArgumentException("storeFlag must be 0 or 1"); + throw new IllegalArgumentException("storeFlag必须为0或1"); } try { @@ -1140,7 +1140,7 @@ public class JnaPcieCryptoService implements PcieCryptoService { Pointer agreementHandle = agreementHandleRef.getValue(); if (agreementHandle == null) { - throw new PcieCryptoException("SDF_GenerateAgreementDataWithECC", -1, "native returned null agreement handle"); + throw new PcieCryptoException("SDF_GenerateAgreementDataWithECC", -1, "原生接口返回空协商句柄"); } PointerByReference keyHandleRef = new PointerByReference(); @@ -1587,7 +1587,7 @@ public class JnaPcieCryptoService implements PcieCryptoService { EccCipher cipher = toEccCipher("cipherBlob", cipherBlob); int outBufferSize = cipher.l; if (outBufferSize <= 0) { - throw new IllegalArgumentException("cipherBlob contains invalid plaintext length: " + outBufferSize); + throw new IllegalArgumentException("cipherBlob明文长度无效:" + outBufferSize); } EccInternalDecryptRequest decryptRequest = new EccInternalDecryptRequest(); @@ -1665,7 +1665,7 @@ public class JnaPcieCryptoService implements PcieCryptoService { int length = outLen.getValue(); if (length <= 0 || length > out.length) { - throw new PcieCryptoException("SDFE_HmacFinal", -1, "native returned invalid HMAC length: " + length); + throw new PcieCryptoException("SDFE_HmacFinal", -1, "原生接口返回的HMAC长度无效:" + length); } HmacResult result = new HmacResult(); @@ -1948,13 +1948,13 @@ public class JnaPcieCryptoService implements PcieCryptoService { } int destroyCode = lib.SDF_DestroyKey(sessionHandle, keyHandle); if (destroyCode != 0) { - throw new PcieCryptoException("SDF_DestroyKey", destroyCode, "destroy key failed"); + throw new PcieCryptoException("SDF_DestroyKey", destroyCode, "销毁密钥失败"); } } private static byte[] fileNameBytes(String fileName) { if (fileName == null || fileName.isBlank()) { - throw new IllegalArgumentException("fileName must not be blank"); + throw new IllegalArgumentException("fileName不能为空"); } return fileName.getBytes(StandardCharsets.UTF_8); } @@ -1978,7 +1978,7 @@ public class JnaPcieCryptoService implements PcieCryptoService { private static BackupDataResult toBackupResult(String apiName, byte[] out, int outLen) { if (outLen < 0 || outLen > out.length) { - throw new PcieCryptoException(apiName, -1, "native returned invalid output length: " + outLen); + throw new PcieCryptoException(apiName, -1, "原生接口返回的输出长度无效:" + outLen); } BackupDataResult result = new BackupDataResult(); result.setLength(outLen); @@ -1996,7 +1996,7 @@ public class JnaPcieCryptoService implements PcieCryptoService { private static int requireUserKeyType(int keyType) { if (keyType != PcieUserKeyType.SIGN.getCode() && keyType != PcieUserKeyType.ENC.getCode()) { - throw new IllegalArgumentException("keyType must be 0(SIGN) or 1(ENC)"); + throw new IllegalArgumentException("keyType必须为0(SIGN)或1(ENC)"); } return keyType; } @@ -2099,11 +2099,11 @@ public class JnaPcieCryptoService implements PcieCryptoService { cipher.read(); int cLength = cipher.l; if (cLength < 0 || cLength > EccCipher.C_MAX_LEN) { - throw new PcieCryptoException(apiName, -1, "native returned invalid ECC cipher C length: " + cLength); + throw new PcieCryptoException(apiName, -1, "原生接口返回的ECC密文C长度无效:" + cLength); } int totalLength = ECC_CIPHER_PREFIX_LENGTH + cLength; if (totalLength < 0 || totalLength > cipher.size()) { - throw new PcieCryptoException(apiName, -1, "native returned invalid ECC cipher blob length: " + totalLength); + throw new PcieCryptoException(apiName, -1, "原生接口返回的ECC密文结构长度无效:" + totalLength); } return cipher.getPointer().getByteArray(0, totalLength); } diff --git a/src/main/java/com/cisd/tms/integration/crypto/pcie/service/MockPcieCryptoService.java b/src/main/java/com/cisd/tms/integration/crypto/pcie/service/MockPcieCryptoService.java index d5afec0..a917655 100644 --- a/src/main/java/com/cisd/tms/integration/crypto/pcie/service/MockPcieCryptoService.java +++ b/src/main/java/com/cisd/tms/integration/crypto/pcie/service/MockPcieCryptoService.java @@ -124,7 +124,7 @@ public class MockPcieCryptoService implements PcieCryptoService { generateIk(1); generateIk(2); if (!checkLmk()) { - throw new IllegalStateException("master key initialization failed"); + throw new IllegalStateException("主密钥初始化失败"); } return exportLmkSeedMac(); } @@ -172,7 +172,7 @@ public class MockPcieCryptoService implements PcieCryptoService { public void generateKeyPairEcc(GenerateKeyPairRequest request) { GenerateKeyPairRequest req = requireRequest("request", request); if (req.getKeyBits() != ECC_KEY_BITS) { - throw new IllegalArgumentException("keyBits must be 256 for ECC"); + throw new IllegalArgumentException("ECC密钥位数必须为256"); } requireNonNegative("keyIndex", req.getKeyIndex()); requireUserKeyType(req.getKeyType()); @@ -201,7 +201,7 @@ public class MockPcieCryptoService implements PcieCryptoService { GenerateKekRequest req = requireRequest("request", request); int keyBits = req.getKeyBits(); if (keyBits != 128 && keyBits != 192 && keyBits != 256) { - throw new IllegalArgumentException("keyBits must be one of 128/192/256 for KEK"); + throw new IllegalArgumentException("KEK密钥位数必须为128/192/256之一"); } requireNonNegative("keyIndex", req.getKeyIndex()); } @@ -246,7 +246,7 @@ public class MockPcieCryptoService implements PcieCryptoService { requireNonEmptyBytes("encryptedKey", req.getEncryptedKey()); int storeFlag = req.getStoreFlag(); if (storeFlag != 0 && storeFlag != 1) { - throw new IllegalArgumentException("storeFlag must be 0 or 1"); + throw new IllegalArgumentException("storeFlag必须为0或1"); } } @@ -784,7 +784,7 @@ public class MockPcieCryptoService implements PcieCryptoService { result.setHmacLength(hmac.length); return result; } catch (Exception ex) { - throw new IllegalStateException("mock hmac failed", ex); + throw new IllegalStateException("模拟HMAC计算失败", ex); } } @@ -940,7 +940,7 @@ public class MockPcieCryptoService implements PcieCryptoService { private static int requireUserKeyType(int keyType) { if (keyType != PcieUserKeyType.SIGN.getCode() && keyType != PcieUserKeyType.ENC.getCode()) { - throw new IllegalArgumentException("keyType must be 0(SIGN) or 1(ENC)"); + throw new IllegalArgumentException("keyType必须为0(SIGN)或1(ENC)"); } return keyType; } diff --git a/src/main/java/com/cisd/tms/modules/auth/dto/AdminChangePasswordRequest.java b/src/main/java/com/cisd/tms/modules/auth/dto/AdminChangePasswordRequest.java index ec52aa1..676e94e 100644 --- a/src/main/java/com/cisd/tms/modules/auth/dto/AdminChangePasswordRequest.java +++ b/src/main/java/com/cisd/tms/modules/auth/dto/AdminChangePasswordRequest.java @@ -6,11 +6,11 @@ import jakarta.validation.constraints.NotBlank; @Schema(description = "管理员修改账号口令请求") public class AdminChangePasswordRequest { - @NotBlank(message = "oldPassword is required") + @NotBlank(message = "oldPassword不能为空") @Schema(description = "旧口令", example = "12345678") private String oldPassword; - @NotBlank(message = "newPassword is required") + @NotBlank(message = "newPassword不能为空") @Schema(description = "新口令", example = "Abc1234!") private String newPassword; diff --git a/src/main/java/com/cisd/tms/modules/auth/dto/ChangePasswordRequest.java b/src/main/java/com/cisd/tms/modules/auth/dto/ChangePasswordRequest.java index 7ed80e2..8b1e853 100644 --- a/src/main/java/com/cisd/tms/modules/auth/dto/ChangePasswordRequest.java +++ b/src/main/java/com/cisd/tms/modules/auth/dto/ChangePasswordRequest.java @@ -7,12 +7,12 @@ import jakarta.validation.constraints.NotBlank; @Schema(description = "修改当前认证账户口令请求") public class ChangePasswordRequest { - @NotBlank(message = "oldPassword is required") + @NotBlank(message = "oldPassword不能为空") @JsonAlias("currentPassword") @Schema(description = "旧口令", example = "12345678") private String oldPassword; - @NotBlank(message = "newPassword is required") + @NotBlank(message = "newPassword不能为空") @Schema(description = "新口令", example = "Abc1234!") private String newPassword; diff --git a/src/main/java/com/cisd/tms/modules/auth/dto/LoginRequest.java b/src/main/java/com/cisd/tms/modules/auth/dto/LoginRequest.java index 5e10e83..f0c89f4 100644 --- a/src/main/java/com/cisd/tms/modules/auth/dto/LoginRequest.java +++ b/src/main/java/com/cisd/tms/modules/auth/dto/LoginRequest.java @@ -7,7 +7,7 @@ import java.util.List; @Schema(description = "内部登录请求") public class LoginRequest { - @NotBlank(message = "roleCode is required") + @NotBlank(message = "roleCode不能为空") @Schema(description = "角色编码", example = "KEY_ADMIN") private String roleCode; @@ -18,7 +18,7 @@ public class LoginRequest { private List fullAccounts; @Schema(description = "UKey 序列号列表,完整登录时需传入角色要求数量的序列号") - private List<@NotBlank(message = "ukey serial must not be blank") String> ukeySerials; + private List<@NotBlank(message = "ukey serial不能为空") String> ukeySerials; public String getRoleCode() { return roleCode; diff --git a/src/main/java/com/cisd/tms/modules/auth/dto/PasswordLoginAccountRequest.java b/src/main/java/com/cisd/tms/modules/auth/dto/PasswordLoginAccountRequest.java index f5ae4f6..6f96eb8 100644 --- a/src/main/java/com/cisd/tms/modules/auth/dto/PasswordLoginAccountRequest.java +++ b/src/main/java/com/cisd/tms/modules/auth/dto/PasswordLoginAccountRequest.java @@ -7,11 +7,11 @@ import jakarta.validation.constraints.NotNull; @Schema(description = "角色口令校验账号") public class PasswordLoginAccountRequest { - @NotNull(message = "uid is required") + @NotNull(message = "uid不能为空") @Schema(description = "角色固定席位编号", example = "1") private Integer uid; - @NotBlank(message = "password is required") + @NotBlank(message = "password不能为空") @Schema(description = "账号口令", example = "12345678") private String password; diff --git a/src/main/java/com/cisd/tms/modules/auth/dto/PasswordLoginRequest.java b/src/main/java/com/cisd/tms/modules/auth/dto/PasswordLoginRequest.java index e753f75..7a1a0ca 100644 --- a/src/main/java/com/cisd/tms/modules/auth/dto/PasswordLoginRequest.java +++ b/src/main/java/com/cisd/tms/modules/auth/dto/PasswordLoginRequest.java @@ -12,20 +12,20 @@ public class PasswordLoginRequest { /** * 统一使用主角色编码,不再暴露旧系统影子角色名。 */ - @NotBlank(message = "roleCode is required") + @NotBlank(message = "roleCode不能为空") @Schema(description = "角色编码", example = "AUDIT_ADMIN") private String roleCode; @Valid - @NotEmpty(message = "accounts is required") + @NotEmpty(message = "accounts不能为空") @Schema(description = "参与本次角色口令校验的账号列表") private List accounts; -// @NotBlank(message = "captchaCode is required") +// @NotBlank(message = "captchaCode不能为空") @Schema(description = "图形验证码", example = "ABCD") private String captchaCode; -// @NotBlank(message = "captchaId is required") +// @NotBlank(message = "captchaId不能为空") @Schema(description = "验证码标识", example = "captcha-001") private String captchaId; diff --git a/src/main/java/com/cisd/tms/modules/auth/dto/UkeyLoginProof.java b/src/main/java/com/cisd/tms/modules/auth/dto/UkeyLoginProof.java index 83cb1ed..a0c05b4 100644 --- a/src/main/java/com/cisd/tms/modules/auth/dto/UkeyLoginProof.java +++ b/src/main/java/com/cisd/tms/modules/auth/dto/UkeyLoginProof.java @@ -7,25 +7,25 @@ import jakarta.validation.constraints.NotNull; @Schema(description = "单个 UKey 登录因子") public class UkeyLoginProof { - @NotBlank(message = "pubKey is required") + @NotBlank(message = "pubKey不能为空") private String pubKey; - @NotNull(message = "uid is required") + @NotNull(message = "uid不能为空") private Integer uid; /** * 对应旧系统下发的 rb,命名改为更明确的服务端随机数。 */ - @NotBlank(message = "serverRandom is required") + @NotBlank(message = "serverRandom不能为空") private String serverRandom; - @NotBlank(message = "issueSignature is required") + @NotBlank(message = "issueSignature不能为空") private String issueSignature; - @NotBlank(message = "loginPayload is required") + @NotBlank(message = "loginPayload不能为空") private String loginPayload; - @NotBlank(message = "loginSignature is required") + @NotBlank(message = "loginSignature不能为空") private String loginSignature; @Schema(description = "当前 UKey 固定席位对应账号的口令,所有角色都必传", example = "12345678") diff --git a/src/main/java/com/cisd/tms/modules/auth/dto/UkeyLoginRandomRequest.java b/src/main/java/com/cisd/tms/modules/auth/dto/UkeyLoginRandomRequest.java index 8a50eb2..64c7b3b 100644 --- a/src/main/java/com/cisd/tms/modules/auth/dto/UkeyLoginRandomRequest.java +++ b/src/main/java/com/cisd/tms/modules/auth/dto/UkeyLoginRandomRequest.java @@ -6,7 +6,7 @@ import jakarta.validation.constraints.NotBlank; @Schema(description = "申请 UKey 登录随机数") public class UkeyLoginRandomRequest { - @NotBlank(message = "roleCode is required") + @NotBlank(message = "roleCode不能为空") @Schema(description = "角色编码", example = "SUPER_ADMIN") private String roleCode; diff --git a/src/main/java/com/cisd/tms/modules/auth/dto/UkeyLoginRequest.java b/src/main/java/com/cisd/tms/modules/auth/dto/UkeyLoginRequest.java index 2b19c52..5fbde9b 100644 --- a/src/main/java/com/cisd/tms/modules/auth/dto/UkeyLoginRequest.java +++ b/src/main/java/com/cisd/tms/modules/auth/dto/UkeyLoginRequest.java @@ -10,12 +10,12 @@ import java.util.List; @Schema(description = "UKey 登录请求") public class UkeyLoginRequest { - @NotBlank(message = "roleCode is required") + @NotBlank(message = "roleCode不能为空") @Schema(description = "角色编码", example = "KEY_ADMIN") private String roleCode; @Valid - @NotEmpty(message = "loginFactors is required") + @NotEmpty(message = "loginFactors不能为空") @JsonAlias("ukeyProofs") @Schema(description = "UKey 登录因子列表") private List loginFactors; diff --git a/src/main/java/com/cisd/tms/modules/auth/security/InternalAuthorizationInterceptor.java b/src/main/java/com/cisd/tms/modules/auth/security/InternalAuthorizationInterceptor.java index f8b4cad..51d39ad 100644 --- a/src/main/java/com/cisd/tms/modules/auth/security/InternalAuthorizationInterceptor.java +++ b/src/main/java/com/cisd/tms/modules/auth/security/InternalAuthorizationInterceptor.java @@ -37,14 +37,14 @@ public class InternalAuthorizationInterceptor implements HandlerInterceptor { if (requireInternalAuth != null) { String currentRole = (String) request.getAttribute(InternalApiAuthInterceptor.ATTR_ROLE_CODE); if (!requireInternalAuth.role().getCode().equals(currentRole)) { - writeForbidden(response, "role not allowed"); - operationAuditService.recordDeniedLog(request, handlerMethod, "role not allowed"); + writeForbidden(response, "角色无权限访问"); + operationAuditService.recordDeniedLog(request, handlerMethod, "角色无权限访问"); return false; } String currentAuthLevel = (String) request.getAttribute(InternalApiAuthInterceptor.ATTR_AUTH_LEVEL); if (!authLevelAllowed(currentAuthLevel, requireInternalAuth.authLevel())) { - writeForbidden(response, "auth level not allowed"); - operationAuditService.recordDeniedLog(request, handlerMethod, "auth level not allowed"); + writeForbidden(response, "认证级别无权限访问"); + operationAuditService.recordDeniedLog(request, handlerMethod, "认证级别无权限访问"); return false; } } diff --git a/src/main/java/com/cisd/tms/modules/auth/service/impl/AuthAdminServiceImpl.java b/src/main/java/com/cisd/tms/modules/auth/service/impl/AuthAdminServiceImpl.java index 163a827..fa0d0b2 100644 --- a/src/main/java/com/cisd/tms/modules/auth/service/impl/AuthAdminServiceImpl.java +++ b/src/main/java/com/cisd/tms/modules/auth/service/impl/AuthAdminServiceImpl.java @@ -79,7 +79,7 @@ public class AuthAdminServiceImpl implements AuthAdminService { List fullAccounts = authFullAccountRepository.findByRoleCode(targetRoleCode); if (fullAccounts.isEmpty()) { - throw new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "target role full accounts not found"); + throw new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "目标角色完整账号不存在"); } for (AuthFullAccountEntity account : fullAccounts) { account.setStatus(RoleAccountStatus.ACTIVE.name()); @@ -101,7 +101,7 @@ public class AuthAdminServiceImpl implements AuthAdminService { List fullAccounts = authFullAccountRepository.findByRoleCode(targetRoleCode); if (fullAccounts.isEmpty()) { - throw new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "target role full accounts not found"); + throw new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "目标角色完整账号不存在"); } for (AuthFullAccountEntity account : fullAccounts) { String newSalt = passwordSaltGenerator.nextSalt(); @@ -166,7 +166,7 @@ public class AuthAdminServiceImpl implements AuthAdminService { throw new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "uid exceeds role ukey requirement"); } AuthFullAccountEntity account = authFullAccountRepository.findByRoleCodeAndUid(targetRoleCode, uid) - .orElseThrow(() -> new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "target role full account not found")); + .orElseThrow(() -> new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "目标角色完整账号不存在")); if (!passwordHasher.matches(oldPassword, account.getPasswordSalt(), account.getPasswordHash())) { throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "old password is incorrect"); } @@ -285,7 +285,7 @@ public class AuthAdminServiceImpl implements AuthAdminService { RoleCode targetRole = resolveRoleCode(targetRoleCode); validateRoleUid(targetRole, uid); authFullAccountRepository.findByRoleCodeAndUid(targetRoleCode, uid) - .orElseThrow(() -> new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "target role full account not found")); + .orElseThrow(() -> new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "目标角色完整账号不存在")); RoleUkeyBindingEntity binding = findExistingActiveBinding(targetRoleCode, uid, ukeySerial, ukeyPubkey); if (binding.getId() == null) { @@ -333,7 +333,7 @@ public class AuthAdminServiceImpl implements AuthAdminService { String signValue = lmkService.signIk(toIssuePayload(dto, authKeyPair, targetRole.getCode())); MasterKeyBackupPacket backupPacket = null; if (request.getUkeySerial() == null || request.getUkeySerial().isBlank()) { - throw new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "ukeySerial is required"); + throw new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "ukeySerial不能为空"); } if (RoleCode.SUPER_ADMIN == targetRole) { backupPacket = lmkService.buildBackupPacket(request.getUid()); @@ -382,7 +382,7 @@ public class AuthAdminServiceImpl implements AuthAdminService { private RoleAccountEntity loadRole(String roleCode) { resolveRoleCode(roleCode); return roleAccountRepository.findByRoleCode(roleCode) - .orElseThrow(() -> new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "target role account not found")); + .orElseThrow(() -> new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "目标角色账号不存在")); } private void validateRoleUid(RoleCode targetRole, Integer uid) { @@ -406,7 +406,7 @@ public class AuthAdminServiceImpl implements AuthAdminService { try { return objectMapper.writeValueAsString(UKeySignEntity.getInstance(dto, authKeyPair, roleCode)); } catch (JsonProcessingException ex) { - throw new IllegalStateException("serialize ukey issue payload failed", ex); + throw new IllegalStateException("serialize ukey issue payload 执行失败", ex); } } } diff --git a/src/main/java/com/cisd/tms/modules/auth/service/impl/AuthServiceImpl.java b/src/main/java/com/cisd/tms/modules/auth/service/impl/AuthServiceImpl.java index 0675e21..93eb692 100644 --- a/src/main/java/com/cisd/tms/modules/auth/service/impl/AuthServiceImpl.java +++ b/src/main/java/com/cisd/tms/modules/auth/service/impl/AuthServiceImpl.java @@ -82,7 +82,7 @@ public class AuthServiceImpl implements AuthService { @Override public LoginResponse login(LoginRequest request) { RoleAccountEntity roleAccount = roleAccountRepository.findByRoleCode(request.getRoleCode()) - .orElseThrow(() -> new BizException(ErrorCode.UNAUTHORIZED.getCode(), "role account not found")); + .orElseThrow(() -> new BizException(ErrorCode.UNAUTHORIZED.getCode(), "角色账号不存在")); validateRoleStatus(roleAccount); AuthMethod authMethod = resolveAuthMethod(request.getUkeySerials()); @@ -180,7 +180,7 @@ public class AuthServiceImpl implements AuthService { public CurrentUserResponse me(String username, String sessionToken) { String normalized = username == null || username.isBlank() ? "admin" : username; RoleAccountEntity roleAccount = roleAccountRepository.findByRoleCode(normalized) - .orElseThrow(() -> new BizException(ErrorCode.UNAUTHORIZED.getCode(), "role account not found")); + .orElseThrow(() -> new BizException(ErrorCode.UNAUTHORIZED.getCode(), "角色账号不存在")); CurrentUserResponse response = new CurrentUserResponse(); response.setUsername(roleAccount.getRoleCode()); response.setDisplayName(roleAccount.getDisplayName()); @@ -211,7 +211,7 @@ public class AuthServiceImpl implements AuthService { throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "account is not authenticated in current session"); } AuthFullAccountEntity fullAccount = authFullAccountRepository.findByRoleCodeAndUid(session.getRoleCode(), uid) - .orElseThrow(() -> new BizException(ErrorCode.UNAUTHORIZED.getCode(), "account not found")); + .orElseThrow(() -> new BizException(ErrorCode.UNAUTHORIZED.getCode(), "账号不存在")); validateFullAccountStatus(fullAccount); if (!passwordHasher.matches(currentPassword, fullAccount.getPasswordSalt(), fullAccount.getPasswordHash())) { onFullPasswordFailed(fullAccount); @@ -262,7 +262,7 @@ public class AuthServiceImpl implements AuthService { } AuthFullAccountEntity roleAccountSeat = authFullAccountRepository .findByRoleCodeAndUid(roleAccount.getRoleCode(), uid) - .orElseThrow(() -> new BizException(ErrorCode.UNAUTHORIZED.getCode(), "account not found")); + .orElseThrow(() -> new BizException(ErrorCode.UNAUTHORIZED.getCode(), "账号不存在")); validateFullAccountStatus(roleAccountSeat); if (!passwordHasher.matches(account.getPassword(), roleAccountSeat.getPasswordSalt(), roleAccountSeat.getPasswordHash())) { onFullPasswordFailed(roleAccountSeat); @@ -333,7 +333,7 @@ public class AuthServiceImpl implements AuthService { } AuthFullAccountEntity fullAccount = authFullAccountRepository .findByRoleCodeAndUid(roleAccount.getRoleCode(), uid) - .orElseThrow(() -> new BizException(ErrorCode.UNAUTHORIZED.getCode(), "full account not found")); + .orElseThrow(() -> new BizException(ErrorCode.UNAUTHORIZED.getCode(), "完整账号不存在")); validateFullAccountStatus(fullAccount); if (!passwordHasher.matches(account.getPassword(), fullAccount.getPasswordSalt(), fullAccount.getPasswordHash())) { onFullPasswordFailed(fullAccount); @@ -357,7 +357,7 @@ public class AuthServiceImpl implements AuthService { throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "uid does not match fixed role account"); } if (proof.getPassword() == null || proof.getPassword().isBlank()) { - throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "account password is required"); + throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "account password不能为空"); } FullLoginAccountRequest accountRequest = new FullLoginAccountRequest(); accountRequest.setUid(uid); @@ -394,7 +394,7 @@ public class AuthServiceImpl implements AuthService { .map(RoleUkeyBindingEntity::getUkeySerial) .collect(Collectors.toSet()); if (!boundSerials.containsAll(uniqueSerials)) { - throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "ukey verification failed"); + throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "ukey verification 执行失败"); } long matchedUidCount = bindings.stream() .filter(binding -> uniqueSerials.contains(binding.getUkeySerial())) @@ -437,7 +437,7 @@ public class AuthServiceImpl implements AuthService { try { return objectMapper.writeValueAsString(UKeySignEntity.getInstance(dto, authKeyPair, RoleCode.valueOf(roleCode).getCode())); } catch (JsonProcessingException ex) { - throw new IllegalStateException("serialize ukey issue payload failed", ex); + throw new IllegalStateException("serialize ukey issue payload 执行失败", ex); } } @@ -502,7 +502,7 @@ public class AuthServiceImpl implements AuthService { try { return objectMapper.writeValueAsString(authenticatedPrincipals); } catch (JsonProcessingException ex) { - throw new IllegalStateException("serialize authenticated principals failed", ex); + throw new IllegalStateException("serialize authenticated principals 执行失败", ex); } } @@ -536,7 +536,7 @@ public class AuthServiceImpl implements AuthService { new TypeReference>() { } ); } catch (JsonProcessingException ex) { - throw new IllegalStateException("deserialize authenticated principals failed", ex); + throw new IllegalStateException("deserialize authenticated principals 执行失败", ex); } } @@ -579,9 +579,9 @@ public class AuthServiceImpl implements AuthService { private AuthSessionEntity requireActiveSession(String sessionToken) { AuthSessionEntity session = authSessionRepository.findBySessionToken(sessionToken) - .orElseThrow(() -> new BizException(ErrorCode.SESSION_INVALID.getCode(), "session not found")); + .orElseThrow(() -> new BizException(ErrorCode.SESSION_INVALID.getCode(), "会话不存在")); if (session.getLogoutAt() != null || session.getExpiresAt() == null || !session.getExpiresAt().isAfter(now())) { - throw new BizException(ErrorCode.SESSION_INVALID.getCode(), "session expired"); + throw new BizException(ErrorCode.SESSION_INVALID.getCode(), "会话已过期"); } return session; } diff --git a/src/main/java/com/cisd/tms/modules/auth/service/impl/InMemoryCaptchaService.java b/src/main/java/com/cisd/tms/modules/auth/service/impl/InMemoryCaptchaService.java index 809b150..de2453c 100644 --- a/src/main/java/com/cisd/tms/modules/auth/service/impl/InMemoryCaptchaService.java +++ b/src/main/java/com/cisd/tms/modules/auth/service/impl/InMemoryCaptchaService.java @@ -63,10 +63,10 @@ public class InMemoryCaptchaService implements CaptchaService { public void verify(String captchaId, String captchaCode) { CaptchaEntry entry = store.remove(captchaId); if (entry == null || entry.expiresAt().isBefore(clock.instant())) { - throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "captcha verification failed"); + throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "captcha verification 执行失败"); } if (captchaCode == null || !entry.code().equalsIgnoreCase(captchaCode.trim())) { - throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "captcha verification failed"); + throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "captcha verification 执行失败"); } } @@ -116,7 +116,7 @@ public class InMemoryCaptchaService implements CaptchaService { ImageIO.write(image, "png", outputStream); return Base64.getEncoder().encodeToString(outputStream.toByteArray()); } catch (Exception ex) { - throw new IllegalStateException("generate captcha image failed", ex); + throw new IllegalStateException("generate captcha image 执行失败", ex); } } diff --git a/src/main/java/com/cisd/tms/modules/auth/service/impl/InMemoryUkeyLoginRandomService.java b/src/main/java/com/cisd/tms/modules/auth/service/impl/InMemoryUkeyLoginRandomService.java index d5a53be..e4ee80d 100644 --- a/src/main/java/com/cisd/tms/modules/auth/service/impl/InMemoryUkeyLoginRandomService.java +++ b/src/main/java/com/cisd/tms/modules/auth/service/impl/InMemoryUkeyLoginRandomService.java @@ -39,7 +39,7 @@ public class InMemoryUkeyLoginRandomService implements UkeyLoginRandomService { @Override public List issue(String roleCode, int count) { if (count <= 0) { - throw new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "count must be positive"); + throw new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "count必须为正数"); } List randoms = new ArrayList<>(count); for (int i = 0; i < count; i++) { @@ -53,10 +53,10 @@ public class InMemoryUkeyLoginRandomService implements UkeyLoginRandomService { public void assertIssued(String roleCode, List randoms) { IssuedRandoms issuedRandoms = store.remove(roleCode); if (issuedRandoms == null || issuedRandoms.expiresAt().isBefore(clock.instant())) { - throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "login randoms are missing or expired"); + throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "登录随机数不存在或已过期"); } if (randoms == null || randoms.size() != issuedRandoms.randoms().size() || !issuedRandoms.randoms().containsAll(randoms)) { - throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "login random verification failed"); + throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "login random verification 执行失败"); } } diff --git a/src/main/java/com/cisd/tms/modules/auth/service/impl/Pbkdf2PasswordHasher.java b/src/main/java/com/cisd/tms/modules/auth/service/impl/Pbkdf2PasswordHasher.java index 5e5be13..46bf982 100644 --- a/src/main/java/com/cisd/tms/modules/auth/service/impl/Pbkdf2PasswordHasher.java +++ b/src/main/java/com/cisd/tms/modules/auth/service/impl/Pbkdf2PasswordHasher.java @@ -23,7 +23,7 @@ public class Pbkdf2PasswordHasher implements PasswordHasher { SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); return Base64.getEncoder().encodeToString(keyFactory.generateSecret(spec).getEncoded()); } catch (GeneralSecurityException ex) { - throw new IllegalStateException("failed to hash password", ex); + throw new IllegalStateException("密码哈希计算失败", ex); } } diff --git a/src/main/java/com/cisd/tms/modules/auth/service/impl/PcieCompatUkeyVerifier.java b/src/main/java/com/cisd/tms/modules/auth/service/impl/PcieCompatUkeyVerifier.java index 20fd7a0..e549802 100644 --- a/src/main/java/com/cisd/tms/modules/auth/service/impl/PcieCompatUkeyVerifier.java +++ b/src/main/java/com/cisd/tms/modules/auth/service/impl/PcieCompatUkeyVerifier.java @@ -60,7 +60,7 @@ public class PcieCompatUkeyVerifier implements CompatUkeyVerifier { } catch (BizException ex) { throw ex; } catch (RuntimeException ex) { - throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "issue signature verification failed"); + throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "issue signature verification 执行失败"); } } @@ -69,36 +69,36 @@ public class PcieCompatUkeyVerifier implements CompatUkeyVerifier { try { PublicKey publicKey = parsePublicKey(pubKey); if (loginSignData == null || loginSignData.isBlank()) { - throw new IllegalArgumentException("login sign data is required"); + throw new IllegalArgumentException("login sign data不能为空"); } byte[] signatureBytes = decodeSignature(loginSign); Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM, PROVIDER); signature.initVerify(publicKey); signature.update(loginSignData.getBytes(StandardCharsets.UTF_8)); if (!signature.verify(signatureBytes)) { - throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "login signature verification failed"); + throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "login signature verification 执行失败"); } } catch (BizException ex) { throw ex; } catch (IllegalArgumentException ex) { throw new BizException(ErrorCode.VALIDATE_FAILED.getCode(), ex.getMessage()); } catch (RuntimeException ex) { - throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "login signature verification failed"); + throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "login signature verification 执行失败"); } catch (Exception ex) { - throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "login signature verification failed"); + throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "login signature verification 执行失败"); } } private PublicKey parsePublicKey(String value) throws Exception { String trimmed = value == null ? "" : value.trim(); if (trimmed.isEmpty()) { - throw new IllegalArgumentException("login public key is invalid"); + throw new IllegalArgumentException("登录公钥无效"); } if (trimmed.contains("BEGIN")) { return parsePemPublicKey(trimmed); } String normalized = normalize(trimmed); - byte[] decoded = decodeHexOrBase64(normalized, "login public key is invalid"); + byte[] decoded = decodeHexOrBase64(normalized, "登录公钥无效"); try { return KeyFactory.getInstance("EC", PROVIDER).generatePublic(new X509EncodedKeySpec(decoded)); } catch (Exception ignored) { @@ -110,7 +110,7 @@ public class PcieCompatUkeyVerifier implements CompatUkeyVerifier { try (PEMParser parser = new PEMParser(new StringReader(pemContent))) { Object parsed = parser.readObject(); if (!(parsed instanceof SubjectPublicKeyInfo publicKeyInfo)) { - throw new IllegalArgumentException("login public key is invalid"); + throw new IllegalArgumentException("登录公钥无效"); } return new JcaPEMKeyConverter().setProvider(PROVIDER).getPublicKey(publicKeyInfo); } @@ -133,7 +133,7 @@ public class PcieCompatUkeyVerifier implements CompatUkeyVerifier { | ((keyBytes[2] & 0xff) << 8) | (keyBytes[3] & 0xff); if (bitsLittleEndian != 256 && bitsBigEndian != 256) { - throw new IllegalArgumentException("login public key is invalid"); + throw new IllegalArgumentException("登录公钥无效"); } byte[] pointBytes = new byte[65]; pointBytes[0] = 0x04; @@ -152,7 +152,7 @@ public class PcieCompatUkeyVerifier implements CompatUkeyVerifier { System.arraycopy(rawPointBytes, 0, pointBytes, 1, rawPointBytes.length); } if (pointBytes.length != 65 || pointBytes[0] != 0x04) { - throw new IllegalArgumentException("login public key is invalid"); + throw new IllegalArgumentException("登录公钥无效"); } ECParameterSpec parameterSpec = ECNamedCurveTable.getParameterSpec(SM2_CURVE); ECPublicKeySpec publicKeySpec = new ECPublicKeySpec( @@ -164,9 +164,9 @@ public class PcieCompatUkeyVerifier implements CompatUkeyVerifier { private byte[] decodeSignature(String value) { if (value == null || value.isBlank()) { - throw new IllegalArgumentException("login signature is required"); + throw new IllegalArgumentException("login signature不能为空"); } - byte[] decoded = decodeHexOrBase64(normalize(value), "login signature is invalid"); + byte[] decoded = decodeHexOrBase64(normalize(value), "登录签名无效"); if (isDerSignature(decoded)) { return decoded; } @@ -176,7 +176,7 @@ public class PcieCompatUkeyVerifier implements CompatUkeyVerifier { if (decoded.length == 64) { return rawRsToDer(decoded); } - throw new IllegalArgumentException("login signature is invalid"); + throw new IllegalArgumentException("登录签名无效"); } private byte[] gm0018SignatureToRawRs(byte[] signatureBytes) { @@ -209,7 +209,7 @@ public class PcieCompatUkeyVerifier implements CompatUkeyVerifier { try { return new DERSequence(vector).getEncoded(ASN1Encoding.DER); } catch (Exception ex) { - throw new IllegalArgumentException("login signature is invalid", ex); + throw new IllegalArgumentException("登录签名无效", ex); } } diff --git a/src/main/java/com/cisd/tms/modules/backup/crypto/ResourceEnvelopeCryptoServiceImpl.java b/src/main/java/com/cisd/tms/modules/backup/crypto/ResourceEnvelopeCryptoServiceImpl.java index 75f994b..cb792c6 100644 --- a/src/main/java/com/cisd/tms/modules/backup/crypto/ResourceEnvelopeCryptoServiceImpl.java +++ b/src/main/java/com/cisd/tms/modules/backup/crypto/ResourceEnvelopeCryptoServiceImpl.java @@ -43,7 +43,7 @@ public class ResourceEnvelopeCryptoServiceImpl implements ResourceEnvelopeCrypto @Override public ResourceSealedPayload seal(byte[] payloadBytes) { if (payloadBytes == null) { - throw new IllegalArgumentException("payloadBytes is required"); + throw new IllegalArgumentException("payloadBytes不能为空"); } // 资源包正文使用随机 SM4 DEK 加密,再由用户密钥把 DEK 封装成 envelope。 // 这样大 payload 仍走对称加密,ECC 只负责保护一次性数据密钥。 @@ -74,7 +74,7 @@ public class ResourceEnvelopeCryptoServiceImpl implements ResourceEnvelopeCrypto sealedPayload.setAuthenticityKeyFingerprint(buildFingerprint("USER_KEY_SIGN", resourceBackupProperties.getAuthenticitySignKeyIndex())); return sealedPayload; } catch (Exception ex) { - throw new IllegalStateException("failed to seal resource payload", ex); + throw new IllegalStateException("资源备份载荷加封失败", ex); } finally { wipe(sm4Key); } @@ -83,7 +83,7 @@ public class ResourceEnvelopeCryptoServiceImpl implements ResourceEnvelopeCrypto @Override public byte[] sign(byte[] dataToSign) { if (dataToSign == null) { - throw new IllegalArgumentException("dataToSign is required"); + throw new IllegalArgumentException("dataToSign不能为空"); } EccInternalSignRequest request = new EccInternalSignRequest(); request.setKeyIndex(resourceBackupProperties.getAuthenticitySignKeyIndex()); @@ -95,10 +95,10 @@ public class ResourceEnvelopeCryptoServiceImpl implements ResourceEnvelopeCrypto @Override public void verify(byte[] dataToVerify, byte[] signature) { if (dataToVerify == null) { - throw new IllegalArgumentException("dataToVerify is required"); + throw new IllegalArgumentException("dataToVerify不能为空"); } if (signature == null || signature.length == 0) { - throw new IllegalArgumentException("signature is required"); + throw new IllegalArgumentException("signature不能为空"); } EccInternalVerifyRequest verifyRequest = new EccInternalVerifyRequest(); verifyRequest.setKeyIndex(resourceBackupProperties.getAuthenticitySignKeyIndex()); @@ -110,16 +110,16 @@ public class ResourceEnvelopeCryptoServiceImpl implements ResourceEnvelopeCrypto @Override public byte[] unsealAndVerify(Map envelope, byte[] cipherBytes, byte[] dataToVerify, byte[] signature) { if (envelope == null) { - throw new IllegalArgumentException("envelope is required"); + throw new IllegalArgumentException("envelope不能为空"); } if (cipherBytes == null) { - throw new IllegalArgumentException("cipherBytes is required"); + throw new IllegalArgumentException("cipherBytes不能为空"); } if (dataToVerify == null) { - throw new IllegalArgumentException("dataToVerify is required"); + throw new IllegalArgumentException("dataToVerify不能为空"); } if (signature == null || signature.length == 0) { - throw new IllegalArgumentException("signature is required"); + throw new IllegalArgumentException("signature不能为空"); } verify(dataToVerify, signature); @@ -137,7 +137,7 @@ public class ResourceEnvelopeCryptoServiceImpl implements ResourceEnvelopeCrypto cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(sm4Key, "SM4"), new IvParameterSpec(iv)); return cipher.doFinal(cipherBytes); } catch (Exception ex) { - throw new IllegalStateException("failed to unseal resource payload", ex); + throw new IllegalStateException("资源备份载荷解封失败", ex); } finally { wipe(sm4Key); } diff --git a/src/main/java/com/cisd/tms/modules/backup/discovery/ResourceDiscoveryServiceImpl.java b/src/main/java/com/cisd/tms/modules/backup/discovery/ResourceDiscoveryServiceImpl.java index bf82495..1718591 100644 --- a/src/main/java/com/cisd/tms/modules/backup/discovery/ResourceDiscoveryServiceImpl.java +++ b/src/main/java/com/cisd/tms/modules/backup/discovery/ResourceDiscoveryServiceImpl.java @@ -19,12 +19,12 @@ public class ResourceDiscoveryServiceImpl implements ResourceDiscoveryService { @Override public List discover(InitTaskEntity initTask) { if (initTask == null) { - throw new IllegalArgumentException("initTask is required"); + throw new IllegalArgumentException("initTask不能为空"); } String productType = upper(initTask.getProductType()); String mqType = upper(initTask.getMqType()); if (productType.isEmpty() || mqType.isEmpty()) { - throw new IllegalArgumentException("productType and mqType are required"); + throw new IllegalArgumentException("productType和mqType不能为空"); } List items = new ArrayList<>(); diff --git a/src/main/java/com/cisd/tms/modules/backup/packagex/MqReplayContextBuilder.java b/src/main/java/com/cisd/tms/modules/backup/packagex/MqReplayContextBuilder.java index 1ce2d78..9d76b79 100644 --- a/src/main/java/com/cisd/tms/modules/backup/packagex/MqReplayContextBuilder.java +++ b/src/main/java/com/cisd/tms/modules/backup/packagex/MqReplayContextBuilder.java @@ -18,7 +18,7 @@ public class MqReplayContextBuilder { public byte[] build(InitTaskEntity task) { if (task == null || isBlank(task.getInitPlanJson())) { - throw new IllegalArgumentException("initPlanJson is empty"); + throw new IllegalArgumentException("initPlanJson为空"); } try { JsonNode root = objectMapper.readTree(task.getInitPlanJson()); @@ -53,7 +53,7 @@ public class MqReplayContextBuilder { return objectMapper.writeValueAsBytes(payload); } catch (Exception ex) { - throw new IllegalArgumentException("failed to build mq replay context", ex); + throw new IllegalArgumentException("构建MQ重放上下文失败", ex); } } diff --git a/src/main/java/com/cisd/tms/modules/backup/packagex/MysqlDatabaseBackupCollector.java b/src/main/java/com/cisd/tms/modules/backup/packagex/MysqlDatabaseBackupCollector.java index 8e845c2..f9fffcb 100644 --- a/src/main/java/com/cisd/tms/modules/backup/packagex/MysqlDatabaseBackupCollector.java +++ b/src/main/java/com/cisd/tms/modules/backup/packagex/MysqlDatabaseBackupCollector.java @@ -64,11 +64,11 @@ public class MysqlDatabaseBackupCollector implements DatabaseBackupCollector { int exitCode = process.waitFor(); if (exitCode != 0) { String error = new String(stderr, StandardCharsets.UTF_8).trim(); - throw new IllegalStateException("mysqldump failed with exitCode=" + exitCode + throw new IllegalStateException("mysqldump 执行失败 with exitCode=" + exitCode + (error.isEmpty() ? "" : ", error=" + error)); } if (stdout.length == 0) { - throw new IllegalStateException("mysqldump produced empty output"); + throw new IllegalStateException("mysqldump输出为空"); } CollectedDatabaseDump dump = new CollectedDatabaseDump(); dump.setDatabaseName(target.databaseName()); @@ -77,19 +77,19 @@ public class MysqlDatabaseBackupCollector implements DatabaseBackupCollector { return dump; } catch (InterruptedException ex) { Thread.currentThread().interrupt(); - throw new IllegalStateException("mysqldump interrupted", ex); + throw new IllegalStateException("mysqldump 被中断", ex); } catch (IOException ex) { - throw new IllegalStateException("failed to run mysqldump", ex); + throw new IllegalStateException("执行mysqldump失败", ex); } } private MysqlConnectionTarget resolveTarget(String configuredDatabaseName, String fallbackDatabaseName) { String url = trim(datasourceUrl); if (url.isEmpty()) { - throw new IllegalStateException("spring.datasource.url is required for database backup"); + throw new IllegalStateException("spring.datasource.url不能为空 for database backup"); } if (!url.startsWith("jdbc:mysql://")) { - throw new IllegalStateException("unsupported datasource url for backup: " + url); + throw new IllegalStateException("不支持用于备份的数据源URL:" + url); } try { String normalizedUrl = url.substring("jdbc:".length()); @@ -111,7 +111,7 @@ public class MysqlDatabaseBackupCollector implements DatabaseBackupCollector { databaseName ); } catch (IllegalArgumentException ex) { - throw new IllegalStateException("invalid datasource url for backup: " + url, ex); + throw new IllegalStateException("用于备份的数据源URL无效:" + url, ex); } } diff --git a/src/main/java/com/cisd/tms/modules/backup/packagex/ResourcePackageFormatSupport.java b/src/main/java/com/cisd/tms/modules/backup/packagex/ResourcePackageFormatSupport.java index d492523..28e5477 100644 --- a/src/main/java/com/cisd/tms/modules/backup/packagex/ResourcePackageFormatSupport.java +++ b/src/main/java/com/cisd/tms/modules/backup/packagex/ResourcePackageFormatSupport.java @@ -17,7 +17,7 @@ final class ResourcePackageFormatSupport { writePart(outputStream, cipherBytes); return outputStream.toByteArray(); } catch (IOException ex) { - throw new IllegalStateException("failed to build package signature source", ex); + throw new IllegalStateException("构建包签名原文失败", ex); } } diff --git a/src/main/java/com/cisd/tms/modules/backup/packagex/ResourcePackageServiceImpl.java b/src/main/java/com/cisd/tms/modules/backup/packagex/ResourcePackageServiceImpl.java index 3f78252..8330794 100644 --- a/src/main/java/com/cisd/tms/modules/backup/packagex/ResourcePackageServiceImpl.java +++ b/src/main/java/com/cisd/tms/modules/backup/packagex/ResourcePackageServiceImpl.java @@ -74,13 +74,13 @@ public class ResourcePackageServiceImpl implements ResourcePackageService { CreateResourceBackupRequest request ) { if (isBlank(taskId) || isBlank(backupId)) { - throw new IllegalArgumentException("taskId and backupId are required"); + throw new IllegalArgumentException("taskId和backupId不能为空"); } if (initTask == null) { - throw new IllegalArgumentException("initTask is required"); + throw new IllegalArgumentException("initTask不能为空"); } if (discoveredItems == null) { - throw new IllegalArgumentException("discoveredItems is required"); + throw new IllegalArgumentException("discoveredItems不能为空"); } try { Path taskDir = Path.of(resourceBackupProperties.getOutputDir()).resolve(taskId).normalize(); @@ -147,14 +147,14 @@ public class ResourcePackageServiceImpl implements ResourcePackageService { result.setManifestSummaryJson(objectMapper.writeValueAsString(manifestSummary)); return result; } catch (IOException ex) { - throw new IllegalStateException("failed to create backup package", ex); + throw new IllegalStateException("创建备份包失败", ex); } } @Override public ResourcePackageInspectResult inspectPackageHeader(Path packagePath) { if (packagePath == null) { - throw new IllegalArgumentException("packagePath is required"); + throw new IllegalArgumentException("packagePath不能为空"); } try (ZipFile zipFile = new ZipFile(packagePath.toFile())) { byte[] manifestBytes = readRequiredEntry(zipFile, "manifest.json"); @@ -173,14 +173,14 @@ public class ResourcePackageServiceImpl implements ResourcePackageService { result.setSignature(signatureBytes); return result; } catch (IOException ex) { - throw new IllegalStateException("failed to inspect backup package header", ex); + throw new IllegalStateException("解析备份包头失败", ex); } } @Override public ResourcePackageInspectResult inspectPackage(Path packagePath) { if (packagePath == null) { - throw new IllegalArgumentException("packagePath is required"); + throw new IllegalArgumentException("packagePath不能为空"); } try (ZipFile zipFile = new ZipFile(packagePath.toFile())) { byte[] manifestBytes = readRequiredEntry(zipFile, "manifest.json"); @@ -206,7 +206,7 @@ public class ResourcePackageServiceImpl implements ResourcePackageService { result.setSignature(signatureBytes); return result; } catch (IOException ex) { - throw new IllegalStateException("failed to inspect backup package", ex); + throw new IllegalStateException("解析备份包失败", ex); } } @@ -335,7 +335,7 @@ public class ResourcePackageServiceImpl implements ResourcePackageService { try { byte[] content = dump.getContent(); if (content == null || content.length == 0) { - throw new IllegalStateException("database backup collector returned empty dump"); + throw new IllegalStateException("数据库备份采集器返回空dump"); } String databaseName = trim(dump.getDatabaseName()).isEmpty() ? "TMS" : trim(dump.getDatabaseName()); String fileName = trim(dump.getFileName()).isEmpty() ? "TMS.sql" : trim(dump.getFileName()); @@ -434,7 +434,7 @@ public class ResourcePackageServiceImpl implements ResourcePackageService { } } } - throw new IllegalStateException("payload archive entry missing: payload-manifest.json"); + throw new IllegalStateException("payload归档条目缺失:payload-manifest.json"); } private static String buildArchiveEntryPath(DiscoveredResourceItem item, String relativeName) { @@ -458,7 +458,7 @@ public class ResourcePackageServiceImpl implements ResourcePackageService { try { return supplier.get(); } catch (IOException ex) { - throw new IllegalStateException("failed to write payload archive entry", ex); + throw new IllegalStateException("写入payload归档条目失败", ex); } } @@ -471,7 +471,7 @@ public class ResourcePackageServiceImpl implements ResourcePackageService { private static byte[] readRequiredEntry(ZipFile zipFile, String entryName) throws IOException { ZipEntry entry = zipFile.getEntry(entryName); if (entry == null) { - throw new IllegalStateException("backup package entry missing: " + entryName); + throw new IllegalStateException("备份包条目缺失:" + entryName); } try (InputStream inputStream = zipFile.getInputStream(entry)) { return inputStream.readAllBytes(); diff --git a/src/main/java/com/cisd/tms/modules/backup/packagex/ResourcePayloadExtractor.java b/src/main/java/com/cisd/tms/modules/backup/packagex/ResourcePayloadExtractor.java index e9eca53..07a0369 100644 --- a/src/main/java/com/cisd/tms/modules/backup/packagex/ResourcePayloadExtractor.java +++ b/src/main/java/com/cisd/tms/modules/backup/packagex/ResourcePayloadExtractor.java @@ -40,12 +40,12 @@ public class ResourcePayloadExtractor { Path payloadDir = normalizedWorkDir.resolve("payload").normalize(); ResourcePackageInspectResult inspectResult = resourcePackageService.inspectPackage(packagePath); byte[] payloadArchiveBytes = Optional.ofNullable(inspectResult.getPayloadArchiveBytes()) - .orElseThrow(() -> new IllegalStateException("payload archive bytes missing")); + .orElseThrow(() -> new IllegalStateException("payload归档内容缺失")); extractPayloadArchive(payloadArchiveBytes, payloadDir); Path manifestPath = payloadDir.resolve("payload-manifest.json").normalize(); if (!Files.isRegularFile(manifestPath)) { - throw new IllegalStateException("payload manifest missing: " + manifestPath); + throw new IllegalStateException("payload清单缺失:" + manifestPath); } Map manifest = readManifest(manifestPath); validateRestoreTargets(payloadDir, manifest); @@ -77,14 +77,14 @@ public class ResourcePayloadExtractor { } } } catch (IOException ex) { - throw new IllegalStateException("failed to extract payload archive", ex); + throw new IllegalStateException("解压payload归档失败", ex); } } private Path resolveSafePayloadTarget(Path payloadDir, String entryName) { Path target = payloadDir.resolve(entryName).normalize(); if (!target.startsWith(payloadDir)) { - throw new IllegalStateException("payload archive entry escapes payload dir: " + entryName); + throw new IllegalStateException("payload归档条目越过payload目录:" + entryName); } return target; } @@ -94,7 +94,7 @@ public class ResourcePayloadExtractor { return objectMapper.readValue(manifestPath.toFile(), new TypeReference>() { }); } catch (IOException ex) { - throw new IllegalStateException("failed to read payload manifest: " + manifestPath, ex); + throw new IllegalStateException("读取payload清单失败:" + manifestPath, ex); } } @@ -154,7 +154,7 @@ public class ResourcePayloadExtractor { Files.writeString(workDir.resolve("restore-mq.tsv"), String.join(System.lineSeparator(), mqLines) + (mqLines.isEmpty() ? "" : System.lineSeparator())); } catch (IOException ex) { - throw new IllegalStateException("failed to write shell restore plans", ex); + throw new IllegalStateException("写入shell恢复计划失败", ex); } } @@ -213,7 +213,7 @@ public class ResourcePayloadExtractor { } Path sourceFile = payloadDir.resolve(entryPath).normalize(); if (!sourceFile.startsWith(payloadDir)) { - throw new IllegalStateException("payload manifest entry escapes payload dir: " + entryPath); + throw new IllegalStateException("payload清单条目越过payload目录:" + entryPath); } } @@ -236,7 +236,7 @@ public class ResourcePayloadExtractor { Path target = Path.of(restorePath).normalize().toAbsolutePath(); boolean allowed = allowedRoots.stream().anyMatch(target::startsWith); if (!allowed) { - throw new IllegalStateException("restore path is not allowed: " + restorePath); + throw new IllegalStateException("恢复路径不允许:" + restorePath); } } @@ -276,7 +276,7 @@ public class ResourcePayloadExtractor { objectMapper.writerWithDefaultPrettyPrinter().writeValue(restoreContextPath.toFile(), context); return restoreContextPath; } catch (IOException ex) { - throw new IllegalStateException("failed to write restore context", ex); + throw new IllegalStateException("写入恢复上下文失败", ex); } } diff --git a/src/main/java/com/cisd/tms/modules/backup/restore/LightweightRestoreApplierLauncher.java b/src/main/java/com/cisd/tms/modules/backup/restore/LightweightRestoreApplierLauncher.java index e7a508c..b911d24 100644 --- a/src/main/java/com/cisd/tms/modules/backup/restore/LightweightRestoreApplierLauncher.java +++ b/src/main/java/com/cisd/tms/modules/backup/restore/LightweightRestoreApplierLauncher.java @@ -46,7 +46,7 @@ public class LightweightRestoreApplierLauncher { ); int exitCode = invoker.run(command, logFile, normalizedWorkDir); if (exitCode != 0) { - throw new IllegalStateException("failed to launch lightweight restore apply script, exitCode=" + exitCode); + throw new IllegalStateException("启动轻量恢复应用脚本失败,exitCode=" + exitCode); } } @@ -76,7 +76,7 @@ public class LightweightRestoreApplierLauncher { } Files.writeString(workDir.resolve("restore-env.sh"), builder.toString()); } catch (IOException ex) { - throw new IllegalStateException("failed to write lightweight restore env", ex); + throw new IllegalStateException("写入轻量恢复环境变量失败", ex); } } diff --git a/src/main/java/com/cisd/tms/modules/backup/restore/ProcessBuilderRestoreCommandInvoker.java b/src/main/java/com/cisd/tms/modules/backup/restore/ProcessBuilderRestoreCommandInvoker.java index 385a096..c3937ec 100644 --- a/src/main/java/com/cisd/tms/modules/backup/restore/ProcessBuilderRestoreCommandInvoker.java +++ b/src/main/java/com/cisd/tms/modules/backup/restore/ProcessBuilderRestoreCommandInvoker.java @@ -27,10 +27,10 @@ public class ProcessBuilderRestoreCommandInvoker implements RestoreCommandInvoke Process process = pb.start(); return process.waitFor(); } catch (IOException ex) { - throw new IllegalStateException("failed to start restore command: " + String.join(" ", command), ex); + throw new IllegalStateException("启动恢复命令失败:" + String.join(" ", command), ex); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); - throw new IllegalStateException("restore command interrupted: " + String.join(" ", command), ex); + throw new IllegalStateException("restore command 被中断: " + String.join(" ", command), ex); } } } diff --git a/src/main/java/com/cisd/tms/modules/backup/restore/RestoreStatusFileReader.java b/src/main/java/com/cisd/tms/modules/backup/restore/RestoreStatusFileReader.java index 1727af2..d2bf072 100644 --- a/src/main/java/com/cisd/tms/modules/backup/restore/RestoreStatusFileReader.java +++ b/src/main/java/com/cisd/tms/modules/backup/restore/RestoreStatusFileReader.java @@ -44,7 +44,7 @@ public class RestoreStatusFileReader { return objectMapper.readValue(statusFile.toFile(), new TypeReference>() { }); } catch (IOException ex) { - throw new IllegalStateException("failed to read restore status file: " + statusFile, ex); + throw new IllegalStateException("读取恢复状态文件失败:" + statusFile, ex); } } @@ -59,7 +59,7 @@ public class RestoreStatusFileReader { } return new String(bytes, bytes.length - MAX_LOG_BYTES, MAX_LOG_BYTES, StandardCharsets.UTF_8); } catch (IOException ex) { - throw new IllegalStateException("failed to read restore log file: " + logFile, ex); + throw new IllegalStateException("读取恢复日志文件失败:" + logFile, ex); } } diff --git a/src/main/java/com/cisd/tms/modules/backup/service/impl/ResourceBackupServiceImpl.java b/src/main/java/com/cisd/tms/modules/backup/service/impl/ResourceBackupServiceImpl.java index 3a9cf67..af522a9 100644 --- a/src/main/java/com/cisd/tms/modules/backup/service/impl/ResourceBackupServiceImpl.java +++ b/src/main/java/com/cisd/tms/modules/backup/service/impl/ResourceBackupServiceImpl.java @@ -59,7 +59,7 @@ public class ResourceBackupServiceImpl implements ResourceBackupService { public CreateResourceBackupResponse createBackupTask(CreateResourceBackupRequest request) { // 资源备份必须基于“最近一次成功初始化”快照收集上下文,避免前端重新传一套可能失真的参数。 InitTaskEntity initTask = initTaskRepository.findLatestByTaskTypeAndStatus("INIT", "SUCCESS") - .orElseThrow(() -> new BizException(ErrorCode.BIZ_ERROR.getCode(), "successful init task is missing")); + .orElseThrow(() -> new BizException(ErrorCode.BIZ_ERROR.getCode(), "成功ful init task is missing")); String taskId = resourceTaskIdGenerator.nextBackupTaskId(); String backupId = resourceTaskIdGenerator.nextBackupId(); @@ -119,7 +119,7 @@ public class ResourceBackupServiceImpl implements ResourceBackupService { @Override public ResourceBackupTaskDetailResponse getBackupTask(String taskId) { ResourceBackupTaskEntity entity = resourceBackupTaskRepository.findByTaskId(trim(taskId)) - .orElseThrow(() -> new BizException(ErrorCode.BIZ_ERROR.getCode(), "resource backup task not found: " + trim(taskId))); + .orElseThrow(() -> new BizException(ErrorCode.BIZ_ERROR.getCode(), "资源备份任务不存在:" + trim(taskId))); ResourceBackupTaskDetailResponse response = new ResourceBackupTaskDetailResponse(); response.setTaskId(entity.getTaskId()); response.setBackupId(entity.getBackupId()); @@ -147,10 +147,10 @@ public class ResourceBackupServiceImpl implements ResourceBackupService { @Override public ResourceBackupDownloadResult downloadBackupPackage(String taskId) { ResourceBackupTaskEntity entity = resourceBackupTaskRepository.findByTaskId(trim(taskId)) - .orElseThrow(() -> new BizException(ErrorCode.BIZ_ERROR.getCode(), "resource backup task not found: " + trim(taskId))); + .orElseThrow(() -> new BizException(ErrorCode.BIZ_ERROR.getCode(), "资源备份任务不存在:" + trim(taskId))); Path packagePath = Path.of(trim(entity.getPackagePath())).normalize().toAbsolutePath(); if (trim(entity.getPackagePath()).isEmpty() || !Files.exists(packagePath) || !Files.isRegularFile(packagePath)) { - throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "resource backup package file not found"); + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "资源备份包文件不存在"); } try { ResourceBackupDownloadResult result = new ResourceBackupDownloadResult(); @@ -166,7 +166,7 @@ public class ResourceBackupServiceImpl implements ResourceBackupService { @Override public List getBackupTaskSteps(String taskId) { ResourceBackupTaskEntity entity = resourceBackupTaskRepository.findByTaskId(trim(taskId)) - .orElseThrow(() -> new BizException(ErrorCode.BIZ_ERROR.getCode(), "resource backup task not found: " + trim(taskId))); + .orElseThrow(() -> new BizException(ErrorCode.BIZ_ERROR.getCode(), "资源备份任务不存在:" + trim(taskId))); ResourceTaskStepItemResponse item = new ResourceTaskStepItemResponse(); item.setStepNo(1); item.setStepCode("PACKAGE_CREATE"); @@ -181,7 +181,7 @@ public class ResourceBackupServiceImpl implements ResourceBackupService { @Override public ResourceTaskStepLogResponse getBackupTaskStepLog(String taskId, int stepNo) { ResourceBackupTaskEntity entity = resourceBackupTaskRepository.findByTaskId(trim(taskId)) - .orElseThrow(() -> new BizException(ErrorCode.BIZ_ERROR.getCode(), "resource backup task not found: " + trim(taskId))); + .orElseThrow(() -> new BizException(ErrorCode.BIZ_ERROR.getCode(), "资源备份任务不存在:" + trim(taskId))); ResourceTaskStepLogResponse response = new ResourceTaskStepLogResponse(); response.setTaskId(trim(taskId)); response.setStepNo(stepNo); diff --git a/src/main/java/com/cisd/tms/modules/backup/service/impl/ResourcePrecheckServiceImpl.java b/src/main/java/com/cisd/tms/modules/backup/service/impl/ResourcePrecheckServiceImpl.java index 6d56162..09fad98 100644 --- a/src/main/java/com/cisd/tms/modules/backup/service/impl/ResourcePrecheckServiceImpl.java +++ b/src/main/java/com/cisd/tms/modules/backup/service/impl/ResourcePrecheckServiceImpl.java @@ -35,7 +35,7 @@ public class ResourcePrecheckServiceImpl implements ResourcePrecheckService { @Override public ResourceRestorePrecheckResponse precheck(ResourceRestorePrecheckRequest request) { FileRecordEntity fileRecord = fileRecordRepository.findByFileId(trim(request.getFileId())) - .orElseThrow(() -> new BizException(ErrorCode.BIZ_ERROR.getCode(), "backup package file not found: " + trim(request.getFileId()))); + .orElseThrow(() -> new BizException(ErrorCode.BIZ_ERROR.getCode(), "备份包文件不存在:" + trim(request.getFileId()))); // 预检阶段只解析包头、验签和比对指纹,不提前解密完整 payload。 ResourcePackageInspectResult inspectResult = resourcePackageService.inspectPackageHeader(Path.of(fileRecord.getStoragePath())); String storageFingerprint = stringValue(inspectResult.getManifest().get("storageEncKeyFingerprint")); diff --git a/src/main/java/com/cisd/tms/modules/backup/service/impl/ResourceRestoreServiceImpl.java b/src/main/java/com/cisd/tms/modules/backup/service/impl/ResourceRestoreServiceImpl.java index 5de7def..7763866 100644 --- a/src/main/java/com/cisd/tms/modules/backup/service/impl/ResourceRestoreServiceImpl.java +++ b/src/main/java/com/cisd/tms/modules/backup/service/impl/ResourceRestoreServiceImpl.java @@ -41,21 +41,21 @@ public class ResourceRestoreServiceImpl implements ResourceRestoreService { @Override public CreateResourceRestoreResponse createRestoreTask(CreateResourceRestoreRequest request) { if (!Boolean.TRUE.equals(request.getConfirmRiskAccepted())) { - throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "restore risk must be accepted"); + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "必须确认恢复风险"); } // 恢复创建必须消费预检结果,确保“用户确认的 backupId”与预检看到的是同一个包。 ResourcePrecheckContext context = resourcePrecheckStateStore.find(request.getPrecheckId()) - .orElseThrow(() -> new BizException(ErrorCode.BIZ_ERROR.getCode(), "resource restore precheck not found")); + .orElseThrow(() -> new BizException(ErrorCode.BIZ_ERROR.getCode(), "资源恢复预检不存在")); ResourceRestorePrecheckResponse precheck = context.getResponse(); validatePrecheck(precheck); if (!trim(request.getConfirmBackupId()).equals(trim(precheck.getBackupId()))) { throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "confirmed backup id does not match precheck result"); } FileRecordEntity packageFile = fileRecordRepository.findByFileId(context.getFileId()) - .orElseThrow(() -> new BizException(ErrorCode.BIZ_ERROR.getCode(), "resource backup package file not found: " + trim(context.getFileId()))); + .orElseThrow(() -> new BizException(ErrorCode.BIZ_ERROR.getCode(), "资源备份包文件不存在:" + trim(context.getFileId()))); String packagePath = trim(packageFile.getStoragePath()); if (packagePath.isEmpty()) { - throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "resource backup package path is empty"); + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "资源备份包路径为空"); } String taskId = resourceTaskIdGenerator.nextRestoreTaskId(); @@ -97,7 +97,7 @@ public class ResourceRestoreServiceImpl implements ResourceRestoreService { try { Files.createDirectories(taskDir); } catch (IOException ex) { - throw new IllegalStateException("failed to create restore task dir: " + taskDir, ex); + throw new IllegalStateException("创建恢复任务目录失败:" + taskDir, ex); } } @@ -107,7 +107,7 @@ public class ResourceRestoreServiceImpl implements ResourceRestoreService { private static void validatePrecheck(ResourceRestorePrecheckResponse precheck) { if (precheck == null) { - throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "resource restore precheck not found"); + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "资源恢复预检不存在"); } if (!Boolean.TRUE.equals(precheck.getCompatible())) { throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "resource restore precheck is not compatible"); @@ -116,10 +116,10 @@ public class ResourceRestoreServiceImpl implements ResourceRestoreService { if (!expiresAt.isEmpty()) { try { if (LocalDateTime.parse(expiresAt).isBefore(LocalDateTime.now())) { - throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "resource restore precheck expired"); + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "资源恢复预检已过期"); } } catch (DateTimeException ex) { - throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "resource restore precheck expiresAt is invalid"); + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "资源恢复预检过期时间无效"); } } } diff --git a/src/main/java/com/cisd/tms/modules/backup/service/impl/ResourceTaskQueryServiceImpl.java b/src/main/java/com/cisd/tms/modules/backup/service/impl/ResourceTaskQueryServiceImpl.java index 1474edd..87035cd 100644 --- a/src/main/java/com/cisd/tms/modules/backup/service/impl/ResourceTaskQueryServiceImpl.java +++ b/src/main/java/com/cisd/tms/modules/backup/service/impl/ResourceTaskQueryServiceImpl.java @@ -30,7 +30,7 @@ public class ResourceTaskQueryServiceImpl implements ResourceTaskQueryService { @Override public ResourceRestoreTaskDetailResponse getRestoreTask(String taskId) { ResourceRestoreTaskEntity entity = resourceRestoreTaskRepository.findByTaskId(trim(taskId)) - .orElseThrow(() -> new BizException(ErrorCode.BIZ_ERROR.getCode(), "resource restore task not found: " + trim(taskId))); + .orElseThrow(() -> new BizException(ErrorCode.BIZ_ERROR.getCode(), "资源恢复任务不存在:" + trim(taskId))); ResourceRestoreTaskDetailResponse response = new ResourceRestoreTaskDetailResponse(); response.setTaskId(entity.getTaskId()); response.setPrecheckId(entity.getPrecheckId()); @@ -59,7 +59,7 @@ public class ResourceTaskQueryServiceImpl implements ResourceTaskQueryService { @Override public List getRestoreTaskSteps(String taskId) { ResourceRestoreTaskEntity entity = resourceRestoreTaskRepository.findByTaskId(trim(taskId)) - .orElseThrow(() -> new BizException(ErrorCode.BIZ_ERROR.getCode(), "resource restore task not found: " + trim(taskId))); + .orElseThrow(() -> new BizException(ErrorCode.BIZ_ERROR.getCode(), "资源恢复任务不存在:" + trim(taskId))); List restorePlan = readStringList(entity.getRestorePlanJson()); Path taskDir = resolveTaskDir(entity); RestoreStatusSnapshot snapshot = restoreStatusFileReader.read(taskDir); @@ -79,7 +79,7 @@ public class ResourceTaskQueryServiceImpl implements ResourceTaskQueryService { @Override public ResourceTaskStepLogResponse getRestoreTaskStepLog(String taskId, int stepNo) { ResourceRestoreTaskEntity entity = resourceRestoreTaskRepository.findByTaskId(trim(taskId)) - .orElseThrow(() -> new BizException(ErrorCode.BIZ_ERROR.getCode(), "resource restore task not found: " + trim(taskId))); + .orElseThrow(() -> new BizException(ErrorCode.BIZ_ERROR.getCode(), "资源恢复任务不存在:" + trim(taskId))); List restorePlan = readStringList(entity.getRestorePlanJson()); String stepCode = stepNo > 0 && stepNo <= restorePlan.size() ? restorePlan.get(stepNo - 1) : ""; RestoreStatusSnapshot snapshot = restoreStatusFileReader.read(resolveTaskDir(entity)); @@ -150,7 +150,7 @@ public class ResourceTaskQueryServiceImpl implements ResourceTaskQueryService { return objectMapper.readValue(json, new TypeReference>() { }); } catch (Exception ex) { - throw new IllegalStateException("failed to parse restore plan json", ex); + throw new IllegalStateException("解析恢复计划JSON失败", ex); } } @@ -161,7 +161,7 @@ public class ResourceTaskQueryServiceImpl implements ResourceTaskQueryService { try { return objectMapper.readValue(json, Object.class); } catch (Exception ex) { - throw new IllegalStateException("failed to parse consistency report json", ex); + throw new IllegalStateException("解析一致性报告JSON失败", ex); } } diff --git a/src/main/java/com/cisd/tms/modules/backup/support/InMemoryResourcePrecheckStateStore.java b/src/main/java/com/cisd/tms/modules/backup/support/InMemoryResourcePrecheckStateStore.java index 25ac38b..d7240fa 100644 --- a/src/main/java/com/cisd/tms/modules/backup/support/InMemoryResourcePrecheckStateStore.java +++ b/src/main/java/com/cisd/tms/modules/backup/support/InMemoryResourcePrecheckStateStore.java @@ -13,7 +13,7 @@ public class InMemoryResourcePrecheckStateStore implements ResourcePrecheckState @Override public void save(ResourcePrecheckContext context) { if (context == null || context.getResponse() == null || context.getResponse().getPrecheckId() == null) { - throw new IllegalArgumentException("context with precheckId is required"); + throw new IllegalArgumentException("context with precheckId不能为空"); } contexts.put(context.getResponse().getPrecheckId(), context); } diff --git a/src/main/java/com/cisd/tms/modules/backup/support/file/FileSystemResourcePrecheckStateStore.java b/src/main/java/com/cisd/tms/modules/backup/support/file/FileSystemResourcePrecheckStateStore.java index 44d9803..5d5a7e2 100644 --- a/src/main/java/com/cisd/tms/modules/backup/support/file/FileSystemResourcePrecheckStateStore.java +++ b/src/main/java/com/cisd/tms/modules/backup/support/file/FileSystemResourcePrecheckStateStore.java @@ -31,14 +31,14 @@ public class FileSystemResourcePrecheckStateStore implements ResourcePrecheckSta @Override public void save(ResourcePrecheckContext context) { if (context == null || context.getResponse() == null || context.getResponse().getPrecheckId() == null) { - throw new IllegalArgumentException("context with precheckId is required"); + throw new IllegalArgumentException("context with precheckId不能为空"); } try { Path file = resolvePath(context.getResponse().getPrecheckId()); Files.createDirectories(file.getParent()); objectMapper.writeValue(file.toFile(), new StoredResourcePrecheckContext(context.getFileId(), context.getResponse())); } catch (IOException ex) { - throw new IllegalStateException("failed to persist resource precheck context", ex); + throw new IllegalStateException("保存资源恢复预检上下文失败", ex); } } @@ -54,7 +54,7 @@ public class FileSystemResourcePrecheckStateStore implements ResourcePrecheckSta ResourceRestorePrecheckResponse response = objectMapper.treeToValue(root.path("response"), ResourceRestorePrecheckResponse.class); return Optional.of(new ResourcePrecheckContext(fileId, response)); } catch (IOException ex) { - throw new IllegalStateException("failed to load resource precheck context", ex); + throw new IllegalStateException("加载资源恢复预检上下文失败", ex); } } diff --git a/src/main/java/com/cisd/tms/modules/backup/support/file/FileSystemResourceTaskStepLogService.java b/src/main/java/com/cisd/tms/modules/backup/support/file/FileSystemResourceTaskStepLogService.java index 912d47a..5ded2e2 100644 --- a/src/main/java/com/cisd/tms/modules/backup/support/file/FileSystemResourceTaskStepLogService.java +++ b/src/main/java/com/cisd/tms/modules/backup/support/file/FileSystemResourceTaskStepLogService.java @@ -31,7 +31,7 @@ public class FileSystemResourceTaskStepLogService implements ResourceTaskStepLog response.setTruncated(false); return response; } catch (Exception ex) { - throw new IllegalStateException("failed to read resource task step log", ex); + throw new IllegalStateException("读取资源任务步骤日志失败", ex); } } } diff --git a/src/main/java/com/cisd/tms/modules/cert/controller/CrlController.java b/src/main/java/com/cisd/tms/modules/cert/controller/CrlController.java index 2caf746..ad707e3 100644 --- a/src/main/java/com/cisd/tms/modules/cert/controller/CrlController.java +++ b/src/main/java/com/cisd/tms/modules/cert/controller/CrlController.java @@ -5,6 +5,8 @@ import com.cisd.tms.common.api.ApiResponse; import com.cisd.tms.modules.auth.enums.AuthLevel; import com.cisd.tms.modules.auth.enums.RoleCode; import com.cisd.tms.modules.auth.security.RequireInternalAuth; +import com.cisd.tms.modules.cert.dto.CrlImportTaskDetailRequest; +import com.cisd.tms.modules.cert.dto.CrlImportTaskResponse; import com.cisd.tms.modules.cert.dto.CrlListRequest; import com.cisd.tms.modules.cert.dto.CrlRevokedItemResponse; import com.cisd.tms.modules.cert.dto.IdRequest; @@ -40,12 +42,18 @@ public class CrlController { // @ReplayProtected @AuditedOperation(module = ModuleCode.KEY, action = ActionType.IMPORT, summary = "导入证书吊销列表") @RequireInternalAuth(role = RoleCode.KEY_ADMIN, authLevel = AuthLevel.FULL) - public ApiResponse importCrl( + public ApiResponse importCrl( @Parameter(description = "CRL 文件,支持 PEM/DER X.509 CRL") @RequestPart("file") MultipartFile file ) { - crlService.importCrl(file); - return ApiResponse.success(); + return ApiResponse.success(crlService.submitImport(file)); + } + + @PostMapping("/import-tasks/detail") + @Operation(summary = "查询CRL导入任务", description = "根据任务号查询 CRL 后台导入任务状态、导入结果和失败原因。") + @RequireInternalAuth(role = RoleCode.KEY_ADMIN, authLevel = AuthLevel.LIMITED) + public ApiResponse importTaskDetail(@Valid @RequestBody CrlImportTaskDetailRequest request) { + return ApiResponse.success(crlService.getImportTask(request.getTaskId())); } @PostMapping("/list") diff --git a/src/main/java/com/cisd/tms/modules/cert/dto/CrlImportTaskDetailRequest.java b/src/main/java/com/cisd/tms/modules/cert/dto/CrlImportTaskDetailRequest.java new file mode 100644 index 0000000..69eb56f --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/cert/dto/CrlImportTaskDetailRequest.java @@ -0,0 +1,14 @@ +package com.cisd.tms.modules.cert.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +@Data +@Schema(description = "CRL导入任务详情请求") +public class CrlImportTaskDetailRequest { + + @NotBlank(message = "任务号不能为空") + @Schema(description = "CRL导入任务号", example = "CRL202604301800001234") + private String taskId; +} diff --git a/src/main/java/com/cisd/tms/modules/cert/dto/CrlImportTaskResponse.java b/src/main/java/com/cisd/tms/modules/cert/dto/CrlImportTaskResponse.java new file mode 100644 index 0000000..4707d87 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/cert/dto/CrlImportTaskResponse.java @@ -0,0 +1,41 @@ +package com.cisd.tms.modules.cert.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +@Schema(description = "CRL导入任务响应") +public class CrlImportTaskResponse { + + @Schema(description = "CRL导入任务号", example = "CRL202604301800001234") + private String taskId; + + @Schema(description = "任务状态:PENDING、RUNNING、SUCCESS、FAILED", example = "RUNNING") + private String status; + + @Schema(description = "上传文件名", example = "revoked.crl") + private String originalFilename; + + @Schema(description = "上传文件大小,单位字节", example = "102400") + private Long fileSize; + + @Schema(description = "导入成功后的CRL记录ID", example = "2049756829261557762") + private Long crlId; + + @Schema(description = "CRL SHA-256指纹", example = "083C476C26DFD8876E0580601EA66726DF06BB20989DFD862BB34B9DD8B68094") + private String fingerprint; + + @Schema(description = "吊销明细数量", example = "128") + private Integer revokedCount; + + @Schema(description = "失败原因", example = "CRL已存在") + private String errorMessage; + + @Schema(description = "任务开始时间", example = "2026-04-30T18:00:00") + private LocalDateTime startedAt; + + @Schema(description = "任务结束时间", example = "2026-04-30T18:01:00") + private LocalDateTime finishedAt; +} diff --git a/src/main/java/com/cisd/tms/modules/cert/entity/CertificateCrlImportTaskEntity.java b/src/main/java/com/cisd/tms/modules/cert/entity/CertificateCrlImportTaskEntity.java new file mode 100644 index 0000000..feb9262 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/cert/entity/CertificateCrlImportTaskEntity.java @@ -0,0 +1,27 @@ +package com.cisd.tms.modules.cert.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.cisd.tms.infrastructure.persistence.entity.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +import java.time.LocalDateTime; + +@Data +@Accessors(chain = true) +@EqualsAndHashCode(callSuper = true) +@TableName("t_certificate_crl_import_task") +public class CertificateCrlImportTaskEntity extends BaseEntity { + + private String taskId; + private String originalFilename; + private Long fileSize; + private String status; + private String fingerprint; + private Long crlId; + private Integer revokedCount; + private String errorMessage; + private LocalDateTime startedAt; + private LocalDateTime finishedAt; +} diff --git a/src/main/java/com/cisd/tms/modules/cert/mapper/CertificateCrlImportTaskMapper.java b/src/main/java/com/cisd/tms/modules/cert/mapper/CertificateCrlImportTaskMapper.java new file mode 100644 index 0000000..2171060 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/cert/mapper/CertificateCrlImportTaskMapper.java @@ -0,0 +1,30 @@ +package com.cisd.tms.modules.cert.mapper; + +import com.cisd.tms.infrastructure.persistence.mapper.BaseMapperX; +import com.cisd.tms.modules.cert.entity.CertificateCrlImportTaskEntity; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.time.LocalDateTime; + +@Mapper +public interface CertificateCrlImportTaskMapper extends BaseMapperX { + + CertificateCrlImportTaskEntity selectByTaskId(@Param("taskId") String taskId); + + int updateRunning(@Param("taskId") String taskId, @Param("startedAt") LocalDateTime startedAt); + + int updateSuccess( + @Param("taskId") String taskId, + @Param("crlId") Long crlId, + @Param("fingerprint") String fingerprint, + @Param("revokedCount") Integer revokedCount, + @Param("finishedAt") LocalDateTime finishedAt + ); + + int updateFailed( + @Param("taskId") String taskId, + @Param("errorMessage") String errorMessage, + @Param("finishedAt") LocalDateTime finishedAt + ); +} diff --git a/src/main/java/com/cisd/tms/modules/cert/mapper/CertificateCrlRevokedEntityMapper.java b/src/main/java/com/cisd/tms/modules/cert/mapper/CertificateCrlRevokedEntityMapper.java index 7cd269b..bf567d7 100644 --- a/src/main/java/com/cisd/tms/modules/cert/mapper/CertificateCrlRevokedEntityMapper.java +++ b/src/main/java/com/cisd/tms/modules/cert/mapper/CertificateCrlRevokedEntityMapper.java @@ -3,7 +3,12 @@ package com.cisd.tms.modules.cert.mapper; import com.cisd.tms.infrastructure.persistence.mapper.BaseMapperX; import com.cisd.tms.modules.cert.entity.CertificateCrlRevokedEntity; import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; @Mapper public interface CertificateCrlRevokedEntityMapper extends BaseMapperX { + + int insertBatch(@Param("list") List list); } diff --git a/src/main/java/com/cisd/tms/modules/cert/repository/CrlRepository.java b/src/main/java/com/cisd/tms/modules/cert/repository/CrlRepository.java index 02ce78a..a03c8d7 100644 --- a/src/main/java/com/cisd/tms/modules/cert/repository/CrlRepository.java +++ b/src/main/java/com/cisd/tms/modules/cert/repository/CrlRepository.java @@ -3,13 +3,22 @@ package com.cisd.tms.modules.cert.repository; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.cisd.tms.modules.cert.dto.CrlListRequest; import com.cisd.tms.modules.cert.entity.CertificateCrlEntity; +import com.cisd.tms.modules.cert.entity.CertificateCrlImportTaskEntity; import com.cisd.tms.modules.cert.entity.CertificateCrlRevokedEntity; +import java.time.LocalDateTime; +import java.util.List; import java.util.Optional; public interface CrlRepository { CertificateCrlEntity saveCrl(CertificateCrlEntity entity); CertificateCrlRevokedEntity saveRevoked(CertificateCrlRevokedEntity entity); + void saveRevokedBatch(List entities); + CertificateCrlImportTaskEntity saveImportTask(CertificateCrlImportTaskEntity entity); + Optional findImportTaskByTaskId(String taskId); + void updateImportTaskRunning(String taskId, LocalDateTime startedAt); + void updateImportTaskSuccess(String taskId, Long crlId, String fingerprint, Integer revokedCount, LocalDateTime finishedAt); + void updateImportTaskFailed(String taskId, String errorMessage, LocalDateTime finishedAt); Optional findCrlById(Long id); Optional findCrlByFingerprint(String fingerprint); void deleteCrlById(Long id); diff --git a/src/main/java/com/cisd/tms/modules/cert/repository/impl/CrlRepositoryImpl.java b/src/main/java/com/cisd/tms/modules/cert/repository/impl/CrlRepositoryImpl.java index 8d6fd8f..03dfea3 100644 --- a/src/main/java/com/cisd/tms/modules/cert/repository/impl/CrlRepositoryImpl.java +++ b/src/main/java/com/cisd/tms/modules/cert/repository/impl/CrlRepositoryImpl.java @@ -2,16 +2,20 @@ package com.cisd.tms.modules.cert.repository.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.core.toolkit.IdWorker; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.cisd.tms.modules.cert.dto.CrlListRequest; import com.cisd.tms.modules.cert.entity.CertificateCrlEntity; +import com.cisd.tms.modules.cert.entity.CertificateCrlImportTaskEntity; import com.cisd.tms.modules.cert.entity.CertificateCrlRevokedEntity; import com.cisd.tms.modules.cert.mapper.CertificateCrlEntityMapper; +import com.cisd.tms.modules.cert.mapper.CertificateCrlImportTaskMapper; import com.cisd.tms.modules.cert.mapper.CertificateCrlRevokedEntityMapper; import com.cisd.tms.modules.cert.repository.CrlRepository; import org.springframework.stereotype.Repository; import org.springframework.util.StringUtils; +import java.time.LocalDateTime; import java.util.List; import java.util.Optional; @@ -20,10 +24,16 @@ public class CrlRepositoryImpl implements CrlRepository { private final CertificateCrlEntityMapper crlMapper; private final CertificateCrlRevokedEntityMapper revokedMapper; + private final CertificateCrlImportTaskMapper importTaskMapper; - public CrlRepositoryImpl(CertificateCrlEntityMapper crlMapper, CertificateCrlRevokedEntityMapper revokedMapper) { + public CrlRepositoryImpl( + CertificateCrlEntityMapper crlMapper, + CertificateCrlRevokedEntityMapper revokedMapper, + CertificateCrlImportTaskMapper importTaskMapper + ) { this.crlMapper = crlMapper; this.revokedMapper = revokedMapper; + this.importTaskMapper = importTaskMapper; } @Override @@ -46,6 +56,56 @@ public class CrlRepositoryImpl implements CrlRepository { return entity; } + @Override + public void saveRevokedBatch(List entities) { + if (entities == null || entities.isEmpty()) { + return; + } + LocalDateTime now = LocalDateTime.now(); + for (CertificateCrlRevokedEntity entity : entities) { + if (entity.getId() == null) { + entity.setId(IdWorker.getId()); + } + if (entity.getCreateTime() == null) { + entity.setCreateTime(now); + } + if (entity.getUpdateTime() == null) { + entity.setUpdateTime(now); + } + } + revokedMapper.insertBatch(entities); + } + + @Override + public CertificateCrlImportTaskEntity saveImportTask(CertificateCrlImportTaskEntity entity) { + if (entity.getId() == null) { + importTaskMapper.insert(entity); + } else { + importTaskMapper.updateById(entity); + } + return entity; + } + + @Override + public Optional findImportTaskByTaskId(String taskId) { + return Optional.ofNullable(importTaskMapper.selectByTaskId(taskId)); + } + + @Override + public void updateImportTaskRunning(String taskId, LocalDateTime startedAt) { + importTaskMapper.updateRunning(taskId, startedAt); + } + + @Override + public void updateImportTaskSuccess(String taskId, Long crlId, String fingerprint, Integer revokedCount, LocalDateTime finishedAt) { + importTaskMapper.updateSuccess(taskId, crlId, fingerprint, revokedCount, finishedAt); + } + + @Override + public void updateImportTaskFailed(String taskId, String errorMessage, LocalDateTime finishedAt) { + importTaskMapper.updateFailed(taskId, errorMessage, finishedAt); + } + @Override public Optional findCrlById(Long id) { return Optional.ofNullable(crlMapper.selectById(id)); @@ -112,6 +172,7 @@ public class CrlRepositoryImpl implements CrlRepository { return new LambdaQueryWrapper() .eq(CertificateCrlRevokedEntity::getIssuerDn, issuerDn) .eq(CertificateCrlRevokedEntity::getSerialNumber, serialNumber) - .apply("EXISTS (SELECT 1 FROM t_certificate_crl c WHERE c.id = crl_id AND (c.next_update IS NULL OR c.next_update >= NOW()))"); + // 吊销状态只来自仍在有效期内的 CRL;过期或缺失 nextUpdate 的 CRL 不能继续影响运行态。 + .apply("EXISTS (SELECT 1 FROM t_certificate_crl c WHERE c.id = crl_id AND c.next_update >= NOW())"); } } diff --git a/src/main/java/com/cisd/tms/modules/cert/service/CertificateService.java b/src/main/java/com/cisd/tms/modules/cert/service/CertificateService.java index 0c42735..69915fc 100644 --- a/src/main/java/com/cisd/tms/modules/cert/service/CertificateService.java +++ b/src/main/java/com/cisd/tms/modules/cert/service/CertificateService.java @@ -11,14 +11,17 @@ import com.cisd.tms.modules.cert.entity.CertificateEntity; import com.cisd.tms.modules.cert.repository.CertificateRepository; import com.cisd.tms.modules.cert.support.CertPemSupport; import com.cisd.tms.modules.cert.support.CertRuntimeStatusResolver; +import org.bouncycastle.asn1.x509.KeyPurposeId; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StringUtils; import org.springframework.web.multipart.MultipartFile; import java.security.cert.X509Certificate; import java.time.LocalDateTime; import java.time.ZoneId; +import java.util.List; import java.util.Optional; @Service @@ -63,6 +66,7 @@ public class CertificateService { //todo file空值判断 X509Certificate certificate = CertPemSupport.parseCertificate(file.getBytes()); + // 终端证书入库前先固定 PKI 边界:只接受当前有效、非 CA、用途满足业务认证的实体/用户证书。 assertValidTime(certificate); assertEndEntityCertificate(certificate); String algoType = CertPemSupport.detectAlgoType(certificate); @@ -70,14 +74,17 @@ public class CertificateService { if (certificateRepository.findByFingerprint(fingerprint).isPresent()) { throw new BizException(ErrorCode.CONFLICT.getCode(), "证书已存在"); } + // 证书类型由公钥是否匹配本地实体自动推导,避免调用方伪造 ENTITY/USER 边界。 Optional matchedEntityId = entityService.findByCertificatePublicKey(certificate.getPublicKey().getEncoded()); Long finalEntityId = matchedEntityId.orElse(null); String certType = matchedEntityId.isPresent() ? "ENTITY" : "USER"; + // 先按签名解析真实 issuer,再递归校验当前可信 CA 链,不能只依赖 issuer DN 字符串相等。 var issuer = trustedCertService.resolveIssuer(certificate); trustedCertService.validateParentChain(issuer); String issuerDn = certificate.getIssuerX500Principal().getName(); String serialNumber = certificate.getSerialNumber().toString(16).toUpperCase(); if (isRevoked(issuerDn, serialNumber)) { + // CRL 可能先于证书入库;拒绝导入前回填 subject,保证吊销明细检索仍可按主体查到。 fillRevokedSubjectIfMissing(issuerDn, serialNumber, certificate.getSubjectX500Principal().getName(), algoType); throw new BizException(ErrorCode.BAD_REQUEST.getCode(), "证书已被CRL吊销"); } @@ -154,10 +161,18 @@ public class CertificateService { } private String resolveRuntimeStatus(CertificateEntity entity) { + // 运行态以“当前有效信任”为准:吊销优先,其次时间窗口,最后再用当前可信 CA 集合重验证书链。 if (isRevoked(entity.getIssuerDn(), entity.getSerialNumber())) { return "REVOKED"; } - return CertRuntimeStatusResolver.resolve(entity.getValidFrom(), entity.getValidTo()); + String timeStatus = CertRuntimeStatusResolver.resolve(entity.getValidFrom(), entity.getValidTo()); + if (!"VALID".equals(timeStatus)) { + return timeStatus; + } + if (StringUtils.hasText(entity.getCertData()) && !trustedCertService.validateCertificateChain(entity.getCertData())) { + return "INVALID_CHAIN"; + } + return timeStatus; } private boolean isRevoked(String issuerDn, String serialNumber) { @@ -195,6 +210,10 @@ public class CertificateService { } private void assertEndEntityCertificate(X509Certificate certificate) { + // RFC 5280 要求无法处理的 critical extension 必须拒绝,不能静默当作普通扩展。 + if (CertPemSupport.hasUnsupportedCriticalExtension(certificate)) { + throw new BizException(ErrorCode.BAD_REQUEST.getCode(), "证书包含不支持的关键扩展"); + } if (certificate.getBasicConstraints() >= 0) { throw new BizException(ErrorCode.BAD_REQUEST.getCode(), "实体/用户证书不能是CA证书"); } @@ -202,5 +221,24 @@ public class CertificateService { if (keyUsage != null && (keyUsage.length <= 0 || !keyUsage[0])) { throw new BizException(ErrorCode.BAD_REQUEST.getCode(), "实体/用户证书必须允许数字签名"); } + assertEndEntityExtendedKeyUsage(certificate); + } + + private void assertEndEntityExtendedKeyUsage(X509Certificate certificate) { + List extendedKeyUsage; + try { + extendedKeyUsage = certificate.getExtendedKeyUsage(); + } catch (Exception ex) { + throw new BizException(ErrorCode.BAD_REQUEST.getCode(), "证书扩展用途无效"); + } + if (extendedKeyUsage == null || extendedKeyUsage.isEmpty()) { + return; + } + if (extendedKeyUsage.contains(KeyPurposeId.anyExtendedKeyUsage.getId()) + || extendedKeyUsage.contains(KeyPurposeId.id_kp_clientAuth.getId()) + || extendedKeyUsage.contains(KeyPurposeId.id_kp_serverAuth.getId())) { + return; + } + throw new BizException(ErrorCode.BAD_REQUEST.getCode(), "实体/用户证书扩展用途不支持业务认证"); } } diff --git a/src/main/java/com/cisd/tms/modules/cert/service/CrlService.java b/src/main/java/com/cisd/tms/modules/cert/service/CrlService.java index d54d972..3c63df8 100644 --- a/src/main/java/com/cisd/tms/modules/cert/service/CrlService.java +++ b/src/main/java/com/cisd/tms/modules/cert/service/CrlService.java @@ -3,9 +3,11 @@ package com.cisd.tms.modules.cert.service; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.cisd.tms.common.enums.ErrorCode; import com.cisd.tms.common.exception.BizException; +import com.cisd.tms.modules.cert.dto.CrlImportTaskResponse; import com.cisd.tms.modules.cert.dto.CrlListRequest; import com.cisd.tms.modules.cert.dto.CrlRevokedItemResponse; import com.cisd.tms.modules.cert.entity.CertificateCrlEntity; +import com.cisd.tms.modules.cert.entity.CertificateCrlImportTaskEntity; import com.cisd.tms.modules.cert.entity.CertificateCrlRevokedEntity; import com.cisd.tms.modules.cert.entity.CertificateEntity; import com.cisd.tms.modules.cert.entity.TrustedCertEntity; @@ -16,42 +18,238 @@ import org.bouncycastle.asn1.ASN1OctetString; import org.bouncycastle.asn1.ASN1Primitive; import org.bouncycastle.asn1.x509.CRLNumber; import org.bouncycastle.asn1.x509.Extension; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.support.TransactionTemplate; import org.springframework.web.multipart.MultipartFile; +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.security.cert.X509CRL; import java.security.cert.X509CRLEntry; +import java.time.format.DateTimeFormatter; import java.time.LocalDateTime; import java.time.ZoneId; +import java.util.ArrayList; +import java.util.Base64; +import java.util.List; +import java.util.Set; import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; @Service public class CrlService { + private static final Logger log = LoggerFactory.getLogger(CrlService.class); + private static final int REVOKED_BATCH_SIZE = 1000; + private static final DateTimeFormatter TASK_ID_TS = DateTimeFormatter.ofPattern("yyyyMMddHHmmss"); + private final CrlRepository crlRepository; private final CertificateRepository certificateRepository; private final TrustedCertService trustedCertService; + private final Path crlTempDir; + private final long maxCrlFileSizeBytes; + private final Executor backgroundExecutor; + private final TransactionTemplate transactionTemplate; public CrlService( CrlRepository crlRepository, CertificateRepository certificateRepository, TrustedCertService trustedCertService + ) { + this( + crlRepository, + certificateRepository, + trustedCertService, + Path.of(System.getProperty("java.io.tmpdir"), "tms-crl-import").toString(), + 500L * 1024 * 1024, + Executors.newSingleThreadExecutor(), + null + ); + } + + @Autowired + public CrlService( + CrlRepository crlRepository, + CertificateRepository certificateRepository, + TrustedCertService trustedCertService, + @Value("${tms.cert.crl.temp-dir:/home/tms/tmp/crl-import}") String crlTempDir, + @Value("${tms.cert.crl.max-file-size-bytes:524288000}") long maxCrlFileSizeBytes, + PlatformTransactionManager transactionManager + ) { + this( + crlRepository, + certificateRepository, + trustedCertService, + crlTempDir, + maxCrlFileSizeBytes, + Executors.newSingleThreadExecutor(), + transactionManager + ); + } + + CrlService( + CrlRepository crlRepository, + CertificateRepository certificateRepository, + TrustedCertService trustedCertService, + String crlTempDir, + long maxCrlFileSizeBytes + ) { + this( + crlRepository, + certificateRepository, + trustedCertService, + crlTempDir, + maxCrlFileSizeBytes, + Executors.newSingleThreadExecutor(), + null + ); + } + + CrlService( + CrlRepository crlRepository, + CertificateRepository certificateRepository, + TrustedCertService trustedCertService, + String crlTempDir, + long maxCrlFileSizeBytes, + Executor backgroundExecutor + ) { + this( + crlRepository, + certificateRepository, + trustedCertService, + crlTempDir, + maxCrlFileSizeBytes, + backgroundExecutor, + null + ); + } + + private CrlService( + CrlRepository crlRepository, + CertificateRepository certificateRepository, + TrustedCertService trustedCertService, + String crlTempDir, + long maxCrlFileSizeBytes, + Executor backgroundExecutor, + PlatformTransactionManager transactionManager ) { this.crlRepository = crlRepository; this.certificateRepository = certificateRepository; this.trustedCertService = trustedCertService; + this.crlTempDir = Path.of(crlTempDir); + this.maxCrlFileSizeBytes = maxCrlFileSizeBytes; + this.backgroundExecutor = backgroundExecutor; + this.transactionTemplate = transactionManager == null ? null : new TransactionTemplate(transactionManager); } @Transactional public void importCrl(MultipartFile file) { + assertFileSizeAllowed(file); + Path uploadFile; try { - X509CRL crl = CertPemSupport.parseCrl(file.getBytes()); + uploadFile = copyUploadToTempFile(file); + } catch (Exception ex) { + log.warn("CRL upload materialization 执行失败:{}", ex.toString()); + throw new BizException(ErrorCode.BAD_REQUEST.getCode(), "CRL文件保存失败"); + } + try { + importCrlFile(uploadFile); + } finally { + deleteQuietly(uploadFile); + } + } + + public CrlImportTaskResponse submitImport(MultipartFile file) { + assertFileSizeAllowed(file); + Path uploadFile; + try { + // 大 CRL 不能在请求线程里转 byte[];先落临时文件,再把解析导入交给后台任务。 + uploadFile = copyUploadToTempFile(file); + } catch (Exception ex) { + log.warn("CRL upload materialization 执行失败:{}", ex.toString()); + throw new BizException(ErrorCode.BAD_REQUEST.getCode(), "CRL文件保存失败"); + } + CertificateCrlImportTaskEntity task = new CertificateCrlImportTaskEntity() + .setTaskId(buildTaskId()) + .setOriginalFilename(file.getOriginalFilename()) + .setFileSize(file.getSize()) + .setStatus("PENDING") + .setRevokedCount(0); + task.setCreateTime(LocalDateTime.now()); + task.setUpdateTime(LocalDateTime.now()); + try { + crlRepository.saveImportTask(task); + CompletableFuture.runAsync(() -> runSubmittedImport(task.getTaskId(), uploadFile), backgroundExecutor); + } catch (RuntimeException ex) { + deleteQuietly(uploadFile); + throw ex; + } + return toTaskResponse(task); + } + + public CrlImportTaskResponse getImportTask(String taskId) { + CertificateCrlImportTaskEntity task = crlRepository.findImportTaskByTaskId(taskId) + .orElseThrow(() -> new BizException(ErrorCode.BAD_REQUEST.getCode(), "CRL导入任务不存在")); + return toTaskResponse(task); + } + + private void runSubmittedImport(String taskId, Path uploadFile) { + crlRepository.updateImportTaskRunning(taskId, LocalDateTime.now()); + try { + // 元数据、CRL 指纹和 revoked 明细必须在同一事务里落库,避免任务成功但明细半导入。 + ImportResult result = runImportInTransaction(uploadFile); + crlRepository.updateImportTaskSuccess( + taskId, + result.crlId(), + result.fingerprint(), + result.revokedCount(), + LocalDateTime.now() + ); + } catch (BizException ex) { + crlRepository.updateImportTaskFailed(taskId, ex.getMessage(), LocalDateTime.now()); + } catch (Exception ex) { + log.warn("CRL async import 执行失败:{}", ex.toString()); + crlRepository.updateImportTaskFailed(taskId, "CRL导入失败", LocalDateTime.now()); + } finally { + deleteQuietly(uploadFile); + } + } + + private ImportResult runImportInTransaction(Path uploadFile) { + if (transactionTemplate == null) { + return importCrlFile(uploadFile); + } + return transactionTemplate.execute(status -> importCrlFile(uploadFile)); + } + + private ImportResult importCrlFile(Path uploadFile) { + MaterializedCrl materialized = null; + try { + // PEM 会流式解码成 DER 临时文件;DER 直接复用上传临时文件,避免 500MB CRL 进入堆内存。 + materialized = materializeCrlToDer(uploadFile); + X509CRL crl; + try (InputStream inputStream = Files.newInputStream(materialized.derFile())) { + crl = CertPemSupport.parseCrl(inputStream); + } assertEffective(crl); - String fingerprint = CertPemSupport.sha256Fingerprint(crl); + String fingerprint = materialized.fingerprint(); if (crlRepository.findCrlByFingerprint(fingerprint).isPresent()) { throw new BizException(ErrorCode.CONFLICT.getCode(), "CRL已存在"); } + // CRL 只有被当前可信 CA 签名且签发者允许 cRLSign 时才参与吊销判断。 TrustedCertEntity issuer = trustedCertService.resolveCrlIssuer(crl); trustedCertService.validateParentChain(issuer); String issuerDn = crl.getIssuerX500Principal().getName(); @@ -60,29 +258,36 @@ public class CrlService { algoType = "SM2"; } String crlNumber = crlNumber(crl); + Set revokedCertificates = crl.getRevokedCertificates(); CertificateCrlEntity entity = new CertificateCrlEntity() .setIssuerDn(issuerDn) .setCrlNumber(crlNumber) .setAlgoType(algoType) .setFingerprint(fingerprint) - .setCrlData(CertPemSupport.toPem(crl)) + // 原始 CRL 不长期保存;证据和查询依赖 fingerprint、元数据和 revoked 明细。 + .setCrlData("") .setThisUpdate(toLocalDateTime(crl.getThisUpdate())) .setNextUpdate(crl.getNextUpdate() == null ? null : toLocalDateTime(crl.getNextUpdate())) - .setRevokedCount(crl.getRevokedCertificates() == null ? 0 : crl.getRevokedCertificates().size()) + .setRevokedCount(revokedCertificates == null ? 0 : revokedCertificates.size()) .setImportTime(LocalDateTime.now()) .setCreateBy("system"); entity.setCreateTime(LocalDateTime.now()); entity.setUpdateTime(LocalDateTime.now()); crlRepository.saveCrl(entity); - if (crl.getRevokedCertificates() != null) { - for (X509CRLEntry entry : crl.getRevokedCertificates()) { - saveRevoked(entity.getId(), issuerDn, crlNumber, algoType, entry); - } + int revokedCount = 0; + if (revokedCertificates != null) { + revokedCount = saveRevokedRows(entity.getId(), issuerDn, crlNumber, algoType, revokedCertificates); } + return new ImportResult(entity.getId(), fingerprint, revokedCount); } catch (BizException ex) { throw ex; } catch (Exception ex) { + log.warn("CRL import 执行失败:{}", ex.toString()); throw new BizException(ErrorCode.BAD_REQUEST.getCode(), "CRL导入失败"); + } finally { + if (materialized != null) { + deleteQuietly(materialized.derFile()); + } } } @@ -101,7 +306,31 @@ public class CrlService { crlRepository.deleteCrlById(id); } - private void saveRevoked(Long crlId, String issuerDn, String crlNumber, String crlAlgoType, X509CRLEntry entry) { + private int saveRevokedRows( + Long crlId, + String issuerDn, + String crlNumber, + String crlAlgoType, + Set revokedCertificates + ) { + // 大 CRL 可能包含几十万条吊销明细,批量插入避免每条记录一次数据库往返。 + List batch = new ArrayList<>(Math.min(REVOKED_BATCH_SIZE, revokedCertificates.size())); + int count = 0; + for (X509CRLEntry entry : revokedCertificates) { + batch.add(buildRevoked(crlId, issuerDn, crlNumber, crlAlgoType, entry)); + count++; + if (batch.size() >= REVOKED_BATCH_SIZE) { + crlRepository.saveRevokedBatch(batch); + batch.clear(); + } + } + if (!batch.isEmpty()) { + crlRepository.saveRevokedBatch(batch); + } + return count; + } + + private CertificateCrlRevokedEntity buildRevoked(Long crlId, String issuerDn, String crlNumber, String crlAlgoType, X509CRLEntry entry) { String serialNumber = entry.getSerialNumber().toString(16).toUpperCase(); Optional certificate = certificateRepository.findByIssuerDnAndSerialNumber(issuerDn, serialNumber); CertificateCrlRevokedEntity revoked = new CertificateCrlRevokedEntity() @@ -115,7 +344,7 @@ public class CrlService { .setRevocationReason(reasonName(entry)); revoked.setCreateTime(LocalDateTime.now()); revoked.setUpdateTime(LocalDateTime.now()); - crlRepository.saveRevoked(revoked); + return revoked; } private CrlRevokedItemResponse toItem(CertificateCrlRevokedEntity entity) { @@ -138,7 +367,11 @@ public class CrlService { if (thisUpdate.isAfter(now)) { throw new BizException(ErrorCode.BAD_REQUEST.getCode(), "CRL生效时间晚于当前时间"); } - if (crl.getNextUpdate() != null && toLocalDateTime(crl.getNextUpdate()).isBefore(now)) { + if (crl.getNextUpdate() == null) { + // RFC 5280 要求 CRL issuer 提供 nextUpdate;缺失时不能让吊销状态永久生效。 + throw new BizException(ErrorCode.BAD_REQUEST.getCode(), "CRL缺少下次更新时间"); + } + if (toLocalDateTime(crl.getNextUpdate()).isBefore(now)) { throw new BizException(ErrorCode.BAD_REQUEST.getCode(), "CRL已过期"); } } @@ -179,4 +412,151 @@ public class CrlService { private LocalDateTime toLocalDateTime(java.util.Date date) { return LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()); } + + private void assertFileSizeAllowed(MultipartFile file) { + long fileSize = file.getSize(); + if (fileSize > maxCrlFileSizeBytes) { + throw new BizException(ErrorCode.BAD_REQUEST.getCode(), "CRL文件过大,最大支持" + readableSize(maxCrlFileSizeBytes)); + } + } + + private Path copyUploadToTempFile(MultipartFile file) throws java.io.IOException { + Files.createDirectories(crlTempDir); + Path uploadFile = Files.createTempFile(crlTempDir, "crl-upload-", ".tmp"); + if (file.getSize() > 0) { + try { + file.transferTo(uploadFile); + if (Files.size(uploadFile) == file.getSize()) { + return uploadFile; + } + } catch (Exception ex) { + Files.deleteIfExists(uploadFile); + uploadFile = Files.createTempFile(crlTempDir, "crl-upload-", ".tmp"); + } + } + try (InputStream inputStream = file.getInputStream()) { + Files.copy(inputStream, uploadFile, java.nio.file.StandardCopyOption.REPLACE_EXISTING); + } + return uploadFile; + } + + private MaterializedCrl materializeCrlToDer(Path uploadFile) throws java.io.IOException { + if (!looksLikePem(uploadFile)) { + return new MaterializedCrl(uploadFile, sha256File(uploadFile)); + } + Path derFile = Files.createTempFile(crlTempDir, "crl-", ".der"); + try { + String fingerprint = decodePemToDer(uploadFile, derFile); + return new MaterializedCrl(derFile, fingerprint); + } finally { + deleteQuietly(uploadFile); + } + } + + private String buildTaskId() { + return "CRL" + TASK_ID_TS.format(LocalDateTime.now()) + String.format("%04d", java.util.concurrent.ThreadLocalRandom.current().nextInt(10_000)); + } + + private CrlImportTaskResponse toTaskResponse(CertificateCrlImportTaskEntity task) { + CrlImportTaskResponse response = new CrlImportTaskResponse(); + response.setTaskId(task.getTaskId()); + response.setStatus(task.getStatus()); + response.setOriginalFilename(task.getOriginalFilename()); + response.setFileSize(task.getFileSize()); + response.setCrlId(task.getCrlId()); + response.setFingerprint(task.getFingerprint()); + response.setRevokedCount(task.getRevokedCount()); + response.setErrorMessage(task.getErrorMessage()); + response.setStartedAt(task.getStartedAt()); + response.setFinishedAt(task.getFinishedAt()); + return response; + } + + private boolean looksLikePem(Path file) throws java.io.IOException { + try (InputStream inputStream = Files.newInputStream(file)) { + byte[] prefix = inputStream.readNBytes(64); + return new String(prefix, java.nio.charset.StandardCharsets.US_ASCII).contains("-----BEGIN"); + } + } + + private String decodePemToDer(Path pemFile, Path derFile) throws java.io.IOException { + MessageDigest digest = sha256Digest(); + Base64.Decoder decoder = Base64.getDecoder(); + boolean inCrl = false; + try (BufferedReader reader = Files.newBufferedReader(pemFile, java.nio.charset.StandardCharsets.US_ASCII); + OutputStream outputStream = Files.newOutputStream(derFile)) { + String line; + while ((line = reader.readLine()) != null) { + String trim = line.trim(); + if (trim.startsWith("-----BEGIN ") && trim.contains("CRL")) { + inCrl = true; + continue; + } + if (trim.startsWith("-----END ") && trim.contains("CRL")) { + break; + } + if (!inCrl || trim.isEmpty()) { + continue; + } + byte[] decoded = decoder.decode(trim); + digest.update(decoded); + outputStream.write(decoded); + } + } + return hex(digest.digest()); + } + + private String sha256File(Path file) throws java.io.IOException { + MessageDigest digest = sha256Digest(); + try (InputStream inputStream = Files.newInputStream(file)) { + byte[] buffer = new byte[1024 * 1024]; + int read; + while ((read = inputStream.read(buffer)) >= 0) { + digest.update(buffer, 0, read); + } + } + return hex(digest.digest()); + } + + private MessageDigest sha256Digest() { + try { + return MessageDigest.getInstance("SHA-256"); + } catch (NoSuchAlgorithmException ex) { + throw new IllegalStateException("SHA-256算法不可用", ex); + } + } + + private String hex(byte[] bytes) { + StringBuilder builder = new StringBuilder(bytes.length * 2); + for (byte b : bytes) { + builder.append(String.format("%02X", b)); + } + return builder.toString(); + } + + private String readableSize(long bytes) { + if (bytes % (1024L * 1024L) == 0) { + return (bytes / 1024L / 1024L) + "MB"; + } + if (bytes % 1024L == 0) { + return (bytes / 1024L) + "KB"; + } + return bytes + "B"; + } + + private void deleteQuietly(Path path) { + if (path == null) { + return; + } + try { + Files.deleteIfExists(path); + } catch (Exception ignored) { + } + } + + private record MaterializedCrl(Path derFile, String fingerprint) { + } + + private record ImportResult(Long crlId, String fingerprint, int revokedCount) { + } } diff --git a/src/main/java/com/cisd/tms/modules/cert/service/EntityService.java b/src/main/java/com/cisd/tms/modules/cert/service/EntityService.java index c5f12e0..fcc07a1 100644 --- a/src/main/java/com/cisd/tms/modules/cert/service/EntityService.java +++ b/src/main/java/com/cisd/tms/modules/cert/service/EntityService.java @@ -7,6 +7,7 @@ import com.cisd.tms.integration.crypto.pcie.PcieUserKeyType; import com.cisd.tms.integration.crypto.pcie.model.BackupDataResult; import com.cisd.tms.integration.crypto.pcie.model.GenerateKeyPairRequest; import com.cisd.tms.integration.crypto.pcie.model.UserKeySm2SignRequest; +import com.cisd.tms.integration.crypto.pcie.jna.RsaRefPublicKey; import com.cisd.tms.integration.crypto.pcie.service.PcieCryptoService; import com.cisd.tms.modules.cert.dto.EntityCreateRequest; import com.cisd.tms.modules.cert.dto.EntityCreateResponse; @@ -35,8 +36,12 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.util.StringUtils; import java.io.OutputStream; +import java.math.BigInteger; import java.nio.charset.StandardCharsets; +import java.security.KeyFactory; import java.security.PublicKey; +import java.security.spec.RSAPublicKeySpec; +import java.security.spec.X509EncodedKeySpec; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Arrays; @@ -79,6 +84,7 @@ public class EntityService { @Transactional public synchronized EntityCreateResponse create(EntityCreateRequest request) { String algoType = normalizeAlgoType(request.getAlgoType()); + // 密钥索引同时存在卡内状态和数据库记录;单 JVM 内串行分配,数据库唯一索引仍是最终保护。 int keyIdx = resolveNextKeyIdx(); int keyLen = SM2.equals(algoType) ? 256 : 2048; GenerateKeyPairRequest generateRequest = new GenerateKeyPairRequest(); @@ -92,7 +98,9 @@ public class EntityService { pcieCryptoService.generateKeyPairRsa(generateRequest); } try { + // 备份块是密码卡返回的二进制结构,Base64 后可能远超短 VARCHAR,库表字段需保持 TEXT。 BackupDataResult backup = pcieCryptoService.backupUserKey(keyIdx, PcieUserKeyType.SIGN.getCode(), 4096); + // publicKey 保存卡原始公钥结构;证书导入时再按算法转换为 X.509 公钥进行匹配。 BackupDataResult publicKey = SM2.equals(algoType) ? pcieCryptoService.exportSignPublicKeyEcc(keyIdx) : pcieCryptoService.exportSignPublicKeyRsa(keyIdx); @@ -113,6 +121,7 @@ public class EntityService { return new EntityCreateResponse(entity.getId()); } catch (RuntimeException ex) { try { + // 数据库落库失败时清理刚生成的卡内用户密钥,避免卡状态与实体表长期不一致。 pcieCryptoService.deleteUserKey(keyIdx); } catch (Exception ignored) { } @@ -156,6 +165,7 @@ public class EntityService { X500Name subjectName = subject.toX500Name(); String subjectDn = subjectName.toString(); PublicKey publicKey = Sm2KeySupport.toSm2PublicKey(Base64.getDecoder().decode(entity.getPublicKey())); + // P10 使用卡内签名密钥完成 SM2-with-SM3 签名,私钥始终不离开密码卡。 PKCS10CertificationRequestBuilder builder = new JcaPKCS10CertificationRequestBuilder(subjectName, publicKey); PKCS10CertificationRequest csr = builder.build(new CardContentSigner(entity.getKeyIdx(), pcieCryptoService)); @@ -225,18 +235,107 @@ public class EntityService { } private boolean certificatePublicKeyMatches(KeyEntity entity, byte[] certificatePublicKey) { - if (entity == null || !SM2.equalsIgnoreCase(entity.getAlgoType()) || !StringUtils.hasText(entity.getPublicKey())) { + // ENTITY 证书绑定只看证书公钥是否等于本地实体公钥,不能信任调用方传入的证书类型。 + if (entity == null || !StringUtils.hasText(entity.getAlgoType()) || !StringUtils.hasText(entity.getPublicKey())) { return false; } try { byte[] entityPublicKey = Base64.getDecoder().decode(entity.getPublicKey()); - byte[] entityX509Key = Sm2KeySupport.toSm2PublicKey(entityPublicKey).getEncoded(); - return Arrays.equals(certificatePublicKey, entityX509Key); + if (SM2.equalsIgnoreCase(entity.getAlgoType())) { + byte[] entityX509Key = Sm2KeySupport.toSm2PublicKey(entityPublicKey).getEncoded(); + return Arrays.equals(certificatePublicKey, entityX509Key); + } + if (RSA.equalsIgnoreCase(entity.getAlgoType())) { + return rsaPublicKeyMatches(entityPublicKey, certificatePublicKey); + } + return false; } catch (Exception ex) { return false; } } + private boolean rsaPublicKeyMatches(byte[] entityPublicKey, byte[] certificatePublicKey) { + if (Arrays.equals(entityPublicKey, certificatePublicKey)) { + return true; + } + if (matchesRsaX509PublicKey(entityPublicKey, certificatePublicKey)) { + return true; + } + return matchesRsaRefPublicKey(entityPublicKey, certificatePublicKey); + } + + private boolean matchesRsaX509PublicKey(byte[] entityPublicKey, byte[] certificatePublicKey) { + try { + PublicKey publicKey = KeyFactory.getInstance(RSA) + .generatePublic(new X509EncodedKeySpec(entityPublicKey)); + return Arrays.equals(certificatePublicKey, publicKey.getEncoded()); + } catch (Exception ex) { + return false; + } + } + + private boolean matchesRsaRefPublicKey(byte[] entityPublicKey, byte[] certificatePublicKey) { + RsaRefPublicKey refPublicKey = new RsaRefPublicKey(); + int size = refPublicKey.size(); + if (entityPublicKey.length != size) { + return false; + } + try { + refPublicKey.getPointer().clear(size); + refPublicKey.getPointer().write(0, entityPublicKey, 0, size); + refPublicKey.read(); + int modulusLength = (refPublicKey.bits + 7) / 8; + if (modulusLength <= 0 || modulusLength > RsaRefPublicKey.MAX_LEN) { + return false; + } + // 不同 SDF 实现可能左右对齐 m/e 字段;同时尝试常见编码,避免 RSA 实体无法绑定证书。 + byte[][] modulusCandidates = { + trimLeadingZeros(Arrays.copyOf(refPublicKey.m, modulusLength)), + trimLeadingZeros(Arrays.copyOfRange(refPublicKey.m, RsaRefPublicKey.MAX_LEN - modulusLength, RsaRefPublicKey.MAX_LEN)) + }; + byte[][] exponentCandidates = { + trimLeadingZeros(refPublicKey.e), + trimTrailingZeros(refPublicKey.e) + }; + KeyFactory keyFactory = KeyFactory.getInstance(RSA); + for (byte[] modulus : modulusCandidates) { + if (modulus.length == 0) { + continue; + } + for (byte[] exponent : exponentCandidates) { + if (exponent.length == 0) { + continue; + } + PublicKey publicKey = keyFactory.generatePublic( + new RSAPublicKeySpec(new BigInteger(1, modulus), new BigInteger(1, exponent)) + ); + if (Arrays.equals(certificatePublicKey, publicKey.getEncoded())) { + return true; + } + } + } + return false; + } catch (Exception ex) { + return false; + } + } + + private byte[] trimLeadingZeros(byte[] value) { + int start = 0; + while (start < value.length && value[start] == 0) { + start++; + } + return Arrays.copyOfRange(value, start, value.length); + } + + private byte[] trimTrailingZeros(byte[] value) { + int end = value.length; + while (end > 0 && value[end - 1] == 0) { + end--; + } + return Arrays.copyOf(value, end); + } + private int resolveNextKeyIdx() { List used = keyEntityRepository.findUsedKeyIdx(); for (int idx = 2; idx <= 8; idx++) { diff --git a/src/main/java/com/cisd/tms/modules/cert/service/TrustedCertService.java b/src/main/java/com/cisd/tms/modules/cert/service/TrustedCertService.java index d208141..81dc0f7 100644 --- a/src/main/java/com/cisd/tms/modules/cert/service/TrustedCertService.java +++ b/src/main/java/com/cisd/tms/modules/cert/service/TrustedCertService.java @@ -18,6 +18,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; +import java.nio.charset.StandardCharsets; import java.security.Security; import java.security.cert.X509CRL; import java.security.cert.X509Certificate; @@ -59,6 +60,7 @@ public class TrustedCertService { public ImportCertificateResponse importTrusted(MultipartFile file, String alias) { try { X509Certificate certificate = CertPemSupport.parseCertificate(file.getBytes()); + // 可信库只保存 CA 证书;终端证书必须走 CertificateService,避免信任锚和业务证书混用。 assertValidTime(certificate); assertTrustedCaCertificate(certificate); String fingerprint = CertPemSupport.sha256Fingerprint(certificate); @@ -67,8 +69,10 @@ public class TrustedCertService { } int caLevel = isSelfSigned(certificate) ? 0 : 1; if (caLevel == 0) { + // 根 CA 必须能自签自验,防止把 subject=issuer 的伪造证书放入信任锚。 certificate.verify(certificate.getPublicKey(), "BC"); } else { + // 中间 CA 必须由当前可信链签发;仅导入叶子中间证书不足以建立信任。 TrustedCertEntity issuer = resolveIssuer(certificate); validateParentChain(issuer); } @@ -143,6 +147,7 @@ public class TrustedCertService { } for (TrustedCertEntity candidate : matched) { try { + // 同名 CA 可能存在轮换或交叉签发,必须逐个用公钥验签找到真实签发者。 X509Certificate issuerCert = CertPemSupport.parseCertificate(candidate.getCertData().getBytes()); assertTrustedCaCertificate(issuerCert); certificate.verify(issuerCert.getPublicKey(), "BC"); @@ -162,6 +167,7 @@ public class TrustedCertService { } for (TrustedCertEntity candidate : matched) { try { + // CRL issuer 也按签名验证解析,避免仅凭 DN 命中错误 CA 后错误采信吊销列表。 X509Certificate issuerCert = CertPemSupport.parseCertificate(candidate.getCertData().getBytes()); assertCrlIssuerCertificate(issuerCert); crl.verify(issuerCert.getPublicKey(), "BC"); @@ -178,6 +184,7 @@ public class TrustedCertService { assertValidTime(certificate); assertTrustedCaCertificate(certificate); if (entity.getSubjectDn().equals(entity.getIssuerDn())) { + // 递归到自签根时仍要验自签,保证整条路径最终落在有效信任锚上。 certificate.verify(certificate.getPublicKey(), "BC"); return; } @@ -190,6 +197,19 @@ public class TrustedCertService { } } + public boolean validateCertificateChain(String certData) { + try { + // 用于列表/详情运行态:每次按当前可信库重新建链,反映 CA 删除、过期或强制变更。 + X509Certificate certificate = CertPemSupport.parseCertificate(certData.getBytes(StandardCharsets.UTF_8)); + assertValidTime(certificate); + TrustedCertEntity issuer = resolveIssuer(certificate); + validateParentChain(issuer); + return true; + } catch (Exception ex) { + return false; + } + } + private TrustedCertItemResponse toItem(TrustedCertEntity entity) { TrustedCertItemResponse response = new TrustedCertItemResponse(); response.setId(entity.getId()); @@ -221,6 +241,9 @@ public class TrustedCertService { } private void assertTrustedCaCertificate(X509Certificate certificate) { + if (CertPemSupport.hasUnsupportedCriticalExtension(certificate)) { + throw new BizException(ErrorCode.BAD_REQUEST.getCode(), "证书包含不支持的关键扩展"); + } if (certificate.getBasicConstraints() < 0) { throw new BizException(ErrorCode.BAD_REQUEST.getCode(), "可信证书必须是CA证书"); } @@ -231,6 +254,9 @@ public class TrustedCertService { } private void assertCrlIssuerCertificate(X509Certificate certificate) { + if (CertPemSupport.hasUnsupportedCriticalExtension(certificate)) { + throw new BizException(ErrorCode.BAD_REQUEST.getCode(), "证书包含不支持的关键扩展"); + } if (certificate.getBasicConstraints() < 0) { throw new BizException(ErrorCode.BAD_REQUEST.getCode(), "CRL签发者必须是CA证书"); } diff --git a/src/main/java/com/cisd/tms/modules/cert/support/CertPemSupport.java b/src/main/java/com/cisd/tms/modules/cert/support/CertPemSupport.java index 4c56d5d..fdbc06e 100644 --- a/src/main/java/com/cisd/tms/modules/cert/support/CertPemSupport.java +++ b/src/main/java/com/cisd/tms/modules/cert/support/CertPemSupport.java @@ -1,8 +1,11 @@ package com.cisd.tms.modules.cert.support; +import org.bouncycastle.asn1.x509.Extension; import org.bouncycastle.jce.provider.BouncyCastleProvider; +import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; +import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.PublicKey; @@ -12,10 +15,30 @@ import java.security.cert.X509CRL; import java.security.cert.X509Certificate; import java.util.Base64; import java.util.Locale; +import java.util.Set; public final class CertPemSupport { private static final String PROVIDER = "BC"; + // 只列入当前模块能理解并正确执行语义的 critical extension,其他关键扩展必须拒绝。 + private static final Set SUPPORTED_CRITICAL_EXTENSION_OIDS = Set.of( + Extension.basicConstraints.getId(), + Extension.keyUsage.getId(), + Extension.extendedKeyUsage.getId(), + Extension.subjectKeyIdentifier.getId(), + Extension.authorityKeyIdentifier.getId(), + Extension.subjectAlternativeName.getId(), + Extension.issuerAlternativeName.getId(), + Extension.cRLDistributionPoints.getId(), + Extension.freshestCRL.getId(), + Extension.authorityInfoAccess.getId(), + Extension.subjectInfoAccess.getId(), + Extension.certificatePolicies.getId(), + Extension.policyMappings.getId(), + Extension.policyConstraints.getId(), + Extension.inhibitAnyPolicy.getId(), + Extension.nameConstraints.getId() + ); static { if (Security.getProvider(PROVIDER) == null) { @@ -46,6 +69,15 @@ public final class CertPemSupport { } } + public static X509CRL parseCrl(InputStream inputStream) { + try { + CertificateFactory factory = CertificateFactory.getInstance("X.509", PROVIDER); + return (X509CRL) factory.generateCRL(new BufferedInputStream(inputStream)); + } catch (Exception e) { + throw new IllegalArgumentException("CRL文件无效", e); + } + } + public static String toPem(X509Certificate certificate) { try { String base64 = Base64.getMimeEncoder(64, "\n".getBytes(StandardCharsets.UTF_8)) @@ -89,6 +121,7 @@ public final class CertPemSupport { if ("RSA".equalsIgnoreCase(publicKey.getAlgorithm())) { return "RSA"; } + // SM2 证书在不同 provider 下可能暴露为 EC 公钥,因此同时参考签名算法名称和国密 OID。 String signatureName = certificate.getSigAlgName(); String signatureOid = certificate.getSigAlgOID(); if ((signatureName != null && signatureName.toUpperCase(Locale.ROOT).contains("SM2")) @@ -101,6 +134,15 @@ public final class CertPemSupport { throw new IllegalArgumentException("不支持的证书算法:" + publicKey.getAlgorithm()); } + public static boolean hasUnsupportedCriticalExtension(X509Certificate certificate) { + // X509Certificate#getCriticalExtensionOIDs() 已剥离 non-critical 扩展,只需关注必须理解的集合。 + Set criticalOids = certificate.getCriticalExtensionOIDs(); + if (criticalOids == null || criticalOids.isEmpty()) { + return false; + } + return criticalOids.stream().anyMatch(oid -> !SUPPORTED_CRITICAL_EXTENSION_OIDS.contains(oid)); + } + private static byte[] toDer(byte[] bytes, String... labels) { String text = new String(bytes, StandardCharsets.UTF_8); for (String label : labels) { diff --git a/src/main/java/com/cisd/tms/modules/device/service/NetworkConfigService.java b/src/main/java/com/cisd/tms/modules/device/service/NetworkConfigService.java index a65e83a..eabe726 100644 --- a/src/main/java/com/cisd/tms/modules/device/service/NetworkConfigService.java +++ b/src/main/java/com/cisd/tms/modules/device/service/NetworkConfigService.java @@ -2916,7 +2916,7 @@ public class NetworkConfigService { int exitCode = process.waitFor(); if (exitCode != 0) { - log.error("Command execution failed. Exit code: {}, Command: [{}], Output: {}", exitCode, commandArgs, output); + log.error("Command execution 执行失败. Exit code: {}, Command: [{}], Output: {}", exitCode, commandArgs, output); throw new RuntimeException("系统底层操作执行失败");} return output; } catch (IOException e){ @@ -2927,7 +2927,7 @@ public class NetworkConfigService { process.destroy(); } Thread.currentThread().interrupt(); - log.error("Process was interrupted while executing command: [{}]", commandArgs, e); + log.error("Process was 被中断 while executing command: [{}]", commandArgs, e); throw new RuntimeException("服务器内部处理中断");} } diff --git a/src/main/java/com/cisd/tms/modules/device/service/impl/TimeConfigServiceImpl.java b/src/main/java/com/cisd/tms/modules/device/service/impl/TimeConfigServiceImpl.java index 98a6181..c3de1db 100644 --- a/src/main/java/com/cisd/tms/modules/device/service/impl/TimeConfigServiceImpl.java +++ b/src/main/java/com/cisd/tms/modules/device/service/impl/TimeConfigServiceImpl.java @@ -38,7 +38,7 @@ public class TimeConfigServiceImpl implements TimeConfigService { String timezone = request.getTimezone(); if (timezone == null || !timezone.matches("^[A-Za-z0-9/_-]+$")) { - throw new IllegalArgumentException("Invalid or empty timezone format"); + throw new IllegalArgumentException("时区格式为空或无效"); } if (!ZoneId.getAvailableZoneIds().contains(timezone)) { @@ -47,12 +47,12 @@ public class TimeConfigServiceImpl implements TimeConfigService { String datetime = request.getDatetime(); if (datetime == null) { - throw new IllegalArgumentException("Invalid or empty datetime format. Expected: YYYY-MM-DD HH:MM"); + throw new IllegalArgumentException("日期时间格式为空或无效,期望格式:YYYY-MM-DD HH:MM"); } try { LocalDateTime.parse(datetime, FORMATTER); } catch (DateTimeParseException e) { - throw new IllegalArgumentException("Invalid or empty datetime format. Expected: YYYY-MM-DD HH:MM", e); + throw new IllegalArgumentException("日期时间格式为空或无效,期望格式:YYYY-MM-DD HH:MM", e); } try{ @@ -80,20 +80,20 @@ public class TimeConfigServiceImpl implements TimeConfigService { List servers = request.getNtpServers(); if (servers == null || servers.isEmpty()) { - throw new IllegalArgumentException("At least one NTP server IP must be provided in NTP mode"); + throw new IllegalArgumentException("NTP模式下至少需要提供一个NTP服务器IP"); } int interval = request.getSyncInterval() != null ? request.getSyncInterval() : 512; if (interval <= 0) { - throw new IllegalArgumentException("SyncInterval must be a positive integer."); + throw new IllegalArgumentException("同步间隔必须为正整数。"); } if ((interval & (interval - 1)) != 0) { - throw new IllegalArgumentException("SyncInterval must be a power of 2."); + throw new IllegalArgumentException("同步间隔必须为2的幂。"); } int MIN_INTERVAL = 8; int MAX_INTERVAL = 131072; if (interval < MIN_INTERVAL || interval > MAX_INTERVAL) { - throw new IllegalArgumentException(String.format("SyncInterval must be between %d and %d.", MIN_INTERVAL, MAX_INTERVAL)); + throw new IllegalArgumentException(String.format("同步间隔必须在%d到%d之间。", MIN_INTERVAL, MAX_INTERVAL)); } int pollExp = Integer.numberOfTrailingZeros(interval); @@ -101,12 +101,12 @@ public class TimeConfigServiceImpl implements TimeConfigService { StringBuilder serverConfLines = new StringBuilder(); for (String server : servers) { if (!server.matches("^[A-Za-z0-9.-]+$")) { - throw new IllegalArgumentException("Invalid NTP server address format: " + server); + throw new IllegalArgumentException("NTP服务器地址格式无效:" + server); } String pingResult = executeCommand("ping", "-c", "1", "-W", "1", server); if (!pingResult.contains("1 received")) { - throw new RuntimeException("NTP server is unreachable via ping: " + server); + throw new RuntimeException("NTP服务器ping不通:" + server); } serverConfLines.append("server ").append(server) .append(" iburst minpoll ").append(pollExp) @@ -131,7 +131,7 @@ public class TimeConfigServiceImpl implements TimeConfigService { String status = executeCommand("chronyc", "tracking"); if (!status.contains("Reference ID")) { - throw new RuntimeException("NTP sync status check failed"); + throw new RuntimeException("NTP sync status check 执行失败"); } } catch (Exception e) { @@ -141,13 +141,13 @@ public class TimeConfigServiceImpl implements TimeConfigService { executeCommand("systemctl", "restart", "chronyd"); } } catch (Exception rollbackEx) { - throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "Failed to apply new Chrony configuration. CRITICAL: Rollback also failed!"); + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "Failed to apply new Chrony configuration. CRITICAL: Rollback also 执行失败!"); } throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "Failed to apply new Chrony configuration. Rolled back to original state."); } } else { - throw new IllegalArgumentException("Unknown mode: " + mode); + throw new IllegalArgumentException("未知模式:" + mode); } } @@ -172,7 +172,7 @@ public class TimeConfigServiceImpl implements TimeConfigService { String outputStr = outputBuilder.toString().trim(); if (exitCode != 0) { - log.error("Command execution failed. Exit code: {}, Command: {}, Output: {}", + log.error("Command execution 执行失败. Exit code: {}, Command: {}, Output: {}", exitCode, Arrays.toString(commandArgs), outputStr); throw new RuntimeException("系统底层操作执行失败"); } @@ -186,7 +186,7 @@ public class TimeConfigServiceImpl implements TimeConfigService { process.destroy(); } Thread.currentThread().interrupt(); - log.error("Process was interrupted while executing command: {}", Arrays.toString(commandArgs), e); + log.error("Process was 被中断 while executing command: {}", Arrays.toString(commandArgs), e); throw new RuntimeException("服务器内部处理中断", e); } } 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 74a667a..c4fafcd 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 @@ -16,7 +16,7 @@ public class DefaultHardwareStatusProbe implements HardwareStatusProbe { Thread.sleep(120L); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); - throw new IllegalStateException("cpu sampling interrupted", ex); + throw new IllegalStateException("cpu sampling 被中断", ex); } CpuTimes after = readCpuTimes(); long totalDiff = after.total - before.total; @@ -40,10 +40,10 @@ public class DefaultHardwareStatusProbe implements HardwareStatusProbe { } } } catch (IOException ex) { - throw new IllegalStateException("memory probe failed", ex); + throw new IllegalStateException("memory probe 执行失败", ex); } if (totalKb <= 0 || availableKb < 0) { - throw new IllegalStateException("memory probe returned invalid value"); + throw new IllegalStateException("内存探测返回无效值"); } double totalGb = totalKb / 1024D / 1024D; double usedGb = (totalKb - availableKb) / 1024D / 1024D; @@ -55,17 +55,17 @@ public class DefaultHardwareStatusProbe implements HardwareStatusProbe { try { List lines = runCommand("df -k /"); if (lines.size() < 2) { - throw new IllegalStateException("disk probe returned no data"); + throw new IllegalStateException("磁盘探测未返回数据"); } String[] parts = lines.get(1).trim().split("\\s+"); if (parts.length < 5) { - throw new IllegalStateException("disk probe returned invalid data"); + 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 failed", ex); + throw new IllegalStateException("disk probe 执行失败", ex); } } @@ -80,7 +80,7 @@ public class DefaultHardwareStatusProbe implements HardwareStatusProbe { } return new CpuTimes(idle, total); } catch (IOException ex) { - throw new IllegalStateException("cpu probe failed", ex); + throw new IllegalStateException("cpu probe 执行失败", ex); } } @@ -98,12 +98,12 @@ public class DefaultHardwareStatusProbe implements HardwareStatusProbe { int exitCode = process.waitFor(); List lines = new String(process.getInputStream().readAllBytes()).lines().toList(); if (exitCode != 0) { - throw new IllegalStateException("command failed: " + command); + throw new IllegalStateException("命令执行失败: " + command); } return lines; } catch (InterruptedException ex) { Thread.currentThread().interrupt(); - throw new IllegalStateException("command interrupted: " + command, ex); + throw new IllegalStateException("command 被中断: " + command, ex); } } diff --git a/src/main/java/com/cisd/tms/modules/device/support/DefaultSoftwareRuntimeProbe.java b/src/main/java/com/cisd/tms/modules/device/support/DefaultSoftwareRuntimeProbe.java index 9647d1b..9ee316f 100644 --- a/src/main/java/com/cisd/tms/modules/device/support/DefaultSoftwareRuntimeProbe.java +++ b/src/main/java/com/cisd/tms/modules/device/support/DefaultSoftwareRuntimeProbe.java @@ -109,9 +109,9 @@ public class DefaultSoftwareRuntimeProbe implements SoftwareRuntimeProbe { List patterns = compilePatterns(tokens); return processSnapshot().stream().anyMatch(commandLine -> matchesAny(commandLine, patterns)); } catch (PatternSyntaxException ex) { - throw new IllegalStateException("invalid process probe tokens: " + String.join(",", tokens), ex); + throw new IllegalStateException("进程探测命令参数无效:" + String.join(",", tokens), ex); } catch (RuntimeException ex) { - throw new IllegalStateException("process probe failed: " + String.join(",", tokens), ex); + throw new IllegalStateException("process probe 执行失败:" + String.join(",", tokens), ex); } } @@ -180,14 +180,14 @@ public class DefaultSoftwareRuntimeProbe implements SoftwareRuntimeProbe { .filter(line -> !line.isBlank()) .toList(); if (exitCode != 0) { - throw new IllegalStateException("ps command failed"); + throw new IllegalStateException("ps 命令执行失败"); } return commandLines; } catch (InterruptedException ex) { Thread.currentThread().interrupt(); - throw new IllegalStateException("process snapshot interrupted", ex); + throw new IllegalStateException("process snapshot 被中断", ex); } catch (IOException ex) { - throw new IllegalStateException("process snapshot failed", ex); + throw new IllegalStateException("process snapshot 执行失败", ex); } } @@ -197,9 +197,9 @@ public class DefaultSoftwareRuntimeProbe implements SoftwareRuntimeProbe { return process.waitFor(); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); - throw new IllegalStateException("command interrupted", ex); + throw new IllegalStateException("command 被中断", ex); } catch (IOException ex) { - throw new IllegalStateException("command failed", ex); + throw new IllegalStateException("命令执行失败", ex); } } diff --git a/src/main/java/com/cisd/tms/modules/device/support/crypto/CryptoPayloadCodec.java b/src/main/java/com/cisd/tms/modules/device/support/crypto/CryptoPayloadCodec.java index 0939066..f23ddc0 100644 --- a/src/main/java/com/cisd/tms/modules/device/support/crypto/CryptoPayloadCodec.java +++ b/src/main/java/com/cisd/tms/modules/device/support/crypto/CryptoPayloadCodec.java @@ -36,7 +36,7 @@ public final class CryptoPayloadCodec { public static Map lmkComponentPayload(BackupDataResult result) { byte[] normalized = normalizeBinary(result.getData(), result.getLength()); if (normalized.length < 56) { - throw new IllegalStateException("Invalid LMK component length: " + normalized.length); + throw new IllegalStateException("LMK分量长度无效:" + normalized.length); } HexFormat hexFormat = HexFormat.of(); @@ -61,7 +61,7 @@ public final class CryptoPayloadCodec { String lmkMacHex = requireText("lmkMacHex", request.getLmkMacHex()); Map components = request.getComponents(); if (components == null || components.isEmpty()) { - throw new IllegalArgumentException("components must not be empty"); + throw new IllegalArgumentException("components不能为空"); } byte[] result = new byte[56]; diff --git a/src/main/java/com/cisd/tms/modules/file/service/impl/FileServiceImpl.java b/src/main/java/com/cisd/tms/modules/file/service/impl/FileServiceImpl.java index 8a34cb3..f203d9f 100644 --- a/src/main/java/com/cisd/tms/modules/file/service/impl/FileServiceImpl.java +++ b/src/main/java/com/cisd/tms/modules/file/service/impl/FileServiceImpl.java @@ -35,10 +35,10 @@ public class FileServiceImpl implements FileService { @Override public FileUploadResponse upload(MultipartFile file) { if (file == null || file.isEmpty() || file.getSize() <= 0) { - throw new IllegalArgumentException("upload file is empty"); + throw new IllegalArgumentException("上传文件为空"); } if (file.getSize() > fileStorageProperties.getMaxFileSizeBytes()) { - throw new IllegalArgumentException("upload file size exceeds limit"); + throw new IllegalArgumentException("上传文件大小超过限制"); } String originalFilename = sanitizeFilename(file.getOriginalFilename()); @@ -72,7 +72,7 @@ public class FileServiceImpl implements FileService { @Override public FileDetailResponse getDetail(String fileId) { FileRecordEntity entity = fileRecordRepository.findByFileId(normalize(fileId)) - .orElseThrow(() -> new BizException(ErrorCode.BIZ_ERROR.getCode(), "file not found: " + normalize(fileId))); + .orElseThrow(() -> new BizException(ErrorCode.BIZ_ERROR.getCode(), "文件不存在:" + normalize(fileId))); Path path = Paths.get(entity.getStoragePath()).normalize().toAbsolutePath(); FileDetailResponse response = new FileDetailResponse(); @@ -98,18 +98,18 @@ public class FileServiceImpl implements FileService { private String sanitizeFilename(String originalFilename) { String normalized = normalize(originalFilename); if (normalized.isEmpty()) { - throw new IllegalArgumentException("original filename is blank"); + throw new IllegalArgumentException("原始文件名为空"); } try { // 先剥离客户端传入文件名里的路径段,避免把目录信息带入服务端存储路径。 String fileName = Paths.get(normalized).getFileName().toString(); String sanitized = fileName.replaceAll("[^A-Za-z0-9._-]", "_"); if (sanitized.isBlank()) { - throw new IllegalArgumentException("original filename is invalid"); + throw new IllegalArgumentException("原始文件名无效"); } return sanitized; } catch (InvalidPathException ex) { - throw new IllegalArgumentException("original filename is invalid"); + throw new IllegalArgumentException("原始文件名无效"); } } diff --git a/src/main/java/com/cisd/tms/modules/init/config/InitCommandProfileService.java b/src/main/java/com/cisd/tms/modules/init/config/InitCommandProfileService.java index e9072b3..f9c98be 100644 --- a/src/main/java/com/cisd/tms/modules/init/config/InitCommandProfileService.java +++ b/src/main/java/com/cisd/tms/modules/init/config/InitCommandProfileService.java @@ -46,7 +46,7 @@ public class InitCommandProfileService { private static Map>> load(ObjectMapper objectMapper) { try (InputStream input = InitCommandProfileService.class.getClassLoader().getResourceAsStream(PROFILE_RESOURCE)) { if (input == null) { - throw new IllegalStateException("missing command profile resource: " + PROFILE_RESOURCE); + throw new IllegalStateException("命令配置资源不存在:" + PROFILE_RESOURCE); } // JSON 结构:{productType:{mqType:{stepCode:command}}} TypeReference>>> typeRef = @@ -55,7 +55,7 @@ public class InitCommandProfileService { Map>> map = objectMapper.readValue(input, typeRef); return map == null ? Collections.emptyMap() : map; } catch (IOException ex) { - throw new IllegalStateException("failed to load command profile resource: " + PROFILE_RESOURCE, ex); + throw new IllegalStateException("加载命令配置资源失败:" + PROFILE_RESOURCE, ex); } } diff --git a/src/main/java/com/cisd/tms/modules/init/dto/InitPreviewRequest.java b/src/main/java/com/cisd/tms/modules/init/dto/InitPreviewRequest.java index ebec32e..88c9fd3 100644 --- a/src/main/java/com/cisd/tms/modules/init/dto/InitPreviewRequest.java +++ b/src/main/java/com/cisd/tms/modules/init/dto/InitPreviewRequest.java @@ -14,37 +14,37 @@ public class InitPreviewRequest { "^(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}$"; private static final String FILE_ID_REGEX = "^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$"; - @NotBlank(message = "orgCodeType is required") - @Pattern(regexp = "BIC|CIPSID|LEI", message = "orgCodeType must be BIC/CIPSID/LEI") + @NotBlank(message = "orgCodeType不能为空") + @Pattern(regexp = "BIC|CIPSID|LEI", message = "orgCodeType必须为BIC/CIPSID/LEI") @Schema(description = "机构代码类型", example = "BIC") private String orgCodeType; - @NotBlank(message = "orgCode is required") - @Size(max = 32, message = "orgCode length must be <= 32") + @NotBlank(message = "orgCode不能为空") + @Size(max = 32, message = "orgCode长度必须小于等于32") @Schema(description = "机构代码", example = "BKCHCNBJXXX") private String orgCode; - @NotBlank(message = "orgNameCn is required") - @Size(max = 128, message = "orgNameCn length must be <= 128") + @NotBlank(message = "orgNameCn不能为空") + @Size(max = 128, message = "orgNameCn长度必须小于等于128") @Schema(description = "机构中文名称", example = "中国银行") private String orgNameCn; - @Size(max = 256, message = "orgNameEn length must be <= 256") + @Size(max = 256, message = "orgNameEn长度必须小于等于256") @Schema(description = "机构英文名称", example = "Bank of China") private String orgNameEn; - @NotBlank(message = "deployMode is required") - @Pattern(regexp = "SINGLE|DUAL|QUAD", message = "deployMode must be SINGLE/DUAL/QUAD") + @NotBlank(message = "deployMode不能为空") + @Pattern(regexp = "SINGLE|DUAL|QUAD", message = "deployMode必须为SINGLE/DUAL/QUAD") @Schema(description = "部署模式", example = "DUAL") private String deployMode; @Valid - @NotNull(message = "nodes is required") + @NotNull(message = "nodes不能为空") @Schema(description = "节点 IP 配置") private Nodes nodes; @Valid - @NotNull(message = "mq is required") + @NotNull(message = "mq不能为空") @Schema(description = "消息中间件配置") private Mq mq; @@ -119,20 +119,20 @@ public class InitPreviewRequest { @Schema(description = "节点 IP 配置") public static class Nodes { - @NotBlank(message = "nodes.node01Ip is required") - @Pattern(regexp = IPV4_REGEX, message = "nodes.node01Ip must be a valid IPv4") + @NotBlank(message = "nodes.node01Ip不能为空") + @Pattern(regexp = IPV4_REGEX, message = "nodes.node01Ip必须是有效IPv4地址") @Schema(description = "节点 1 IP", example = "192.168.1.11") private String node01Ip; - @Pattern(regexp = IPV4_REGEX, message = "nodes.node02Ip must be a valid IPv4") + @Pattern(regexp = IPV4_REGEX, message = "nodes.node02Ip必须是有效IPv4地址") @Schema(description = "节点 2 IP", example = "192.168.1.12") private String node02Ip; - @Pattern(regexp = IPV4_REGEX, message = "nodes.node03Ip must be a valid IPv4") + @Pattern(regexp = IPV4_REGEX, message = "nodes.node03Ip必须是有效IPv4地址") @Schema(description = "节点 3 IP", example = "192.168.1.13") private String node03Ip; - @Pattern(regexp = IPV4_REGEX, message = "nodes.node04Ip must be a valid IPv4") + @Pattern(regexp = IPV4_REGEX, message = "nodes.node04Ip必须是有效IPv4地址") @Schema(description = "节点 4 IP", example = "192.168.1.14") private String node04Ip; @@ -172,37 +172,37 @@ public class InitPreviewRequest { @Schema(description = "消息中间件配置") public static class Mq { - @NotBlank(message = "mq.mqType is required") + @NotBlank(message = "mq.mqType不能为空") @Schema(description = "消息中间件类型", example = "RABBITMQ") private String mqType; - @NotBlank(message = "mq.channelUsername is required") - @Size(max = 32, message = "mq.channelUsername length must be <= 32") + @NotBlank(message = "mq.channelUsername不能为空") + @Size(max = 32, message = "mq.channelUsername长度必须小于等于32") @Schema(description = "RABBITMQ通道用户名", example = "tms") private String channelUsername; - @NotBlank(message = "mq.channelPassword is required") - @Size(max = 128, message = "mq.channelPassword length must be <= 128") + @NotBlank(message = "mq.channelPassword不能为空") + @Size(max = 128, message = "mq.channelPassword长度必须小于等于128") @Schema(description = "RABBITMQ通道密码") private String channelPassword; - @Size(max = 128, message = "mq.tlqLicenseFileId length must be <= 128") - @Pattern(regexp = FILE_ID_REGEX, message = "mq.tlqLicenseFileId must be a fileId") + @Size(max = 128, message = "mq.tlqLicenseFileId长度必须小于等于128") + @Pattern(regexp = FILE_ID_REGEX, message = "mq.tlqLicenseFileId必须是有效fileId") @Schema(description = "TLQ 许可证文件 fileId。作用:用于 TLQ 相关模式的许可证下发。使用条件:`TLQ` 或 `RABBITMQ_TLQ` 场景必填", example = "FILE-20260310-000001") private String tlqLicenseFileId; - @Size(max = 64, message = "mq.cipsGatewayHost length must be <= 64") + @Size(max = 64, message = "mq.cipsGatewayHost长度必须小于等于64") @Schema(description = "CIPS 中心网关地址。作用:回写到 TLQ 网关脚本中的中心网关主机地址。使用条件:仅 `DIRECT + RABBITMQ_TLQ` 必填", example = "192.168.10.20") private String cipsGatewayHost; - @Size(max = 128, message = "mq.cfgZipFileId length must be <= 128") - @Pattern(regexp = FILE_ID_REGEX, message = "mq.cfgZipFileId must be a fileId") + @Size(max = 128, message = "mq.cfgZipFileId长度必须小于等于128") + @Pattern(regexp = FILE_ID_REGEX, message = "mq.cfgZipFileId必须是有效fileId") @Schema(description = "直参 PMTS-CAE 客户化配置包 fileId。使用条件:仅 `DIRECT` 使用;`RABBITMQ_TLQ`必填", example = "FILE-20260310-000201") private String cfgZipFileId; - @Size(max = 128, message = "mq.cfmqConfigFileId length must be <= 128") - @Pattern(regexp = FILE_ID_REGEX, message = "mq.cfmqConfigFileId must be a fileId") + @Size(max = 128, message = "mq.cfmqConfigFileId长度必须小于等于128") + @Pattern(regexp = FILE_ID_REGEX, message = "mq.cfmqConfigFileId必须是有效fileId") @Schema(description = "直参 CFMQ 客户化脚本/配置 fileId。使用条件:仅 `DIRECT + RABBITMQ_CFMQ` 必填", example = "FILE-20260310-000002") private String cfmqConfigFileId; @@ -267,14 +267,14 @@ public class InitPreviewRequest { @Schema(description = "许可证和配置包引用") public static class Licenses { - @NotBlank(message = "licenses.receiverLicenseFileId is required") - @Size(max = 128, message = "licenses.receiverLicenseFileId length must be <= 128") - @Pattern(regexp = FILE_ID_REGEX, message = "licenses.receiverLicenseFileId must be a fileId") + @NotBlank(message = "licenses.receiverLicenseFileId不能为空") + @Size(max = 128, message = "licenses.receiverLicenseFileId长度必须小于等于128") + @Pattern(regexp = FILE_ID_REGEX, message = "licenses.receiverLicenseFileId必须是有效fileId") @Schema(description = "接收端许可证文件 fileId", example = "FILE-20260310-000003") private String receiverLicenseFileId; - @Size(max = 128, message = "licenses.cfgZipFileId length must be <= 128") - @Pattern(regexp = FILE_ID_REGEX, message = "licenses.cfgZipFileId must be a fileId") + @Size(max = 128, message = "licenses.cfgZipFileId长度必须小于等于128") + @Pattern(regexp = FILE_ID_REGEX, message = "licenses.cfgZipFileId必须是有效fileId") @Schema(description = "标准版配置包 zip 文件 fileId。作用:用于标准版/间参版初始化时引用配置包。使用条件:`ENTERPRISE`、`INDIRECT` 场景按对应流程使用,`DIRECT` 不使用此字段", example = "FILE-20260310-000004") private String cfgZipFileId; diff --git a/src/main/java/com/cisd/tms/modules/init/executor/ConfigurableInitStepExecutor.java b/src/main/java/com/cisd/tms/modules/init/executor/ConfigurableInitStepExecutor.java index 596c462..a5637e5 100644 --- a/src/main/java/com/cisd/tms/modules/init/executor/ConfigurableInitStepExecutor.java +++ b/src/main/java/com/cisd/tms/modules/init/executor/ConfigurableInitStepExecutor.java @@ -67,7 +67,7 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { public InitStepExecutionResult execute(InitTaskEntity task, InitTaskStepEntity step) { String command = step.getCommandLine(); if (isBlank(command)) { - return InitStepExecutionResult.failure("empty command", -1, null); + return InitStepExecutionResult.failure("命令不能为空", -1, null); } if (command.startsWith(INTERNAL_PREFIX)) { @@ -76,19 +76,19 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { // SIMULATE 模式不触发外部脚本,便于联调和单测 if (properties.getMode() == InitExecutorProperties.Mode.SIMULATE) { - return InitStepExecutionResult.success("simulated success", 0, null); + return InitStepExecutionResult.success("模拟执行成功", 0, null); } // LOCAL 模式下只允许执行白名单前缀命令,避免注入或误执行 if (!isWhitelisted(command, properties.getWhitelistPrefixes())) { - return InitStepExecutionResult.failure("command rejected by whitelist", -1, null); + return InitStepExecutionResult.failure("命令未通过白名单校验", -1, null); } Path logFile; try { logFile = prepareLogPath(task, step); } catch (IOException ex) { - return InitStepExecutionResult.failure("prepare log path failed: " + ex.getMessage(), -1, null); + return InitStepExecutionResult.failure("准备日志路径失败:" + ex.getMessage(), -1, null); } Process process = null; @@ -102,19 +102,19 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { boolean finished = process.waitFor(properties.getTimeoutSec(), TimeUnit.SECONDS); if (!finished) { process.destroyForcibly(); - return InitStepExecutionResult.failure("command timeout", -1, logFile.toString()); + return InitStepExecutionResult.failure("命令执行超时", -1, logFile.toString()); } int exitCode = process.exitValue(); if (exitCode == 0) { - return InitStepExecutionResult.success("command success", exitCode, logFile.toString()); + return InitStepExecutionResult.success("命令执行成功", exitCode, logFile.toString()); } - return InitStepExecutionResult.failure("command failed", exitCode, logFile.toString()); + return InitStepExecutionResult.failure("命令执行失败", exitCode, logFile.toString()); } catch (IOException | InterruptedException ex) { if (ex instanceof InterruptedException) { Thread.currentThread().interrupt(); } - return InitStepExecutionResult.failure("executor exception: " + ex.getMessage(), -1, logFile.toString()); + return InitStepExecutionResult.failure("执行器异常:" + ex.getMessage(), -1, logFile.toString()); } finally { if (process != null && process.isAlive()) { process.destroyForcibly(); @@ -157,13 +157,13 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { case "cfmq_route_apply" -> executeCfmqRouteApply(task, step); case "verify" -> executeVerify(task, step); default -> { - String logPath = writeInternalLog(task, step, "skip unsupported internal step: " + internalCode); - yield InitStepExecutionResult.success("skip unsupported internal step: " + internalCode, 0, logPath); + String logPath = writeInternalLog(task, step, "跳过不支持的内部步骤:" + internalCode); + yield InitStepExecutionResult.success("跳过不支持的内部步骤:" + internalCode, 0, logPath); } }; } catch (Exception ex) { String logPath = writeInternalLog(task, step, "internal step exception: " + ex.getMessage()); - return InitStepExecutionResult.failure("internal step failed: " + ex.getMessage(), -1, logPath); + return InitStepExecutionResult.failure("内部步骤执行失败:" + ex.getMessage(), -1, logPath); } } @@ -172,7 +172,7 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { ResolvedCpconfig resolvedCpconfig = resolveCpconfigPath(task, context); Path cpconfigPath = resolvedCpconfig.path(); if (!Files.exists(cpconfigPath) || !Files.isRegularFile(cpconfigPath)) { - String detail = "cpconfig not found: " + cpconfigPath + ", candidates=" + formatPathList(resolvedCpconfig.candidates()); + String detail = "cpconfig不存在:" + cpconfigPath + ",候选路径=" + formatPathList(resolvedCpconfig.candidates()); String logPath = writeInternalLog(task, step, detail); return InitStepExecutionResult.failure(detail, -1, logPath); } @@ -235,7 +235,7 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { appendLog(logFile, "tlq license copied: " + tlqSource + " -> " + tlqTarget); } } - return InitStepExecutionResult.success("file prepare success", 0, logFile.toString()); + return InitStepExecutionResult.success("文件准备成功", 0, logFile.toString()); } private InitStepExecutionResult executeMqUserApply(InitTaskEntity task, InitTaskStepEntity step) throws IOException { @@ -245,21 +245,21 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { return executeDirectMqUserApply(task, step, context); } if (!"ENTERPRISE".equals(context.productType()) && !"INDIRECT".equals(context.productType())) { - String logPath = writeInternalLog(task, step, "mq_user_apply skipped for non-standard product type"); - return InitStepExecutionResult.success("mq_user_apply skipped", 0, logPath); + String logPath = writeInternalLog(task, step, "mq_user_apply 已跳过 for non-standard product type"); + return InitStepExecutionResult.success("mq_user_apply 已跳过", 0, logPath); } if (!shouldApplyStandardRabbitmq(context)) { - String logPath = writeInternalLog(task, step, "mq_user_apply skipped for current standard mode"); - return InitStepExecutionResult.success("mq_user_apply skipped", 0, logPath); + String logPath = writeInternalLog(task, step, "mq_user_apply 已跳过 for current standard mode"); + return InitStepExecutionResult.success("mq_user_apply 已跳过", 0, logPath); } if (properties.getMode() == InitExecutorProperties.Mode.SIMULATE) { - String logPath = writeInternalLog(task, step, "mq_user_apply simulated"); - return InitStepExecutionResult.success("mq_user_apply simulated", 0, logPath); + String logPath = writeInternalLog(task, step, "mq_user_apply 模拟执行成功"); + return InitStepExecutionResult.success("mq_user_apply 模拟执行成功", 0, logPath); } if (isBlank(context.channelUsername()) || isBlank(context.channelPassword())) { - String logPath = writeInternalLog(task, step, "mq_user_apply failed: channel account is blank"); - return InitStepExecutionResult.failure("mq_user_apply failed: channel account is blank", -1, logPath); + String logPath = writeInternalLog(task, step, "mq_user_apply 执行失败:通道账号为空"); + return InitStepExecutionResult.failure("mq_user_apply 执行失败:通道账号为空", -1, logPath); } try { @@ -274,29 +274,29 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { scriptContext.stagingScriptDir() ); if (code != 0) { - return InitStepExecutionResult.failure("mq_user_apply failed: setuprabq.sh failed", code, logFile.toString()); + return InitStepExecutionResult.failure("mq_user_apply 执行失败:setuprabq.sh执行失败", code, logFile.toString()); } - return InitStepExecutionResult.success("mq user applied by setuprabq.sh", 0, logFile.toString()); + return InitStepExecutionResult.success("MQ用户已通过setuprabq.sh应用", 0, logFile.toString()); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); - String logPath = writeInternalLog(task, step, "mq_user_apply interrupted"); - return InitStepExecutionResult.failure("mq_user_apply interrupted", -1, logPath); + String logPath = writeInternalLog(task, step, "mq_user_apply 被中断"); + return InitStepExecutionResult.failure("mq_user_apply 被中断", -1, logPath); } } private InitStepExecutionResult executeMqQueueApply(InitTaskEntity task, InitTaskStepEntity step) throws IOException { PlanContext context = loadPlanContext(task); if (!"ENTERPRISE".equals(context.productType()) && !"INDIRECT".equals(context.productType())) { - String logPath = writeInternalLog(task, step, "mq_queue_apply skipped for non-standard product type"); - return InitStepExecutionResult.success("mq_queue_apply skipped", 0, logPath); + String logPath = writeInternalLog(task, step, "mq_queue_apply 已跳过 for non-standard product type"); + return InitStepExecutionResult.success("mq_queue_apply 已跳过", 0, logPath); } if (!shouldApplyStandardRabbitmq(context)) { - String logPath = writeInternalLog(task, step, "mq_queue_apply skipped for current standard mode"); - return InitStepExecutionResult.success("mq_queue_apply skipped", 0, logPath); + String logPath = writeInternalLog(task, step, "mq_queue_apply 已跳过 for current standard mode"); + return InitStepExecutionResult.success("mq_queue_apply 已跳过", 0, logPath); } if (properties.getMode() == InitExecutorProperties.Mode.SIMULATE) { - String logPath = writeInternalLog(task, step, "mq_queue_apply simulated"); - return InitStepExecutionResult.success("mq_queue_apply simulated", 0, logPath); + String logPath = writeInternalLog(task, step, "mq_queue_apply 模拟执行成功"); + return InitStepExecutionResult.success("mq_queue_apply 模拟执行成功", 0, logPath); } try { @@ -313,13 +313,13 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { scriptContext.stagingScriptDir() ); if (code != 0) { - return InitStepExecutionResult.failure("mq_queue_apply failed: " + selectedScriptName + " failed", code, logFile.toString()); + return InitStepExecutionResult.failure("mq_queue_apply 执行失败:" + selectedScriptName + " 执行失败", code, logFile.toString()); } - return InitStepExecutionResult.success("mq queues applied by " + selectedScriptName, 0, logFile.toString()); + return InitStepExecutionResult.success("MQ队列已通过" + selectedScriptName, 0, logFile.toString()); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); - String logPath = writeInternalLog(task, step, "mq_queue_apply interrupted"); - return InitStepExecutionResult.failure("mq_queue_apply interrupted", -1, logPath); + String logPath = writeInternalLog(task, step, "mq_queue_apply 被中断"); + return InitStepExecutionResult.failure("mq_queue_apply 被中断", -1, logPath); } } @@ -329,12 +329,12 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { PlanContext context ) throws IOException { if (properties.getMode() == InitExecutorProperties.Mode.SIMULATE) { - String logPath = writeInternalLog(task, step, "mq_user_apply simulated"); - return InitStepExecutionResult.success("mq_user_apply simulated", 0, logPath); + String logPath = writeInternalLog(task, step, "mq_user_apply 模拟执行成功"); + return InitStepExecutionResult.success("mq_user_apply 模拟执行成功", 0, logPath); } if (isBlank(context.channelUsername()) || isBlank(context.channelPassword())) { - String logPath = writeInternalLog(task, step, "mq_user_apply failed: channel account is blank"); - return InitStepExecutionResult.failure("mq_user_apply failed: channel account is blank", -1, logPath); + String logPath = writeInternalLog(task, step, "mq_user_apply 执行失败:通道账号为空"); + return InitStepExecutionResult.failure("mq_user_apply 执行失败:通道账号为空", -1, logPath); } try { @@ -345,7 +345,7 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { String pass = context.channelPassword(); if (!waitRabbitmqReady(logFile, ctl)) { - appendLog(logFile, "rabbitmqctl status check failed, continue with apply flow"); + appendLog(logFile, "rabbitmqctl状态检查失败,继续执行应用流程"); } runCommandAllowAlreadyExists(logFile, Arrays.asList(ctl, "add_vhost", vhost), "vhost"); @@ -365,21 +365,21 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { if (permissionCode != 0) { String lastLine = readLastLogLine(logFile); return InitStepExecutionResult.failure( - "mq_user_apply failed: set permissions failed" + "mq_user_apply 执行失败:set permissions执行失败" + ", exit=" + permissionCode + (isBlank(lastLine) ? "" : ", detail=" + lastLine), permissionCode, logFile.toString() ); } - return InitStepExecutionResult.success("mq user applied", 0, logFile.toString()); + return InitStepExecutionResult.success("MQ用户已应用", 0, logFile.toString()); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); - String logPath = writeInternalLog(task, step, "mq_user_apply interrupted"); - return InitStepExecutionResult.failure("mq_user_apply interrupted", -1, logPath); + String logPath = writeInternalLog(task, step, "mq_user_apply 被中断"); + return InitStepExecutionResult.failure("mq_user_apply 被中断", -1, logPath); } catch (IllegalArgumentException ex) { - String logPath = writeInternalLog(task, step, "mq_user_apply failed: " + ex.getMessage()); - return InitStepExecutionResult.failure("mq_user_apply failed: " + ex.getMessage(), -1, logPath); + String logPath = writeInternalLog(task, step, "mq_user_apply 执行失败:" + ex.getMessage()); + return InitStepExecutionResult.failure("mq_user_apply 执行失败:" + ex.getMessage(), -1, logPath); } } @@ -613,7 +613,7 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { String rabbitAdminCmd = firstNonBlank(properties.getRabbitmqAdminCommand(), "/usr/sbin/rabbitmqadmin"); String adminUser = firstNonBlank(properties.getRabbitAdminUsername(), "admin"); String adminPass = firstNonBlank(properties.getRabbitAdminPassword(), "admin"); - appendLog(logFile, "rabbitmqctl add/change failed, fallback to rabbitmqadmin"); + appendLog(logFile, "rabbitmqctl新增或修改失败,切换到rabbitmqadmin兜底"); runCommand( logFile, @@ -641,7 +641,7 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { if (userCode != 0) { String lastLine = readLastLogLine(logFile); return InitStepExecutionResult.failure( - "mq_user_apply failed: add/change user failed" + "mq_user_apply 执行失败:add/change user 执行失败" + ", addExit=" + addUserCode + ", changeExit=" + changeCode + ", fallbackExit=" + userCode @@ -668,7 +668,7 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { if (permissionCode != 0) { String lastLine = readLastLogLine(logFile); return InitStepExecutionResult.failure( - "mq_user_apply failed: fallback set permissions failed" + "mq_user_apply 执行失败:fallback set permissions执行失败" + ", exit=" + permissionCode + (isBlank(lastLine) ? "" : ", detail=" + lastLine), permissionCode, @@ -676,7 +676,7 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { ); } return InitStepExecutionResult.success( - "mq user applied by rabbitmqadmin fallback (rabbitmqctl addExit=" + addUserCode + ", changeExit=" + changeCode + ")", + "MQ用户已应用 通过rabbitmqadmin兜底应用 (rabbitmqctl addExit=" + addUserCode + ", changeExit=" + changeCode + ")", 0, logFile.toString() ); @@ -731,7 +731,7 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { } } throw new IllegalArgumentException( - "rabbitmqctl not found, please set tms.init.executor.rabbitmq-ctl-command to absolute path" + "rabbitmqctl不存在,请将tms.init.executor.rabbitmq-ctl-command配置为绝对路径" ); } @@ -761,27 +761,27 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { private InitStepExecutionResult executeAppMqCredentialsApply(InitTaskEntity task, InitTaskStepEntity step) throws IOException { PlanContext context = loadPlanContext(task); if (!"ENTERPRISE".equals(context.productType()) && !"INDIRECT".equals(context.productType())) { - String logPath = writeInternalLog(task, step, "app_mq_credentials_apply skipped for non-standard product type"); - return InitStepExecutionResult.success("app_mq_credentials_apply skipped", 0, logPath); + String logPath = writeInternalLog(task, step, "app_mq_credentials_apply 已跳过 for non-standard product type"); + return InitStepExecutionResult.success("app_mq_credentials_apply 已跳过", 0, logPath); } if (!shouldApplyStandardRabbitmq(context)) { - String logPath = writeInternalLog(task, step, "app_mq_credentials_apply skipped for current standard mode"); - return InitStepExecutionResult.success("app_mq_credentials_apply skipped", 0, logPath); + String logPath = writeInternalLog(task, step, "app_mq_credentials_apply 已跳过 for current standard mode"); + return InitStepExecutionResult.success("app_mq_credentials_apply 已跳过", 0, logPath); } if (properties.getMode() == InitExecutorProperties.Mode.SIMULATE) { - String logPath = writeInternalLog(task, step, "app_mq_credentials_apply simulated"); - return InitStepExecutionResult.success("app_mq_credentials_apply simulated", 0, logPath); + String logPath = writeInternalLog(task, step, "app_mq_credentials_apply 模拟执行成功"); + return InitStepExecutionResult.success("app_mq_credentials_apply 模拟执行成功", 0, logPath); } if (isBlank(context.channelUsername()) || isBlank(context.channelPassword())) { - String logPath = writeInternalLog(task, step, "app_mq_credentials_apply failed: channel account is blank"); - return InitStepExecutionResult.failure("app_mq_credentials_apply failed: channel account is blank", -1, logPath); + String logPath = writeInternalLog(task, step, "app_mq_credentials_apply 执行失败:通道账号为空"); + return InitStepExecutionResult.failure("app_mq_credentials_apply 执行失败:通道账号为空", -1, logPath); } Path logFile = prepareLogPath(task, step); List configFiles = resolveGlobFiles(properties.getStandardRabbitAppConfigPatterns()); if (configFiles.isEmpty()) { appendLog(logFile, "config patterns=" + properties.getStandardRabbitAppConfigPatterns()); - return InitStepExecutionResult.failure("app_mq_credentials_apply failed: no app config file matched", -1, logFile.toString()); + return InitStepExecutionResult.failure("app_mq_credentials_apply 执行失败:未匹配到应用配置文件", -1, logFile.toString()); } String configuredUsername = context.channelUsername(); @@ -791,8 +791,8 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { configuredUsername = encryptWith3DesHex(context.channelUsername(), properties.getStandardRabbitCredentialSecretKey()); configuredPassword = encryptWith3DesHex(context.channelPassword(), properties.getStandardRabbitCredentialSecretKey()); } catch (Exception ex) { - appendLog(logFile, "encrypt rabbitmq credential failed: " + ex.getMessage()); - return InitStepExecutionResult.failure("app_mq_credentials_apply failed: encrypt credential failed", -1, logFile.toString()); + appendLog(logFile, "encrypt rabbitmq credential 执行失败:" + ex.getMessage()); + return InitStepExecutionResult.failure("app_mq_credentials_apply 执行失败:加密凭据失败", -1, logFile.toString()); } } @@ -802,24 +802,24 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { changed += replaceProperty(configFile, "spring.rabbitmq.password", configuredPassword); appendLog(logFile, "updated app rabbitmq credentials: " + configFile); } - return InitStepExecutionResult.success("app mq credentials applied, changed=" + changed, 0, logFile.toString()); + return InitStepExecutionResult.success("应用MQ凭据已应用,变更数量=" + changed, 0, logFile.toString()); } private InitStepExecutionResult executeDirectDbInit(InitTaskEntity task, InitTaskStepEntity step) throws IOException { PlanContext context = loadPlanContext(task); if (!"DIRECT".equals(context.productType())) { - String logPath = writeInternalLog(task, step, "direct_db_init skipped for non-direct product type"); - return InitStepExecutionResult.success("direct_db_init skipped", 0, logPath); + String logPath = writeInternalLog(task, step, "direct_db_init 已跳过 for non-direct product type"); + return InitStepExecutionResult.success("direct_db_init 已跳过", 0, logPath); } - String logPath = writeInternalLog(task, step, "direct_db_init skipped: out of direct customer-config scope"); - return InitStepExecutionResult.success("direct_db_init skipped", 0, logPath); + String logPath = writeInternalLog(task, step, "direct_db_init 已跳过: out of direct customer-config scope"); + return InitStepExecutionResult.success("direct_db_init 已跳过", 0, logPath); } private InitStepExecutionResult executeStandardDbRender(InitTaskEntity task, InitTaskStepEntity step) throws IOException { PlanContext context = loadPlanContext(task); if ("DIRECT".equals(context.productType())) { - String logPath = writeInternalLog(task, step, "db_render skipped for direct product type"); - return InitStepExecutionResult.success("db_render skipped", 0, logPath); + String logPath = writeInternalLog(task, step, "db_render 已跳过 for direct product type"); + return InitStepExecutionResult.success("db_render 已跳过", 0, logPath); } Path scriptBaseDir = Paths.get(firstNonBlank( @@ -837,8 +837,8 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { List requiredFiles = List.of(cmtpTemplate, cmepDmlTemplate, cmepEntryTemplate, schemaTemplate, upOrgTemplate); for (Path requiredFile : requiredFiles) { if (!Files.exists(requiredFile) || !Files.isRegularFile(requiredFile)) { - String logPath = writeInternalLog(task, step, "db_render failed: required sql template not found: " + requiredFile); - return InitStepExecutionResult.failure("db_render failed: sql template not found", -1, logPath); + String logPath = writeInternalLog(task, step, "db_render 执行失败:必需SQL模板不存在: " + requiredFile); + return InitStepExecutionResult.failure("db_render 执行失败:SQL模板不存在", -1, logPath); } } @@ -858,19 +858,19 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { private InitStepExecutionResult executeStandardDbApply(InitTaskEntity task, InitTaskStepEntity step) throws IOException { PlanContext context = loadPlanContext(task); if ("DIRECT".equals(context.productType())) { - String logPath = writeInternalLog(task, step, "db_apply skipped for direct product type"); - return InitStepExecutionResult.success("db_apply skipped", 0, logPath); + String logPath = writeInternalLog(task, step, "db_apply 已跳过 for direct product type"); + return InitStepExecutionResult.success("db_apply 已跳过", 0, logPath); } Path stageSqlDir = resolveStandardDbStageSqlDir(task); if (!Files.isDirectory(stageSqlDir)) { - String logPath = writeInternalLog(task, step, "db_apply failed: staged sql dir not found: " + stageSqlDir); - return InitStepExecutionResult.failure("db_apply failed: staged sql dir not found", -1, logPath); + String logPath = writeInternalLog(task, step, "db_apply 执行失败:暂存SQL目录不存在: " + stageSqlDir); + return InitStepExecutionResult.failure("db_apply 执行失败:暂存SQL目录不存在", -1, logPath); } if (properties.getMode() == InitExecutorProperties.Mode.SIMULATE) { - String logPath = writeInternalLog(task, step, "db_apply simulated, staged dir=" + stageSqlDir); - return InitStepExecutionResult.success("db_apply simulated", 0, logPath); + String logPath = writeInternalLog(task, step, "db_apply 模拟执行成功, staged dir=" + stageSqlDir); + return InitStepExecutionResult.success("db_apply 模拟执行成功", 0, logPath); } Path loadDir = Paths.get(firstNonBlank( @@ -890,8 +890,8 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { appendLog(logFile, "staged sql copied: " + stageSqlDir + " -> " + loadDir); String applyCommand = normalize(properties.getStandardDbApplyCommand()); if (isBlank(applyCommand)) { - appendLog(logFile, "db_apply failed: standardDbApplyCommand is blank"); - return InitStepExecutionResult.failure("db_apply failed: apply command is blank", -1, logFile.toString()); + appendLog(logFile, "db_apply 执行失败:standardDbApplyCommand is blank"); + return InitStepExecutionResult.failure("db_apply 执行失败:应用命令为空", -1, logFile.toString()); } try { @@ -903,20 +903,20 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { buildStandardDbApplyEnvironment(loadDir, context) ); if (code != 0) { - return InitStepExecutionResult.failure("db_apply failed", code, logFile.toString()); + return InitStepExecutionResult.failure("db_apply 执行失败", code, logFile.toString()); } - return InitStepExecutionResult.success("db sql applied to load dir=" + loadDir + ", command executed", 0, logFile.toString()); + return InitStepExecutionResult.success("数据库SQL已应用到加载目录=" + loadDir + ",命令已执行", 0, logFile.toString()); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); - return InitStepExecutionResult.failure("db_apply interrupted", -1, logFile.toString()); + return InitStepExecutionResult.failure("db_apply 被中断", -1, logFile.toString()); } } private InitStepExecutionResult executeStandardNginxConfigApply(InitTaskEntity task, InitTaskStepEntity step) throws IOException { PlanContext context = loadPlanContext(task); if ("DIRECT".equals(context.productType())) { - String logPath = writeInternalLog(task, step, "nginx_config_apply skipped for direct product type"); - return InitStepExecutionResult.success("nginx_config_apply skipped", 0, logPath); + String logPath = writeInternalLog(task, step, "nginx_config_apply 已跳过 for direct product type"); + return InitStepExecutionResult.success("nginx_config_apply 已跳过", 0, logPath); } Path templatePath = Paths.get(firstNonBlank( @@ -924,8 +924,8 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { properties.getStandardHomeDir() + "/cpackage/software/nginx.conf" )).normalize().toAbsolutePath(); if (!Files.exists(templatePath) || !Files.isRegularFile(templatePath)) { - String logPath = writeInternalLog(task, step, "nginx_config_apply failed: template not found: " + templatePath); - return InitStepExecutionResult.failure("nginx_config_apply failed: template not found", -1, logPath); + String logPath = writeInternalLog(task, step, "nginx_config_apply 执行失败:模板不存在: " + templatePath); + return InitStepExecutionResult.failure("nginx_config_apply 执行失败:模板不存在", -1, logPath); } Path stageFile = resolveStandardNginxStageFile(task); @@ -937,25 +937,25 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { Files.writeString(stageFile, rendered, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); Path logFile = prepareLogPath(task, step); - appendLog(logFile, "nginx config staged: " + templatePath + " -> " + stageFile); + appendLog(logFile, "nginx配置已暂存: " + templatePath + " -> " + stageFile); if (properties.getMode() == InitExecutorProperties.Mode.SIMULATE) { - return InitStepExecutionResult.success("nginx config staged", 0, logFile.toString()); + return InitStepExecutionResult.success("nginx配置已暂存", 0, logFile.toString()); } Path targetPath = Paths.get(firstNonBlank(properties.getStandardNginxTargetPath(), "/usr/local/nginx/conf/nginx.conf")) .normalize() .toAbsolutePath(); copyToTarget(stageFile, targetPath); - appendLog(logFile, "nginx config applied: " + stageFile + " -> " + targetPath); - return InitStepExecutionResult.success("nginx config applied", 0, logFile.toString()); + appendLog(logFile, "nginx配置已应用: " + stageFile + " -> " + targetPath); + return InitStepExecutionResult.success("nginx配置已应用", 0, logFile.toString()); } private InitStepExecutionResult executeStandardWebConfigApply(InitTaskEntity task, InitTaskStepEntity step) throws IOException { PlanContext context = loadPlanContext(task); if ("DIRECT".equals(context.productType())) { - String logPath = writeInternalLog(task, step, "web_config_apply skipped for direct product type"); - return InitStepExecutionResult.success("web_config_apply skipped", 0, logPath); + String logPath = writeInternalLog(task, step, "web_config_apply 已跳过 for direct product type"); + return InitStepExecutionResult.success("web_config_apply 已跳过", 0, logPath); } Path frontZip = Paths.get(firstNonBlank( @@ -963,8 +963,8 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { properties.getStandardHomeDir() + "/cpackage/front/front.zip" )).normalize().toAbsolutePath(); if (!Files.exists(frontZip) || !Files.isRegularFile(frontZip)) { - String logPath = writeInternalLog(task, step, "web_config_apply failed: front zip not found: " + frontZip); - return InitStepExecutionResult.failure("web_config_apply failed: front zip not found", -1, logPath); + String logPath = writeInternalLog(task, step, "web_config_apply 执行失败:前端zip文件不存在: " + frontZip); + return InitStepExecutionResult.failure("web_config_apply 执行失败:前端zip文件不存在", -1, logPath); } Path stagedWebDir = resolveStandardWebStageDir(task); @@ -981,10 +981,10 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { ); Path logFile = prepareLogPath(task, step); - appendLog(logFile, "web assets staged: " + frontZip + " -> " + stagedWebDir); + appendLog(logFile, "前端资源已暂存: " + frontZip + " -> " + stagedWebDir); if (properties.getMode() == InitExecutorProperties.Mode.SIMULATE) { - return InitStepExecutionResult.success("web assets staged", 0, logFile.toString()); + return InitStepExecutionResult.success("前端资源已暂存", 0, logFile.toString()); } Path targetDir = Paths.get(firstNonBlank(properties.getStandardWebTargetDir(), "/usr/local/nginx/html")) @@ -998,9 +998,9 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { "/usr/local/nginx/html/organization.json" )).normalize().toAbsolutePath(); copyToTarget(stagedOrganizationJson, organizationJsonTarget); - appendLog(logFile, "web assets applied: " + stagedWebDir + " -> " + targetDir); - appendLog(logFile, "organization.json applied: " + stagedOrganizationJson + " -> " + organizationJsonTarget); - return InitStepExecutionResult.success("web assets applied", 0, logFile.toString()); + appendLog(logFile, "前端资源已应用: " + stagedWebDir + " -> " + targetDir); + appendLog(logFile, "organization.json 已应用: " + stagedOrganizationJson + " -> " + organizationJsonTarget); + return InitStepExecutionResult.success("前端资源已应用", 0, logFile.toString()); } private InitStepExecutionResult executeStandardStartCommand( @@ -1011,22 +1011,22 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { ) throws IOException { PlanContext context = loadPlanContext(task); if ("DIRECT".equals(context.productType())) { - String logPath = writeInternalLog(task, step, stepName + " skipped for direct product type"); - return InitStepExecutionResult.success(stepName + " skipped", 0, logPath); + String logPath = writeInternalLog(task, step, stepName + " 已跳过 for direct product type"); + return InitStepExecutionResult.success(stepName + " 已跳过", 0, logPath); } if (properties.getMode() == InitExecutorProperties.Mode.SIMULATE) { - String logPath = writeInternalLog(task, step, stepName + " simulated"); - return InitStepExecutionResult.success(stepName + " simulated", 0, logPath); + String logPath = writeInternalLog(task, step, stepName + " 模拟执行成功"); + return InitStepExecutionResult.success(stepName + " 模拟执行成功", 0, logPath); } if (isBlank(command)) { - String logPath = writeInternalLog(task, step, stepName + " failed: command is blank"); - return InitStepExecutionResult.failure(stepName + " failed: command is blank", -1, logPath); + String logPath = writeInternalLog(task, step, stepName + " 执行失败:command is blank"); + return InitStepExecutionResult.failure(stepName + " 执行失败:command is blank", -1, logPath); } Path logFile = prepareLogPath(task, step); Path workingDirectory = resolveExistingStandardWorkingDirectory(); if (workingDirectory == null) { - appendLog(logFile, "cwd skipped: standardHomeDir not found"); + appendLog(logFile, "cwd 已跳过: standardHomeDir不存在"); } try { int code = runCommand( @@ -1037,12 +1037,12 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { buildStandardStartEnvironment(stepName) ); if (code != 0) { - return InitStepExecutionResult.failure(stepName + " failed", code, logFile.toString()); + return InitStepExecutionResult.failure(stepName + " 执行失败", code, logFile.toString()); } - return InitStepExecutionResult.success(stepName + " success", 0, logFile.toString()); + return InitStepExecutionResult.success(stepName + " 成功", 0, logFile.toString()); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); - return InitStepExecutionResult.failure(stepName + " interrupted", -1, logFile.toString()); + return InitStepExecutionResult.failure(stepName + " 被中断", -1, logFile.toString()); } } @@ -1053,14 +1053,14 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { ) throws IOException { PlanContext context = loadPlanContext(task); if ("DIRECT".equals(context.productType())) { - String logPath = writeInternalLog(task, step, "reset db drop skipped for direct product type"); - return InitStepExecutionResult.success("reset db drop skipped", 0, logPath); + String logPath = writeInternalLog(task, step, "reset db drop 已跳过 for direct product type"); + return InitStepExecutionResult.success("reset db drop 已跳过", 0, logPath); } String databaseName = isBlank(fixedDatabaseName) ? normalize(context.orgCode()) : fixedDatabaseName; String identifier = requireMysqlIdentifier(databaseName); if (properties.getMode() == InitExecutorProperties.Mode.SIMULATE) { - String logPath = writeInternalLog(task, step, "reset db drop simulated, database=" + identifier); - return InitStepExecutionResult.success("reset db drop simulated", 0, logPath); + String logPath = writeInternalLog(task, step, "reset db drop 模拟执行成功, database=" + identifier); + return InitStepExecutionResult.success("reset db drop 模拟执行成功", 0, logPath); } Path logFile = prepareLogPath(task, step); @@ -1076,28 +1076,28 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { buildStandardDbApplyEnvironment(null, context) ); if (code != 0) { - return InitStepExecutionResult.failure("reset db drop failed", code, logFile.toString()); + return InitStepExecutionResult.failure("reset db drop 执行失败", code, logFile.toString()); } - return InitStepExecutionResult.success("reset db dropped: " + identifier, 0, logFile.toString()); + return InitStepExecutionResult.success("重置数据库已删除:" + identifier, 0, logFile.toString()); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); - return InitStepExecutionResult.failure("reset db drop interrupted", -1, logFile.toString()); + return InitStepExecutionResult.failure("reset db drop 被中断", -1, logFile.toString()); } } private InitStepExecutionResult executeResetMqCleanup(InitTaskEntity task, InitTaskStepEntity step) throws IOException { PlanContext context = loadPlanContext(task); if (!shouldApplyStandardRabbitmq(context)) { - String logPath = writeInternalLog(task, step, "mq_cleanup skipped for current mode"); - return InitStepExecutionResult.success("mq_cleanup skipped", 0, logPath); + String logPath = writeInternalLog(task, step, "mq_cleanup 已跳过 for current mode"); + return InitStepExecutionResult.success("mq_cleanup 已跳过", 0, logPath); } if (properties.getMode() == InitExecutorProperties.Mode.SIMULATE) { - String logPath = writeInternalLog(task, step, "mq_cleanup simulated"); - return InitStepExecutionResult.success("mq_cleanup simulated", 0, logPath); + String logPath = writeInternalLog(task, step, "mq_cleanup 模拟执行成功"); + return InitStepExecutionResult.success("mq_cleanup 模拟执行成功", 0, logPath); } if (isBlank(context.channelUsername())) { - String logPath = writeInternalLog(task, step, "mq_cleanup failed: channel username is blank"); - return InitStepExecutionResult.failure("mq_cleanup failed: channel username is blank", -1, logPath); + String logPath = writeInternalLog(task, step, "mq_cleanup 执行失败:通道用户名为空"); + return InitStepExecutionResult.failure("mq_cleanup 执行失败:通道用户名为空", -1, logPath); } Path logFile = prepareLogPath(task, step); @@ -1106,7 +1106,7 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { String user = normalize(context.channelUsername()); try { if (!waitRabbitmqReady(logFile, ctl)) { - appendLog(logFile, "rabbitmqctl status check failed, continue with cleanup flow"); + appendLog(logFile, "rabbitmqctl status check 执行失败, continue with cleanup flow"); } for (String queue : buildStandardCustomerDefaultVhostQueues(normalize(context.orgCode()))) { @@ -1117,18 +1117,18 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { } runCommand(logFile, Arrays.asList(ctl, "clear_permissions", "-p", vhost, user), true); runCommand(logFile, Arrays.asList(ctl, "delete_user", user), true); - return InitStepExecutionResult.success("mq cleanup applied", 0, logFile.toString()); + return InitStepExecutionResult.success("mq cleanup 已应用", 0, logFile.toString()); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); - return InitStepExecutionResult.failure("mq cleanup interrupted", -1, logFile.toString()); + return InitStepExecutionResult.failure("mq cleanup 被中断", -1, logFile.toString()); } } private InitStepExecutionResult executeResetStopRabbitmq(InitTaskEntity task, InitTaskStepEntity step) throws IOException { PlanContext context = loadPlanContext(task); if (!shouldApplyStandardRabbitmq(context)) { - String logPath = writeInternalLog(task, step, "stop_rabbitmq skipped for current mode"); - return InitStepExecutionResult.success("stop_rabbitmq skipped", 0, logPath); + String logPath = writeInternalLog(task, step, "stop_rabbitmq 已跳过 for current mode"); + return InitStepExecutionResult.success("stop_rabbitmq 已跳过", 0, logPath); } return executeStandardStartCommand(task, step, "stop_rabbitmq", properties.getStandardRabbitStopCommand()); } @@ -1136,37 +1136,37 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { private InitStepExecutionResult executeResetTlqLicenseCleanup(InitTaskEntity task, InitTaskStepEntity step) throws IOException { PlanContext context = loadPlanContext(task); if (!"TLQ".equals(context.mqType()) && !"RABBITMQ_TLQ".equals(context.mqType())) { - String logPath = writeInternalLog(task, step, "tlq_license_cleanup skipped for non-TLQ mode"); - return InitStepExecutionResult.success("tlq_license_cleanup skipped", 0, logPath); + String logPath = writeInternalLog(task, step, "tlq_license_cleanup 已跳过 for non-TLQ mode"); + return InitStepExecutionResult.success("tlq_license_cleanup 已跳过", 0, logPath); } Path target = Paths.get(resolveTlqLicenseTargetPath(context.productType())).normalize().toAbsolutePath(); Path logFile = prepareLogPath(task, step); if (properties.getMode() == InitExecutorProperties.Mode.SIMULATE) { - appendLog(logFile, "tlq license cleanup simulated: " + target); - return InitStepExecutionResult.success("tlq license cleanup simulated", 0, logFile.toString()); + appendLog(logFile, "tlq license cleanup 模拟执行成功: " + target); + return InitStepExecutionResult.success("tlq license cleanup 模拟执行成功", 0, logFile.toString()); } Files.deleteIfExists(target); appendLog(logFile, "tlq license deleted if exists: " + target); - return InitStepExecutionResult.success("tlq license cleanup applied", 0, logFile.toString()); + return InitStepExecutionResult.success("tlq license cleanup 已应用", 0, logFile.toString()); } private InitStepExecutionResult executeResetCleanStaging(InitTaskEntity task, InitTaskStepEntity step) throws IOException { Path stageDir = Paths.get(properties.getStagingRootDir()).resolve(task.getTaskId()).normalize().toAbsolutePath(); Path logFile = prepareLogPath(task, step); if (properties.getMode() == InitExecutorProperties.Mode.SIMULATE) { - appendLog(logFile, "clean_staging simulated: " + stageDir); - return InitStepExecutionResult.success("clean_staging simulated", 0, logFile.toString()); + appendLog(logFile, "clean_staging 模拟执行成功: " + stageDir); + return InitStepExecutionResult.success("clean_staging 模拟执行成功", 0, logFile.toString()); } deleteRecursively(stageDir); appendLog(logFile, "clean_staging removed: " + stageDir); - return InitStepExecutionResult.success("clean_staging applied", 0, logFile.toString()); + return InitStepExecutionResult.success("clean_staging 已应用", 0, logFile.toString()); } private InitStepExecutionResult executeVerifyReset(InitTaskEntity task, InitTaskStepEntity step) throws IOException { PlanContext context = loadPlanContext(task); if (properties.getMode() == InitExecutorProperties.Mode.SIMULATE) { - String logPath = writeInternalLog(task, step, "verify_reset simulated"); - return InitStepExecutionResult.success("verify_reset simulated", 0, logPath); + String logPath = writeInternalLog(task, step, "verify_reset 模拟执行成功"); + return InitStepExecutionResult.success("verify_reset 模拟执行成功", 0, logPath); } Path logFile = prepareLogPath(task, step); @@ -1183,29 +1183,29 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { if (!failures.isEmpty()) { String detail = String.join("; ", failures); - appendLog(logFile, "verify_reset failed: " + detail); - return InitStepExecutionResult.failure("verify_reset failed: " + detail, -1, logFile.toString()); + appendLog(logFile, "verify_reset 执行失败:" + detail); + return InitStepExecutionResult.failure("verify_reset 执行失败:" + detail, -1, logFile.toString()); } - appendLog(logFile, "verify_reset success"); - return InitStepExecutionResult.success("verify_reset success", 0, logFile.toString()); + appendLog(logFile, "verify_reset 成功"); + return InitStepExecutionResult.success("verify_reset 成功", 0, logFile.toString()); } private InitStepExecutionResult executeTlqInitCfg(InitTaskEntity task, InitTaskStepEntity step) throws IOException { PlanContext context = loadPlanContext(task); if (!"DIRECT".equals(context.productType()) || !"RABBITMQ_TLQ".equals(context.mqType())) { - String logPath = writeInternalLog(task, step, "tlq_init_cfg skipped for non-direct TLQ mode"); - return InitStepExecutionResult.success("tlq_init_cfg skipped", 0, logPath); + String logPath = writeInternalLog(task, step, "tlq_init_cfg 已跳过 for non-direct TLQ mode"); + return InitStepExecutionResult.success("tlq_init_cfg 已跳过", 0, logPath); } - String logPath = writeInternalLog(task, step, "tlq_init_cfg skipped: no longer part of direct customer-config scope"); - return InitStepExecutionResult.success("tlq_init_cfg skipped", 0, logPath); + String logPath = writeInternalLog(task, step, "tlq_init_cfg 已跳过: no longer part of direct customer-config scope"); + return InitStepExecutionResult.success("tlq_init_cfg 已跳过", 0, logPath); } private InitStepExecutionResult executeCaePackageApply(InitTaskEntity task, InitTaskStepEntity step) throws IOException { PlanContext context = loadPlanContext(task); if (!"DIRECT".equals(context.productType())) { - String logPath = writeInternalLog(task, step, "cae_package_apply skipped for non-direct product type"); - return InitStepExecutionResult.success("cae_package_apply skipped", 0, logPath); + String logPath = writeInternalLog(task, step, "cae_package_apply 已跳过 for non-direct product type"); + return InitStepExecutionResult.success("cae_package_apply 已跳过", 0, logPath); } Path zipFile = resolveAndStageInputFile(task, context.cfgZipFileId(), "cae_package", null); @@ -1219,7 +1219,7 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { Files.createDirectories(targetDir); copyDirectoryContents(unpackDir, targetDir); - String message = "cae package applied: " + zipFile + " -> " + targetDir; + String message = "cae package 已应用: " + zipFile + " -> " + targetDir; String logPath = writeInternalLog(task, step, message); return InitStepExecutionResult.success(message, 0, logPath); } @@ -1227,18 +1227,18 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { private InitStepExecutionResult executeCaeUpdate(InitTaskEntity task, InitTaskStepEntity step) throws IOException { PlanContext context = loadPlanContext(task); if (!"DIRECT".equals(context.productType())) { - String logPath = writeInternalLog(task, step, "cae_update skipped for non-direct product type"); - return InitStepExecutionResult.success("cae_update skipped", 0, logPath); + String logPath = writeInternalLog(task, step, "cae_update 已跳过 for non-direct product type"); + return InitStepExecutionResult.success("cae_update 已跳过", 0, logPath); } if (properties.getMode() == InitExecutorProperties.Mode.SIMULATE) { - String logPath = writeInternalLog(task, step, "cae_update simulated"); - return InitStepExecutionResult.success("cae_update simulated", 0, logPath); + String logPath = writeInternalLog(task, step, "cae_update 模拟执行成功"); + return InitStepExecutionResult.success("cae_update 模拟执行成功", 0, logPath); } String command = normalize(properties.getDirectCaeUpdateCommand()); if (isBlank(command)) { - String logPath = writeInternalLog(task, step, "cae_update validated only: command is blank"); - return InitStepExecutionResult.success("cae_update validated", 0, logPath); + String logPath = writeInternalLog(task, step, "cae_update 已校验 only: command is blank"); + return InitStepExecutionResult.success("cae_update 已校验", 0, logPath); } Path scriptDir = Paths.get(firstNonBlank(properties.getDirectCaeUpdateScriptDir(), "/opt/CAE_Install_App_Resource/CAE")) @@ -1248,26 +1248,26 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { try { int code = runCommand(logFile, Arrays.asList("/bin/zsh", "-lc", command), false, scriptDir); if (code != 0) { - return InitStepExecutionResult.failure("cae_update failed", code, logFile.toString()); + return InitStepExecutionResult.failure("cae_update 执行失败", code, logFile.toString()); } - return InitStepExecutionResult.success("cae_update applied", 0, logFile.toString()); + return InitStepExecutionResult.success("cae_update 已应用", 0, logFile.toString()); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); - return InitStepExecutionResult.failure("cae_update interrupted", -1, logFile.toString()); + return InitStepExecutionResult.failure("cae_update 被中断", -1, logFile.toString()); } } private InitStepExecutionResult executeTlqGatewayApply(InitTaskEntity task, InitTaskStepEntity step) throws IOException { PlanContext context = loadPlanContext(task); if (!"DIRECT".equals(context.productType()) || !"RABBITMQ_TLQ".equals(context.mqType())) { - String logPath = writeInternalLog(task, step, "tlq_gateway_apply skipped for non-direct TLQ mode"); - return InitStepExecutionResult.success("tlq_gateway_apply skipped", 0, logPath); + String logPath = writeInternalLog(task, step, "tlq_gateway_apply 已跳过 for non-direct TLQ mode"); + return InitStepExecutionResult.success("tlq_gateway_apply 已跳过", 0, logPath); } Path sourceScript = resolveTlqGatewaySourceScript(task); if (!Files.exists(sourceScript) || !Files.isRegularFile(sourceScript)) { - String logPath = writeInternalLog(task, step, "tlq_gateway_apply failed: source script not found: " + sourceScript); - return InitStepExecutionResult.failure("tlq_gateway_apply failed: source script not found", -1, logPath); + String logPath = writeInternalLog(task, step, "tlq_gateway_apply 执行失败:源脚本不存在: " + sourceScript); + return InitStepExecutionResult.failure("tlq_gateway_apply 执行失败:源脚本不存在", -1, logPath); } String rewritten = rewriteGatewayHost(Files.readString(sourceScript), context.cipsGatewayHost()); @@ -1279,58 +1279,58 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { Files.writeString(targetScript, rewritten, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); if (properties.getMode() == InitExecutorProperties.Mode.SIMULATE) { - String logPath = writeInternalLog(task, step, "tlq_gateway_apply simulated: " + targetScript); - return InitStepExecutionResult.success("tlq_gateway_apply simulated", 0, logPath); + String logPath = writeInternalLog(task, step, "tlq_gateway_apply 模拟执行成功: " + targetScript); + return InitStepExecutionResult.success("tlq_gateway_apply 模拟执行成功", 0, logPath); } Path logFile = prepareLogPath(task, step); try { int code = runCommand(logFile, Arrays.asList("bash", targetScript.toString()), false); if (code != 0) { - return InitStepExecutionResult.failure("tlq_gateway_apply failed", code, logFile.toString()); + return InitStepExecutionResult.failure("tlq_gateway_apply 执行失败", code, logFile.toString()); } - return InitStepExecutionResult.success("tlq gateway applied: " + targetScript, 0, logFile.toString()); + return InitStepExecutionResult.success("tlq gateway 已应用: " + targetScript, 0, logFile.toString()); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); - return InitStepExecutionResult.failure("tlq_gateway_apply interrupted", -1, logFile.toString()); + return InitStepExecutionResult.failure("tlq_gateway_apply 被中断", -1, logFile.toString()); } } private InitStepExecutionResult executeRestartTlq(InitTaskEntity task, InitTaskStepEntity step) throws IOException { PlanContext context = loadPlanContext(task); if (!"DIRECT".equals(context.productType()) || !"RABBITMQ_TLQ".equals(context.mqType())) { - String logPath = writeInternalLog(task, step, "restart_tlq skipped for non-direct TLQ mode"); - return InitStepExecutionResult.success("restart_tlq skipped", 0, logPath); + String logPath = writeInternalLog(task, step, "restart_tlq 已跳过 for non-direct TLQ mode"); + return InitStepExecutionResult.success("restart_tlq 已跳过", 0, logPath); } if (properties.getMode() == InitExecutorProperties.Mode.SIMULATE) { - String logPath = writeInternalLog(task, step, "restart_tlq simulated"); - return InitStepExecutionResult.success("restart_tlq simulated", 0, logPath); + String logPath = writeInternalLog(task, step, "restart_tlq 模拟执行成功"); + return InitStepExecutionResult.success("restart_tlq 模拟执行成功", 0, logPath); } String command = normalize(properties.getDirectTlqRestartCommand()); if (isBlank(command)) { - String logPath = writeInternalLog(task, step, "restart_tlq validated only: command is blank"); - return InitStepExecutionResult.success("restart_tlq validated", 0, logPath); + String logPath = writeInternalLog(task, step, "restart_tlq 已校验 only: command is blank"); + return InitStepExecutionResult.success("restart_tlq 已校验", 0, logPath); } Path logFile = prepareLogPath(task, step); try { int code = runCommand(logFile, Arrays.asList("/bin/zsh", "-lc", command), false); if (code != 0) { - return InitStepExecutionResult.failure("restart_tlq failed", code, logFile.toString()); + return InitStepExecutionResult.failure("restart_tlq 执行失败", code, logFile.toString()); } - return InitStepExecutionResult.success("restart_tlq applied", 0, logFile.toString()); + return InitStepExecutionResult.success("restart_tlq 已应用", 0, logFile.toString()); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); - return InitStepExecutionResult.failure("restart_tlq interrupted", -1, logFile.toString()); + return InitStepExecutionResult.failure("restart_tlq 被中断", -1, logFile.toString()); } } private InitStepExecutionResult executeCfmqLinkApply(InitTaskEntity task, InitTaskStepEntity step) throws IOException { PlanContext context = loadPlanContext(task); if (!"DIRECT".equals(context.productType()) || !"RABBITMQ_CFMQ".equals(context.mqType())) { - String logPath = writeInternalLog(task, step, "cfmq_link_apply skipped for non-direct CFMQ mode"); - return InitStepExecutionResult.success("cfmq_link_apply skipped", 0, logPath); + String logPath = writeInternalLog(task, step, "cfmq_link_apply 已跳过 for non-direct CFMQ mode"); + return InitStepExecutionResult.success("cfmq_link_apply 已跳过", 0, logPath); } Path sourceScript = resolveAndStageInputFile(task, context.cfmqConfigFileId(), "cfmq_link_script", null); @@ -1341,28 +1341,28 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { Files.writeString(stagedScript, rewritten, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); if (properties.getMode() == InitExecutorProperties.Mode.SIMULATE) { - String logPath = writeInternalLog(task, step, "cfmq_link_apply simulated: " + stagedScript); - return InitStepExecutionResult.success("cfmq_link_apply simulated", 0, logPath); + String logPath = writeInternalLog(task, step, "cfmq_link_apply 模拟执行成功: " + stagedScript); + return InitStepExecutionResult.success("cfmq_link_apply 模拟执行成功", 0, logPath); } Path logFile = prepareLogPath(task, step); try { int code = runCommand(logFile, Arrays.asList("bash", stagedScript.toString()), false); if (code != 0) { - return InitStepExecutionResult.failure("cfmq_link_apply failed", code, logFile.toString()); + return InitStepExecutionResult.failure("cfmq_link_apply 执行失败", code, logFile.toString()); } - return InitStepExecutionResult.success("cfmq_link_apply applied", 0, logFile.toString()); + return InitStepExecutionResult.success("cfmq_link_apply 已应用", 0, logFile.toString()); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); - return InitStepExecutionResult.failure("cfmq_link_apply interrupted", -1, logFile.toString()); + return InitStepExecutionResult.failure("cfmq_link_apply 被中断", -1, logFile.toString()); } } private InitStepExecutionResult executeCfmqRouteApply(InitTaskEntity task, InitTaskStepEntity step) throws IOException { PlanContext context = loadPlanContext(task); if (!"DIRECT".equals(context.productType()) || !"RABBITMQ_CFMQ".equals(context.mqType())) { - String logPath = writeInternalLog(task, step, "cfmq_route_apply skipped for non-direct CFMQ mode"); - return InitStepExecutionResult.success("cfmq_route_apply skipped", 0, logPath); + String logPath = writeInternalLog(task, step, "cfmq_route_apply 已跳过 for non-direct CFMQ mode"); + return InitStepExecutionResult.success("cfmq_route_apply 已跳过", 0, logPath); } Path targetDir = Paths.get(firstNonBlank(properties.getDirectCaeTargetDir(), properties.getCfmqTargetDir())) @@ -1387,7 +1387,7 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { Path pmtsMsgHdl = targetDir.resolve("pmtsmsghdl.cfg"); Files.deleteIfExists(pmtsMsgHdl); appendLog(logFile, "cfmq route files changed=" + routeChanged + ", pmtsmsghdl deleted if exists=" + pmtsMsgHdl); - return InitStepExecutionResult.success("cfmq_route_apply applied", 0, logFile.toString()); + return InitStepExecutionResult.success("cfmq_route_apply 已应用", 0, logFile.toString()); } private InitStepExecutionResult executeVerify(InitTaskEntity task, InitTaskStepEntity step) throws IOException { @@ -1398,10 +1398,10 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { String logPath = writeInternalLog( task, step, - "verify failed: cpconfig not found: " + cpconfigPath - + ", candidates=" + formatPathList(resolvedCpconfig.candidates()) + "verify 执行失败:cpconfig不存在:" + cpconfigPath + + ",候选路径=" + formatPathList(resolvedCpconfig.candidates()) ); - return InitStepExecutionResult.failure("verify failed: cpconfig not found", -1, logPath); + return InitStepExecutionResult.failure("verify执行失败:cpconfig不存在", -1, logPath); } Map configMap = readConfigMap(cpconfigPath); @@ -1415,11 +1415,11 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { if (!failures.isEmpty()) { String detail = String.join("; ", failures); - String logPath = writeInternalLog(task, step, "verify failed: " + detail); - return InitStepExecutionResult.failure("verify failed: " + detail, -1, logPath); + String logPath = writeInternalLog(task, step, "verify 执行失败:" + detail); + return InitStepExecutionResult.failure("verify 执行失败:" + detail, -1, logPath); } - String message = "verify success, cpconfig=" + cpconfigPath; + String message = "verify 成功, cpconfig=" + cpconfigPath; String logPath = writeInternalLog(task, step, message); return InitStepExecutionResult.success(message, 0, logPath); } @@ -1707,7 +1707,7 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { private void ensureFileId(String rawValue) { if (!isLikelyFileId(rawValue)) { - throw new IllegalArgumentException("fileId is invalid: " + normalize(rawValue)); + throw new IllegalArgumentException("fileId无效:" + normalize(rawValue)); } } @@ -1730,7 +1730,7 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { ) throws IOException { ResolvedInputFile resolved = resolveInputFile(fileId, preferredFileName); if (resolved.path() == null) { - String detail = fieldName + " file not found, candidates=" + formatPathList(resolved.candidates()); + String detail = fieldName + " 文件不存在,候选路径=" + formatPathList(resolved.candidates()); throw new IllegalArgumentException(detail); } String stageFileName = firstNonBlank(preferredFileName, resolved.path().getFileName() == null ? fieldName : resolved.path().getFileName().toString()); @@ -1927,7 +1927,7 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { private void runCommandAllowAlreadyExists(Path logFile, List command, String scene) throws IOException, InterruptedException { int code = runCommand(logFile, command, true); if (code != 0) { - appendLog(logFile, scene + " already exists or command failed with exit code=" + code + ", ignored"); + appendLog(logFile, scene + " already exists or 命令执行失败 with exit code=" + code + ", ignored"); } } @@ -1961,12 +1961,12 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { boolean finished = process.waitFor(properties.getTimeoutSec(), TimeUnit.SECONDS); if (!finished) { process.destroyForcibly(); - appendLog(logFile, "command timeout: " + String.join(" ", command)); + appendLog(logFile, "命令执行超时: " + String.join(" ", command)); return -1; } int exitCode = process.exitValue(); if (!ignoreFailure && exitCode != 0) { - appendLog(logFile, "command failed: " + String.join(" ", command) + ", exitCode=" + exitCode); + appendLog(logFile, "命令执行失败: " + String.join(" ", command) + ", exitCode=" + exitCode); } return exitCode; } @@ -2077,7 +2077,7 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { String plain = normalize(input); String key = normalize(secretKey); if (isBlank(key)) { - throw new IllegalArgumentException("secret key is blank"); + throw new IllegalArgumentException("密钥为空"); } SecretKeySpec desKey = new SecretKeySpec(key.getBytes(), "DESede"); Cipher cipher = Cipher.getInstance("DESede"); @@ -2105,12 +2105,12 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { private PlanContext loadPlanContext(InitTaskEntity task) throws IOException { if (isBlank(task.getInitPlanJson())) { - throw new IllegalArgumentException("initPlanJson is empty"); + throw new IllegalArgumentException("initPlanJson为空"); } JsonNode root = objectMapper.readTree(task.getInitPlanJson()); JsonNode request = root.path("request"); if (request.isMissingNode() || request.isNull()) { - throw new IllegalArgumentException("initPlanJson.request missing, recreate task with latest schema"); + throw new IllegalArgumentException("initPlanJson.request缺失,请使用最新结构重新创建任务"); } JsonNode nodes = request.path("nodes"); @@ -2254,7 +2254,7 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { } } catch (InterruptedException ex) { Thread.currentThread().interrupt(); - failures.add(label + " process verification interrupted"); + failures.add(label + " process verification 被中断"); } } @@ -2280,7 +2280,7 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { } } catch (InterruptedException ex) { Thread.currentThread().interrupt(); - failures.add("database verification interrupted: " + identifier); + failures.add("database verification 被中断: " + identifier); } } @@ -2307,7 +2307,7 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { } } catch (InterruptedException ex) { Thread.currentThread().interrupt(); - failures.add(message + " (verification interrupted)"); + failures.add(message + " (verification 被中断)"); } } @@ -2324,7 +2324,7 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { private String requireMysqlIdentifier(String databaseName) { String normalized = normalize(databaseName); if (!normalized.matches("[A-Za-z0-9_]+")) { - throw new IllegalArgumentException("invalid database identifier: " + databaseName); + throw new IllegalArgumentException("数据库标识无效:" + databaseName); } return normalized; } diff --git a/src/main/java/com/cisd/tms/modules/init/service/impl/InitServiceImpl.java b/src/main/java/com/cisd/tms/modules/init/service/impl/InitServiceImpl.java index a95aa0c..713f6ee 100644 --- a/src/main/java/com/cisd/tms/modules/init/service/impl/InitServiceImpl.java +++ b/src/main/java/com/cisd/tms/modules/init/service/impl/InitServiceImpl.java @@ -88,7 +88,7 @@ public class InitServiceImpl implements InitService { @Override public InitPreviewResponse preview(InitPreviewRequest request) { if (request == null) { - throw new IllegalArgumentException("request must not be null"); + throw new IllegalArgumentException("请求不能为空"); } String productType = resolvePresetProductType(); @@ -122,7 +122,7 @@ public class InitServiceImpl implements InitService { @Override public InitCreateTaskResponse createTask(InitPreviewRequest request) { if (request == null) { - throw new IllegalArgumentException("request must not be null"); + throw new IllegalArgumentException("请求不能为空"); } String productType = resolvePresetProductType(); @@ -300,7 +300,7 @@ public class InitServiceImpl implements InitService { @Override public InitTaskStepLogResponse getTaskStepLog(String taskId, int stepNo) { if (stepNo <= 0) { - throw new IllegalArgumentException("stepNo must be > 0"); + throw new IllegalArgumentException("stepNo必须大于0"); } requireTask(taskId); InitTaskStepEntity step = requireStep(taskId, stepNo); @@ -343,7 +343,7 @@ public class InitServiceImpl implements InitService { InitTaskEntity task = requireTask(taskId); String taskType = normalize(task.getTaskType()); if (!TASK_TYPE_RESET.equals(taskType)) { - throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "reset task not found: " + taskId); + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "重置任务不存在:" + taskId); } return executeTaskInternal(taskId); } @@ -450,7 +450,7 @@ public class InitServiceImpl implements InitService { private InitTaskEntity requireTask(String taskId) { return initTaskRepository.findByTaskId(taskId) - .orElseThrow(() -> new BizException(ErrorCode.BIZ_ERROR.getCode(), "init task not found: " + taskId)); + .orElseThrow(() -> new BizException(ErrorCode.BIZ_ERROR.getCode(), "初始化任务不存在:" + taskId)); } private InitTaskStepEntity requireStep(String taskId, int stepNo) { @@ -459,7 +459,7 @@ public class InitServiceImpl implements InitService { .findFirst() .orElseThrow(() -> new BizException( ErrorCode.BIZ_ERROR.getCode(), - "init task step not found: taskId=" + taskId + ", stepNo=" + stepNo + "初始化任务步骤不存在:taskId=" + taskId + ", stepNo=" + stepNo )); } @@ -611,14 +611,14 @@ public class InitServiceImpl implements InitService { private String resolvePresetProductType() { String raw = normalize(cisdPresetProperties.getProductType()); if (isBlank(raw)) { - throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "device preset product type is missing, initialization is forbidden"); + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "设备预置产品类型缺失,禁止初始化"); } String productType = raw.toUpperCase(Locale.ROOT); if (!PRESET_PRODUCT_TYPES.contains(productType)) { throw new BizException( ErrorCode.BIZ_ERROR.getCode(), - "device preset product type is invalid: " + raw + ", initialization is forbidden" + "设备预置产品类型无效:" + raw + ",禁止初始化" ); } return productType; @@ -634,10 +634,10 @@ public class InitServiceImpl implements InitService { private void validateCommon(InitPreviewRequest request) { if (request.getNodes() == null) { - throw new IllegalArgumentException("nodes is required"); + throw new IllegalArgumentException("nodes不能为空"); } if (request.getMq() == null) { - throw new IllegalArgumentException("mq is required"); + throw new IllegalArgumentException("mq不能为空"); } requireNotBlank("orgCode", request.getOrgCode()); @@ -681,21 +681,21 @@ public class InitServiceImpl implements InitService { ResolvedResetContext context = resolveResetContextFromInitTask(latestInit.get()); if (!productType.equals(context.productType())) { - throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "device preset product type changed, reset context is invalid"); + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "设备预置产品类型已变更,重置上下文无效"); } requireAllowed("mqType", context.mqType(), resolveStandardMqAllowed(productType)); if (("RABBITMQ".equals(context.mqType()) || "RABBITMQ_TLQ".equals(context.mqType())) && isBlank(context.channelUsername())) { - throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "current init context missing rabbitmq channel username"); + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "当前初始化上下文缺少RabbitMQ通道用户名"); } return context; } private ResolvedResetContext resolveResetContextFromInitTask(InitTaskEntity task) { if (task == null) { - throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "successful init task is missing"); + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "成功ful init task is missing"); } if (isBlank(task.getInitPlanJson())) { - throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "successful init task snapshot is missing"); + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "成功ful init task snapshot is missing"); } try { JsonNode root = objectMapper.readTree(task.getInitPlanJson()); @@ -707,20 +707,20 @@ public class InitServiceImpl implements InitService { String channelUsername = firstNonBlank(text(mq, "channelUsername"), text(request, "channelUsername")); if (isBlank(productType) || isBlank(orgCode) || isBlank(mqType)) { - throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "successful init task snapshot is incomplete"); + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "成功ful init task snapshot is incomplete"); } return new ResolvedResetContext(productType, normalize(orgCode), normalizeUpper(mqType), normalize(channelUsername), task.getTaskId()); } catch (JsonProcessingException ex) { - throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "failed to parse successful init task snapshot"); + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "failed to parse 成功ful init task snapshot"); } } private CurrentInitConfigResponse resolveCurrentInitConfigFromTask(InitTaskEntity task) { if (task == null) { - throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "successful init task is missing"); + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "成功ful init task is missing"); } if (isBlank(task.getInitPlanJson())) { - throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "successful init task snapshot is missing"); + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "成功ful init task snapshot is missing"); } try { JsonNode root = objectMapper.readTree(task.getInitPlanJson()); @@ -747,7 +747,7 @@ public class InitServiceImpl implements InitService { response.setCfmqFileNames(buildCfmqFileNames(mq, mqType)); return response; } catch (JsonProcessingException ex) { - throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "failed to parse successful init task snapshot"); + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "failed to parse 成功ful init task snapshot"); } } @@ -812,7 +812,7 @@ public class InitServiceImpl implements InitService { requireAllowed("deployMode", deployMode, DEPLOY_STANDARD); requireAllowed("mq.mqType", mqType, resolveStandardMqAllowed(productType)); if (request.getLicenses() == null) { - throw new IllegalArgumentException("licenses is required"); + throw new IllegalArgumentException("licenses不能为空"); } requireNotBlank("licenses.receiverLicenseFileId", request.getLicenses().getReceiverLicenseFileId()); requireFileId("licenses.receiverLicenseFileId", request.getLicenses().getReceiverLicenseFileId()); @@ -834,7 +834,7 @@ public class InitServiceImpl implements InitService { if ("INDIRECT".equals(productType)) { return MQ_INDIRECT; } - throw new IllegalArgumentException("unsupported standard productType: " + productType); + throw new IllegalArgumentException("不支持的标准产品类型:" + productType); } private void validateDirect(InitPreviewRequest request, String deployMode, String mqType) { diff --git a/src/main/java/com/cisd/tms/modules/log/service/AuditBackupService.java b/src/main/java/com/cisd/tms/modules/log/service/AuditBackupService.java index 2f75b44..a8c177a 100644 --- a/src/main/java/com/cisd/tms/modules/log/service/AuditBackupService.java +++ b/src/main/java/com/cisd/tms/modules/log/service/AuditBackupService.java @@ -174,7 +174,7 @@ public class AuditBackupService { log.info("审计日志备份完成,共备份 {} 条", logList.size()); - BackupConfigEntity config = backupConfigRepository.find().orElseThrow(() -> new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "backupConfig not found")); + BackupConfigEntity config = backupConfigRepository.find().orElseThrow(() -> new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "备份配置不存在")); config.setLastBackupTime(LocalDateTime.now()); backupConfigRepository.saveOrUpdate(config); @@ -308,7 +308,7 @@ public class AuditBackupService { log.info("审计日志备份完成,共备份 {} 条", logList.size()); - BackupConfigEntity config = backupConfigRepository.find().orElseThrow(() -> new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "backupConfig not found")); + BackupConfigEntity config = backupConfigRepository.find().orElseThrow(() -> new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "备份配置不存在")); config.setLastBackupTime(LocalDateTime.now()); backupConfigRepository.saveOrUpdate(config); @@ -362,7 +362,7 @@ public class AuditBackupService { writePart(outputStream, cipherBytes); return outputStream.toByteArray(); } catch (IOException ex) { - throw new IllegalStateException("failed to build package signature source", ex); + throw new IllegalStateException("构建包签名原文失败", ex); } } @@ -374,7 +374,7 @@ public class AuditBackupService { private byte[] sign(byte[] dataToSign){ if (dataToSign == null) { - throw new IllegalArgumentException("dataToSign is required"); + throw new IllegalArgumentException("dataToSign不能为空"); } UserKeySignRequest request = new UserKeySignRequest(); request.setKeyIndex(1); @@ -384,7 +384,7 @@ public class AuditBackupService { private BackupDataResult signWithDigest(byte[] dataToSign){ if (dataToSign == null) { - throw new IllegalArgumentException("dataToSign is required"); + throw new IllegalArgumentException("dataToSign不能为空"); } UserKeySignRequest request = new UserKeySignRequest(); request.setKeyIndex(1); diff --git a/src/main/java/com/cisd/tms/modules/log/service/AuditLogRestoreService.java b/src/main/java/com/cisd/tms/modules/log/service/AuditLogRestoreService.java index 2226a71..ef8a52d 100644 --- a/src/main/java/com/cisd/tms/modules/log/service/AuditLogRestoreService.java +++ b/src/main/java/com/cisd/tms/modules/log/service/AuditLogRestoreService.java @@ -63,8 +63,8 @@ public class AuditLogRestoreService { Path extractDir = Paths.get(TEMP_RESTORE_DIR, batchId); int totalCount = 0; - int skippedCount = 0; - int successCount = 0; + int 已跳过Count = 0; + int 成功Count = 0; boolean restoreSuccess = false; try { log.info("开始审计日志恢复任务, 批次号: {}", batchId); @@ -105,7 +105,7 @@ public class AuditLogRestoreService { // } catch (BizException ex) { // throw ex; // } catch (RuntimeException ex) { -// throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "signature verification failed"); +// throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "signature verification 执行失败"); // } try { @@ -157,7 +157,7 @@ public class AuditLogRestoreService { for (OperationAuditLogEntity logEntity : parsedLogs) { if (existSignSet.contains(logEntity.getSignValue())) { - skippedCount ++; // 若已存在:跳过 + 已跳过Count ++; // 若已存在:跳过 } else { pendingInsertList.add(logEntity); // 若不存在:加入待插入队列 } @@ -165,16 +165,16 @@ public class AuditLogRestoreService { // 执行批量插入 if (!pendingInsertList.isEmpty()) { - successCount = auditLogRepository.addBatch(pendingInsertList); + 成功Count = auditLogRepository.addBatch(pendingInsertList); } // RestoreResultResponse result = new RestoreResultResponse(); // result.setTotalCount(totalCount); // result.setSkippedCount(skippedCount); -// result.setSuccessCount(successCount); +// result.setSuccessCount(成功Count); restoreSuccess = true; return String.format("恢复完成。共解析 %d 条,跳过已存在 %d 条,成功恢复 %d 条", - totalCount, skippedCount, successCount); + totalCount, 已跳过Count, 成功Count); } catch (Exception e) { log.error("恢复审计日志失败", e); @@ -187,7 +187,7 @@ public class AuditLogRestoreService { log.error("清理临时目录失败: {}", extractDir, e); } - recordRestoreAuditLog(file.getOriginalFilename(), totalCount, skippedCount, successCount, restoreSuccess); + recordRestoreAuditLog(file.getOriginalFilename(), totalCount, 已跳过Count, 成功Count, restoreSuccess); } } @@ -225,7 +225,7 @@ public class AuditLogRestoreService { writePart(outputStream, cipherBytes); return outputStream.toByteArray(); } catch (IOException ex) { - throw new IllegalStateException("failed to build package signature source", ex); + throw new IllegalStateException("构建包签名原文失败", ex); } } @@ -311,7 +311,7 @@ public class AuditLogRestoreService { } - private void recordRestoreAuditLog(String fileName, int total, int skipped, int success, boolean restoreSuccess) { + private void recordRestoreAuditLog(String fileName, int total, int 已跳过, int 成功, boolean restoreSuccess) { OperationAuditCommand command = new OperationAuditCommand(); ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); diff --git a/src/main/java/com/cisd/tms/modules/log/service/OperationAuditService.java b/src/main/java/com/cisd/tms/modules/log/service/OperationAuditService.java index c491de9..8944688 100644 --- a/src/main/java/com/cisd/tms/modules/log/service/OperationAuditService.java +++ b/src/main/java/com/cisd/tms/modules/log/service/OperationAuditService.java @@ -199,7 +199,7 @@ public class OperationAuditService { // } catch (BizException ex) { // throw ex; // } catch (RuntimeException ex) { -// throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "signature verification failed"); +// throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "signature verification 执行失败"); // } 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 66f755f..3443626 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 @@ -74,12 +74,12 @@ public class LmkController { private void validatePacketIndexes(List packets) { if (packets == null || packets.size() != 2) { - throw new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "packets size must be 2"); + throw new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "packets数量必须为2"); } boolean hasPacketOne = packets.stream().anyMatch(packet -> packet != null && packet.getPacketIndex() == 1); boolean hasPacketTwo = packets.stream().anyMatch(packet -> packet != null && packet.getPacketIndex() == 2); if (!hasPacketOne || !hasPacketTwo) { - throw new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "packetIndex must be 1 or 2"); + throw new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "packetIndex必须为1或2"); } } } diff --git a/src/main/java/com/cisd/tms/modules/mk/dto/MasterKeyBackupPacket.java b/src/main/java/com/cisd/tms/modules/mk/dto/MasterKeyBackupPacket.java index 5f2ca7b..2f286e2 100644 --- a/src/main/java/com/cisd/tms/modules/mk/dto/MasterKeyBackupPacket.java +++ b/src/main/java/com/cisd/tms/modules/mk/dto/MasterKeyBackupPacket.java @@ -11,8 +11,8 @@ import java.util.List; public class MasterKeyBackupPacket { - @Min(value = 1, message = "packetIndex must be 1 or 2") - @Max(value = 2, message = "packetIndex must be 1 or 2") + @Min(value = 1, message = "packetIndex必须为1或2") + @Max(value = 2, message = "packetIndex必须为1或2") @JsonProperty("packetIndex") private int packetIndex; @@ -41,7 +41,7 @@ public class MasterKeyBackupPacket { } @JsonIgnore - @AssertTrue(message = "packetIndex must be 1 or 2") + @AssertTrue(message = "packetIndex必须为1或2") public boolean isPacketIndexValid() { return packetIndex == 1 || packetIndex == 2; } diff --git a/src/main/java/com/cisd/tms/modules/mk/dto/MasterKeyRecoverPacketsRequest.java b/src/main/java/com/cisd/tms/modules/mk/dto/MasterKeyRecoverPacketsRequest.java index 53088da..c8a8853 100644 --- a/src/main/java/com/cisd/tms/modules/mk/dto/MasterKeyRecoverPacketsRequest.java +++ b/src/main/java/com/cisd/tms/modules/mk/dto/MasterKeyRecoverPacketsRequest.java @@ -8,8 +8,8 @@ import java.util.List; public class MasterKeyRecoverPacketsRequest { - @NotEmpty(message = "packets must not be empty") - @Size(min = 2, max = 2, message = "packets size must be 2") + @NotEmpty(message = "packets不能为空") + @Size(min = 2, max = 2, message = "packets数量必须为2") private List<@Valid MasterKeyBackupPacket> packets; public List getPackets() { diff --git a/src/main/java/com/cisd/tms/modules/mk/dto/MasterKeyRecoverRequest.java b/src/main/java/com/cisd/tms/modules/mk/dto/MasterKeyRecoverRequest.java index bf5c599..2127971 100644 --- a/src/main/java/com/cisd/tms/modules/mk/dto/MasterKeyRecoverRequest.java +++ b/src/main/java/com/cisd/tms/modules/mk/dto/MasterKeyRecoverRequest.java @@ -8,12 +8,12 @@ import java.util.List; public class MasterKeyRecoverRequest { - @NotBlank(message = "lmkMac must not be blank") + @NotBlank(message = "lmkMac不能为空") private String lmkMac; - @NotEmpty(message = "components must not be empty") - @Size(min = 2, max = 2, message = "components size must be 2") - private List<@NotBlank(message = "component must not be blank") String> components; + @NotEmpty(message = "components不能为空") + @Size(min = 2, max = 2, message = "components数量必须为2") + private List<@NotBlank(message = "component不能为空") String> components; public String getLmkMac() { return lmkMac; diff --git a/src/main/java/com/cisd/tms/modules/mk/dto/UKeySignDTO.java b/src/main/java/com/cisd/tms/modules/mk/dto/UKeySignDTO.java index 6668534..222b546 100644 --- a/src/main/java/com/cisd/tms/modules/mk/dto/UKeySignDTO.java +++ b/src/main/java/com/cisd/tms/modules/mk/dto/UKeySignDTO.java @@ -12,19 +12,19 @@ public class UKeySignDTO { /** * UKey 公钥 */ - @NotBlank(message = "pubKey is required") + @NotBlank(message = "pubKey不能为空") private String pubKey; /** * UKey 序列号。发行签名接口会直接完成绑定,因此必传。 */ - @NotBlank(message = "ukeySerial is required") + @NotBlank(message = "ukeySerial不能为空") private String ukeySerial; /** * 角色内固定席位编号。 */ - @NotNull(message = "uid is required") + @NotNull(message = "uid不能为空") private Integer uid; } 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 650565a..ca5a1e8 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 @@ -220,16 +220,16 @@ public class LmkServiceImpl implements LmkService { private String masterKeyCompose(String lmkMacHex, List components) { if (lmkMacHex == null || lmkMacHex.isBlank()) { - throw new IllegalArgumentException("lmkMac must not be blank"); + throw new IllegalArgumentException("lmkMac不能为空"); } if (components == null || components.size() != LMKConstant.COMPONENT_NUM) { - throw new IllegalArgumentException("master key components size must be " + LMKConstant.COMPONENT_NUM); + throw new IllegalArgumentException("主密钥分量数量必须为" + LMKConstant.COMPONENT_NUM); } StringBuilder lmk = new StringBuilder(lmkMacHex); for (int index = 0; index < LMKConstant.COMPONENT_NUM; index++) { String component = components.get(index); if (component == null || component.isBlank()) { - throw new IllegalArgumentException("master key component missing: " + (index + 1)); + throw new IllegalArgumentException("主密钥分量缺失:" + (index + 1)); } lmk.append(component); } @@ -307,7 +307,7 @@ public class LmkServiceImpl implements LmkService { BackupDataResult backupData = pcieCryptoService.backupLmkEx(); byte[] normalized = normalizeBackupData(backupData); if (normalized.length < LMK_MAC_BYTE_LENGTH) { - throw new IllegalStateException("backup lmk data length is too short: " + normalized.length); + throw new IllegalStateException("备份LMK数据长度过短:" + normalized.length); } // 厂商返回格式:LMK_MAC + component1 + component2 + component3。 // 这里先按原生结构完整解析,后续 buildPacket 时再投影成两份外部包。 @@ -320,7 +320,7 @@ public class LmkServiceImpl implements LmkService { BackupDataResult backupData = pcieCryptoService.backupIkComponent(keyType); byte[] normalized = normalizeBackupData(backupData); if (normalized.length < IK_COMPONENT_TOTAL_BYTE_LENGTH) { - throw new IllegalStateException("backup ik data length is too short: " + normalized.length); + throw new IllegalStateException("备份IK数据长度过短:" + normalized.length); } // 厂商返回格式:component1 + component2 + component3 + mac + publicKey + publicKeyMac。 // 与 LMK 一样,先解析成原生三分量,再由外层决定如何投影到两份包中。 @@ -402,7 +402,7 @@ public class LmkServiceImpl implements LmkService { private int requirePacketIndex(int componentIndex) { if (componentIndex < PACKET_INDEX_ONE || componentIndex > PACKET_INDEX_TWO) { - throw new IllegalArgumentException("packetIndex must be 1 or 2"); + throw new IllegalArgumentException("packetIndex必须为1或2"); } // 这里校验的是“外部包编号”,不是原生分量编号。 return componentIndex; @@ -450,19 +450,19 @@ public class LmkServiceImpl implements LmkService { private String requireConsistentLmkMac(List packets) { if (packets == null || packets.size() != LMKConstant.COMPONENT_NUM) { - throw new IllegalArgumentException("packets size must be " + LMKConstant.COMPONENT_NUM); + throw new IllegalArgumentException("packets数量必须为" + LMKConstant.COMPONENT_NUM); } String lmkMac = null; for (MasterKeyBackupPacket packet : packets) { if (packet == null || packet.getLmk() == null || packet.getLmk().getLmkMac() == null || packet.getLmk().getLmkMac().isBlank()) { - throw new IllegalArgumentException("lmk packet missing"); + throw new IllegalArgumentException("LMK分包缺失"); } if (lmkMac == null) { lmkMac = packet.getLmk().getLmkMac(); continue; } if (!lmkMac.equals(packet.getLmk().getLmkMac())) { - throw new IllegalArgumentException("lmk mac is inconsistent"); + throw new IllegalArgumentException("LMK MAC不一致"); } } return lmkMac; @@ -473,14 +473,14 @@ public class LmkServiceImpl implements LmkService { for (MasterKeyBackupPacket packet : packets) { MasterKeyPacketIkPart part = keyType == IKEnums.KEY_TYPE_AUTH.getCode() ? packet.getIkAuth() : packet.getIkDevice(); if (part == null || part.getMac() == null || part.getMac().isBlank()) { - throw new IllegalArgumentException("ik mac missing"); + throw new IllegalArgumentException("IK MAC缺失"); } if (mac == null) { mac = part.getMac(); continue; } if (!mac.equals(part.getMac())) { - throw new IllegalArgumentException("ik mac is inconsistent"); + throw new IllegalArgumentException("IK MAC不一致"); } } return mac; @@ -491,14 +491,14 @@ public class LmkServiceImpl implements LmkService { for (MasterKeyBackupPacket packet : packets) { MasterKeyPacketIkPart part = keyType == IKEnums.KEY_TYPE_AUTH.getCode() ? packet.getIkAuth() : packet.getIkDevice(); if (part == null || part.getPublicKey() == null || part.getPublicKey().isBlank()) { - throw new IllegalArgumentException("ik publicKey missing"); + throw new IllegalArgumentException("IK公钥缺失"); } if (publicKey == null) { publicKey = part.getPublicKey(); continue; } if (!publicKey.equals(part.getPublicKey())) { - throw new IllegalArgumentException("ik publicKey is inconsistent"); + throw new IllegalArgumentException("IK公钥不一致"); } } return publicKey; @@ -509,14 +509,14 @@ public class LmkServiceImpl implements LmkService { for (MasterKeyBackupPacket packet : packets) { MasterKeyPacketIkPart part = keyType == IKEnums.KEY_TYPE_AUTH.getCode() ? packet.getIkAuth() : packet.getIkDevice(); if (part == null || part.getPublicKeyMac() == null || part.getPublicKeyMac().isBlank()) { - throw new IllegalArgumentException("ik publicKeyMac missing"); + throw new IllegalArgumentException("IK公钥MAC缺失"); } if (publicKeyMac == null) { publicKeyMac = part.getPublicKeyMac(); continue; } if (!publicKeyMac.equals(part.getPublicKeyMac())) { - throw new IllegalArgumentException("ik publicKeyMac is inconsistent"); + throw new IllegalArgumentException("IK公钥MAC不一致"); } } return publicKeyMac; @@ -526,12 +526,12 @@ public class LmkServiceImpl implements LmkService { return packet.getUserKeys().stream() .filter(item -> item.getKeyIndex() == keyIndex && item.getKeyType() == keyType) .findFirst() - .orElseThrow(() -> new IllegalArgumentException("user key component missing in packet " + packet.getPacketIndex())); + .orElseThrow(() -> new IllegalArgumentException("用户密钥分量缺失,分包:" + packet.getPacketIndex())); } private List sortPackets(List packets) { if (packets == null || packets.size() != LMKConstant.COMPONENT_NUM) { - throw new IllegalArgumentException("packets size must be " + LMKConstant.COMPONENT_NUM); + throw new IllegalArgumentException("packets数量必须为" + LMKConstant.COMPONENT_NUM); } List sorted = new ArrayList<>(packets); sorted.sort(Comparator.comparingInt(MasterKeyBackupPacket::getPacketIndex)); @@ -539,7 +539,7 @@ public class LmkServiceImpl implements LmkService { for (int index = 0; index < sorted.size(); index++) { int expected = index + 1; if (sorted.get(index).getPacketIndex() != expected) { - throw new IllegalArgumentException("packetIndex must be 1,2"); + throw new IllegalArgumentException("packetIndex必须为1或2"); } } return sorted; diff --git a/src/main/java/com/cisd/tms/modules/security/replay/service/impl/ReplayProtectionServiceImpl.java b/src/main/java/com/cisd/tms/modules/security/replay/service/impl/ReplayProtectionServiceImpl.java index dc6c02d..8d33406 100644 --- a/src/main/java/com/cisd/tms/modules/security/replay/service/impl/ReplayProtectionServiceImpl.java +++ b/src/main/java/com/cisd/tms/modules/security/replay/service/impl/ReplayProtectionServiceImpl.java @@ -80,48 +80,48 @@ public class ReplayProtectionServiceImpl implements ReplayProtectionService { return buildClaimedResult(entity); } } catch (RuntimeException ex) { - throw new ReplayProtectionException("failed to persist replay nonce", ex); + throw new ReplayProtectionException("保存防重放nonce失败", ex); } var reloaded = replayNonceRepository.findByScopeAndPrincipalIdAndNonce(scope, principalId, nonce); if (reloaded.isEmpty()) { - throw new ReplayProtectionException("replay nonce claim failed without existing record"); + throw new ReplayProtectionException("replay nonce claim 执行失败 without existing record"); } return buildReplayResult(request, reloaded.get()); } private void reclaimExpiredNonce(ReplayNonceEntity expiredEntity) { if (expiredEntity.getId() == null) { - throw new ReplayProtectionException("expired replay nonce id is missing"); + throw new ReplayProtectionException("过期防重放nonce ID缺失"); } try { replayNonceRepository.deleteById(expiredEntity.getId()); } catch (RuntimeException ex) { - throw new ReplayProtectionException("failed to remove expired replay nonce", ex); + throw new ReplayProtectionException("删除过期防重放nonce失败", ex); } } private void validateRequest(ReplayCheckRequest request) { if (request == null) { - throw new ReplayProtectionException("replay request must not be null"); + throw new ReplayProtectionException("replay 请求不能为空"); } if (request.getScope() == null) { - throw new ReplayProtectionException("replay scope is required"); + throw new ReplayProtectionException("replay scope不能为空"); } if (isBlank(request.getPrincipalId())) { - throw new ReplayProtectionException("principal id is required"); + throw new ReplayProtectionException("principal id不能为空"); } if (isBlank(request.getNonce())) { - throw new ReplayProtectionException("nonce is required"); + throw new ReplayProtectionException("nonce不能为空"); } if (request.getRequestTimestamp() <= 0L) { - throw new ReplayProtectionException("request timestamp is required"); + throw new ReplayProtectionException("request timestamp不能为空"); } } private void ensureTimestampWithinWindow(long requestTimestamp, long nowSeconds, long allowedSkewSeconds) { if (Math.abs(nowSeconds - requestTimestamp) > allowedSkewSeconds) { - throw new ReplayProtectionException("request timestamp is outside allowed window"); + throw new ReplayProtectionException("请求时间戳超出允许窗口"); } } @@ -165,7 +165,7 @@ public class ReplayProtectionServiceImpl implements ReplayProtectionService { try { securityEventRepository.save(event); } catch (RuntimeException ex) { - throw new ReplayProtectionException("failed to persist security event", ex); + throw new ReplayProtectionException("保存安全事件失败", ex); } } diff --git a/src/main/java/com/cisd/tms/modules/sign/dto/internal/InternalSignPreviewRequest.java b/src/main/java/com/cisd/tms/modules/sign/dto/internal/InternalSignPreviewRequest.java index 0c9edf3..ba798cd 100644 --- a/src/main/java/com/cisd/tms/modules/sign/dto/internal/InternalSignPreviewRequest.java +++ b/src/main/java/com/cisd/tms/modules/sign/dto/internal/InternalSignPreviewRequest.java @@ -6,7 +6,7 @@ import jakarta.validation.constraints.NotBlank; @Schema(description = "内部签名预览请求") public class InternalSignPreviewRequest { - @NotBlank(message = "plainText is required") + @NotBlank(message = "plainText不能为空") @Schema(description = "待签名明文", example = "hello-tms") private String plainText; diff --git a/src/main/java/com/cisd/tms/modules/sign/dto/openapi/OpenSignRequest.java b/src/main/java/com/cisd/tms/modules/sign/dto/openapi/OpenSignRequest.java index d18e0de..cbf696b 100644 --- a/src/main/java/com/cisd/tms/modules/sign/dto/openapi/OpenSignRequest.java +++ b/src/main/java/com/cisd/tms/modules/sign/dto/openapi/OpenSignRequest.java @@ -4,10 +4,10 @@ import jakarta.validation.constraints.NotBlank; public class OpenSignRequest { - @NotBlank(message = "requestId is required") + @NotBlank(message = "requestId不能为空") private String requestId; - @NotBlank(message = "payload is required") + @NotBlank(message = "payload不能为空") private String payload; private String algorithm; diff --git a/src/main/java/com/cisd/tms/modules/sign/support/Sha256MockSignProvider.java b/src/main/java/com/cisd/tms/modules/sign/support/Sha256MockSignProvider.java index 9bfe5c0..3397959 100644 --- a/src/main/java/com/cisd/tms/modules/sign/support/Sha256MockSignProvider.java +++ b/src/main/java/com/cisd/tms/modules/sign/support/Sha256MockSignProvider.java @@ -32,14 +32,14 @@ public class Sha256MockSignProvider implements SignProvider { if ("SHA-256".equals(value)) { return DEFAULT_ALGORITHM; } - throw new IllegalArgumentException("Unsupported sign algorithm: " + algorithm); + throw new IllegalArgumentException("不支持的签名算法:" + algorithm); } private byte[] digest(String algorithm, byte[] source) { try { return MessageDigest.getInstance(algorithm).digest(source); } catch (NoSuchAlgorithmException ex) { - throw new IllegalStateException("Algorithm unavailable: " + algorithm, ex); + throw new IllegalStateException("算法不可用:" + algorithm, ex); } } diff --git a/src/main/java/com/cisd/tms/modules/upgrade/executor/UpgradeTaskRunner.java b/src/main/java/com/cisd/tms/modules/upgrade/executor/UpgradeTaskRunner.java index f905939..d92eb27 100644 --- a/src/main/java/com/cisd/tms/modules/upgrade/executor/UpgradeTaskRunner.java +++ b/src/main/java/com/cisd/tms/modules/upgrade/executor/UpgradeTaskRunner.java @@ -53,7 +53,7 @@ public class UpgradeTaskRunner { Files.writeString(logPath, ""); FileRecordEntity record = fileRecordRepository.findByFileId(task.getPackageFileId()) - .orElseThrow(() -> new BizException(ErrorCode.BIZ_ERROR.getCode(), "upgrade package file not found: " + task.getPackageFileId())); + .orElseThrow(() -> new BizException(ErrorCode.BIZ_ERROR.getCode(), "升级包文件不存在:" + task.getPackageFileId())); Path stagedDir = upgradePackageStagingService.stage(task.getTaskId(), Path.of(record.getStoragePath())); UpgradeManifest manifest = objectMapper.readValue(Files.readString(stagedDir.resolve("manifest.json")), UpgradeManifest.class); upgradePackageStagingService.extractPayloadZip(stagedDir); @@ -114,7 +114,7 @@ public class UpgradeTaskRunner { } catch (Exception ex) { try { Files.createDirectories(logPath.getParent()); - Files.writeString(logPath, ex.getMessage() == null ? "upgrade failed" : ex.getMessage()); + Files.writeString(logPath, ex.getMessage() == null ? "upgrade 执行失败" : ex.getMessage()); } catch (IOException ignored) { } upgradeTaskRepository.updateStatus( @@ -136,13 +136,13 @@ public class UpgradeTaskRunner { Files.writeString(logPath, ""); FileRecordEntity record = fileRecordRepository.findByFileId(task.getPackageFileId()) - .orElseThrow(() -> new BizException(ErrorCode.BIZ_ERROR.getCode(), "upgrade package file not found: " + task.getPackageFileId())); + .orElseThrow(() -> new BizException(ErrorCode.BIZ_ERROR.getCode(), "升级包文件不存在:" + task.getPackageFileId())); Path stagedDir = upgradePackageStagingService.stage(task.getTaskId() + "-rollback", Path.of(record.getStoragePath())); UpgradeManifest manifest = objectMapper.readValue(Files.readString(stagedDir.resolve("manifest.json")), UpgradeManifest.class); upgradePackageStagingService.extractPayloadZip(stagedDir); String rollbackPath = manifest.getEntrypoints() == null ? "" : trim(manifest.getEntrypoints().getRollback()); if (rollbackPath.isEmpty()) { - throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "upgrade rollback script not found"); + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "升级回滚脚本不存在"); } // 回滚不自动触发,只有外部调用 rollback 接口后才执行包内 rollback 脚本。 int exitCode = runRequiredScript("rollback", stagedDir.normalize().toAbsolutePath(), rollbackPath, task, logPath); @@ -170,7 +170,7 @@ public class UpgradeTaskRunner { } catch (Exception ex) { try { Files.createDirectories(logPath.getParent()); - Files.writeString(logPath, ex.getMessage() == null ? "upgrade rollback failed" : ex.getMessage()); + Files.writeString(logPath, ex.getMessage() == null ? "upgrade rollback 执行失败" : ex.getMessage()); } catch (IOException ignored) { } upgradeTaskRepository.updateStatus( @@ -214,8 +214,8 @@ public class UpgradeTaskRunner { return; } try { - writeLogLine(logPath, rollback ? "rollback success, scheduling detached TMS restart" - : "upgrade success, scheduling detached TMS restart"); + writeLogLine(logPath, rollback ? "rollback 成功, scheduling detached TMS restart" + : "upgrade 成功, scheduling detached TMS restart"); triggerDetachedTmsRestart(logPath, rollback); } catch (IOException ex) { throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "failed to schedule detached tms restart"); diff --git a/src/main/java/com/cisd/tms/modules/upgrade/service/UpgradePackageService.java b/src/main/java/com/cisd/tms/modules/upgrade/service/UpgradePackageService.java index a53c986..a6f8ea5 100644 --- a/src/main/java/com/cisd/tms/modules/upgrade/service/UpgradePackageService.java +++ b/src/main/java/com/cisd/tms/modules/upgrade/service/UpgradePackageService.java @@ -97,16 +97,16 @@ public class UpgradePackageService { try { return objectMapper.readValue(Files.readString(manifestPath), UpgradeManifest.class); } catch (IOException ex) { - throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "manifest.json is invalid"); + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "manifest.json无效"); } } private StagedUpgradePackage stagePackage(String fileId) { FileRecordEntity record = fileRecordRepository.findByFileId(normalize(fileId)) - .orElseThrow(() -> new BizException(ErrorCode.BIZ_ERROR.getCode(), "upgrade package file not found: " + normalize(fileId))); + .orElseThrow(() -> new BizException(ErrorCode.BIZ_ERROR.getCode(), "升级包文件不存在:" + normalize(fileId))); Path packagePath = Path.of(record.getStoragePath()).normalize().toAbsolutePath(); if (!Files.exists(packagePath)) { - throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "upgrade package file not found on disk"); + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "磁盘上的升级包文件不存在"); } // 同一个 fileId 每次预检都会重新解压,确保读取的是当前上传文件内容。 @@ -114,25 +114,25 @@ public class UpgradePackageService { Path manifestPath = stagedDir.resolve("manifest.json"); Path signaturePath = stagedDir.resolve("signature.sig"); if (!Files.exists(manifestPath)) { - throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "upgrade package structure invalid"); + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "升级包结构无效"); } return new StagedUpgradePackage(stagedDir, manifestPath, signaturePath, readManifest(manifestPath)); } private void validateManifest(UpgradeManifest manifest) { if (manifest == null) { - throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "manifest.json is invalid"); + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "manifest.json无效"); } if (normalize(manifest.getPackageId()).isEmpty() || normalize(manifest.getTaskType()).isEmpty() || normalize(manifest.getProductType()).isEmpty() || normalize(manifest.getVersion()).isEmpty() || normalize(manifest.getMinCompatibleVersion()).isEmpty()) { - throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "manifest.json is invalid"); + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "manifest.json无效"); } UpgradeManifest.Entrypoints entrypoints = manifest.getEntrypoints(); if (entrypoints == null || normalize(entrypoints.getExecute()).isEmpty()) { - throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "upgrade execute script not found"); + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "升级执行脚本不存在"); } } @@ -152,15 +152,15 @@ public class UpgradePackageService { private void validatePayloadZipSm3(Path stagedDir, UpgradeManifest manifest) { String expected = normalize(manifest.getPayloadSm3()).toLowerCase(Locale.ROOT); if (expected.isEmpty()) { - throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "upgrade package payload sm3 missing"); + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "升级包payload SM3缺失"); } if (!expected.matches("[0-9a-f]{64}")) { - throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "upgrade package payload sm3 invalid"); + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "升级包payload SM3无效"); } Path root = stagedDir.normalize().toAbsolutePath(); Path payloadZip = root.resolve("payload.zip").normalize().toAbsolutePath(); if (!payloadZip.startsWith(root) || !Files.exists(payloadZip) || !Files.isRegularFile(payloadZip)) { - throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "upgrade payload.zip not found"); + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "升级payload.zip不存在"); } if (!expected.equals(sm3Hex(payloadZip))) { throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "upgrade package payload sm3 mismatch"); @@ -171,7 +171,7 @@ public class UpgradePackageService { Path executePath = stagedDir.resolve(normalize(manifest.getEntrypoints().getExecute())).normalize().toAbsolutePath(); // execute 路径必须在 staging 目录内,避免 manifest 声明外部系统脚本。 if (!executePath.startsWith(stagedDir.normalize().toAbsolutePath()) || !Files.exists(executePath)) { - throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "upgrade execute script not found"); + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "升级执行脚本不存在"); } } @@ -202,7 +202,7 @@ public class UpgradePackageService { } return builder.toString(); } catch (IOException ex) { - throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "upgrade package checksum invalid"); + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "升级包校验和无效"); } } diff --git a/src/main/java/com/cisd/tms/modules/upgrade/service/UpgradeService.java b/src/main/java/com/cisd/tms/modules/upgrade/service/UpgradeService.java index 25ec112..f806b09 100644 --- a/src/main/java/com/cisd/tms/modules/upgrade/service/UpgradeService.java +++ b/src/main/java/com/cisd/tms/modules/upgrade/service/UpgradeService.java @@ -91,28 +91,28 @@ public class UpgradeService { public UpgradePreviewResponse preview(UpgradePreviewRequest request) { if (request == null || request.getFileId() == null || request.getFileId().isBlank()) { - throw new IllegalArgumentException("fileId must not be blank"); + throw new IllegalArgumentException("fileId不能为空"); } return upgradePackageService.preview(request.getFileId()); } public UpgradeCreateTaskResponse createTask(UpgradeCreateTaskRequest request) { if (request == null || request.getFileId() == null || request.getFileId().isBlank()) { - throw new IllegalArgumentException("fileId must not be blank"); + throw new IllegalArgumentException("fileId不能为空"); } if (request.getTaskType() == null || request.getTaskType().isBlank()) { - throw new IllegalArgumentException("taskType must not be blank"); + throw new IllegalArgumentException("taskType不能为空"); } // 创建任务前重新预检一次,避免用户上传后替换文件或设备版本状态发生变化。 UpgradePreviewResponse preview = upgradePackageService.preview(request.getFileId()); if (!preview.isAllowed()) { - throw new BizException(ErrorCode.BIZ_ERROR.getCode(), blankAs(preview.getBlockReason(), "upgrade preview not allowed")); + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), blankAs(preview.getBlockReason(), "升级预检不允许继续")); } if (!trim(request.getTaskType()).equals(trim(preview.getTaskType()))) { throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "selected taskType does not match package taskType"); } FileRecordEntity fileRecord = fileRecordRepository.findByFileId(request.getFileId().trim()) - .orElseThrow(() -> new BizException(ErrorCode.BIZ_ERROR.getCode(), "upgrade package file not found: " + request.getFileId().trim())); + .orElseThrow(() -> new BizException(ErrorCode.BIZ_ERROR.getCode(), "升级包文件不存在:" + request.getFileId().trim())); UpgradeTaskEntity entity = new UpgradeTaskEntity(); entity.setTaskId(buildTaskId()); @@ -175,7 +175,7 @@ public class UpgradeService { // 回滚能力由升级包声明;TMS 不假定所有包都能安全回滚。 UpgradeManifest manifest = upgradePackageService.loadManifest(task.getPackageFileId()); if (manifest.getEntrypoints() == null || trim(manifest.getEntrypoints().getRollback()).isEmpty()) { - throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "upgrade rollback script not found"); + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "升级回滚脚本不存在"); } UpgradeTaskEntity runningTask = upgradeTaskRepository.findRunningTask().orElse(null); if (runningTask != null && !runningTask.getTaskId().equals(taskId)) { @@ -259,7 +259,7 @@ public class UpgradeService { private UpgradeTaskEntity loadTask(String taskId) { return upgradeTaskRepository.findByTaskId(trim(taskId)) - .orElseThrow(() -> new BizException(ErrorCode.BIZ_ERROR.getCode(), "upgrade task not found: " + trim(taskId))); + .orElseThrow(() -> new BizException(ErrorCode.BIZ_ERROR.getCode(), "升级任务不存在:" + trim(taskId))); } private UpgradeTaskResponse toResponse(UpgradeTaskEntity entity) { @@ -312,7 +312,7 @@ public class UpgradeService { try { return LocalDate.parse(upgradeDate, UPGRADE_DATE_FORMAT); } catch (DateTimeParseException ex) { - throw new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "upgradeDate must be yyyy-MM-dd"); + throw new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "upgradeDate必须为yyyy-MM-dd"); } } } diff --git a/src/main/java/com/cisd/tms/modules/upgrade/support/NoopUpgradePackageSignatureVerifier.java b/src/main/java/com/cisd/tms/modules/upgrade/support/NoopUpgradePackageSignatureVerifier.java index 593c539..9421750 100644 --- a/src/main/java/com/cisd/tms/modules/upgrade/support/NoopUpgradePackageSignatureVerifier.java +++ b/src/main/java/com/cisd/tms/modules/upgrade/support/NoopUpgradePackageSignatureVerifier.java @@ -11,7 +11,7 @@ public class NoopUpgradePackageSignatureVerifier implements UpgradePackageSignat @Override public void verify(UpgradeManifest manifest, Path manifestPath, Path signaturePath) { if (!Files.exists(signaturePath)) { - throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "upgrade package signature file not found"); + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "升级包签名文件不存在"); } } } diff --git a/src/main/java/com/cisd/tms/modules/upgrade/support/SoftUpgradePackageSignatureVerifier.java b/src/main/java/com/cisd/tms/modules/upgrade/support/SoftUpgradePackageSignatureVerifier.java index 8e26d75..582960b 100644 --- a/src/main/java/com/cisd/tms/modules/upgrade/support/SoftUpgradePackageSignatureVerifier.java +++ b/src/main/java/com/cisd/tms/modules/upgrade/support/SoftUpgradePackageSignatureVerifier.java @@ -41,7 +41,7 @@ public class SoftUpgradePackageSignatureVerifier implements UpgradePackageSignat @Override public void verify(UpgradeManifest manifest, Path manifestPath, Path signaturePath) { if (!Files.exists(signaturePath)) { - throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "upgrade package signature file not found"); + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "升级包签名文件不存在"); } // 第一版升级包仅对 manifest.json 做离线软验签,避免在包内脚本执行前信任未授权包。 @@ -54,12 +54,12 @@ public class SoftUpgradePackageSignatureVerifier implements UpgradePackageSignat signature.initVerify(publicKey); signature.update(manifestBytes); if (!signature.verify(signatureBytes)) { - throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "upgrade package signature verification failed"); + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "upgrade package signature verification 执行失败"); } } catch (BizException ex) { throw ex; } catch (Exception ex) { - throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "upgrade package signature verification failed"); + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "upgrade package signature verification 执行失败"); } } @@ -69,7 +69,7 @@ public class SoftUpgradePackageSignatureVerifier implements UpgradePackageSignat throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "upgrade signature public key pem path not configured"); } if (!Files.exists(pemPath)) { - throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "upgrade signature public key pem file not found"); + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "升级签名公钥PEM文件不存在"); } try { String pemContent = Files.readString(pemPath, StandardCharsets.UTF_8); @@ -77,7 +77,7 @@ public class SoftUpgradePackageSignatureVerifier implements UpgradePackageSignat Object parsed = parser.readObject(); // 约定使用 X.509 SubjectPublicKeyInfo 格式的 PUBLIC KEY PEM。 if (!(parsed instanceof SubjectPublicKeyInfo publicKeyInfo)) { - throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "upgrade signature public key pem is invalid"); + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "升级签名公钥PEM无效"); } return new JcaPEMKeyConverter().setProvider(PROVIDER).getPublicKey(publicKeyInfo); } @@ -86,14 +86,14 @@ public class SoftUpgradePackageSignatureVerifier implements UpgradePackageSignat } catch (IOException ex) { throw new BizException(ErrorCode.INTERNAL_ERROR.getCode(), "failed to read upgrade signature public key pem"); } catch (Exception ex) { - throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "upgrade signature public key pem is invalid"); + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "升级签名公钥PEM无效"); } } private byte[] loadSignature(Path signaturePath) { byte[] raw = readBytes(signaturePath, "failed to read upgrade signature file"); if (raw.length == 0) { - throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "upgrade package signature file is empty"); + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "升级包签名文件为空"); } String text = new String(raw, StandardCharsets.UTF_8).trim(); diff --git a/src/main/java/com/cisd/tms/modules/upgrade/support/UpgradePackageStagingService.java b/src/main/java/com/cisd/tms/modules/upgrade/support/UpgradePackageStagingService.java index 5cc7abe..4ad4d88 100644 --- a/src/main/java/com/cisd/tms/modules/upgrade/support/UpgradePackageStagingService.java +++ b/src/main/java/com/cisd/tms/modules/upgrade/support/UpgradePackageStagingService.java @@ -39,7 +39,7 @@ public class UpgradePackageStagingService { Path root = stagedDir.normalize().toAbsolutePath(); Path payloadZip = root.resolve("payload.zip").normalize().toAbsolutePath(); if (!payloadZip.startsWith(root) || !Files.exists(payloadZip) || !Files.isRegularFile(payloadZip)) { - throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "upgrade payload.zip not found"); + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "升级payload.zip不存在"); } try { deleteDirectoryIfExists(root.resolve("payload")); diff --git a/src/main/java/com/cisd/tms/security/internal/InternalApiAuthInterceptor.java b/src/main/java/com/cisd/tms/security/internal/InternalApiAuthInterceptor.java index c12d987..2aca9b3 100644 --- a/src/main/java/com/cisd/tms/security/internal/InternalApiAuthInterceptor.java +++ b/src/main/java/com/cisd/tms/security/internal/InternalApiAuthInterceptor.java @@ -53,17 +53,17 @@ public class InternalApiAuthInterceptor implements HandlerInterceptor { String sessionToken = extractSessionToken(request); if (sessionToken == null || sessionToken.isBlank()) { - writeSessionInvalid(response, "session token missing"); + writeSessionInvalid(response, "会话令牌缺失"); return false; } AuthSessionEntity session = authSessionRepository.findBySessionToken(sessionToken).orElse(null); if (session == null || session.getLogoutAt() != null) { - writeSessionInvalid(response, "session not found"); + writeSessionInvalid(response, "会话不存在"); return false; } if (session.getExpiresAt() == null || !session.getExpiresAt().isAfter(now())) { - writeSessionInvalid(response, "session expired"); + writeSessionInvalid(response, "会话已过期"); return false; } diff --git a/src/main/java/com/cisd/tms/security/internal/InternalApiReplayInterceptor.java b/src/main/java/com/cisd/tms/security/internal/InternalApiReplayInterceptor.java index a5e457b..fd8f716 100644 --- a/src/main/java/com/cisd/tms/security/internal/InternalApiReplayInterceptor.java +++ b/src/main/java/com/cisd/tms/security/internal/InternalApiReplayInterceptor.java @@ -51,7 +51,7 @@ public class InternalApiReplayInterceptor implements HandlerInterceptor { String sessionToken = (String) request.getAttribute(InternalApiAuthInterceptor.ATTR_SESSION_TOKEN); if (isBlank(sessionToken)) { - writeJson(response, HttpServletResponse.SC_UNAUTHORIZED, ErrorCode.UNAUTHORIZED.getCode(), "session token missing"); + writeJson(response, HttpServletResponse.SC_UNAUTHORIZED, ErrorCode.UNAUTHORIZED.getCode(), "会话令牌缺失"); return false; } @@ -122,7 +122,7 @@ public class InternalApiReplayInterceptor implements HandlerInterceptor { } return builder.toString(); } catch (Exception ex) { - throw new ReplayProtectionException("failed to hash request body", ex); + throw new ReplayProtectionException("请求体哈希计算失败", ex); } } diff --git a/src/main/java/com/cisd/tms/security/openapi/OpenApiSignAuthInterceptor.java b/src/main/java/com/cisd/tms/security/openapi/OpenApiSignAuthInterceptor.java index 5883b64..a0e6f22 100644 --- a/src/main/java/com/cisd/tms/security/openapi/OpenApiSignAuthInterceptor.java +++ b/src/main/java/com/cisd/tms/security/openapi/OpenApiSignAuthInterceptor.java @@ -75,7 +75,7 @@ public class OpenApiSignAuthInterceptor implements HandlerInterceptor { long now = Instant.ofEpochMilli(clock.millis()).getEpochSecond(); long skew = securityProperties.getOpenapi().getTimestampSkewSeconds(); if (Math.abs(now - requestEpoch) > skew) { - writeUnauthorized(response, "timestamp expired"); + writeUnauthorized(response, "时间戳已过期"); return false; } } @@ -84,7 +84,7 @@ public class OpenApiSignAuthInterceptor implements HandlerInterceptor { String expected = HmacUtil.hmacSha256Hex(appSecret, content); if (!secureEquals(expected, signature.toLowerCase())) { - writeUnauthorized(response, "signature invalid"); + writeUnauthorized(response, "签名无效"); return false; } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 59f1f25..46b163d 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -5,8 +5,8 @@ spring: servlet: multipart: # Spring/Tomcat multipart 解析上限;必须不小于业务上传上限,否则请求会在进入 Controller 前被拒绝。 - max-file-size: ${TMS_FILE_STORAGE_MAX_FILE_SIZE_BYTES:104857600} - max-request-size: ${TMS_FILE_STORAGE_MAX_FILE_SIZE_BYTES:104857600} + max-file-size: ${TMS_MULTIPART_MAX_FILE_SIZE_BYTES:524288000} + max-request-size: ${TMS_MULTIPART_MAX_REQUEST_SIZE_BYTES:536870912} profiles: # 默认激活环境;可通过 --spring.profiles.active=prod 覆盖。 active: dev @@ -49,6 +49,12 @@ tms: upload-base-dir: ${TMS_FILE_STORAGE_UPLOAD_BASE_DIR:/home/tms/uploads} # 上传文件大小上限(字节)。 max-file-size-bytes: ${TMS_FILE_STORAGE_MAX_FILE_SIZE_BYTES:104857600} + cert: + crl: + # CRL 导入临时目录;原始文件只在解析过程中暂存,导入结束后删除。 + temp-dir: ${TMS_CERT_CRL_TEMP_DIR:/home/tms/tmp/crl-import} + # CRL 文件大小上限,默认 500MiB。 + max-file-size-bytes: ${TMS_CERT_CRL_MAX_FILE_SIZE_BYTES:524288000} init: executor: # 初始化执行模式:SIMULATE(仅模拟,不执行外部命令) / LOCAL(本机执行脚本命令)。 diff --git a/src/main/resources/db/migration/V1__tms_schema_full.sql b/src/main/resources/db/migration/V1__tms_schema_full.sql index 1864cb1..938ba6c 100644 --- a/src/main/resources/db/migration/V1__tms_schema_full.sql +++ b/src/main/resources/db/migration/V1__tms_schema_full.sql @@ -542,7 +542,7 @@ CREATE TABLE IF NOT EXISTS t_key_entity ( algo_type VARCHAR(16) NOT NULL COMMENT 'SM2 / RSA', key_len INT NOT NULL COMMENT 'SM2 fixed 256 in V1', key_idx INT NOT NULL COMMENT 'card key index, allowed range [2..8]', - backup_data VARCHAR(500) NOT NULL COMMENT 'SDFE_BackupUserKey output', + backup_data TEXT NOT NULL COMMENT 'SDFE_BackupUserKey output', public_key VARCHAR(500) NOT NULL COMMENT 'public key, Base64/PEM', p10_data VARCHAR(2000) NULL COMMENT 'PKCS#10 PEM content', subject_dn VARCHAR(500) NULL COMMENT 'DN used when generating P10', @@ -587,7 +587,7 @@ CREATE TABLE IF NOT EXISTS t_certificate ( CREATE TABLE IF NOT EXISTS t_trusted_cert ( id BIGINT PRIMARY KEY, - alias VARCHAR(128) NOT NULL, + alias VARCHAR(128) NULL, subject_dn VARCHAR(1024) NOT NULL, issuer_dn VARCHAR(1024) NOT NULL, serial_number VARCHAR(128) NOT NULL, @@ -614,7 +614,7 @@ CREATE TABLE IF NOT EXISTS t_certificate_crl ( crl_number VARCHAR(128) NULL, algo_type VARCHAR(16) NOT NULL COMMENT 'SM2 in V1', fingerprint VARCHAR(128) NOT NULL COMMENT 'SHA-256 fingerprint', - crl_data MEDIUMTEXT NOT NULL COMMENT 'CRL PEM content', + crl_data MEDIUMTEXT NOT NULL COMMENT 'reserved legacy CRL PEM content; large CRLs are not stored inline', this_update DATETIME(3) NOT NULL, next_update DATETIME(3) NULL, revoked_count INT NOT NULL, @@ -646,6 +646,25 @@ CREATE TABLE IF NOT EXISTS t_certificate_crl_revoked ( KEY idx_t_certificate_crl_revoked_revocation_time (revocation_time) ); +CREATE TABLE IF NOT EXISTS t_certificate_crl_import_task ( + id BIGINT PRIMARY KEY, + task_id VARCHAR(64) NOT NULL, + original_filename VARCHAR(255) NULL, + file_size BIGINT NULL, + status VARCHAR(32) NOT NULL COMMENT 'PENDING/RUNNING/SUCCESS/FAILED', + fingerprint VARCHAR(128) NULL COMMENT 'SHA-256 fingerprint after successful parse', + crl_id BIGINT NULL, + revoked_count INT NOT NULL DEFAULT 0, + error_message VARCHAR(512) NULL, + started_at DATETIME(3) NULL, + finished_at DATETIME(3) NULL, + create_time DATETIME(3) NOT NULL, + update_time DATETIME(3) NOT NULL, + UNIQUE KEY uk_t_certificate_crl_import_task_task_id (task_id), + KEY idx_t_certificate_crl_import_task_status (status), + KEY idx_t_certificate_crl_import_task_create_time (create_time) +); + -- ----------------------------------------------------------------------------- -- 日志表 @@ -714,4 +733,4 @@ CREATE TABLE IF NOT EXISTS tms_backup_config ( last_backup_time datetime DEFAULT NULL COMMENT '上次执行时间', sign_data varchar(255) NOT NULL COMMENT '其他字段的签名值', PRIMARY KEY (`id`) -) \ No newline at end of file +) diff --git a/src/main/resources/db/migration/V7__add_cert_module_tables.sql b/src/main/resources/db/migration/V7__add_cert_module_tables.sql index d062d1e..f3d09cf 100644 --- a/src/main/resources/db/migration/V7__add_cert_module_tables.sql +++ b/src/main/resources/db/migration/V7__add_cert_module_tables.sql @@ -9,7 +9,7 @@ CREATE TABLE IF NOT EXISTS t_key_entity ( algo_type VARCHAR(16) NOT NULL COMMENT 'SM2 / RSA', key_len INT NOT NULL COMMENT 'SM2 fixed 256 in V1', key_idx INT NOT NULL COMMENT 'card key index, allowed range [2..8]', - backup_data VARCHAR(500) NOT NULL COMMENT 'SDFE_BackupUserKey output', + backup_data TEXT NOT NULL COMMENT 'SDFE_BackupUserKey output', public_key VARCHAR(500) NOT NULL COMMENT 'public key, Base64/PEM', p10_data VARCHAR(2000) NULL COMMENT 'PKCS#10 PEM content', subject_dn VARCHAR(500) NULL COMMENT 'DN used when generating P10', @@ -54,7 +54,7 @@ CREATE TABLE IF NOT EXISTS t_certificate ( CREATE TABLE IF NOT EXISTS t_trusted_cert ( id BIGINT PRIMARY KEY, - alias VARCHAR(128) NOT NULL, + alias VARCHAR(128) NULL, subject_dn VARCHAR(1024) NOT NULL, issuer_dn VARCHAR(1024) NOT NULL, serial_number VARCHAR(128) NOT NULL, @@ -81,7 +81,7 @@ CREATE TABLE IF NOT EXISTS t_certificate_crl ( crl_number VARCHAR(128) NULL, algo_type VARCHAR(16) NOT NULL COMMENT 'SM2 in V1', fingerprint VARCHAR(128) NOT NULL COMMENT 'SHA-256 fingerprint', - crl_data MEDIUMTEXT NOT NULL COMMENT 'CRL PEM content', + crl_data MEDIUMTEXT NOT NULL COMMENT 'reserved legacy CRL PEM content; large CRLs are not stored inline', this_update DATETIME(3) NOT NULL, next_update DATETIME(3) NULL, revoked_count INT NOT NULL, @@ -112,3 +112,22 @@ CREATE TABLE IF NOT EXISTS t_certificate_crl_revoked ( KEY idx_t_certificate_crl_revoked_subject_dn (subject_dn(512)), KEY idx_t_certificate_crl_revoked_revocation_time (revocation_time) ); + +CREATE TABLE IF NOT EXISTS t_certificate_crl_import_task ( + id BIGINT PRIMARY KEY, + task_id VARCHAR(64) NOT NULL, + original_filename VARCHAR(255) NULL, + file_size BIGINT NULL, + status VARCHAR(32) NOT NULL COMMENT 'PENDING/RUNNING/SUCCESS/FAILED', + fingerprint VARCHAR(128) NULL COMMENT 'SHA-256 fingerprint after successful parse', + crl_id BIGINT NULL, + revoked_count INT NOT NULL DEFAULT 0, + error_message VARCHAR(512) NULL, + started_at DATETIME(3) NULL, + finished_at DATETIME(3) NULL, + create_time DATETIME(3) NOT NULL, + update_time DATETIME(3) NOT NULL, + UNIQUE KEY uk_t_certificate_crl_import_task_task_id (task_id), + KEY idx_t_certificate_crl_import_task_status (status), + KEY idx_t_certificate_crl_import_task_create_time (create_time) +); diff --git a/src/main/resources/mapper/cert/CertificateCrlImportTaskMapper.xml b/src/main/resources/mapper/cert/CertificateCrlImportTaskMapper.xml new file mode 100644 index 0000000..033c6b8 --- /dev/null +++ b/src/main/resources/mapper/cert/CertificateCrlImportTaskMapper.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + id, task_id, original_filename, file_size, status, fingerprint, crl_id, revoked_count, + error_message, started_at, finished_at, create_time, update_time + + + + + + UPDATE t_certificate_crl_import_task + SET status = 'RUNNING', + error_message = NULL, + started_at = #{startedAt}, + update_time = NOW(3) + WHERE task_id = #{taskId} + + + + UPDATE t_certificate_crl_import_task + SET status = 'SUCCESS', + crl_id = #{crlId}, + fingerprint = #{fingerprint}, + revoked_count = #{revokedCount}, + error_message = NULL, + finished_at = #{finishedAt}, + update_time = NOW(3) + WHERE task_id = #{taskId} + + + + UPDATE t_certificate_crl_import_task + SET status = 'FAILED', + error_message = #{errorMessage}, + finished_at = #{finishedAt}, + update_time = NOW(3) + WHERE task_id = #{taskId} + + + diff --git a/src/main/resources/mapper/cert/CertificateCrlRevokedEntityMapper.xml b/src/main/resources/mapper/cert/CertificateCrlRevokedEntityMapper.xml new file mode 100644 index 0000000..6a1ef57 --- /dev/null +++ b/src/main/resources/mapper/cert/CertificateCrlRevokedEntityMapper.xml @@ -0,0 +1,23 @@ + + + + + + INSERT INTO t_certificate_crl_revoked ( + id, crl_id, issuer_dn, serial_number, subject_dn, algo_type, crl_number, + revocation_time, revocation_reason, create_time, update_time + ) + VALUES + + ( + #{item.id}, #{item.crlId}, #{item.issuerDn}, #{item.serialNumber}, + #{item.subjectDn}, #{item.algoType}, #{item.crlNumber}, + #{item.revocationTime}, #{item.revocationReason}, + #{item.createTime}, #{item.updateTime} + ) + + + + diff --git a/src/test/java/com/cisd/tms/common/ChineseErrorMessageContractTest.java b/src/test/java/com/cisd/tms/common/ChineseErrorMessageContractTest.java new file mode 100644 index 0000000..b703919 --- /dev/null +++ b/src/test/java/com/cisd/tms/common/ChineseErrorMessageContractTest.java @@ -0,0 +1,67 @@ +package com.cisd.tms.common; + +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +class ChineseErrorMessageContractTest { + + private static final Path MAIN_SOURCE = Path.of("src/main/java/com/cisd/tms"); + private static final Pattern CHINESE = Pattern.compile("\\p{IsHan}"); + private static final Pattern USER_FACING_MESSAGE = Pattern.compile( + "(?:new\\s+(?:BizException|IllegalArgumentException|IllegalStateException|RuntimeException|ReplayProtectionException)\\(|" + + "ApiResponse\\.fail\\(|" + + "write(?:Unauthorized|Forbidden|SessionInvalid|Json)\\(|" + + "InitStepExecutionResult\\.(?:成功|failure)\\(|" + + "PcieCryptoException\\([^\\n]*,\\s*)" + + "\"([^\"]*[A-Za-z][^\"]*)\"" + ); + private static final Pattern VALIDATION_MESSAGE = Pattern.compile( + "@(?:NotNull|NotBlank|NotEmpty|Size|Pattern|Min|Max|Positive|AssertTrue)\\([^\\n]*message\\s*=\\s*\"([^\"]+)\"" + ); + + @Test + void productionErrorMessagesShouldBeChinese() throws IOException { + List violations = new ArrayList<>(); + try (var files = Files.walk(MAIN_SOURCE)) { + files.filter(path -> path.toString().endsWith(".java")) + .forEach(path -> collectViolations(path, violations)); + } + + assertTrue(violations.isEmpty(), String.join("\n", violations)); + } + + private static void collectViolations(Path path, List violations) { + try { + List lines = Files.readAllLines(path); + for (int index = 0; index < lines.size(); index++) { + String line = lines.get(index); + if (line.trim().startsWith("//") || line.contains("log.")) { + continue; + } + collect(line, USER_FACING_MESSAGE, path, index, violations); + collect(line, VALIDATION_MESSAGE, path, index, violations); + } + } catch (IOException ex) { + throw new IllegalStateException("读取生产源码失败", ex); + } + } + + private static void collect(String line, Pattern pattern, Path path, int index, List violations) { + Matcher matcher = pattern.matcher(line); + while (matcher.find()) { + String message = matcher.group(1); + if (!CHINESE.matcher(message).find()) { + violations.add(path + ":" + (index + 1) + " -> " + message); + } + } + } +} diff --git a/src/test/java/com/cisd/tms/common/exception/GlobalExceptionHandlerTest.java b/src/test/java/com/cisd/tms/common/exception/GlobalExceptionHandlerTest.java index f948bd0..9bf41a8 100644 --- a/src/test/java/com/cisd/tms/common/exception/GlobalExceptionHandlerTest.java +++ b/src/test/java/com/cisd/tms/common/exception/GlobalExceptionHandlerTest.java @@ -26,7 +26,7 @@ class GlobalExceptionHandlerTest { Assertions.assertNotNull(response.getBody()); Assertions.assertFalse(response.getBody().isSuccess()); Assertions.assertEquals(ErrorCode.VALIDATE_FAILED.getCode(), response.getBody().getCode()); - Assertions.assertEquals("upload file size exceeds limit", response.getBody().getMsg()); + Assertions.assertEquals("上传文件大小超过限制", response.getBody().getMsg()); Assertions.assertEquals("/api/v1/upgrade-packages", response.getBody().getPath()); } diff --git a/src/test/java/com/cisd/tms/config/MultipartUploadConfigurationTest.java b/src/test/java/com/cisd/tms/config/MultipartUploadConfigurationTest.java index e202a99..122f98a 100644 --- a/src/test/java/com/cisd/tms/config/MultipartUploadConfigurationTest.java +++ b/src/test/java/com/cisd/tms/config/MultipartUploadConfigurationTest.java @@ -18,8 +18,8 @@ class MultipartUploadConfigurationTest { Object maxFileSize = findProperty(propertySources, "spring.servlet.multipart.max-file-size"); Object maxRequestSize = findProperty(propertySources, "spring.servlet.multipart.max-request-size"); - Assertions.assertEquals("${TMS_FILE_STORAGE_MAX_FILE_SIZE_BYTES:104857600}", maxFileSize); - Assertions.assertEquals("${TMS_FILE_STORAGE_MAX_FILE_SIZE_BYTES:104857600}", maxRequestSize); + Assertions.assertEquals("${TMS_MULTIPART_MAX_FILE_SIZE_BYTES:524288000}", maxFileSize); + Assertions.assertEquals("${TMS_MULTIPART_MAX_REQUEST_SIZE_BYTES:536870912}", maxRequestSize); } private Object findProperty(List> propertySources, String name) { diff --git a/src/test/java/com/cisd/tms/modules/auth/security/InternalAuthorizationInterceptorTest.java b/src/test/java/com/cisd/tms/modules/auth/security/InternalAuthorizationInterceptorTest.java index c7a5358..af52ecd 100644 --- a/src/test/java/com/cisd/tms/modules/auth/security/InternalAuthorizationInterceptorTest.java +++ b/src/test/java/com/cisd/tms/modules/auth/security/InternalAuthorizationInterceptorTest.java @@ -27,8 +27,8 @@ class InternalAuthorizationInterceptorTest { Assertions.assertFalse(allowed); Assertions.assertEquals(403, response.getStatus()); Assertions.assertTrue(response.getContentAsString().contains("\"code\":" + ErrorCode.FORBIDDEN.getCode())); - Assertions.assertTrue(response.getContentAsString().contains("role not allowed")); - Mockito.verify(operationAuditService).recordDeniedLog(Mockito.eq(request), Mockito.any(HandlerMethod.class), Mockito.eq("role not allowed")); + Assertions.assertTrue(response.getContentAsString().contains("角色无权限访问")); + Mockito.verify(operationAuditService).recordDeniedLog(Mockito.eq(request), Mockito.any(HandlerMethod.class), Mockito.eq("角色无权限访问")); } @Test @@ -45,8 +45,8 @@ class InternalAuthorizationInterceptorTest { Assertions.assertFalse(allowed); Assertions.assertEquals(403, response.getStatus()); Assertions.assertTrue(response.getContentAsString().contains("\"code\":" + ErrorCode.FORBIDDEN.getCode())); - Assertions.assertTrue(response.getContentAsString().contains("auth level not allowed")); - Mockito.verify(operationAuditService).recordDeniedLog(Mockito.eq(request), Mockito.any(HandlerMethod.class), Mockito.eq("auth level not allowed")); + Assertions.assertTrue(response.getContentAsString().contains("认证级别无权限访问")); + Mockito.verify(operationAuditService).recordDeniedLog(Mockito.eq(request), Mockito.any(HandlerMethod.class), Mockito.eq("认证级别无权限访问")); } @Test diff --git a/src/test/java/com/cisd/tms/modules/auth/service/AuthServiceTest.java b/src/test/java/com/cisd/tms/modules/auth/service/AuthServiceTest.java index 1337628..905d745 100644 --- a/src/test/java/com/cisd/tms/modules/auth/service/AuthServiceTest.java +++ b/src/test/java/com/cisd/tms/modules/auth/service/AuthServiceTest.java @@ -219,7 +219,7 @@ class AuthServiceTest { .changeAccountPassword("token-expired-001", 1, "12345678", "Abc1234!")); Assertions.assertEquals(ErrorCode.SESSION_INVALID.getCode(), exception.getCode()); - Assertions.assertEquals("session expired", exception.getMessage()); + Assertions.assertEquals("会话已过期", exception.getMessage()); } private static AuthService newAuthService( diff --git a/src/test/java/com/cisd/tms/modules/auth/service/impl/PcieCompatUkeyVerifierTest.java b/src/test/java/com/cisd/tms/modules/auth/service/impl/PcieCompatUkeyVerifierTest.java index 6cee0eb..7ac1454 100644 --- a/src/test/java/com/cisd/tms/modules/auth/service/impl/PcieCompatUkeyVerifierTest.java +++ b/src/test/java/com/cisd/tms/modules/auth/service/impl/PcieCompatUkeyVerifierTest.java @@ -69,7 +69,7 @@ class PcieCompatUkeyVerifierTest { () -> verifier().verifyLoginSignature(pubKey, "LOGIN-DATA", rawZeroSignatureHex) ); - Assertions.assertEquals("login signature verification failed", exception.getMessage()); + Assertions.assertEquals("login signature verification 执行失败", exception.getMessage()); } @Test @@ -83,7 +83,7 @@ class PcieCompatUkeyVerifierTest { () -> verifier().verifyLoginSignature(pubKey, payload, signature) ); - Assertions.assertEquals("login signature verification failed", exception.getMessage()); + Assertions.assertEquals("login signature verification 执行失败", exception.getMessage()); } @Test @@ -95,7 +95,7 @@ class PcieCompatUkeyVerifierTest { () -> verifier().verifyLoginSignature(pubKey, "LOGIN-DATA", null) ); - Assertions.assertEquals("login signature is required", exception.getMessage()); + Assertions.assertEquals("login signature不能为空", exception.getMessage()); } @Test @@ -110,7 +110,7 @@ class PcieCompatUkeyVerifierTest { () -> verifier().verifyLoginSignature(base64PublicKey, payload, signatureBase64) ); - Assertions.assertEquals("login signature verification failed", exception.getMessage()); + Assertions.assertEquals("login signature verification 执行失败", exception.getMessage()); } private PcieCompatUkeyVerifier verifier() { diff --git a/src/test/java/com/cisd/tms/modules/backup/packagex/MqReplayContextBuilderTest.java b/src/test/java/com/cisd/tms/modules/backup/packagex/MqReplayContextBuilderTest.java index 747d284..7a7a57f 100644 --- a/src/test/java/com/cisd/tms/modules/backup/packagex/MqReplayContextBuilderTest.java +++ b/src/test/java/com/cisd/tms/modules/backup/packagex/MqReplayContextBuilderTest.java @@ -41,6 +41,6 @@ class MqReplayContextBuilderTest { IllegalArgumentException ex = Assertions.assertThrows(IllegalArgumentException.class, () -> builder.build(task)); - Assertions.assertTrue(ex.getMessage().contains("initPlanJson is empty")); + Assertions.assertTrue(ex.getMessage().contains("initPlanJson为空")); } } diff --git a/src/test/java/com/cisd/tms/modules/backup/packagex/ResourcePayloadExtractorTest.java b/src/test/java/com/cisd/tms/modules/backup/packagex/ResourcePayloadExtractorTest.java index e8aeaa8..79a7978 100644 --- a/src/test/java/com/cisd/tms/modules/backup/packagex/ResourcePayloadExtractorTest.java +++ b/src/test/java/com/cisd/tms/modules/backup/packagex/ResourcePayloadExtractorTest.java @@ -78,7 +78,7 @@ class ResourcePayloadExtractorTest { () -> extractor.extract(tempDir.resolve("backup.tmsbak"), tempDir.resolve("restore")) ); - Assertions.assertTrue(ex.getMessage().contains("escapes payload dir")); + Assertions.assertTrue(ex.getMessage().contains("越过payload目录")); } @Test @@ -106,7 +106,7 @@ class ResourcePayloadExtractorTest { () -> extractor.extract(tempDir.resolve("backup.tmsbak"), tempDir.resolve("restore")) ); - Assertions.assertTrue(ex.getMessage().contains("restore path is not allowed")); + Assertions.assertTrue(ex.getMessage().contains("恢复路径不允许")); } private static byte[] payloadArchive(String manifestJson, String extraEntry, String extraContent) throws Exception { diff --git a/src/test/java/com/cisd/tms/modules/backup/service/ResourceRestoreHandoffServiceTest.java b/src/test/java/com/cisd/tms/modules/backup/service/ResourceRestoreHandoffServiceTest.java index f861152..9c9fa6f 100644 --- a/src/test/java/com/cisd/tms/modules/backup/service/ResourceRestoreHandoffServiceTest.java +++ b/src/test/java/com/cisd/tms/modules/backup/service/ResourceRestoreHandoffServiceTest.java @@ -149,7 +149,7 @@ class ResourceRestoreHandoffServiceTest { BizException exception = Assertions.assertThrows(BizException.class, () -> service.createRestoreTask(validCreateRequest())); - Assertions.assertTrue(exception.getMessage().contains("expired")); + Assertions.assertTrue(exception.getMessage().contains("已过期")); Mockito.verifyNoInteractions(repository, fileRecordRepository, payloadExtractor, applierLauncher); } diff --git a/src/test/java/com/cisd/tms/modules/cert/controller/CertControllerSecurityAnnotationTest.java b/src/test/java/com/cisd/tms/modules/cert/controller/CertControllerSecurityAnnotationTest.java index 672c656..be15a32 100644 --- a/src/test/java/com/cisd/tms/modules/cert/controller/CertControllerSecurityAnnotationTest.java +++ b/src/test/java/com/cisd/tms/modules/cert/controller/CertControllerSecurityAnnotationTest.java @@ -49,6 +49,7 @@ class CertControllerSecurityAnnotationTest { assertReadProtected(EntityController.class.getMethod("list", com.cisd.tms.modules.cert.dto.EntityListRequest.class)); assertReadProtected(EntityController.class.getMethod("p10", com.cisd.tms.modules.cert.dto.IdRequest.class)); assertReadProtected(CrlController.class.getMethod("list", com.cisd.tms.modules.cert.dto.CrlListRequest.class)); + assertReadProtected(CrlController.class.getMethod("importTaskDetail", com.cisd.tms.modules.cert.dto.CrlImportTaskDetailRequest.class)); } private void assertWriteProtected(Method method) { diff --git a/src/test/java/com/cisd/tms/modules/cert/controller/CertSwaggerAnnotationTest.java b/src/test/java/com/cisd/tms/modules/cert/controller/CertSwaggerAnnotationTest.java index 9901c41..6ad4852 100644 --- a/src/test/java/com/cisd/tms/modules/cert/controller/CertSwaggerAnnotationTest.java +++ b/src/test/java/com/cisd/tms/modules/cert/controller/CertSwaggerAnnotationTest.java @@ -5,6 +5,8 @@ import com.cisd.tms.modules.cert.dto.CertificateDetailResponse; import com.cisd.tms.modules.cert.dto.CertificateItemResponse; import com.cisd.tms.modules.cert.dto.CertificateListRequest; import com.cisd.tms.modules.cert.dto.CommonPageRequest; +import com.cisd.tms.modules.cert.dto.CrlImportTaskDetailRequest; +import com.cisd.tms.modules.cert.dto.CrlImportTaskResponse; import com.cisd.tms.modules.cert.dto.CrlListRequest; import com.cisd.tms.modules.cert.dto.CrlRevocationInfo; import com.cisd.tms.modules.cert.dto.CrlRevokedItemResponse; @@ -51,6 +53,8 @@ class CertSwaggerAnnotationTest { CertificateItemResponse.class, CertificateListRequest.class, CommonPageRequest.class, + CrlImportTaskDetailRequest.class, + CrlImportTaskResponse.class, CrlListRequest.class, CrlRevocationInfo.class, CrlRevokedItemResponse.class, diff --git a/src/test/java/com/cisd/tms/modules/cert/repository/impl/CertSchemaContractTest.java b/src/test/java/com/cisd/tms/modules/cert/repository/impl/CertSchemaContractTest.java new file mode 100644 index 0000000..2767207 --- /dev/null +++ b/src/test/java/com/cisd/tms/modules/cert/repository/impl/CertSchemaContractTest.java @@ -0,0 +1,32 @@ +package com.cisd.tms.modules.cert.repository.impl; + +import org.junit.jupiter.api.Test; +import org.springframework.core.io.ClassPathResource; + +import java.nio.charset.StandardCharsets; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class CertSchemaContractTest { + + @Test + void certSchemaShouldAllowTrustedAliasToBeNullAndStoreLargeKeyBackupData() throws Exception { + String v1Sql = readClasspathResource("db/migration/V1__tms_schema_full.sql"); + String v7Sql = readClasspathResource("db/migration/V7__add_cert_module_tables.sql"); + + assertFalse(v1Sql.contains("alias VARCHAR(128) NOT NULL"), v1Sql); + assertFalse(v7Sql.contains("alias VARCHAR(128) NOT NULL"), v7Sql); + assertTrue(v1Sql.contains("alias VARCHAR(128) NULL"), v1Sql); + assertTrue(v7Sql.contains("alias VARCHAR(128) NULL"), v7Sql); + assertFalse(v1Sql.contains("backup_data VARCHAR(500)"), v1Sql); + assertFalse(v7Sql.contains("backup_data VARCHAR(500)"), v7Sql); + assertTrue(v1Sql.contains("backup_data TEXT NOT NULL"), v1Sql); + assertTrue(v7Sql.contains("backup_data TEXT NOT NULL"), v7Sql); + } + + private String readClasspathResource(String location) throws Exception { + ClassPathResource resource = new ClassPathResource(location); + return new String(resource.getInputStream().readAllBytes(), StandardCharsets.UTF_8); + } +} diff --git a/src/test/java/com/cisd/tms/modules/cert/repository/impl/CrlRepositoryImplTest.java b/src/test/java/com/cisd/tms/modules/cert/repository/impl/CrlRepositoryImplTest.java index 60537a3..11de9ba 100644 --- a/src/test/java/com/cisd/tms/modules/cert/repository/impl/CrlRepositoryImplTest.java +++ b/src/test/java/com/cisd/tms/modules/cert/repository/impl/CrlRepositoryImplTest.java @@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.core.metadata.TableInfoHelper; import com.cisd.tms.modules.cert.dto.CrlListRequest; import com.cisd.tms.modules.cert.entity.CertificateCrlRevokedEntity; import com.cisd.tms.modules.cert.mapper.CertificateCrlEntityMapper; +import com.cisd.tms.modules.cert.mapper.CertificateCrlImportTaskMapper; import com.cisd.tms.modules.cert.mapper.CertificateCrlRevokedEntityMapper; import com.cisd.tms.modules.cert.repository.CrlRepository; import org.apache.ibatis.builder.MapperBuilderAssistant; @@ -26,7 +27,11 @@ class CrlRepositoryImplTest { CertificateCrlRevokedEntity.class ); CertificateCrlRevokedEntityMapper revokedMapper = Mockito.mock(CertificateCrlRevokedEntityMapper.class); - CrlRepository repository = new CrlRepositoryImpl(Mockito.mock(CertificateCrlEntityMapper.class), revokedMapper); + CrlRepository repository = new CrlRepositoryImpl( + Mockito.mock(CertificateCrlEntityMapper.class), + revokedMapper, + Mockito.mock(CertificateCrlImportTaskMapper.class) + ); Mockito.when(revokedMapper.selectCount(Mockito.any())).thenReturn(0L); Mockito.when(revokedMapper.selectList(Mockito.any())).thenReturn(List.of()); CrlListRequest request = new CrlListRequest(); @@ -49,7 +54,11 @@ class CrlRepositoryImplTest { CertificateCrlRevokedEntity.class ); CertificateCrlRevokedEntityMapper revokedMapper = Mockito.mock(CertificateCrlRevokedEntityMapper.class); - CrlRepository repository = new CrlRepositoryImpl(Mockito.mock(CertificateCrlEntityMapper.class), revokedMapper); + CrlRepository repository = new CrlRepositoryImpl( + Mockito.mock(CertificateCrlEntityMapper.class), + revokedMapper, + Mockito.mock(CertificateCrlImportTaskMapper.class) + ); Mockito.when(revokedMapper.selectCount(Mockito.any())).thenReturn(0L); repository.existsRevoked("CN=Root", "A"); @@ -59,6 +68,7 @@ class CrlRepositoryImplTest { String sqlSegment = captor.getValue().getSqlSegment(); assertTrue(sqlSegment.contains("t_certificate_crl"), sqlSegment); assertTrue(sqlSegment.contains("next_update"), sqlSegment); + org.junit.jupiter.api.Assertions.assertFalse(sqlSegment.contains("IS NULL"), sqlSegment); } @Test @@ -68,7 +78,11 @@ class CrlRepositoryImplTest { CertificateCrlRevokedEntity.class ); CertificateCrlRevokedEntityMapper revokedMapper = Mockito.mock(CertificateCrlRevokedEntityMapper.class); - CrlRepository repository = new CrlRepositoryImpl(Mockito.mock(CertificateCrlEntityMapper.class), revokedMapper); + CrlRepository repository = new CrlRepositoryImpl( + Mockito.mock(CertificateCrlEntityMapper.class), + revokedMapper, + Mockito.mock(CertificateCrlImportTaskMapper.class) + ); repository.updateRevokedSubjectIfMissing("CN=Root", "A", "CN=User", "SM2"); diff --git a/src/test/java/com/cisd/tms/modules/cert/service/CertificateServiceTest.java b/src/test/java/com/cisd/tms/modules/cert/service/CertificateServiceTest.java index 753a023..2af0f35 100644 --- a/src/test/java/com/cisd/tms/modules/cert/service/CertificateServiceTest.java +++ b/src/test/java/com/cisd/tms/modules/cert/service/CertificateServiceTest.java @@ -11,7 +11,9 @@ import com.cisd.tms.modules.cert.repository.CertificateRepository; import com.cisd.tms.modules.cert.support.CertPemSupport; import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.asn1.x509.BasicConstraints; +import org.bouncycastle.asn1.x509.ExtendedKeyUsage; import org.bouncycastle.asn1.x509.Extension; +import org.bouncycastle.asn1.x509.KeyPurposeId; import org.bouncycastle.asn1.x509.KeyUsage; import org.bouncycastle.cert.X509CertificateHolder; import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; @@ -328,6 +330,69 @@ class CertificateServiceTest { assertEquals("REVOKED", service.detail(1L).getRuntimeStatus()); } + @Test + void shouldReturnInvalidChainRuntimeStatusWhenCurrentTrustedPathCannotBeValidated() { + CertificateRepository certificateRepository = Mockito.mock(CertificateRepository.class); + EntityService entityService = Mockito.mock(EntityService.class); + TrustedCertService trustedCertService = Mockito.mock(TrustedCertService.class); + CrlRevocationService crlRevocationService = Mockito.mock(CrlRevocationService.class); + CertificateService service = new CertificateService( + certificateRepository, + entityService, + trustedCertService, + crlRevocationService + ); + CertificateEntity certificate = new CertificateEntity() + .setSubjectDn("CN=entity") + .setIssuerDn("CN=Root") + .setSerialNumber("A") + .setAlgoType("SM2") + .setFingerprint("ABC") + .setCertData("CERT") + .setValidFrom(LocalDateTime.now().minusDays(1)) + .setValidTo(LocalDateTime.now().plusDays(30)); + certificate.setId(1L); + Mockito.when(certificateRepository.findById(1L)).thenReturn(Optional.of(certificate)); + Mockito.when(crlRevocationService.isRevoked("CN=Root", "A")).thenReturn(false); + Mockito.when(trustedCertService.validateCertificateChain("CERT")).thenReturn(false); + + assertEquals("INVALID_CHAIN", service.detail(1L).getRuntimeStatus()); + } + + @Test + void shouldRejectEndEntityCertificateWithUnsupportedCriticalExtension() throws Exception { + CertificateRepository certificateRepository = Mockito.mock(CertificateRepository.class); + EntityService entityService = Mockito.mock(EntityService.class); + TrustedCertService trustedCertService = Mockito.mock(TrustedCertService.class); + CertificateService service = new CertificateService(certificateRepository, entityService, trustedCertService); + KeyPair caKeyPair = sm2KeyPair(); + KeyPair userKeyPair = sm2KeyPair(); + X509Certificate userCert = certificateWithUnsupportedCriticalExtension(userKeyPair, caKeyPair); + Mockito.when(certificateRepository.findByFingerprint(any())).thenReturn(Optional.empty()); + + BizException exception = assertThrows(BizException.class, () -> service.importCertificate(multipart(userCert))); + + assertEquals("证书包含不支持的关键扩展", exception.getMessage()); + Mockito.verify(certificateRepository, Mockito.never()).save(any()); + } + + @Test + void shouldRejectEndEntityCertificateWhenExtendedKeyUsageDoesNotAllowClientOrServerAuth() throws Exception { + CertificateRepository certificateRepository = Mockito.mock(CertificateRepository.class); + EntityService entityService = Mockito.mock(EntityService.class); + TrustedCertService trustedCertService = Mockito.mock(TrustedCertService.class); + CertificateService service = new CertificateService(certificateRepository, entityService, trustedCertService); + KeyPair caKeyPair = sm2KeyPair(); + KeyPair userKeyPair = sm2KeyPair(); + X509Certificate userCert = certificateWithExtendedKeyUsage(userKeyPair, caKeyPair, KeyPurposeId.id_kp_OCSPSigning); + Mockito.when(certificateRepository.findByFingerprint(any())).thenReturn(Optional.empty()); + + BizException exception = assertThrows(BizException.class, () -> service.importCertificate(multipart(userCert))); + + assertEquals("实体/用户证书扩展用途不支持业务认证", exception.getMessage()); + Mockito.verify(certificateRepository, Mockito.never()).save(any()); + } + private KeyPair sm2KeyPair() throws Exception { KeyPairGenerator generator = KeyPairGenerator.getInstance("EC", "BC"); generator.initialize(new ECGenParameterSpec("sm2p256v1")); @@ -407,6 +472,52 @@ class CertificateServiceTest { return new JcaX509CertificateConverter().getCertificate(holder); } + private X509Certificate certificateWithUnsupportedCriticalExtension(KeyPair subjectKeyPair, KeyPair issuerKeyPair) throws Exception { + Instant now = Instant.now(); + JcaX509v3CertificateBuilder builder = new JcaX509v3CertificateBuilder( + new X500Name("CN=Root CA"), + BigInteger.valueOf(System.nanoTime()).abs(), + Date.from(now.minus(1, ChronoUnit.DAYS)), + Date.from(now.plus(30, ChronoUnit.DAYS)), + new X500Name("CN=User"), + subjectKeyPair.getPublic() + ); + builder.addExtension(Extension.basicConstraints, true, new BasicConstraints(false)); + builder.addExtension(Extension.keyUsage, true, new KeyUsage(KeyUsage.digitalSignature)); + builder.addExtension(new org.bouncycastle.asn1.ASN1ObjectIdentifier("1.2.3.4.5.6.7.8.1"), true, new byte[] {0x01, 0x02}); + X509CertificateHolder holder = builder.build( + new JcaContentSignerBuilder("SM3withSM2") + .setProvider("BC") + .build(issuerKeyPair.getPrivate()) + ); + return new JcaX509CertificateConverter() + .setProvider("BC") + .getCertificate(holder); + } + + private X509Certificate certificateWithExtendedKeyUsage(KeyPair subjectKeyPair, KeyPair issuerKeyPair, KeyPurposeId keyPurposeId) throws Exception { + Instant now = Instant.now(); + JcaX509v3CertificateBuilder builder = new JcaX509v3CertificateBuilder( + new X500Name("CN=Root CA"), + BigInteger.valueOf(System.nanoTime()).abs(), + Date.from(now.minus(1, ChronoUnit.DAYS)), + Date.from(now.plus(30, ChronoUnit.DAYS)), + new X500Name("CN=User"), + subjectKeyPair.getPublic() + ); + builder.addExtension(Extension.basicConstraints, true, new BasicConstraints(false)); + builder.addExtension(Extension.keyUsage, true, new KeyUsage(KeyUsage.digitalSignature)); + builder.addExtension(Extension.extendedKeyUsage, false, new ExtendedKeyUsage(keyPurposeId)); + X509CertificateHolder holder = builder.build( + new JcaContentSignerBuilder("SM3withSM2") + .setProvider("BC") + .build(issuerKeyPair.getPrivate()) + ); + return new JcaX509CertificateConverter() + .setProvider("BC") + .getCertificate(holder); + } + private TrustedCertEntity trustedIssuer(X509Certificate certificate) { return new TrustedCertEntity() .setSubjectDn(certificate.getSubjectX500Principal().getName()) diff --git a/src/test/java/com/cisd/tms/modules/cert/service/CrlServiceTest.java b/src/test/java/com/cisd/tms/modules/cert/service/CrlServiceTest.java index 17e9c40..f3ca30e 100644 --- a/src/test/java/com/cisd/tms/modules/cert/service/CrlServiceTest.java +++ b/src/test/java/com/cisd/tms/modules/cert/service/CrlServiceTest.java @@ -24,10 +24,14 @@ import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; import org.mockito.Mockito; import org.springframework.mock.web.MockMultipartFile; +import org.springframework.web.multipart.MultipartFile; import java.math.BigInteger; +import java.nio.file.Files; +import java.nio.file.Path; import java.nio.charset.StandardCharsets; import java.security.KeyPair; import java.security.KeyPairGenerator; @@ -42,6 +46,7 @@ import java.util.Date; import java.util.Optional; import java.util.List; import java.time.LocalDateTime; +import java.util.concurrent.Executor; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.any; @@ -83,7 +88,7 @@ class CrlServiceTest { ); X509CRL crl = crl(caCert, caKeyPair, leafCert.getSerialNumber(), org.bouncycastle.asn1.x509.CRLReason.keyCompromise); Mockito.when(crlRepository.findCrlByFingerprint(any())).thenReturn(Optional.empty()); - Mockito.when(trustedCertService.resolveCrlIssuer(crl)).thenReturn(trusted(caCert)); + Mockito.when(trustedCertService.resolveCrlIssuer(any())).thenReturn(trusted(caCert)); Mockito.when(certificateRepository.findByIssuerDnAndSerialNumber(caCert.getSubjectX500Principal().getName(), "A")) .thenReturn(Optional.of(localCertificate(leafCert))); Mockito.when(crlRepository.saveCrl(any())).thenAnswer(invocation -> { @@ -99,13 +104,157 @@ class CrlServiceTest { && "SM2".equals(saved.getAlgoType()) && "CN=Root".equals(saved.getIssuerDn()) )); - Mockito.verify(crlRepository).saveRevoked(Mockito.argThat(saved -> - "CN=entity".equals(saved.getSubjectDn()) - && "A".equals(saved.getSerialNumber()) - && "keyCompromise".equals(saved.getRevocationReason()) + Mockito.verify(crlRepository).saveRevokedBatch(Mockito.argThat(saved -> + saved.size() == 1 + && "CN=entity".equals(saved.get(0).getSubjectDn()) + && "A".equals(saved.get(0).getSerialNumber()) + && "keyCompromise".equals(saved.get(0).getRevocationReason()) )); } + @Test + void shouldImportCrlWithoutReadingWholeMultipartIntoHeapOrPersistingOriginalContent(@TempDir Path tempDir) throws Exception { + CrlRepository crlRepository = Mockito.mock(CrlRepository.class); + CertificateRepository certificateRepository = Mockito.mock(CertificateRepository.class); + TrustedCertService trustedCertService = Mockito.mock(TrustedCertService.class); + CrlService service = new CrlService(crlRepository, certificateRepository, trustedCertService, tempDir.toString(), 500L * 1024 * 1024); + KeyPair caKeyPair = sm2KeyPair(); + X509Certificate caCert = certificate( + caKeyPair, + caKeyPair, + "CN=Root", + "CN=Root", + true, + KeyUsage.keyCertSign | KeyUsage.cRLSign, + BigInteger.ONE + ); + X509CRL crl = crl(caCert, caKeyPair, BigInteger.TEN, org.bouncycastle.asn1.x509.CRLReason.keyCompromise); + byte[] crlBytes = crl.getEncoded(); + MultipartFile file = Mockito.mock(MultipartFile.class); + Mockito.when(file.getInputStream()).thenAnswer(invocation -> new java.io.ByteArrayInputStream(crlBytes)); + Mockito.when(crlRepository.findCrlByFingerprint(any())).thenReturn(Optional.empty()); + Mockito.when(trustedCertService.resolveCrlIssuer(any())).thenReturn(trusted(caCert)); + Mockito.when(crlRepository.saveCrl(any())).thenAnswer(invocation -> { + CertificateCrlEntity saved = invocation.getArgument(0); + saved.setId(100L); + return saved; + }); + + service.importCrl(file); + + Mockito.verify(file, Mockito.never()).getBytes(); + Mockito.verify(crlRepository).saveCrl(Mockito.argThat(saved -> + "".equals(saved.getCrlData()) + )); + try (var files = Files.list(tempDir)) { + org.junit.jupiter.api.Assertions.assertEquals(0L, files.count()); + } + } + + @Test + void shouldRejectCrlLargerThanConfiguredLimitBeforeReadingStream(@TempDir Path tempDir) throws Exception { + CrlRepository crlRepository = Mockito.mock(CrlRepository.class); + CertificateRepository certificateRepository = Mockito.mock(CertificateRepository.class); + TrustedCertService trustedCertService = Mockito.mock(TrustedCertService.class); + CrlService service = new CrlService(crlRepository, certificateRepository, trustedCertService, tempDir.toString(), 1024); + MultipartFile file = Mockito.mock(MultipartFile.class); + Mockito.when(file.getSize()).thenReturn(1025L); + + com.cisd.tms.common.exception.BizException exception = assertThrows(com.cisd.tms.common.exception.BizException.class, () -> service.importCrl(file)); + + assertEquals("CRL文件过大,最大支持1KB", exception.getMessage()); + Mockito.verify(file, Mockito.never()).getInputStream(); + Mockito.verifyNoInteractions(crlRepository, certificateRepository, trustedCertService); + } + + @Test + void shouldSubmitCrlImportTaskAndReturnBeforeBackgroundWorkCompletes(@TempDir Path tempDir) throws Exception { + CrlRepository crlRepository = Mockito.mock(CrlRepository.class); + CertificateRepository certificateRepository = Mockito.mock(CertificateRepository.class); + TrustedCertService trustedCertService = Mockito.mock(TrustedCertService.class); + java.util.List submitted = new java.util.ArrayList<>(); + Executor capturingExecutor = submitted::add; + CrlService service = new CrlService( + crlRepository, + certificateRepository, + trustedCertService, + tempDir.toString(), + 500L * 1024 * 1024, + capturingExecutor + ); + KeyPair caKeyPair = sm2KeyPair(); + X509Certificate caCert = certificate( + caKeyPair, + caKeyPair, + "CN=Root", + "CN=Root", + true, + KeyUsage.keyCertSign | KeyUsage.cRLSign, + BigInteger.ONE + ); + X509CRL crl = crl(caCert, caKeyPair, BigInteger.TEN, org.bouncycastle.asn1.x509.CRLReason.keyCompromise); + + com.cisd.tms.modules.cert.dto.CrlImportTaskResponse response = service.submitImport(multipart(crl)); + + org.junit.jupiter.api.Assertions.assertNotNull(response.getTaskId()); + assertEquals("PENDING", response.getStatus()); + assertEquals(1, submitted.size()); + Mockito.verify(crlRepository).saveImportTask(Mockito.argThat(task -> + response.getTaskId().equals(task.getTaskId()) + && "PENDING".equals(task.getStatus()) + )); + Mockito.verify(crlRepository, Mockito.never()).saveCrl(any()); + } + + @Test + void shouldRunSubmittedCrlImportInBackgroundAndUpdateTaskStatus(@TempDir Path tempDir) throws Exception { + CrlRepository crlRepository = Mockito.mock(CrlRepository.class); + CertificateRepository certificateRepository = Mockito.mock(CertificateRepository.class); + TrustedCertService trustedCertService = Mockito.mock(TrustedCertService.class); + java.util.List submitted = new java.util.ArrayList<>(); + CrlService service = new CrlService( + crlRepository, + certificateRepository, + trustedCertService, + tempDir.toString(), + 500L * 1024 * 1024, + submitted::add + ); + KeyPair caKeyPair = sm2KeyPair(); + X509Certificate caCert = certificate( + caKeyPair, + caKeyPair, + "CN=Root", + "CN=Root", + true, + KeyUsage.keyCertSign | KeyUsage.cRLSign, + BigInteger.ONE + ); + X509CRL crl = crl(caCert, caKeyPair, BigInteger.TEN, org.bouncycastle.asn1.x509.CRLReason.keyCompromise); + Mockito.when(crlRepository.findCrlByFingerprint(any())).thenReturn(Optional.empty()); + Mockito.when(trustedCertService.resolveCrlIssuer(any())).thenReturn(trusted(caCert)); + Mockito.when(crlRepository.saveCrl(any())).thenAnswer(invocation -> { + CertificateCrlEntity saved = invocation.getArgument(0); + saved.setId(100L); + return saved; + }); + + com.cisd.tms.modules.cert.dto.CrlImportTaskResponse response = service.submitImport(multipart(crl)); + submitted.get(0).run(); + + Mockito.verify(crlRepository).updateImportTaskRunning(Mockito.eq(response.getTaskId()), any()); + Mockito.verify(crlRepository).updateImportTaskSuccess( + Mockito.eq(response.getTaskId()), + Mockito.eq(100L), + any(), + Mockito.eq(1), + any() + ); + try (var files = Files.list(tempDir)) { + org.junit.jupiter.api.Assertions.assertEquals(0L, files.count()); + } + } + @Test void shouldUseIssuerAlgorithmWhenSavingCrlMetadataAndRevokedRows() throws Exception { CrlRepository crlRepository = Mockito.mock(CrlRepository.class); @@ -126,7 +275,7 @@ class CrlServiceTest { TrustedCertEntity issuer = trusted(caCert); issuer.setAlgoType("RSA"); Mockito.when(crlRepository.findCrlByFingerprint(any())).thenReturn(Optional.empty()); - Mockito.when(trustedCertService.resolveCrlIssuer(crl)).thenReturn(issuer); + Mockito.when(trustedCertService.resolveCrlIssuer(any())).thenReturn(issuer); Mockito.when(crlRepository.saveCrl(any())).thenAnswer(invocation -> { CertificateCrlEntity saved = invocation.getArgument(0); saved.setId(101L); @@ -136,7 +285,9 @@ class CrlServiceTest { service.importCrl(multipart(crl)); Mockito.verify(crlRepository).saveCrl(Mockito.argThat(saved -> "RSA".equals(saved.getAlgoType()))); - Mockito.verify(crlRepository).saveRevoked(Mockito.argThat(saved -> "RSA".equals(saved.getAlgoType()))); + Mockito.verify(crlRepository).saveRevokedBatch(Mockito.argThat(saved -> + saved.size() == 1 && "RSA".equals(saved.get(0).getAlgoType()) + )); } @Test @@ -181,7 +332,7 @@ class CrlServiceTest { ); X509CRL crl = crl(caCert, caKeyPair, BigInteger.TEN, org.bouncycastle.asn1.x509.CRLReason.keyCompromise); Mockito.when(crlRepository.findCrlByFingerprint(any())).thenReturn(Optional.empty()); - Mockito.when(trustedCertService.resolveCrlIssuer(crl)).thenThrow(new RuntimeException("issuer missing")); + Mockito.when(trustedCertService.resolveCrlIssuer(any())).thenThrow(new RuntimeException("issuer missing")); assertThrows(RuntimeException.class, () -> service.importCrl(multipart(crl))); @@ -246,6 +397,28 @@ class CrlServiceTest { Mockito.verify(crlRepository, Mockito.never()).saveCrl(any()); } + @Test + void shouldRejectCrlWhenNextUpdateIsMissing() throws Exception { + CrlRepository crlRepository = Mockito.mock(CrlRepository.class); + CrlService service = new CrlService(crlRepository, Mockito.mock(CertificateRepository.class), Mockito.mock(TrustedCertService.class)); + KeyPair caKeyPair = sm2KeyPair(); + X509Certificate caCert = certificate( + caKeyPair, + caKeyPair, + "CN=Root", + "CN=Root", + true, + KeyUsage.keyCertSign | KeyUsage.cRLSign, + BigInteger.ONE + ); + X509CRL crl = crlWithoutNextUpdate(caCert, caKeyPair, BigInteger.TEN); + + com.cisd.tms.common.exception.BizException exception = assertThrows(com.cisd.tms.common.exception.BizException.class, () -> service.importCrl(multipart(crl))); + + assertEquals("CRL缺少下次更新时间", exception.getMessage()); + Mockito.verify(crlRepository, Mockito.never()).saveCrl(any()); + } + @Test void shouldListRevokedCertificateRows() { CrlRepository crlRepository = Mockito.mock(CrlRepository.class); @@ -366,6 +539,28 @@ class CrlServiceTest { .getCRL(holder); } + private X509CRL crlWithoutNextUpdate( + X509Certificate issuer, + KeyPair issuerKeyPair, + BigInteger revokedSerial + ) throws Exception { + Instant thisUpdate = Instant.now().minus(1, ChronoUnit.HOURS); + X509v2CRLBuilder builder = new X509v2CRLBuilder( + new X500Name(issuer.getSubjectX500Principal().getName()), + Date.from(thisUpdate) + ); + builder.addCRLEntry(revokedSerial, Date.from(thisUpdate), org.bouncycastle.asn1.x509.CRLReason.keyCompromise); + builder.addExtension(Extension.cRLNumber, false, new CRLNumber(BigInteger.valueOf(123))); + X509CRLHolder holder = builder.build( + new JcaContentSignerBuilder("SM3withSM2") + .setProvider("BC") + .build(issuerKeyPair.getPrivate()) + ); + return new JcaX509CRLConverter() + .setProvider("BC") + .getCRL(holder); + } + private TrustedCertEntity trusted(X509Certificate certificate) { return new TrustedCertEntity() .setSubjectDn(certificate.getSubjectX500Principal().getName()) diff --git a/src/test/java/com/cisd/tms/modules/cert/service/EntityServiceTest.java b/src/test/java/com/cisd/tms/modules/cert/service/EntityServiceTest.java index 2973a8d..808178c 100644 --- a/src/test/java/com/cisd/tms/modules/cert/service/EntityServiceTest.java +++ b/src/test/java/com/cisd/tms/modules/cert/service/EntityServiceTest.java @@ -28,6 +28,7 @@ import org.mockito.Mockito; import java.io.StringReader; import java.lang.reflect.Modifier; +import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.Security; import java.security.interfaces.ECPublicKey; @@ -180,6 +181,28 @@ class EntityServiceTest { assertEquals("CREATED", entityCaptor.getValue().getStatus()); } + @Test + void shouldMatchRsaCertificatePublicKeyToRsaEntity() throws Exception { + KeyEntityRepository keyEntityRepository = Mockito.mock(KeyEntityRepository.class); + CertificateRepository certificateRepository = Mockito.mock(CertificateRepository.class); + EntityService service = new EntityService( + keyEntityRepository, + certificateRepository, + Mockito.mock(PcieCryptoService.class), + new ObjectMapper() + ); + KeyPair keyPair = generateRsaKeyPair(); + KeyEntity entity = new KeyEntity() + .setAlgoType("RSA") + .setPublicKey(Base64.getEncoder().encodeToString(keyPair.getPublic().getEncoded())); + entity.setId(88L); + Mockito.when(keyEntityRepository.findAll()).thenReturn(List.of(entity)); + + Optional result = service.findByCertificatePublicKey(keyPair.getPublic().getEncoded()); + + assertEquals(Optional.of(88L), result); + } + @Test void shouldUpdateEntityOrgFields() { KeyEntityRepository keyEntityRepository = Mockito.mock(KeyEntityRepository.class); @@ -307,6 +330,12 @@ class EntityServiceTest { return (ECPublicKey) keyPairGenerator.generateKeyPair().getPublic(); } + private KeyPair generateRsaKeyPair() throws Exception { + KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA"); + generator.initialize(2048); + return generator.generateKeyPair(); + } + private byte[] gm0018EccRefPublicKey(ECPublicKey publicKey) { byte[] x = toFixed32(publicKey.getW().getAffineX().toByteArray()); byte[] y = toFixed32(publicKey.getW().getAffineY().toByteArray()); diff --git a/src/test/java/com/cisd/tms/modules/device/service/DeviceRuntimeStatusServiceTest.java b/src/test/java/com/cisd/tms/modules/device/service/DeviceRuntimeStatusServiceTest.java index 2652ae8..17677c3 100644 --- a/src/test/java/com/cisd/tms/modules/device/service/DeviceRuntimeStatusServiceTest.java +++ b/src/test/java/com/cisd/tms/modules/device/service/DeviceRuntimeStatusServiceTest.java @@ -211,17 +211,17 @@ class DeviceRuntimeStatusServiceTest { return new HardwareStatusProbe() { @Override public Double cpuUsagePercent() { - throw new IllegalStateException("cpu probe failed"); + throw new IllegalStateException("cpu probe 执行失败"); } @Override public UsageSnapshot memoryUsage() { - throw new IllegalStateException("memory probe failed"); + throw new IllegalStateException("memory probe 执行失败"); } @Override public UsageSnapshot diskUsage() { - throw new IllegalStateException("disk probe failed"); + throw new IllegalStateException("disk probe 执行失败"); } }; } @@ -313,37 +313,37 @@ class DeviceRuntimeStatusServiceTest { return new SoftwareRuntimeProbe() { @Override public boolean isRabbitmqRunning() { - throw new IllegalStateException("rabbitmq probe failed"); + throw new IllegalStateException("rabbitmq probe 执行失败"); } @Override public boolean isTlqRunning() { - throw new IllegalStateException("tlq probe failed"); + throw new IllegalStateException("tlq probe 执行失败"); } @Override public boolean isTidbRunning() { - throw new IllegalStateException("tidb probe failed"); + throw new IllegalStateException("tidb probe 执行失败"); } @Override public boolean isNginxRunning() { - throw new IllegalStateException("nginx probe failed"); + throw new IllegalStateException("nginx probe 执行失败"); } @Override public boolean isStandardAppRunning() { - throw new IllegalStateException("app probe failed"); + throw new IllegalStateException("app probe 执行失败"); } @Override public boolean isDirectAppRunning() { - throw new IllegalStateException("app probe failed"); + throw new IllegalStateException("app probe 执行失败"); } @Override public boolean isTmsRunning() { - throw new IllegalStateException("tms probe failed"); + throw new IllegalStateException("tms probe 执行失败"); } }; } diff --git a/src/test/java/com/cisd/tms/modules/file/FileServiceTest.java b/src/test/java/com/cisd/tms/modules/file/FileServiceTest.java index f32133e..ba8e936 100644 --- a/src/test/java/com/cisd/tms/modules/file/FileServiceTest.java +++ b/src/test/java/com/cisd/tms/modules/file/FileServiceTest.java @@ -77,7 +77,7 @@ class FileServiceTest { IllegalArgumentException ex = Assertions.assertThrows(IllegalArgumentException.class, () -> service.upload(file)); - Assertions.assertTrue(ex.getMessage().contains("empty")); + Assertions.assertTrue(ex.getMessage().contains("为空")); } @Test @@ -88,7 +88,7 @@ class FileServiceTest { IllegalArgumentException ex = Assertions.assertThrows(IllegalArgumentException.class, () -> service.upload(file)); - Assertions.assertTrue(ex.getMessage().contains("size")); + Assertions.assertTrue(ex.getMessage().contains("大小")); } @Test diff --git a/src/test/java/com/cisd/tms/modules/init/executor/ConfigurableInitStepExecutorTest.java b/src/test/java/com/cisd/tms/modules/init/executor/ConfigurableInitStepExecutorTest.java index 708fb3c..46d842e 100644 --- a/src/test/java/com/cisd/tms/modules/init/executor/ConfigurableInitStepExecutorTest.java +++ b/src/test/java/com/cisd/tms/modules/init/executor/ConfigurableInitStepExecutorTest.java @@ -572,7 +572,7 @@ class ConfigurableInitStepExecutorTest { properties.setStandardCpconfigPath(cpconfig.toString()); properties.setStandardDbScriptBaseDir(standardHome.resolve("cpackage").toString()); properties.setStandardDbLoadDir(loadDir.toString()); - properties.setStandardDbApplyCommand("test -f CMEP.sql && echo applied > '" + marker + "'"); + properties.setStandardDbApplyCommand("test -f CMEP.sql && echo 已应用 > '" + marker + "'"); ConfigurableInitStepExecutor executor = new ConfigurableInitStepExecutor(properties, new ObjectMapper()); InitTaskEntity task = new InitTaskEntity(); @@ -667,7 +667,7 @@ class ConfigurableInitStepExecutorTest { InitStepExecutionResult result = executor.execute(task, dbApplyStep); Assertions.assertFalse(result.isSuccess()); - Assertions.assertTrue(result.getMessage().contains("apply command is blank")); + Assertions.assertTrue(result.getMessage().contains("应用命令为空")); } @Test @@ -707,7 +707,7 @@ class ConfigurableInitStepExecutorTest { + " && test \"$LOAD_DIR\" = '" + loadDir.toAbsolutePath() + "'" + " && test \"$ORG_CODE\" = \"AAAABBBBXXX\"" + " && test \"$ORG_NAME_CN\" = \"测试机构\"" - + " && echo applied > '" + marker + "'" + + " && echo 已应用 > '" + marker + "'" ); ConfigurableInitStepExecutor executor = new ConfigurableInitStepExecutor( properties, @@ -1153,7 +1153,7 @@ class ConfigurableInitStepExecutorTest { InitStepExecutionResult result = executor.execute(task, step); Assertions.assertTrue(result.isSuccess()); - Assertions.assertTrue(result.getMessage().contains("skipped")); + Assertions.assertTrue(result.getMessage().contains("已跳过")); Assertions.assertEquals("spring.rabbitmq.username=OLD\nspring.rabbitmq.password=OLD\n", Files.readString(cmspCfg)); } @@ -1278,7 +1278,7 @@ class ConfigurableInitStepExecutorTest { InitStepExecutionResult result = executor.execute(task, step); Assertions.assertTrue(result.isSuccess()); - Assertions.assertFalse(result.getMessage().contains("skipped")); + Assertions.assertFalse(result.getMessage().contains("已跳过")); Assertions.assertTrue(Files.exists(tempRoot.resolve("staging") .resolve("TASK-RABBIT-SETUP-INDIRECT-TLQ-1") .resolve("scripts") diff --git a/src/test/java/com/cisd/tms/modules/init/service/InitServiceTest.java b/src/test/java/com/cisd/tms/modules/init/service/InitServiceTest.java index 917212b..6b443a4 100644 --- a/src/test/java/com/cisd/tms/modules/init/service/InitServiceTest.java +++ b/src/test/java/com/cisd/tms/modules/init/service/InitServiceTest.java @@ -51,7 +51,7 @@ class InitServiceTest { InitPreviewRequest request = validEnterpriseRequest(); BizException ex = Assertions.assertThrows(BizException.class, () -> service.preview(request)); - Assertions.assertTrue(ex.getMessage().contains("preset product type")); + Assertions.assertTrue(ex.getMessage().contains("预置产品类型")); } @Test @@ -624,7 +624,7 @@ class InitServiceTest { void shouldThrowWhenQueryNonExistingTask() { InitService service = newService(emptyRepository(), emptyStepRepository(), preset("ENTERPRISE", "V1.3.2", "CONFIG"), successExecutor(), "/tmp/tms-init-logs"); BizException ex = Assertions.assertThrows(BizException.class, () -> service.getTask("NOT-EXIST")); - Assertions.assertTrue(ex.getMessage().contains("init task not found")); + Assertions.assertTrue(ex.getMessage().contains("初始化任务不存在")); } @Test @@ -825,9 +825,9 @@ class InitServiceTest { private static InitStepExecutor failOnDeployStepExecutor() { return (task, step) -> { if ("DEPLOY_RECEIVER".equals(step.getStepCode())) { - return InitStepExecutionResult.failure("simulated failed", 1, "/tmp/fail.log"); + return InitStepExecutionResult.failure("模拟执行失败", 1, "/tmp/fail.log"); } - return InitStepExecutionResult.success("ok", 0, "/tmp/success.log"); + return InitStepExecutionResult.success("ok", 0, "/tmp/成功.log"); }; } @@ -841,7 +841,7 @@ class InitServiceTest { Files.writeString(logPath, "step=" + step.getStepCode()); return InitStepExecutionResult.success("ok", 0, logPath.toString()); } catch (IOException ex) { - return InitStepExecutionResult.failure("write log failed", -1, null); + return InitStepExecutionResult.failure("write log 执行失败", -1, null); } }; } @@ -1123,9 +1123,9 @@ class InitServiceTest { public InitStepExecutionResult execute(InitTaskEntity task, InitTaskStepEntity step) { executedStepCodes.add(step.getStepCode()); if (failStepCode.equals(step.getStepCode()) && failed.compareAndSet(false, true)) { - return InitStepExecutionResult.failure("simulated failed", 1, "/tmp/fail.log"); + return InitStepExecutionResult.failure("模拟执行失败", 1, "/tmp/fail.log"); } - return InitStepExecutionResult.success("ok", 0, "/tmp/success.log"); + return InitStepExecutionResult.success("ok", 0, "/tmp/成功.log"); } int executionCount(String stepCode) { diff --git a/src/test/java/com/cisd/tms/modules/log/aspect/OperationAuditAspectLoginTest.java b/src/test/java/com/cisd/tms/modules/log/aspect/OperationAuditAspectLoginTest.java index 4c420e5..1efa952 100644 --- a/src/test/java/com/cisd/tms/modules/log/aspect/OperationAuditAspectLoginTest.java +++ b/src/test/java/com/cisd/tms/modules/log/aspect/OperationAuditAspectLoginTest.java @@ -72,7 +72,7 @@ class OperationAuditAspectLoginTest { UkeyLoginProof proof = new UkeyLoginProof(); proof.setUid(1); request.setLoginFactors(List.of(proof)); - RuntimeException failure = new RuntimeException("ukey verification failed"); + RuntimeException failure = new RuntimeException("ukey verification 执行失败"); ProceedingJoinPoint joinPoint = joinPointThrowing(request, failure); bindRequest("10.0.0.9"); @@ -89,7 +89,7 @@ class OperationAuditAspectLoginTest { Assertions.assertNull(command.getOperatorAuthLevel()); Assertions.assertEquals(OperationResult.FAILED, command.getOperationResult()); Assertions.assertEquals("UKey 登录 [role=KEY_ADMIN, principal=uid=1]", command.getSummary()); - Assertions.assertEquals("ukey verification failed", command.getErrorMessage()); + Assertions.assertEquals("ukey verification 执行失败", command.getErrorMessage()); Assertions.assertEquals("10.0.0.9", command.getRemoteIp()); } 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 0ea8313..5569424 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 @@ -58,13 +58,13 @@ class LmkServiceTest { PcieCryptoService pcieCryptoService = Mockito.mock(PcieCryptoService.class); MasterKeyInitProperties properties = configuredPins("MTIzNDU2Nzg=", "ODc2NTQzMjE="); Mockito.when(pcieCryptoService.initializeMasterKey(Mockito.any(), Mockito.any())) - .thenThrow(new IllegalStateException("master key initialization failed")); + .thenThrow(new IllegalStateException("主密钥初始化失败")); LmkService service = new LmkServiceImpl(pcieCryptoService, properties); IllegalStateException exception = Assertions.assertThrows(IllegalStateException.class, service::initMasterKey); - Assertions.assertEquals("master key initialization failed", exception.getMessage()); + Assertions.assertEquals("主密钥初始化失败", exception.getMessage()); } @Test diff --git a/src/test/java/com/cisd/tms/modules/upgrade/service/UpgradePackageServiceTest.java b/src/test/java/com/cisd/tms/modules/upgrade/service/UpgradePackageServiceTest.java index 86e39fd..69c6e8c 100644 --- a/src/test/java/com/cisd/tms/modules/upgrade/service/UpgradePackageServiceTest.java +++ b/src/test/java/com/cisd/tms/modules/upgrade/service/UpgradePackageServiceTest.java @@ -168,7 +168,7 @@ class UpgradePackageServiceTest { ); BizException exception = Assertions.assertThrows(BizException.class, () -> service.preview(fileRecord.getFileId())); - Assertions.assertEquals("upgrade execute script not found", exception.getMessage()); + Assertions.assertEquals("升级执行脚本不存在", exception.getMessage()); } @Test @@ -189,7 +189,7 @@ class UpgradePackageServiceTest { ); BizException exception = Assertions.assertThrows(BizException.class, () -> service.preview(fileRecord.getFileId())); - Assertions.assertEquals("upgrade package payload sm3 missing", exception.getMessage()); + Assertions.assertEquals("升级包payload SM3缺失", exception.getMessage()); } @Test diff --git a/src/test/java/com/cisd/tms/modules/upgrade/service/UpgradeServiceTest.java b/src/test/java/com/cisd/tms/modules/upgrade/service/UpgradeServiceTest.java index 0cf012b..cfa7589 100644 --- a/src/test/java/com/cisd/tms/modules/upgrade/service/UpgradeServiceTest.java +++ b/src/test/java/com/cisd/tms/modules/upgrade/service/UpgradeServiceTest.java @@ -254,7 +254,7 @@ class UpgradeServiceTest { BizException exception = Assertions.assertThrows(BizException.class, () -> service.rollbackTask("UPG-001")); - Assertions.assertEquals("upgrade rollback script not found", exception.getMessage()); + Assertions.assertEquals("升级回滚脚本不存在", exception.getMessage()); } private static UpgradeProperties properties() { diff --git a/src/test/java/com/cisd/tms/modules/upgrade/support/SoftUpgradePackageSignatureVerifierTest.java b/src/test/java/com/cisd/tms/modules/upgrade/support/SoftUpgradePackageSignatureVerifierTest.java index 4a9e31a..304c8cd 100644 --- a/src/test/java/com/cisd/tms/modules/upgrade/support/SoftUpgradePackageSignatureVerifierTest.java +++ b/src/test/java/com/cisd/tms/modules/upgrade/support/SoftUpgradePackageSignatureVerifierTest.java @@ -111,7 +111,7 @@ class SoftUpgradePackageSignatureVerifierTest { ); Assertions.assertEquals(ErrorCode.BIZ_ERROR.getCode(), exception.getCode()); - Assertions.assertEquals("upgrade package signature verification failed", exception.getMessage()); + Assertions.assertEquals("upgrade package signature verification 执行失败", exception.getMessage()); } private KeyPair generateSm2KeyPair() throws Exception { diff --git a/src/test/java/com/cisd/tms/security/internal/InternalApiAuthInterceptorTest.java b/src/test/java/com/cisd/tms/security/internal/InternalApiAuthInterceptorTest.java index 88a17ec..81a5594 100644 --- a/src/test/java/com/cisd/tms/security/internal/InternalApiAuthInterceptorTest.java +++ b/src/test/java/com/cisd/tms/security/internal/InternalApiAuthInterceptorTest.java @@ -37,7 +37,7 @@ class InternalApiAuthInterceptorTest { Assertions.assertFalse(allowed); Assertions.assertEquals(401, response.getStatus()); Assertions.assertTrue(response.getContentAsString().contains("\"code\":" + ErrorCode.SESSION_INVALID.getCode())); - Assertions.assertTrue(response.getContentAsString().contains("session token missing")); + Assertions.assertTrue(response.getContentAsString().contains("会话令牌缺失")); Mockito.verifyNoInteractions(authSessionRepository); } @@ -66,7 +66,7 @@ class InternalApiAuthInterceptorTest { Assertions.assertFalse(allowed); Assertions.assertEquals(401, response.getStatus()); Assertions.assertTrue(response.getContentAsString().contains("\"code\":" + ErrorCode.SESSION_INVALID.getCode())); - Assertions.assertTrue(response.getContentAsString().contains("session expired")); + Assertions.assertTrue(response.getContentAsString().contains("会话已过期")); } private static TmsSecurityProperties enabledSecurityProperties() { diff --git a/src/test/java/com/cisd/tms/security/openapi/OpenApiSignAuthInterceptorTest.java b/src/test/java/com/cisd/tms/security/openapi/OpenApiSignAuthInterceptorTest.java index 20fc944..9afe711 100644 --- a/src/test/java/com/cisd/tms/security/openapi/OpenApiSignAuthInterceptorTest.java +++ b/src/test/java/com/cisd/tms/security/openapi/OpenApiSignAuthInterceptorTest.java @@ -85,7 +85,7 @@ class OpenApiSignAuthInterceptorTest { Assertions.assertFalse(allowed); Assertions.assertEquals(401, response.getStatus()); - Assertions.assertTrue(response.getContentAsString().contains("signature invalid")); + Assertions.assertTrue(response.getContentAsString().contains("签名无效")); Mockito.verifyNoInteractions(replayProtectionService); }