package com.yau.digitalrmb.institutionidentity.application; import com.yau.digitalrmb.institutionidentity.domain.InstitutionIdentifierApplication; import com.yau.digitalrmb.institutionidentity.domain.InstitutionIdentifierRepository; import com.yau.digitalrmb.institutionidentity.domain.InstitutionIdentityCryptography; import com.yau.digitalrmb.shared.api.ErrorCode; import com.yau.digitalrmb.shared.exception.BusinessException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; import java.time.Clock; import java.time.LocalDateTime; import java.time.ZoneOffset; import java.time.format.DateTimeFormatter; @Service public class InstitutionIdentifierStepService { private static final DateTimeFormatter TIMESTAMP = DateTimeFormatter.ofPattern("yyyyMMddHHmmss"); @Resource private InstitutionIdentityCryptography cryptography; @Resource private InstitutionKeyService keyService; @Resource private InstitutionIdentifierRepository repository; @Resource private InstitutionTrainingErrorRecorder errorRecorder; private final Clock clock = Clock.systemUTC(); @Transactional public InstitutionInformationPreparationResult prepare(String bankCode, String operator) { String normalized = normalize(bankCode); int trainingRound = repository.nextTrainingRound(normalized, operator); String timestamp = LocalDateTime.ofInstant(clock.instant(), ZoneOffset.UTC).format(TIMESTAMP); InstitutionIdentifierApplication application = InstitutionIdentifierApplication.prepare(normalized, timestamp); long applicationId = repository.save(application, trainingRound, operator); return new InstitutionInformationPreparationResult(applicationId, application.getStatus().name(), normalized, timestamp, normalized + "|" + timestamp, trainingRound, 0); } @Transactional public InstitutionDigestResult digest(long applicationId, String operator) { InstitutionIdentifierApplication application = requireApplication(applicationId); requireStatus(application, InstitutionIdentifierApplication.Status.PREPARED, "当前步骤不是摘要计算,不能操作", operator); InstitutionIdentifierApplication.Status expectedStatus = application.getStatus(); String text = application.getBankCode() + "|" + application.getTimestamp(); String digest = cryptography.sm3(text); invokeDomain(() -> application.recordDigest(digest)); repository.update(application, expectedStatus, "DIGEST", "使用SM3计算并保存业务摘要", operator); return new InstitutionDigestResult(applicationId, application.getStatus().name(), "SM3", text, digest); } @Transactional public InstitutionSignatureResult sign(long applicationId, String keyId, InstitutionKeySubject subject, String operator) { InstitutionIdentifierApplication application = requireApplication(applicationId); if (!InstitutionKeyService.BANK_SECOND_KEY.equals(keyId)) { recordAndThrow(application, "WRONG_KEY", "SIGN", keyId, "签名必须选择商业银行第二密钥", operator); } requireStatus(application, InstitutionIdentifierApplication.Status.DIGESTED, "当前步骤不是商业银行签名,不能操作", operator); InstitutionIdentifierApplication.Status expectedStatus = application.getStatus(); String signature = keyService.signCommercialBank(subject, application.getDigest()); invokeDomain(() -> application.recordBankSignature(signature)); repository.update(application, expectedStatus, "SIGN", "使用商业银行第二私钥完成SM2签名", operator); return new InstitutionSignatureResult(applicationId, application.getStatus().name(), "SM2", keyId, "COMMERCIAL_BANK_SECOND_PRIVATE_KEY", signature); } @Transactional public InstitutionApplicationPackageResult packageApplication(long applicationId, String operator) { InstitutionIdentifierApplication application = requireApplication(applicationId); requireStatus(application, InstitutionIdentifierApplication.Status.SIGNED, "当前步骤不是申请组装,不能操作", operator); InstitutionIdentifierApplication.Status expectedStatus = application.getStatus(); invokeDomain(application::packageApplication); repository.update(application, expectedStatus, "PACKAGE", "组装机构标识申请材料并提交央行待验证", operator); return new InstitutionApplicationPackageResult(applicationId, application.getStatus().name(), application.getBankCode(), application.getTimestamp(), application.getDigest(), application.getBankSignature()); } private InstitutionIdentifierApplication requireApplication(long applicationId) { return repository.findById(applicationId).orElseThrow(() -> new BusinessException(ErrorCode.RESOURCE_NOT_FOUND, "机构标识申请不存在")); } private void requireStatus(InstitutionIdentifierApplication application, InstitutionIdentifierApplication.Status expectedStatus, String message, String operator) { if (application.getStatus() != expectedStatus) { recordAndThrow(application, "INVALID_STEP", expectedStatus.name(), null, message + ",当前状态:" + application.getStatus().name(), operator); } } private void recordAndThrow(InstitutionIdentifierApplication application, String errorType, String operationStep, String providedKeyId, String message, String operator) { int errorSequence = errorRecorder.record(application.getId(), errorType, operationStep, providedKeyId, message, operator); invalid(message + ",本次实训第" + errorSequence + "次错误"); } private static void invokeDomain(Runnable operation) { try { operation.run(); } catch (IllegalArgumentException | IllegalStateException exception) { invalid(exception.getMessage()); } } private static String normalize(String value) { return value.trim().toUpperCase(); } private static void invalid(String message) { throw new BusinessException(ErrorCode.VALIDATION_ERROR, message); } }