feat:文件上传的功能开发

chore/issuance-comments-zh
赵佳正 3 weeks ago
parent 21d9a63b83
commit a5a0b882f7

@ -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<CorporateWalletKeyInfoEntity>()
.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);

@ -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<CorporateWalletKeyInfoEntity>()
.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<CorporateWalletKeyInfoEntity> existingLegalPersonKeys = keyInfoMapper.selectList(
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<CorporateWalletKeyInfoEntity>()
.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<String, CorporateWalletKeyInfoEntity> 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<CorporateWalletKeyInfoEntity>()
.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 远程开立申请

@ -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);

@ -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, "请先更新钱包登记备案");

@ -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<WalletApplication> walletApp = walletApplicationRepository
.findLatest(subject.getUserId(), subject.getSchoolId(), subject.getClassId());

@ -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, "请先输出开通确认报文");

@ -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, "请先生成证书序列号");

@ -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();
java.util.Optional<BankReceivedApplication> 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<String> 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<InstitutionIdentifierApplicationEntity>()

@ -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, "请先输出备案请求报文");

@ -5,4 +5,6 @@ import java.util.Optional;
public interface BankReceivedApplicationRepository {
BankReceivedApplication save(BankReceivedApplication application);
Optional<BankReceivedApplication> findLatest(String userId, long schoolId, long classId);
/** 重复发送时更新同一条接收记录,避免产生多条重复数据 */
void update(BankReceivedApplication application);
}

@ -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<BankReceivedApplication> findLatest(String userId, long schoolId, long classId) {
BankReceivedApplicationEntity entity = mapper.selectOne(new LambdaQueryWrapper<BankReceivedApplicationEntity>()

Loading…
Cancel
Save