You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
digital-rmb-backend/src/main/java/com/yau/digitalrmb/walletopening/application/CentralWalletActivationServ...

323 lines
17 KiB
Java

package com.yau.digitalrmb.walletopening.application;
import com.yau.digitalrmb.institutionidentity.application.InstitutionKeyService;
import com.yau.digitalrmb.institutionidentity.application.InstitutionKeySubject;
import com.yau.digitalrmb.institutionidentity.infrastructure.SmInstitutionIdentityCryptography;
import com.yau.digitalrmb.shared.api.ErrorCode;
import com.yau.digitalrmb.shared.exception.BusinessException;
import com.yau.digitalrmb.walletopening.domain.CentralWalletActivation;
import com.yau.digitalrmb.walletopening.domain.CentralWalletActivationRepository;
import com.yau.digitalrmb.walletopening.domain.CentralWalletRegistration;
import com.yau.digitalrmb.walletopening.domain.CentralWalletRegistrationRepository;
import com.yau.digitalrmb.walletopening.domain.SmartContractGeneration;
import com.yau.digitalrmb.walletopening.domain.SmartContractGenerationRepository;
import com.yau.digitalrmb.walletopening.domain.WalletApplication;
import com.yau.digitalrmb.walletopening.domain.WalletApplicationRepository;
import com.yau.digitalrmb.walletopening.domain.WalletIdentifierGeneration;
import com.yau.digitalrmb.walletopening.domain.WalletIdentifierGenerationRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
@Service
public class CentralWalletActivationService {
private static final DateTimeFormatter TIMESTAMP_FORMAT = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyyyMMdd");
@Resource
private CentralWalletActivationRepository activationRepository;
@Resource
private SmartContractGenerationRepository contractRepository;
@Resource
private WalletApplicationRepository applicationRepository;
@Resource
private WalletIdentifierGenerationRepository identifierRepository;
@Resource
private CentralWalletRegistrationRepository centralRegistrationRepository;
@Resource
private InstitutionKeyService institutionKeyService;
@Resource
private SmInstitutionIdentityCryptography cryptography;
@Resource
private WalletOpeningScoreService scoreService;
@Transactional(readOnly = true)
public CentralWalletActivationPageResult getPage(InstitutionKeySubject subject) {
CentralWalletActivation a = activationRepository
.findLatest(subject.getUserId(), subject.getSchoolId(), subject.getClassId()).orElse(null);
return new CentralWalletActivationPageResult(a != null ? a : CentralWalletActivation.create(
subject.getUserId(), subject.getSchoolId(), subject.getClassId()));
}
@Transactional
public CentralWalletActivationPageResult receive(InstitutionKeySubject subject) {
CentralWalletActivation a = getOrCreate(subject);
if (a.isReceived()) {
return new CentralWalletActivationPageResult(a);
}
SmartContractGeneration c = contractRepository
.findLatest(subject.getUserId(), subject.getSchoolId(), subject.getClassId()).orElse(null);
if (c == null || !c.isSent()) {
scoreService.recordError(subject.getUserId());
throw new BusinessException(ErrorCode.VALIDATION_ERROR, "尚未收到商业银行发送的开通确认报文");
}
String receiveTime = LocalDateTime.now().format(TIMESTAMP_FORMAT);
a.receive(c.getConfirmId(), c.getWalletId(), c.getOrgCode(), c.getContractId(),
c.getActivateDigest(), c.getActivateSignature(), c.getActivateTimestamp(), c.getConfirmJson());
activationRepository.update(a);
return new CentralWalletActivationPageResult(a);
}
@Transactional
public CentralWalletActivationPageResult verifySignature(InstitutionKeySubject subject, String publicKeyInput, String algorithm) {
if (!"SM2".equalsIgnoreCase(algorithm != null ? algorithm.trim() : "")) {
scoreService.recordError(subject.getUserId());
throw new BusinessException(ErrorCode.VALIDATION_ERROR, "算法选择有误,错误数+1");
}
CentralWalletActivation a = getOrCreate(subject);
if (!a.isReceived()) {
scoreService.recordError(subject.getUserId());
throw new BusinessException(ErrorCode.VALIDATION_ERROR, "请先接收开通确认报文");
}
String storedPublicKey = institutionKeyService.commercialBankSecondPublicKeyOrNull(subject);
if (storedPublicKey == null) {
scoreService.recordError(subject.getUserId());
throw new BusinessException(ErrorCode.VALIDATION_ERROR, "商业银行第二公钥不存在,请先在模块一生成机构密钥");
}
boolean keyMatches = storedPublicKey.equalsIgnoreCase(publicKeyInput.trim());
if (!keyMatches) {
a.verifySignature(publicKeyInput, false);
activationRepository.update(a);
scoreService.recordError(subject.getUserId());
throw new BusinessException(ErrorCode.VALIDATION_ERROR, "选择的公钥与商业银行第二公钥不匹配");
}
boolean verified = cryptography.verify(storedPublicKey, a.getDigest(), a.getSignature());
a.verifySignature(publicKeyInput.trim(), verified);
if (!verified) {
activationRepository.update(a);
scoreService.recordError(subject.getUserId());
throw new BusinessException(ErrorCode.VALIDATION_ERROR, "签名验证失败");
}
activationRepository.update(a);
return new CentralWalletActivationPageResult(a);
}
@Transactional
public CentralWalletActivationPageResult recalculateDigest(InstitutionKeySubject subject, String algorithm) {
if (!"SM3".equalsIgnoreCase(algorithm != null ? algorithm.trim() : "")) {
scoreService.recordError(subject.getUserId());
throw new BusinessException(ErrorCode.VALIDATION_ERROR, "算法选择有误,错误数+1");
}
CentralWalletActivation a = getOrCreate(subject);
if (a.getSignVerifyResult() == null || !a.getSignVerifyResult().startsWith("通过")) {
scoreService.recordError(subject.getUserId());
throw new BusinessException(ErrorCode.VALIDATION_ERROR, "请先完成签名验证");
}
String plaintext = "ACTIVATE|" + a.getWalletId() + "|" + a.getOrgCode() + "|"
+ a.getContractId() + "|" + a.getActivateTime();
String newDigest = cryptography.sm3(plaintext);
a.recalculateDigest(newDigest);
activationRepository.update(a);
return new CentralWalletActivationPageResult(a);
}
@Transactional
public CentralWalletActivationPageResult matchDigest(InstitutionKeySubject subject) {
CentralWalletActivation a = getOrCreate(subject);
if (a.getRecalculatedDigest() == null) {
scoreService.recordError(subject.getUserId());
throw new BusinessException(ErrorCode.VALIDATION_ERROR, "请先计算新摘要值H2");
}
boolean matches = a.getRecalculatedDigest().equalsIgnoreCase(a.getDigest());
a.matchDigest(matches);
if (!matches) {
activationRepository.update(a);
scoreService.recordError(subject.getUserId());
throw new BusinessException(ErrorCode.VALIDATION_ERROR, "摘要不匹配,报文可能被篡改");
}
activationRepository.update(a);
return new CentralWalletActivationPageResult(a);
}
@Transactional
public CentralWalletActivationPageResult activateWallet(InstitutionKeySubject subject) {
CentralWalletActivation a = getOrCreate(subject);
if (a.getDigestMatchResult() == null || !a.getDigestMatchResult().startsWith("通过")) {
scoreService.recordError(subject.getUserId());
throw new BusinessException(ErrorCode.VALIDATION_ERROR, "请先完成摘要匹配校验");
}
a.activateWallet();
activationRepository.update(a);
scoreService.markStepCompleted(subject.getUserId(), 7);
return new CentralWalletActivationPageResult(a);
}
@Transactional
public String verifyFinalSignature(InstitutionKeySubject subject, String publicKeyInput, String algorithm) {
if (!"SM2".equalsIgnoreCase(algorithm != null ? algorithm.trim() : "")) {
scoreService.recordError(subject.getUserId());
throw new BusinessException(ErrorCode.VALIDATION_ERROR, "算法选择有误,错误数+1");
}
CentralWalletActivation a = getOrCreate(subject);
if (!a.isReceived()) {
scoreService.recordError(subject.getUserId());
throw new BusinessException(ErrorCode.VALIDATION_ERROR, "请先完成步骤四开通确认报文发送");
}
if (a.getStep5SignVerifyResult() != null && a.getStep5SignVerifyResult().startsWith("通过")) {
return "通过";
}
String storedPublicKey = institutionKeyService.commercialBankSecondPublicKeyOrNull(subject);
if (storedPublicKey == null) {
scoreService.recordError(subject.getUserId());
throw new BusinessException(ErrorCode.VALIDATION_ERROR, "商业银行第二公钥不存在,请先在模块一生成机构密钥");
}
boolean keyMatches = storedPublicKey.equalsIgnoreCase(publicKeyInput.trim());
if (!keyMatches) {
a.verifyFinalSignature(publicKeyInput, false);
activationRepository.update(a);
scoreService.recordError(subject.getUserId());
throw new BusinessException(ErrorCode.VALIDATION_ERROR, "选择的公钥与商业银行第二公钥不匹配");
}
boolean verified = cryptography.verify(storedPublicKey, a.getDigest(), a.getSignature());
a.verifyFinalSignature(publicKeyInput.trim(), verified);
if (!verified) {
activationRepository.update(a);
scoreService.recordError(subject.getUserId());
throw new BusinessException(ErrorCode.VALIDATION_ERROR, "签名验证失败");
}
activationRepository.update(a);
return "通过";
}
@Transactional
public String finalSign(InstitutionKeySubject subject, String algorithm,
String digestValue, String privateKeyInput) {
if (!"SM2".equalsIgnoreCase(algorithm != null ? algorithm.trim() : "")) {
scoreService.recordError(subject.getUserId());
throw new BusinessException(ErrorCode.VALIDATION_ERROR, "算法选择有误,错误数+1");
}
CentralWalletActivation a = getOrCreate(subject);
if (a.getStep5SignVerifyResult() == null || !a.getStep5SignVerifyResult().startsWith("通过")) {
scoreService.recordError(subject.getUserId());
throw new BusinessException(ErrorCode.VALIDATION_ERROR, "请先完成SM2签名验证");
}
// 支持重复点击:按最新摘要重新确权签名
if (!a.getDigest().equalsIgnoreCase(digestValue != null ? digestValue.trim() : "")) {
scoreService.recordError(subject.getUserId());
throw new BusinessException(ErrorCode.VALIDATION_ERROR, "摘要值与开通确认报文不一致");
}
String storedPrivateKey = institutionKeyService.centralBankFirstPrivateKeyOrNull(subject);
if (storedPrivateKey == null) {
scoreService.recordError(subject.getUserId());
throw new BusinessException(ErrorCode.VALIDATION_ERROR, "央行第一私钥不存在,请先生成央行密钥对");
}
if (!storedPrivateKey.equalsIgnoreCase(privateKeyInput.trim())) {
scoreService.recordError(subject.getUserId());
throw new BusinessException(ErrorCode.VALIDATION_ERROR, "签名密钥错误,请使用正确的央行第一私钥");
}
String sig = cryptography.sign(storedPrivateKey, a.getDigest());
String cbFinalSignature = "CB_FINAL_WALLET_" + sig;
a.finalSign(cbFinalSignature);
activationRepository.update(a);
return cbFinalSignature;
}
@Transactional
public String updateWalletRegisterStatus(InstitutionKeySubject subject) {
CentralWalletActivation a = getOrCreate(subject);
if (a.getCbFinalSignature() == null) {
scoreService.recordError(subject.getUserId());
throw new BusinessException(ErrorCode.VALIDATION_ERROR, "请先完成央行最终确权签名");
}
// 支持重复点击:重复更新钱包登记状态
a.updateWalletRegisterStatus();
activationRepository.update(a);
return "成功";
}
@Transactional
public FinalConfirmOutputResult outputFinalConfirm(InstitutionKeySubject subject) {
CentralWalletActivation a = getOrCreate(subject);
if (a.getWalletRegisterStatus() == null) {
scoreService.recordError(subject.getUserId());
throw new BusinessException(ErrorCode.VALIDATION_ERROR, "请先更新钱包登记状态");
}
// 支持重复点击:按最新确权签名重新输出最终确认报文
String date = LocalDateTime.now().format(DATE_FORMAT);
String finalConfirmId = "FINAL_CONFIRM_" + date + "_001";
String finalTime = LocalDateTime.now().format(TIMESTAMP_FORMAT);
String json = buildFinalConfirmJson(finalConfirmId, a.getWalletId(),
a.getCbFinalSignature(), finalTime);
a.outputFinalConfirm(finalConfirmId, json, finalTime);
activationRepository.update(a);
return new FinalConfirmOutputResult(
finalConfirmId, a.getWalletId(), "AVAILABLE",
a.getCbFinalSignature(), finalTime);
}
@Transactional
public String sendFinal(InstitutionKeySubject subject) {
CentralWalletActivation a = getOrCreate(subject);
if (a.getFinalConfirmJson() == null) {
scoreService.recordError(subject.getUserId());
throw new BusinessException(ErrorCode.VALIDATION_ERROR, "请先输出最终确权通知");
}
a.sendFinal();
activationRepository.update(a);
scoreService.markStepCompleted(subject.getUserId(), 5);
return "成功";
}
@Transactional(readOnly = true)
public UserFinalResult getUserFinalResult(InstitutionKeySubject subject) {
CentralWalletActivation a = activationRepository
.findLatest(subject.getUserId(), subject.getSchoolId(), subject.getClassId()).orElse(null);
if (a == null || !a.isFinalSent()) {
return null;
}
WalletApplication app = applicationRepository
.findLatest(subject.getUserId(), subject.getSchoolId(), subject.getClassId()).orElse(null);
WalletIdentifierGeneration ident = identifierRepository
.findLatest(subject.getUserId(), subject.getSchoolId(), subject.getClassId()).orElse(null);
SmartContractGeneration contract = contractRepository
.findLatest(subject.getUserId(), subject.getSchoolId(), subject.getClassId()).orElse(null);
CentralWalletRegistration centralReg = centralRegistrationRepository
.findLatest(subject.getUserId(), subject.getSchoolId(), subject.getClassId()).orElse(null);
return UserFinalResult.build(a, app, ident, contract, centralReg);
}
private CentralWalletActivation getOrCreate(InstitutionKeySubject subject) {
CentralWalletActivation a = activationRepository
.findLatest(subject.getUserId(), subject.getSchoolId(), subject.getClassId())
.orElse(null);
if (a == null) {
a = activationRepository.save(
CentralWalletActivation.create(subject.getUserId(), subject.getSchoolId(), subject.getClassId()));
}
if (!a.isReceived()) {
SmartContractGeneration c = contractRepository
.findLatest(subject.getUserId(), subject.getSchoolId(), subject.getClassId()).orElse(null);
if (c != null && c.isSent()) {
a.receive(c.getConfirmId(), c.getWalletId(), c.getOrgCode(), c.getContractId(),
c.getActivateDigest(), c.getActivateSignature(), c.getActivateTimestamp(), c.getConfirmJson());
activationRepository.update(a);
}
}
return a;
}
private String buildFinalConfirmJson(String finalConfirmId, String walletId,
String cbFinalSignature, String finalTime) {
return "{\n"
+ " \"finalConfirmId\": \"" + finalConfirmId + "\",\n"
+ " \"walletId\": \"" + walletId + "\",\n"
+ " \"status\": \"AVAILABLE\",\n"
+ " \"cbFinalSignature\": \"" + cbFinalSignature + "\",\n"
+ " \"finalTime\": \"" + finalTime + "\"\n"
+ "}";
}
}