diff --git a/src/main/java/com/yau/digitalrmb/corporatewallet/application/CorporateWalletKeyService.java b/src/main/java/com/yau/digitalrmb/corporatewallet/application/CorporateWalletKeyService.java index 59b85ab..bf12e0a 100644 --- a/src/main/java/com/yau/digitalrmb/corporatewallet/application/CorporateWalletKeyService.java +++ b/src/main/java/com/yau/digitalrmb/corporatewallet/application/CorporateWalletKeyService.java @@ -98,9 +98,9 @@ public class CorporateWalletKeyService { */ private String[] findOrCreateNewKeyPair(InstitutionKeySubject subject, String privateKeyType, String publicKeyType, String holder, String operator) { - CorporateWalletKeyInfoEntity existingPrivate = find(subject, privateKeyType); + CorporateWalletKeyInfoEntity existingPrivate = findByHolder(subject, privateKeyType, holder); if (existingPrivate != null) { - CorporateWalletKeyInfoEntity existingPublic = find(subject, publicKeyType); + CorporateWalletKeyInfoEntity existingPublic = findByHolder(subject, publicKeyType, holder); return new String[]{existingPrivate.getKeyValue(), existingPublic != null ? existingPublic.getKeyValue() : ""}; } @@ -121,6 +121,18 @@ public class CorporateWalletKeyService { .last("LIMIT 1")); } + private CorporateWalletKeyInfoEntity findByHolder(InstitutionKeySubject subject, String keyType, String holder) { + return keyInfoMapper.selectOne(new LambdaQueryWrapper() + .eq(CorporateWalletKeyInfoEntity::getUserId, subject.getUserId()) + .eq(CorporateWalletKeyInfoEntity::getSchoolId, subject.getSchoolId()) + .eq(CorporateWalletKeyInfoEntity::getClassId, subject.getClassId()) + .eq(CorporateWalletKeyInfoEntity::getKeyType, keyType) + .eq(CorporateWalletKeyInfoEntity::getHolder, holder) + .eq(CorporateWalletKeyInfoEntity::getDeleted, false) + .orderByDesc(CorporateWalletKeyInfoEntity::getCreatedAt) + .last("LIMIT 1")); + } + private void saveKeyInfo(InstitutionKeySubject subject, String keyType, String holder, String keyValue, boolean isPrivateKey, String source, String operator) { CorporateWalletKeyInfoEntity entity = new CorporateWalletKeyInfoEntity(); @@ -147,7 +159,7 @@ public class CorporateWalletKeyService { * 适用于:企业法人私钥、数币操作员私钥、数币复核员私钥、监管机构私钥等。 */ public String getOrCreatePrivateKey(InstitutionKeySubject subject, String keyType, String holder) { - CorporateWalletKeyInfoEntity existing = find(subject, keyType); + CorporateWalletKeyInfoEntity existing = findByHolder(subject, keyType, holder); if (existing != null) { return existing.getKeyValue(); } @@ -161,13 +173,13 @@ public class CorporateWalletKeyService { * 按需获取公钥:若数据库中已存在则直接返回,否则基于已有私钥生成公钥并存入。 */ public String getOrCreatePublicKey(InstitutionKeySubject subject, String keyType, String holder) { - CorporateWalletKeyInfoEntity existing = find(subject, keyType); + CorporateWalletKeyInfoEntity existing = findByHolder(subject, keyType, holder); if (existing != null) { return existing.getKeyValue(); } // 尝试从对应私钥推导 String privateKeyType = keyType.replace("公钥", "私钥"); - CorporateWalletKeyInfoEntity privateEntity = find(subject, privateKeyType); + CorporateWalletKeyInfoEntity privateEntity = findByHolder(subject, privateKeyType, holder); if (privateEntity != null) { String publicKey = cryptography.derivePublicKey(privateEntity.getKeyValue()); saveKeyInfo(subject, keyType, holder, publicKey, false, "NEW", holder); diff --git a/src/main/java/com/yau/digitalrmb/corporatewallet/application/RemoteWalletService.java b/src/main/java/com/yau/digitalrmb/corporatewallet/application/RemoteWalletService.java index c7f5887..028a53c 100644 --- a/src/main/java/com/yau/digitalrmb/corporatewallet/application/RemoteWalletService.java +++ b/src/main/java/com/yau/digitalrmb/corporatewallet/application/RemoteWalletService.java @@ -151,14 +151,15 @@ public class RemoteWalletService { } String trimmedPrivateKey = providedPrivateKey.trim(); - // 检查数据库中是否已存在企业法人私钥 - String legalPersonName = app.getLegalPerson() != null ? app.getLegalPerson() : "企业法人"; + // 检查数据库中是否已存在企业法人私钥(按远程开立法人姓名过滤,避免取到张三等其他法人的私钥) + String legalPersonName = app.getLegalPerson() != null ? app.getLegalPerson() : "李四"; CorporateWalletKeyInfoEntity existingKey = keyInfoMapper.selectOne( new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() .eq(CorporateWalletKeyInfoEntity::getUserId, subject.getUserId()) .eq(CorporateWalletKeyInfoEntity::getSchoolId, subject.getSchoolId()) .eq(CorporateWalletKeyInfoEntity::getClassId, subject.getClassId()) .eq(CorporateWalletKeyInfoEntity::getKeyType, LEGAL_PERSON_KEY_TYPE) + .eq(CorporateWalletKeyInfoEntity::getHolder, legalPersonName) .eq(CorporateWalletKeyInfoEntity::getIsPrivateKey, true) .eq(CorporateWalletKeyInfoEntity::getDeleted, false) .orderByDesc(CorporateWalletKeyInfoEntity::getCreatedAt) @@ -362,13 +363,66 @@ public class RemoteWalletService { results.add(buildKeyResult("商业银行第二公钥", "中国银行", bankKey.getPublicKey(), false, "REUSED")); - // 3. 企业法人密钥(按需生成) + // 3. 企业法人密钥 + // 原型要求固定持有方名册:临柜法人(如张三)与远程法人(如李四)都必须有"私钥+公钥"一对, + // 缺失的按需生成后加入列表;同一法人只输出最新一对。 RemoteWalletApplication app = findOrCreate(subject); - String legalPersonName = app.getLegalPerson() != null ? app.getLegalPerson() : "企业法人"; - String[] legalPersonPair = getOrCreateKeyPair(subject, "企业法人私钥", "企业法人公钥", - legalPersonName); - results.add(buildKeyResult("企业法人私钥", legalPersonName, legalPersonPair[0], true, "NEW")); - results.add(buildKeyResult("企业法人公钥", legalPersonName, legalPersonPair[1], false, "NEW")); + String remoteLegalPerson = app.getLegalPerson() != null ? app.getLegalPerson() : "李四"; + com.yau.digitalrmb.corporatewallet.domain.CorporateWalletApplication counterApp = corporateRepository + .findLatest(subject.getUserId(), subject.getSchoolId(), subject.getClassId()) + .orElse(null); + String counterLegalPerson = counterApp != null && counterApp.getLegalPerson() != null + ? counterApp.getLegalPerson() : "张三"; + String counterCorpName = counterApp != null && counterApp.getCorpName() != null + ? counterApp.getCorpName() : "创新科技公司"; + + // 3a. 查出已有企业法人私钥,按法人姓名(剥离企业名后缀)去重,保留每人最新一条 + java.util.List existingLegalPersonKeys = keyInfoMapper.selectList( + new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() + .eq(CorporateWalletKeyInfoEntity::getUserId, subject.getUserId()) + .eq(CorporateWalletKeyInfoEntity::getSchoolId, subject.getSchoolId()) + .eq(CorporateWalletKeyInfoEntity::getClassId, subject.getClassId()) + .eq(CorporateWalletKeyInfoEntity::getKeyType, "企业法人私钥") + .eq(CorporateWalletKeyInfoEntity::getDeleted, false) + .orderByDesc(CorporateWalletKeyInfoEntity::getCreatedAt)); + java.util.Map latestKeyByPerson = new java.util.LinkedHashMap<>(); + for (CorporateWalletKeyInfoEntity existing : existingLegalPersonKeys) { + latestKeyByPerson.putIfAbsent(personName(existing.getHolder()), existing); + } + + // 3b. 临柜法人(如张三(创新科技公司)):已有则复用(公钥缺失则推导补齐),没有则新生成一对 + String counterPrivate; + String counterPublic; + CorporateWalletKeyInfoEntity counterExisting = latestKeyByPerson.get(counterLegalPerson); + if (counterExisting != null) { + counterPrivate = counterExisting.getKeyValue(); + counterPublic = findOrDerivePublicKey(subject, counterExisting); + } else { + String[] pair = getOrCreateKeyPair(subject, "企业法人私钥", "企业法人公钥", counterLegalPerson); + counterPrivate = pair[0]; + counterPublic = pair[1]; + } + results.add(buildKeyResult("企业法人私钥", displayHolder(counterLegalPerson, counterCorpName), + counterPrivate, true, counterExisting != null ? "REUSED" : "NEW")); + results.add(buildKeyResult("企业法人公钥", displayHolder(counterLegalPerson, counterCorpName), + counterPublic, false, counterExisting != null ? "REUSED" : "NEW")); + + // 3c. 远程法人(如李四(企业名称)):已有则复用,没有则新生成一对;与临柜法人同名时只输出一对 + String remotePrivate; + String remotePublic; + CorporateWalletKeyInfoEntity remoteExisting = latestKeyByPerson.get(remoteLegalPerson); + if (remoteLegalPerson.equals(counterLegalPerson)) { + remotePrivate = counterPrivate; + remotePublic = counterPublic; + } else if (remoteExisting != null) { + remotePrivate = remoteExisting.getKeyValue(); + remotePublic = findOrDerivePublicKey(subject, remoteExisting); + } else { + String[] pair = getOrCreateKeyPair(subject, "企业法人私钥", "企业法人公钥", remoteLegalPerson); + remotePrivate = pair[0]; + remotePublic = pair[1]; + } + String remoteDisplayHolder = displayHolder(remoteLegalPerson, app.getCorpName()); // 4. 数币操作员密钥(按需生成) String[] operatorPair = getOrCreateKeyPair(subject, "数币操作员私钥", "数币操作员公钥", @@ -382,6 +436,12 @@ public class RemoteWalletService { results.add(buildKeyResult("数币复核员私钥", "赵六", reviewerPair[0], true, "NEW")); results.add(buildKeyResult("数币复核员公钥", "赵六", reviewerPair[1], false, "NEW")); + // 5b. 远程法人密钥对按原型顺序插入:位于数币复核员之后、监管机构之前 + results.add(buildKeyResult("企业法人私钥", remoteDisplayHolder, + remotePrivate, true, remoteExisting != null ? "REUSED" : "NEW")); + results.add(buildKeyResult("企业法人公钥", remoteDisplayHolder, + remotePublic, false, remoteExisting != null ? "REUSED" : "NEW")); + // 6. 监管机构密钥(按需生成) String[] regulatorPair = getOrCreateKeyPair(subject, "监管机构私钥", "监管机构公钥", "民政部公益监管中心"); @@ -402,11 +462,58 @@ public class RemoteWalletService { return new String[]{privateKey, publicKey}; } + /** + * 读取指定法人已有私钥对应的公钥:库中存在则返回;缺失时从私钥推导并落库,保证公钥私钥成对。 + */ + private String findOrDerivePublicKey(InstitutionKeySubject subject, CorporateWalletKeyInfoEntity privateKeyEntity) { + CorporateWalletKeyInfoEntity existingPub = keyInfoMapper.selectOne( + new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() + .eq(CorporateWalletKeyInfoEntity::getUserId, subject.getUserId()) + .eq(CorporateWalletKeyInfoEntity::getSchoolId, subject.getSchoolId()) + .eq(CorporateWalletKeyInfoEntity::getClassId, subject.getClassId()) + .eq(CorporateWalletKeyInfoEntity::getKeyType, "企业法人公钥") + .eq(CorporateWalletKeyInfoEntity::getHolder, privateKeyEntity.getHolder()) + .eq(CorporateWalletKeyInfoEntity::getDeleted, false) + .orderByDesc(CorporateWalletKeyInfoEntity::getCreatedAt) + .last("LIMIT 1")); + if (existingPub != null && existingPub.getKeyValue() != null && !existingPub.getKeyValue().trim().isEmpty()) { + return existingPub.getKeyValue(); + } + String derived = cryptography.derivePublicKey(privateKeyEntity.getKeyValue()); + saveKeyInfo(subject, "企业法人公钥", privateKeyEntity.getHolder(), derived, false, "NEW"); + return derived; + } + private RemoteWalletKeyResult buildKeyResult(String keyType, String holder, String keyValue, boolean isPrivateKey, String source) { return new RemoteWalletKeyResult(keyType, holder, "SM2", keyValue, isPrivateKey, source); } + /** + * 从持有方名称中提取法人姓名:兼容“张三”“张三(创新科技公司)”两种存储格式。 + */ + private String personName(String holder) { + if (holder == null || holder.trim().isEmpty()) { + return "企业法人"; + } + String trimmed = holder.trim(); + int idx = trimmed.indexOf('('); + if (idx < 0) { + idx = trimmed.indexOf('('); + } + return idx > 0 ? trimmed.substring(0, idx).trim() : trimmed; + } + + /** + * 持有方展示名:法人姓名(企业名称),如“张三(创新科技公司)”;无企业名时仅返回姓名。 + */ + private String displayHolder(String person, String corpName) { + if (corpName == null || corpName.trim().isEmpty()) { + return person; + } + return person + "(" + corpName.trim() + ")"; + } + /** * 校验远程开立和临柜开立的企业信息一致性 * @param remoteApp 远程开立申请 diff --git a/src/main/java/com/yau/digitalrmb/walletopening/application/CentralWalletActivationService.java b/src/main/java/com/yau/digitalrmb/walletopening/application/CentralWalletActivationService.java index 14d1df4..5d42843 100644 --- a/src/main/java/com/yau/digitalrmb/walletopening/application/CentralWalletActivationService.java +++ b/src/main/java/com/yau/digitalrmb/walletopening/application/CentralWalletActivationService.java @@ -204,9 +204,7 @@ public class CentralWalletActivationService { scoreService.recordError(subject.getUserId()); throw new BusinessException(ErrorCode.VALIDATION_ERROR, "请先完成SM2签名验证"); } - if (a.getCbFinalSignature() != null) { - return a.getCbFinalSignature(); - } + // 支持重复点击:按最新摘要重新确权签名 if (!a.getDigest().equalsIgnoreCase(digestValue != null ? digestValue.trim() : "")) { scoreService.recordError(subject.getUserId()); throw new BusinessException(ErrorCode.VALIDATION_ERROR, "摘要值与开通确认报文不一致"); @@ -234,9 +232,7 @@ public class CentralWalletActivationService { scoreService.recordError(subject.getUserId()); throw new BusinessException(ErrorCode.VALIDATION_ERROR, "请先完成央行最终确权签名"); } - if (a.getWalletRegisterStatus() != null) { - return "成功"; - } + // 支持重复点击:重复更新钱包登记状态 a.updateWalletRegisterStatus(); activationRepository.update(a); return "成功"; @@ -249,11 +245,7 @@ public class CentralWalletActivationService { scoreService.recordError(subject.getUserId()); throw new BusinessException(ErrorCode.VALIDATION_ERROR, "请先更新钱包登记状态"); } - if (a.getFinalConfirmJson() != null) { - return new FinalConfirmOutputResult( - a.getFinalConfirmId(), a.getWalletId(), "AVAILABLE", - a.getCbFinalSignature(), a.getFinalTime()); - } + // 支持重复点击:按最新确权签名重新输出最终确认报文 String date = LocalDateTime.now().format(DATE_FORMAT); String finalConfirmId = "FINAL_CONFIRM_" + date + "_001"; String finalTime = LocalDateTime.now().format(TIMESTAMP_FORMAT); diff --git a/src/main/java/com/yau/digitalrmb/walletopening/application/CentralWalletRegistrationService.java b/src/main/java/com/yau/digitalrmb/walletopening/application/CentralWalletRegistrationService.java index 824f84c..0c051f0 100644 --- a/src/main/java/com/yau/digitalrmb/walletopening/application/CentralWalletRegistrationService.java +++ b/src/main/java/com/yau/digitalrmb/walletopening/application/CentralWalletRegistrationService.java @@ -151,9 +151,7 @@ public class CentralWalletRegistrationService { scoreService.recordError(subject.getUserId()); throw new BusinessException(ErrorCode.VALIDATION_ERROR, "请先完成摘要完整性校验"); } - if (c.getCbRootSignature() != null) { - return c.getCbRootSignature(); - } + // 支持重复点击:按最新摘要重新根签 String storedPrivateKey = institutionKeyService.centralBankFirstPrivateKeyOrNull(subject); if (storedPrivateKey == null) { scoreService.recordError(subject.getUserId()); @@ -185,9 +183,7 @@ public class CentralWalletRegistrationService { @Transactional public CentralWalletConfirmResult outputConfirm(InstitutionKeySubject subject) { CentralWalletRegistration c = getOrCreate(subject); - if (c.getConfirmJson() != null) { - return buildConfirmResult(c); - } + // 支持重复点击:按最新根签重新输出备案确认报文 if (!c.isWalletRegistered()) { scoreService.recordError(subject.getUserId()); throw new BusinessException(ErrorCode.VALIDATION_ERROR, "请先更新钱包登记备案"); diff --git a/src/main/java/com/yau/digitalrmb/walletopening/application/CommercialBankWalletOpeningService.java b/src/main/java/com/yau/digitalrmb/walletopening/application/CommercialBankWalletOpeningService.java index 3b1122d..c2f990d 100644 --- a/src/main/java/com/yau/digitalrmb/walletopening/application/CommercialBankWalletOpeningService.java +++ b/src/main/java/com/yau/digitalrmb/walletopening/application/CommercialBankWalletOpeningService.java @@ -83,9 +83,7 @@ public class CommercialBankWalletOpeningService { .orElseGet(() -> walletVerificationRepository.save( WalletVerification.create(subject.getUserId(), subject.getSchoolId(), subject.getClassId()))); - if (verification.getStatus() != null && verification.getStatus().ordinal() >= WalletVerification.Status.VERIFIED.ordinal()) { - throw new BusinessException(ErrorCode.VALIDATION_ERROR, "验证已完成,请勿重复操作"); - } + // 支持重复点击:重复验证时按最新申请数据重新生成验证结果并覆盖 LocalDateTime now = LocalDateTime.now(); String idTime = formatTime(now, 0); @@ -107,9 +105,7 @@ public class CommercialBankWalletOpeningService { if (verification.getStatus() == null || verification.getStatus().ordinal() < WalletVerification.Status.VERIFIED.ordinal()) { throw new BusinessException(ErrorCode.VALIDATION_ERROR, "请先完成用户信息验证"); } - if (verification.getStatus().ordinal() >= WalletVerification.Status.RECORD_OUTPUT.ordinal()) { - return new WalletVerificationPageResult(verification); - } + // 支持重复点击:重新生成验证记录编号与记录内容 String verifyTime = LocalDateTime.now().format(TIMESTAMP_FORMAT); String verificationId = "VERIFY_" + verifyTime; @@ -126,9 +122,7 @@ public class CommercialBankWalletOpeningService { if (verification.getStatus() == null || verification.getStatus().ordinal() < WalletVerification.Status.RECORD_OUTPUT.ordinal()) { throw new BusinessException(ErrorCode.VALIDATION_ERROR, "请先完成验证记录输出"); } - if (verification.getStatus() == WalletVerification.Status.COLLECTED) { - return new WalletVerificationPageResult(verification); - } + // 支持重复点击:重新生成归集确认内容 Optional walletApp = walletApplicationRepository .findLatest(subject.getUserId(), subject.getSchoolId(), subject.getClassId()); diff --git a/src/main/java/com/yau/digitalrmb/walletopening/application/SmartContractService.java b/src/main/java/com/yau/digitalrmb/walletopening/application/SmartContractService.java index 4288f26..588b0d4 100644 --- a/src/main/java/com/yau/digitalrmb/walletopening/application/SmartContractService.java +++ b/src/main/java/com/yau/digitalrmb/walletopening/application/SmartContractService.java @@ -63,9 +63,7 @@ public class SmartContractService { throw new BusinessException(ErrorCode.VALIDATION_ERROR, "算法选择有误,错误数+1"); } SmartContractGeneration c = getOrCreate(subject); - if (c.getContractDigest() != null) { - return c.getContractDigest(); - } + // 支持重复点击:按最新合约参数重新计算摘要 verifyStep3RegistrationConfirmed(subject); String storedWalletId = resolveWalletId(subject); if (!storedWalletId.equalsIgnoreCase(request.getWalletId().trim())) { @@ -90,15 +88,7 @@ public class SmartContractService { @Transactional public ContractOutputResult outputContract(InstitutionKeySubject subject) { SmartContractGeneration c = getOrCreate(subject); - if (c.getContractJson() != null) { - ContractLimits limits = new ContractLimits( - c.getSinglePaymentLimit(), c.getDailyPaymentLimit(), - c.getAnnualPaymentLimit(), c.getBalanceCeiling()); - return new ContractOutputResult( - c.getContractId(), c.getWalletId(), c.getWalletType(), - limits, c.getValidity(), c.getContractDigest(), - "已生效", c.getContractEffectiveTime()); - } + // 支持重复点击:按最新摘要重新输出合约 if (c.getContractDigest() == null) { scoreService.recordError(subject.getUserId()); throw new BusinessException(ErrorCode.VALIDATION_ERROR, "请先生成合约摘要"); @@ -121,10 +111,7 @@ public class SmartContractService { @Transactional public ActivateTimestampResult acquireActivateTimestamp(InstitutionKeySubject subject) { SmartContractGeneration c = getOrCreate(subject); - if (c.getStatus() != null && c.getStatus().ordinal() >= SmartContractGeneration.Status.ACTIVATE_TIMESTAMP_ACQUIRED.ordinal()) { - return new ActivateTimestampResult( - c.getActivateTimestamp(), c.getWalletId(), c.getOrgCode(), c.getContractId()); - } + // 支持重复点击:重新读取当前时间戳 if (c.getContractId() == null) { scoreService.recordError(subject.getUserId()); throw new BusinessException(ErrorCode.VALIDATION_ERROR, "请先生成并输出智能合约"); @@ -143,9 +130,7 @@ public class SmartContractService { throw new BusinessException(ErrorCode.VALIDATION_ERROR, "算法选择有误,错误数+1"); } SmartContractGeneration c = getOrCreate(subject); - if (c.getActivateDigest() != null) { - return c.getActivateDigest(); - } + // 支持重复点击:按最新明文重新计算确认摘要 if (c.getContractId() == null) { scoreService.recordError(subject.getUserId()); throw new BusinessException(ErrorCode.VALIDATION_ERROR, "请先生成并输出智能合约"); @@ -168,9 +153,7 @@ public class SmartContractService { throw new BusinessException(ErrorCode.VALIDATION_ERROR, "算法选择有误,错误数+1"); } SmartContractGeneration c = getOrCreate(subject); - if (c.getActivateSignature() != null) { - return c.getActivateSignature(); - } + // 支持重复点击:按最新摘要重新签名 if (c.getActivateDigest() == null) { scoreService.recordError(subject.getUserId()); throw new BusinessException(ErrorCode.VALIDATION_ERROR, "请先生成确认摘要"); @@ -194,11 +177,7 @@ public class SmartContractService { @Transactional public ActivateConfirmOutputResult outputConfirm(InstitutionKeySubject subject) { SmartContractGeneration c = getOrCreate(subject); - if (c.getConfirmJson() != null) { - return new ActivateConfirmOutputResult( - c.getConfirmId(), c.getWalletId(), c.getOrgCode(), c.getContractId(), - c.getActivateDigest(), c.getActivateSignature(), c.getActivateTimestamp(), "ACTIVATED"); - } + // 支持重复点击:按最新签名重新输出开通确认报文 if (c.getActivateSignature() == null) { scoreService.recordError(subject.getUserId()); throw new BusinessException(ErrorCode.VALIDATION_ERROR, "请先完成SM2签名"); @@ -216,9 +195,7 @@ public class SmartContractService { @Transactional public String send(InstitutionKeySubject subject) { SmartContractGeneration c = getOrCreate(subject); - if (c.isSent()) { - return "成功"; - } + // 支持重复点击:重复发送时刷新发送状态 if (c.getConfirmJson() == null) { scoreService.recordError(subject.getUserId()); throw new BusinessException(ErrorCode.VALIDATION_ERROR, "请先输出开通确认报文"); diff --git a/src/main/java/com/yau/digitalrmb/walletopening/application/WalletIdentifierService.java b/src/main/java/com/yau/digitalrmb/walletopening/application/WalletIdentifierService.java index 91f3cd8..937510f 100644 --- a/src/main/java/com/yau/digitalrmb/walletopening/application/WalletIdentifierService.java +++ b/src/main/java/com/yau/digitalrmb/walletopening/application/WalletIdentifierService.java @@ -55,9 +55,7 @@ public class WalletIdentifierService { @Transactional public WalletTimestampResult acquireTimestamp(InstitutionKeySubject subject) { WalletIdentifierGeneration generation = getOrCreate(subject); - if (generation.getStatus() != null && generation.getStatus().ordinal() >= WalletIdentifierGeneration.Status.TIMESTAMP_ACQUIRED.ordinal()) { - return new WalletTimestampResult(generation.getCurrentTimestamp()); - } + // 支持重复点击:每次点击重新获取当前时间戳并覆盖 verifyStep2Completed(subject); String timestamp = LocalDateTime.now().format(TIMESTAMP_FORMAT); generation.acquireTimestamp(timestamp); @@ -68,9 +66,7 @@ public class WalletIdentifierService { @Transactional public WalletIdentifierPageResult concatenate(InstitutionKeySubject subject) { WalletIdentifierGeneration generation = getOrCreate(subject); - if (generation.getStatus() != null && generation.getStatus().ordinal() >= WalletIdentifierGeneration.Status.CONCATENATED.ordinal()) { - return new WalletIdentifierPageResult(generation); - } + // 支持重复点击:按最新时间戳重新拼接原文 if (generation.getCurrentTimestamp() == null) { scoreService.recordError(subject.getUserId()); throw new BusinessException(ErrorCode.VALIDATION_ERROR, "请先获取当前时间戳"); @@ -87,9 +83,7 @@ public class WalletIdentifierService { throw new BusinessException(ErrorCode.VALIDATION_ERROR, "算法选择有误,错误数+1"); } WalletIdentifierGeneration generation = getOrCreate(subject); - if (generation.getDigestValue() != null) { - return new WalletDigestResult(generation.getHashAlgorithm(), generation.getDigestValue()); - } + // 支持重复点击:按最新原文重新计算摘要 if (generation.getConcatenatedMessage() == null) { scoreService.recordError(subject.getUserId()); throw new BusinessException(ErrorCode.VALIDATION_ERROR, "请先完成原文拼接"); @@ -103,9 +97,7 @@ public class WalletIdentifierService { @Transactional public WalletIdentifierResult outputIdentifier(InstitutionKeySubject subject) { WalletIdentifierGeneration generation = getOrCreate(subject); - if (generation.getWalletIdentifier() != null) { - return new WalletIdentifierResult(generation.getWalletIdentifier()); - } + // 支持重复点击:按最新摘要重新生成钱包标识 if (generation.getDigestValue() == null) { scoreService.recordError(subject.getUserId()); throw new BusinessException(ErrorCode.VALIDATION_ERROR, "请先完成SM3哈希运算"); @@ -120,9 +112,7 @@ public class WalletIdentifierService { @Transactional public WalletCertKeyResult generateCertKeyPair(InstitutionKeySubject subject, String walletIdInput) { WalletIdentifierGeneration generation = getOrCreate(subject); - if (generation.getCertPrivateKey() != null && generation.getCertPublicKey() != null) { - return new WalletCertKeyResult(generation.getCertPrivateKey(), generation.getCertPublicKey()); - } + // 支持重复点击:重新生成证书密钥对并覆盖 if (generation.getWalletIdentifier() == null) { scoreService.recordError(subject.getUserId()); throw new BusinessException(ErrorCode.VALIDATION_ERROR, "请先完成钱包标识输出"); @@ -141,9 +131,7 @@ public class WalletIdentifierService { @Transactional public WalletCertSerialResult generateSerialNumber(InstitutionKeySubject subject) { WalletIdentifierGeneration generation = getOrCreate(subject); - if (generation.getCertSerialNumber() != null) { - return new WalletCertSerialResult(generation.getCertSerialNumber()); - } + // 支持重复点击:按最新公钥重新计算证书序列号 if (generation.getCertPublicKey() == null) { scoreService.recordError(subject.getUserId()); throw new BusinessException(ErrorCode.VALIDATION_ERROR, "请先输出证书公私钥对"); @@ -157,9 +145,7 @@ public class WalletIdentifierService { @Transactional public WalletCertificateResult issueCertificate(InstitutionKeySubject subject) { WalletIdentifierGeneration generation = getOrCreate(subject); - if (generation.getCertIssued() != null) { - return buildCertificateResult(generation); - } + // 支持重复点击:按最新序列号重新签发证书 if (generation.getCertSerialNumber() == null) { scoreService.recordError(subject.getUserId()); throw new BusinessException(ErrorCode.VALIDATION_ERROR, "请先生成证书序列号"); diff --git a/src/main/java/com/yau/digitalrmb/walletopening/application/WalletOpeningService.java b/src/main/java/com/yau/digitalrmb/walletopening/application/WalletOpeningService.java index c825f1e..60ba49c 100644 --- a/src/main/java/com/yau/digitalrmb/walletopening/application/WalletOpeningService.java +++ b/src/main/java/com/yau/digitalrmb/walletopening/application/WalletOpeningService.java @@ -24,12 +24,19 @@ import java.time.format.DateTimeFormatter; public class WalletOpeningService { private static final DateTimeFormatter TIMESTAMP_FORMAT = DateTimeFormatter.ofPattern("yyyyMMddHHmmss"); + /** 原型“用户信息”面板中的固定身份信息,输出申请报文时必须与之一致 */ + private static final String FIXED_PHONE = "13800138000"; + private static final String FIXED_ID_NUMBER = "110101199001011234"; + private static final String FIXED_BANK_CARD_NUMBER = "6216610100001234567"; + @Resource private WalletApplicationRepository walletRepository; @Resource private BankReceivedApplicationRepository bankReceivedRepository; @Resource private InstitutionIdentifierApplicationMapper institutionIdentifierMapper; + @Resource + private WalletOpeningScoreService scoreService; @Transactional(readOnly = true) public UserWalletPageResult getPage(InstitutionKeySubject subject, JwtUser jwtUser) { @@ -70,9 +77,7 @@ public class WalletOpeningService { return walletRepository.save(newApp); }); - if (app.getStatus() != null && app.getStatus().ordinal() >= WalletApplication.Status.OUTPUT.ordinal()) { - throw new BusinessException(ErrorCode.VALIDATION_ERROR, "申请报文已输出,请勿重复操作"); - } + // 支持重复点击:已输出过则按最新表单内容重新生成报文并覆盖,不再拦截 if (app.getFaceRecognitionStatus() == null || app.getFaceRecognitionStatus() != 2) { throw new BusinessException(ErrorCode.VALIDATION_ERROR, "请先完成人脸识别"); } @@ -80,6 +85,9 @@ public class WalletOpeningService { throw new BusinessException(ErrorCode.VALIDATION_ERROR, "请选择数字钱包类型"); } + // 校验手机号、身份证号、银行卡号必须与原型“用户信息”一致,不一致则错误数+1 + validateFixedUserInfo(subject, request); + BigDecimal accountBalance = request.getAccountBalance(); if (accountBalance == null || accountBalance.compareTo(BigDecimal.ZERO) <= 0) { accountBalance = new BigDecimal("50000.00"); @@ -105,9 +113,7 @@ public class WalletOpeningService { if (app.getStatus() == null || app.getStatus().ordinal() < WalletApplication.Status.OUTPUT.ordinal()) { throw new BusinessException(ErrorCode.VALIDATION_ERROR, "请先输出申请报文"); } - if (app.getStatus() == WalletApplication.Status.SUBMITTED) { - throw new BusinessException(ErrorCode.VALIDATION_ERROR, "申请已发送,请勿重复操作"); - } + // 支持重复点击:重复发送只刷新发送状态与商业银行接收记录内容 app.submit(); walletRepository.update(app); @@ -119,16 +125,74 @@ public class WalletOpeningService { String effectivePhone = app.getInputPhone() != null ? app.getInputPhone() : app.getPhone(); String effectiveBankCardNumber = app.getInputBankCardNumber() != null ? app.getInputBankCardNumber() : app.getBankCardNumber(); - BankReceivedApplication received = BankReceivedApplication.receive( - subject.getUserId(), subject.getSchoolId(), subject.getClassId(), - receiveId, app.getApplicationId(), orgCode, receiveTime, - app.getUserName(), effectiveIdNumber, effectivePhone, - effectiveBankCardNumber, app.getWalletType(), app.getApplyTime()); - bankReceivedRepository.save(received); + java.util.Optional existing = bankReceivedRepository + .findLatest(subject.getUserId(), subject.getSchoolId(), subject.getClassId()); + if (existing.isPresent()) { + BankReceivedApplication received = existing.get(); + received.setReceiveId(receiveId); + received.setApplicationId(app.getApplicationId()); + received.setOrgCode(orgCode); + received.setReceiveTime(receiveTime); + received.setUserName(app.getUserName()); + received.setIdNumber(effectiveIdNumber); + received.setPhone(effectivePhone); + received.setBankCardNumber(effectiveBankCardNumber); + received.setWalletType(app.getWalletType()); + received.setApplyTime(app.getApplyTime()); + bankReceivedRepository.update(received); + } else { + BankReceivedApplication received = BankReceivedApplication.receive( + subject.getUserId(), subject.getSchoolId(), subject.getClassId(), + receiveId, app.getApplicationId(), orgCode, receiveTime, + app.getUserName(), effectiveIdNumber, effectivePhone, + effectiveBankCardNumber, app.getWalletType(), app.getApplyTime()); + bankReceivedRepository.save(received); + } return new WalletSendResult("已提交"); } + /** + * 校验手机号、身份证号、银行卡号必须与原型“用户信息”面板一致。 + * 任一字段不一致:错误数+1(独立事务记录),并拒绝本次输出。 + */ + private void validateFixedUserInfo(InstitutionKeySubject subject, WalletApplicationRequest request) { + java.util.List mismatches = new java.util.ArrayList<>(); + + String phone = normalized(request.getInputPhone()); + if (!FIXED_PHONE.equals(phone)) { + mismatches.add("手机号"); + } + + String idNumber = normalized(request.getInputIdNumber() != null + && !request.getInputIdNumber().trim().isEmpty() + ? request.getInputIdNumber() : request.getIdNumber()); + if (!FIXED_ID_NUMBER.equals(idNumber)) { + mismatches.add("身份证号"); + } + + String bankCardNumber = normalized(request.getInputBankCardNumber() != null + && !request.getInputBankCardNumber().trim().isEmpty() + ? request.getInputBankCardNumber() : request.getBankCardNumber()); + if (!FIXED_BANK_CARD_NUMBER.equals(bankCardNumber)) { + mismatches.add("银行卡号"); + } + + if (!mismatches.isEmpty()) { + scoreService.recordError(subject.getUserId()); + throw new BusinessException(ErrorCode.VALIDATION_ERROR, + String.join("、", mismatches) + "与用户信息不一致,错误数+1"); + } + } + + /** 去除空格与全角空格后比较,兼容“621661 010000 1234567”这类带分隔的输入 */ + private String normalized(String value) { + if (value == null) { + return ""; + } + return value.replace(" ", "").replace("\u3000", "").trim(); + } + private String resolveOrgCode(InstitutionKeySubject subject) { InstitutionIdentifierApplicationEntity entity = institutionIdentifierMapper.selectOne( new LambdaQueryWrapper() diff --git a/src/main/java/com/yau/digitalrmb/walletopening/application/WalletRegistrationService.java b/src/main/java/com/yau/digitalrmb/walletopening/application/WalletRegistrationService.java index b89689e..0a6003f 100644 --- a/src/main/java/com/yau/digitalrmb/walletopening/application/WalletRegistrationService.java +++ b/src/main/java/com/yau/digitalrmb/walletopening/application/WalletRegistrationService.java @@ -55,14 +55,7 @@ public class WalletRegistrationService { @Transactional public WalletRegistrationInfoResult readInfo(InstitutionKeySubject subject) { WalletRegistrationRequest request = getOrCreate(subject); - if (request.getStatus() != null && request.getStatus().ordinal() >= WalletRegistrationRequest.Status.INFO_READ.ordinal()) { - if (request.getStatus() == WalletRegistrationRequest.Status.INFO_READ) { - populateInfoIfReady(subject, request); - } - return new WalletRegistrationInfoResult(request.getWalletId(), request.getUserName(), - request.getIdNumber(), request.getBankCardNumber(), request.getWalletType(), - request.getOrgCode(), request.getCertSerial(), request.getRequestTimestamp()); - } + // 支持重复点击:每次都重新读取最新钱包、证书与申请信息并覆盖 verifyStep3CertificateCompleted(subject); WalletApplication app = walletApplicationRepository .findLatest(subject.getUserId(), subject.getSchoolId(), subject.getClassId()) @@ -87,9 +80,7 @@ public class WalletRegistrationService { @Transactional public WalletRegistrationPageResult concatenate(InstitutionKeySubject subject) { WalletRegistrationRequest request = getOrCreate(subject); - if (request.getStatus() != null && request.getStatus().ordinal() >= WalletRegistrationRequest.Status.CONCATENATED.ordinal()) { - return new WalletRegistrationPageResult(request); - } + // 支持重复点击:按最新信息重新拼接请求原文 if (request.getWalletId() == null) { scoreService.recordError(subject.getUserId()); throw new BusinessException(ErrorCode.VALIDATION_ERROR, "请先读取请求信息"); @@ -106,9 +97,7 @@ public class WalletRegistrationService { throw new BusinessException(ErrorCode.VALIDATION_ERROR, "算法选择有误,错误数+1"); } WalletRegistrationRequest request = getOrCreate(subject); - if (request.getDigestValue() != null) { - return new WalletRegistrationDigestResult(request.getDigestValue()); - } + // 支持重复点击:按最新原文重新计算摘要 if (request.getRequestMessage() == null) { scoreService.recordError(subject.getUserId()); throw new BusinessException(ErrorCode.VALIDATION_ERROR, "请先拼接请求原文"); @@ -126,9 +115,7 @@ public class WalletRegistrationService { throw new BusinessException(ErrorCode.VALIDATION_ERROR, "算法选择有误,错误数+1"); } WalletRegistrationRequest request = getOrCreate(subject); - if (request.getSignature() != null) { - return new WalletRegistrationSignatureResult(request.getSignature()); - } + // 支持重复点击:按最新摘要重新签名 if (request.getDigestValue() == null) { scoreService.recordError(subject.getUserId()); throw new BusinessException(ErrorCode.VALIDATION_ERROR, "请先生成SM3摘要"); @@ -151,9 +138,7 @@ public class WalletRegistrationService { @Transactional public WalletRegistrationOutputResult output(InstitutionKeySubject subject) { WalletRegistrationRequest request = getOrCreate(subject); - if (request.getRequestJson() != null) { - return buildOutputResult(request); - } + // 支持重复点击:按最新签名重新输出备案报文 if (request.getSignature() == null) { scoreService.recordError(subject.getUserId()); throw new BusinessException(ErrorCode.VALIDATION_ERROR, "请先完成SM2签名"); @@ -169,9 +154,7 @@ public class WalletRegistrationService { @Transactional public WalletRegistrationPageResult send(InstitutionKeySubject subject) { WalletRegistrationRequest request = getOrCreate(subject); - if (request.isSentToCentral()) { - return new WalletRegistrationPageResult(request); - } + // 支持重复点击:重复发送时刷新发送状态与发送时间 if (request.getRequestJson() == null) { scoreService.recordError(subject.getUserId()); throw new BusinessException(ErrorCode.VALIDATION_ERROR, "请先输出备案请求报文"); diff --git a/src/main/java/com/yau/digitalrmb/walletopening/domain/BankReceivedApplicationRepository.java b/src/main/java/com/yau/digitalrmb/walletopening/domain/BankReceivedApplicationRepository.java index 20a6940..1edf56e 100644 --- a/src/main/java/com/yau/digitalrmb/walletopening/domain/BankReceivedApplicationRepository.java +++ b/src/main/java/com/yau/digitalrmb/walletopening/domain/BankReceivedApplicationRepository.java @@ -5,4 +5,6 @@ import java.util.Optional; public interface BankReceivedApplicationRepository { BankReceivedApplication save(BankReceivedApplication application); Optional findLatest(String userId, long schoolId, long classId); + /** 重复发送时更新同一条接收记录,避免产生多条重复数据 */ + void update(BankReceivedApplication application); } diff --git a/src/main/java/com/yau/digitalrmb/walletopening/infrastructure/MybatisBankReceivedApplicationRepository.java b/src/main/java/com/yau/digitalrmb/walletopening/infrastructure/MybatisBankReceivedApplicationRepository.java index e70d212..fc6c2b9 100644 --- a/src/main/java/com/yau/digitalrmb/walletopening/infrastructure/MybatisBankReceivedApplicationRepository.java +++ b/src/main/java/com/yau/digitalrmb/walletopening/infrastructure/MybatisBankReceivedApplicationRepository.java @@ -21,6 +21,14 @@ public class MybatisBankReceivedApplicationRepository implements BankReceivedApp return application; } + @Override + public void update(BankReceivedApplication application) { + if (application == null || application.getId() == null) { + return; + } + mapper.updateById(toEntity(application)); + } + @Override public Optional findLatest(String userId, long schoolId, long classId) { BankReceivedApplicationEntity entity = mapper.selectOne(new LambdaQueryWrapper()