package com.yau.digitalrmb.institutionidentity.application; import com.yau.digitalrmb.institutionidentity.domain.CurrencyGenerationRequest; import com.yau.digitalrmb.institutionidentity.domain.CurrencyGenerationRequestRepository; import com.yau.digitalrmb.institutionidentity.application.InstitutionKeyService; import com.yau.digitalrmb.institutionidentity.application.InstitutionKeySubject; import com.yau.digitalrmb.institutionidentity.application.IssuedInstitutionIdentifierQueryService; import com.yau.digitalrmb.institutionidentity.application.IssuedInstitutionIdentifierResult; 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.math.BigDecimal; @Service public class CurrencyGenerationRequestService { @Resource private CurrencyGenerationRequestRepository repository; @Resource private IssuedInstitutionIdentifierQueryService identifierQueryService; @Resource private InstitutionIdentityCryptography cryptography; @Resource private InstitutionKeyService keyService; @Resource private InstitutionTrainingScoreService scoreService; @Resource private InstitutionTrainingErrorRecorder errorRecorder; @Transactional public CurrencyDigestResult generateDigest(BigDecimal amount, String deliveryNodeCode, String institutionIdentifier, String requestTimestamp, InstitutionKeySubject subject, String operator) { IssuedInstitutionIdentifierResult identifier = identifierQueryService.requireLatestFeedbacked(subject); if (!identifier.getInstitutionIdentifier().equals(institutionIdentifier)) { throw new BusinessException(ErrorCode.VALIDATION_ERROR, "机构标识与步骤一反馈给当前实训主体的机构标识不一致"); } long identifierApplicationId = identifier.getApplicationId(); CurrencyGenerationRequest request = repository.findLatestBySubject(subject.getUserId(), subject.getSchoolId(), subject.getClassId()).orElse(null); CurrencyGenerationRequest input = invokeDomainWithResult(() -> CurrencyGenerationRequest.prepare( identifierApplicationId, institutionIdentifier, identifier.getFullInstitutionIdentifier(), amount, deliveryNodeCode, requestTimestamp)); if (request == null) { long requestId = repository.save(input, subject.getUserId(), subject.getSchoolId(), subject.getClassId(), operator); request = CurrencyGenerationRequest.restore(requestId, input.getIdentifierApplicationId(), input.getInstitutionIdentifier(), input.getFullInstitutionIdentifier(), input.getAmount(), input.getDeliveryNodeCode(), input.getRequestTimestamp(), input.getRequestOriginalText(), null, null, null, null, null, input.getStatus()); } String digest = cryptography.sm3(input.getRequestOriginalText()); CurrencyGenerationRequest.Status expected = request.getStatus(); CurrencyGenerationRequest current = request; invokeDomain(() -> current.replaceDigestInput(input.getIdentifierApplicationId(), input.getInstitutionIdentifier(), input.getFullInstitutionIdentifier(), input.getAmount(), input.getDeliveryNodeCode(), input.getRequestTimestamp(), digest)); repository.update(request, expected, "DIGEST", expected == CurrencyGenerationRequest.Status.DIGESTED ? "修改当前货币生成请求并重新计算SM3摘要" : "初始化请求后使用SM3计算并保存请求摘要", operator); return new CurrencyDigestResult(digest); } @Transactional public CurrencyRequestResult sign(String privateKey, InstitutionKeySubject subject, String operator) { CurrencyGenerationRequest request = requireCurrentIdentifierRequest(subject); CurrencyGenerationRequest.Status expected = request.getStatus(); String signature = keyService.signCommercialBank(subject, privateKey, requireDigest(request)); if (signature == null) { int errorSequence = errorRecorder.recordScoreError(subject.getUserId()); throw new BusinessException(ErrorCode.VALIDATION_ERROR, "签名必须使用当前实训主体的商业银行第二私钥,本次实训第" + errorSequence + "次错误"); } invokeDomain(() -> request.recordSignature(InstitutionKeyService.BANK_SECOND_KEY, signature, privateKey)); repository.update(request, expected, "SIGN", "使用商业银行第二私钥完成SM2签名", operator); return new CurrencyRequestResult(request); } @Transactional public CurrencyRequestResult packageRequest(InstitutionKeySubject subject, String operator) { CurrencyGenerationRequest request = requireCurrentIdentifierRequest(subject); return new CurrencyRequestResult(packageForSending(request, operator)); } @Transactional public CurrencyRequestResult send(InstitutionKeySubject subject, String operator) { CurrencyGenerationRequest request = requireCurrentIdentifierRequest(subject); CurrencyGenerationRequest.Status expected = request.getStatus(); invokeDomain(request::send); repository.update(request, expected, "SEND", "商业银行发送货币生成请求至央行", operator); expected = request.getStatus(); invokeDomain(request::receive); repository.update(request, expected, "RECEIVE", "央行成功接收货币生成请求", operator); scoreService.markStepCompleted(subject.getUserId(), 2); return new CurrencyRequestResult(request); } private CurrencyGenerationRequest packageForSending(CurrencyGenerationRequest request, String operator) { CurrencyGenerationRequest.Status expected = request.getStatus(); String message = buildRequestMessage(request); invokeDomain(() -> request.packageMessage(message)); repository.update(request, expected, "PACKAGE", "发送前由后端自动组装货币生成请求报文", operator); return request; } private static String buildRequestMessage(CurrencyGenerationRequest request) { return "{\"requestId\":\"" + displayRequestId(request) + "\",\"orgId\":\"" + request.getInstitutionIdentifier() + "\",\"amount\":\"" + request.getAmount().setScale(2).toPlainString() + "\",\"systemId\":\"" + request.getDeliveryNodeCode() + "\",\"timestamp\":\"" + request.getRequestTimestamp() + "\",\"digest\":\"" + request.getRequestDigest() + "\",\"signature\":\"" + request.getBankSignature() + "\"}"; } public static String displayRequestId(CurrencyGenerationRequest request) { String date = request.getRequestTimestamp().substring(0, 8); return "REQ_" + date + "_" + String.format("%03d", request.getId() % 1000); } @Transactional(readOnly = true) public CurrencyRequestResult page(InstitutionKeySubject subject) { return repository.findLatestBySubject(subject.getUserId(), subject.getSchoolId(), subject.getClassId()) .map(CurrencyRequestResult::new) .orElseGet(CurrencyRequestResult::new); } @Transactional(readOnly = true) public CurrencyGenerationRequest requireReceived(long requestId, InstitutionKeySubject subject) { CurrencyGenerationRequest request = requireRequest(requestId, subject); if (request.getStatus() != CurrencyGenerationRequest.Status.RECEIVED) { throw new BusinessException(ErrorCode.VALIDATION_ERROR, "步骤二尚未完成央行接收,不能进入步骤三"); } return request; } private CurrencyGenerationRequest requireRequest(long requestId, InstitutionKeySubject subject) { return repository.findByIdAndSubject(requestId, subject.getUserId(), subject.getSchoolId(), subject.getClassId()).orElseThrow(() -> new BusinessException(ErrorCode.RESOURCE_NOT_FOUND, "当前实训主体下的货币生成请求不存在或已删除")); } private CurrencyGenerationRequest requireCurrentIdentifierRequest(InstitutionKeySubject subject) { IssuedInstitutionIdentifierResult identifier = identifierQueryService.requireLatestFeedbacked(subject); CurrencyGenerationRequest request = requireLatestRequest(subject); if (!identifier.getInstitutionIdentifier().equals(request.getInstitutionIdentifier())) { throw new BusinessException(ErrorCode.RESOURCE_NOT_FOUND, "当前步骤一机构标识尚未创建步骤二货币生成请求"); } return request; } private CurrencyGenerationRequest requireLatestRequest(InstitutionKeySubject subject) { return repository.findLatestBySubject(subject.getUserId(), subject.getSchoolId(), subject.getClassId()) .orElseThrow(() -> new BusinessException(ErrorCode.RESOURCE_NOT_FOUND, "当前实训主体下没有可查询的货币生成请求")); } private static String requireOriginalText(CurrencyGenerationRequest request) { if (request.getRequestOriginalText() == null) { throw new BusinessException(ErrorCode.VALIDATION_ERROR, "请先完成请求原文拼接"); } return request.getRequestOriginalText(); } private static String requireDigest(CurrencyGenerationRequest request) { if (request.getRequestDigest() == null) { throw new BusinessException(ErrorCode.VALIDATION_ERROR, "请先完成SM3摘要计算"); } return request.getRequestDigest(); } private static void invokeDomain(Runnable operation) { try { operation.run(); } catch (IllegalArgumentException | IllegalStateException exception) { throw new BusinessException(ErrorCode.VALIDATION_ERROR, exception.getMessage()); } } private static T invokeDomainWithResult(DomainSupplier supplier) { try { return supplier.get(); } catch (IllegalArgumentException | IllegalStateException exception) { throw new BusinessException(ErrorCode.VALIDATION_ERROR, exception.getMessage()); } } private interface DomainSupplier { T get(); } }