|
|
|
@ -1,13 +1,17 @@
|
|
|
|
package com.yau.digitalrmb.corporatewallet.application;
|
|
|
|
package com.yau.digitalrmb.corporatewallet.application;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import com.yau.digitalrmb.corporatewallet.application.CorporateWalletKeyService;
|
|
|
|
import com.yau.digitalrmb.corporatewallet.domain.CorporateWalletApplication;
|
|
|
|
import com.yau.digitalrmb.corporatewallet.domain.CorporateWalletApplication;
|
|
|
|
import com.yau.digitalrmb.corporatewallet.domain.CorporateWalletApplicationRepository;
|
|
|
|
import com.yau.digitalrmb.corporatewallet.domain.CorporateWalletApplicationRepository;
|
|
|
|
import com.yau.digitalrmb.corporatewallet.domain.RemoteWalletApplication;
|
|
|
|
import com.yau.digitalrmb.corporatewallet.domain.RemoteWalletApplication;
|
|
|
|
import com.yau.digitalrmb.corporatewallet.domain.RemoteWalletApplicationRepository;
|
|
|
|
import com.yau.digitalrmb.corporatewallet.domain.RemoteWalletApplicationRepository;
|
|
|
|
|
|
|
|
import com.yau.digitalrmb.corporatewallet.infrastructure.CorporateWalletKeyInfoEntity;
|
|
|
|
|
|
|
|
import com.yau.digitalrmb.corporatewallet.infrastructure.CorporateWalletKeyInfoMapper;
|
|
|
|
import com.yau.digitalrmb.corporatewallet.interfaces.rest.dto.RemoteWalletApplicationRequest;
|
|
|
|
import com.yau.digitalrmb.corporatewallet.interfaces.rest.dto.RemoteWalletApplicationRequest;
|
|
|
|
|
|
|
|
import com.yau.digitalrmb.institutionidentity.application.InstitutionKeyService;
|
|
|
|
import com.yau.digitalrmb.institutionidentity.application.InstitutionKeySubject;
|
|
|
|
import com.yau.digitalrmb.institutionidentity.application.InstitutionKeySubject;
|
|
|
|
import com.yau.digitalrmb.institutionidentity.domain.InstitutionSm2KeyPair;
|
|
|
|
|
|
|
|
import com.yau.digitalrmb.institutionidentity.infrastructure.SmInstitutionIdentityCryptography;
|
|
|
|
import com.yau.digitalrmb.institutionidentity.infrastructure.SmInstitutionIdentityCryptography;
|
|
|
|
|
|
|
|
import com.yau.digitalrmb.security.context.AuthContextHolder;
|
|
|
|
import com.yau.digitalrmb.shared.api.ErrorCode;
|
|
|
|
import com.yau.digitalrmb.shared.api.ErrorCode;
|
|
|
|
import com.yau.digitalrmb.shared.exception.BusinessException;
|
|
|
|
import com.yau.digitalrmb.shared.exception.BusinessException;
|
|
|
|
import org.springframework.stereotype.Service;
|
|
|
|
import org.springframework.stereotype.Service;
|
|
|
|
@ -28,6 +32,16 @@ public class RemoteWalletService {
|
|
|
|
private CorporateWalletApplicationRepository corporateRepository;
|
|
|
|
private CorporateWalletApplicationRepository corporateRepository;
|
|
|
|
@Resource
|
|
|
|
@Resource
|
|
|
|
private SmInstitutionIdentityCryptography cryptography;
|
|
|
|
private SmInstitutionIdentityCryptography cryptography;
|
|
|
|
|
|
|
|
@Resource
|
|
|
|
|
|
|
|
private CorporateWalletKeyInfoMapper keyInfoMapper;
|
|
|
|
|
|
|
|
@Resource
|
|
|
|
|
|
|
|
private SalaryScoreService scoreService;
|
|
|
|
|
|
|
|
@Resource
|
|
|
|
|
|
|
|
private CorporateWalletKeyService keyService;
|
|
|
|
|
|
|
|
@Resource
|
|
|
|
|
|
|
|
private InstitutionKeyService institutionKeyService;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static final String LEGAL_PERSON_KEY_TYPE = "企业法人私钥";
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== 步骤一:企业法人在线填写信息 ====================
|
|
|
|
// ==================== 步骤一:企业法人在线填写信息 ====================
|
|
|
|
|
|
|
|
|
|
|
|
@ -122,23 +136,81 @@ public class RemoteWalletService {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Transactional
|
|
|
|
@Transactional
|
|
|
|
public RemoteSignatureResult computeSignature(InstitutionKeySubject subject) {
|
|
|
|
public RemoteSignatureResult computeSignature(InstitutionKeySubject subject, String providedPrivateKey) {
|
|
|
|
RemoteWalletApplication app = findRequired(subject);
|
|
|
|
RemoteWalletApplication app = findRequired(subject);
|
|
|
|
if (app.getSigningDigestValue() == null) {
|
|
|
|
if (app.getSigningDigestValue() == null) {
|
|
|
|
throw new BusinessException(ErrorCode.VALIDATION_ERROR, "请先完成SM3摘要运算");
|
|
|
|
throw new BusinessException(ErrorCode.VALIDATION_ERROR, "请先完成SM3摘要运算");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (app.getSignature() != null) {
|
|
|
|
if (app.getSignature() != null) {
|
|
|
|
return new RemoteSignatureResult(app.getSignPrivateKey(), "SM2", app.getSignature());
|
|
|
|
return new RemoteSignatureResult("SM2", app.getSignature());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
InstitutionSm2KeyPair keyPair = cryptography.generateSm2KeyPair();
|
|
|
|
// 验证前端传入的私钥不能为空
|
|
|
|
String privateKey = keyPair.getPrivateKey();
|
|
|
|
if (providedPrivateKey == null || providedPrivateKey.trim().isEmpty()) {
|
|
|
|
String signature = cryptography.sign(privateKey, app.getSigningExtractMessage());
|
|
|
|
throw new BusinessException(ErrorCode.VALIDATION_ERROR, "企业法人私钥不能为空");
|
|
|
|
|
|
|
|
}
|
|
|
|
app.setSignPrivateKey(privateKey);
|
|
|
|
String trimmedPrivateKey = providedPrivateKey.trim();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 检查数据库中是否已存在企业法人私钥
|
|
|
|
|
|
|
|
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::getIsPrivateKey, true)
|
|
|
|
|
|
|
|
.eq(CorporateWalletKeyInfoEntity::getDeleted, false)
|
|
|
|
|
|
|
|
.orderByDesc(CorporateWalletKeyInfoEntity::getCreatedAt)
|
|
|
|
|
|
|
|
.last("LIMIT 1"));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (existingKey != null) {
|
|
|
|
|
|
|
|
// 【场景1】数据库中已有密钥,验证前端传入的私钥是否正确
|
|
|
|
|
|
|
|
if (!existingKey.getKeyValue().equalsIgnoreCase(trimmedPrivateKey)) {
|
|
|
|
|
|
|
|
scoreService.recordError(subject.getUserId());
|
|
|
|
|
|
|
|
throw new BusinessException(ErrorCode.VALIDATION_ERROR, "企业法人私钥不正确,错误次数+1");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// 使用数据库中已有的私钥进行签名
|
|
|
|
|
|
|
|
String signature = cryptography.sign(existingKey.getKeyValue(), app.getSigningExtractMessage());
|
|
|
|
|
|
|
|
app.setSignPrivateKey(existingKey.getKeyValue());
|
|
|
|
app.setSignature(signature);
|
|
|
|
app.setSignature(signature);
|
|
|
|
repository.update(app);
|
|
|
|
repository.update(app);
|
|
|
|
return new RemoteSignatureResult(privateKey, "SM2", signature);
|
|
|
|
return new RemoteSignatureResult("SM2", signature);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
// 【场景2】数据库中没有密钥,说明用户没有先调用 /keys 接口获取密钥
|
|
|
|
|
|
|
|
// 这种情况视为错误,记录错误次数并提示用户
|
|
|
|
|
|
|
|
scoreService.recordError(subject.getUserId());
|
|
|
|
|
|
|
|
this.getAllKeys(
|
|
|
|
|
|
|
|
InstitutionKeySubject.from(AuthContextHolder.get()), AuthContextHolder.get().getUsername());
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
throw new BusinessException(ErrorCode.VALIDATION_ERROR,
|
|
|
|
|
|
|
|
"企业法人私钥有误,错误次数+1");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 保存密钥信息到 corporate_wallet_key_info 表
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
private void saveKeyInfo(InstitutionKeySubject subject, String keyType, String holder,
|
|
|
|
|
|
|
|
String keyValue, boolean isPrivateKey, String source) {
|
|
|
|
|
|
|
|
CorporateWalletKeyInfoEntity entity = new CorporateWalletKeyInfoEntity();
|
|
|
|
|
|
|
|
entity.setUserId(subject.getUserId());
|
|
|
|
|
|
|
|
entity.setSchoolId(subject.getSchoolId());
|
|
|
|
|
|
|
|
entity.setClassId(subject.getClassId());
|
|
|
|
|
|
|
|
entity.setKeyType(keyType);
|
|
|
|
|
|
|
|
entity.setHolder(holder);
|
|
|
|
|
|
|
|
entity.setKeyAlgorithm("SM2");
|
|
|
|
|
|
|
|
entity.setKeyValue(keyValue);
|
|
|
|
|
|
|
|
entity.setIsPrivateKey(isPrivateKey);
|
|
|
|
|
|
|
|
entity.setSource(source);
|
|
|
|
|
|
|
|
java.time.LocalDateTime now = java.time.LocalDateTime.now();
|
|
|
|
|
|
|
|
entity.setCreatedAt(now);
|
|
|
|
|
|
|
|
entity.setUpdatedAt(now);
|
|
|
|
|
|
|
|
entity.setCreatedBy(holder);
|
|
|
|
|
|
|
|
entity.setUpdatedBy(holder);
|
|
|
|
|
|
|
|
entity.setDeleted(false);
|
|
|
|
|
|
|
|
keyInfoMapper.insert(entity);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Transactional
|
|
|
|
@Transactional
|
|
|
|
@ -221,6 +293,9 @@ public class RemoteWalletService {
|
|
|
|
subject.getSchoolId(), subject.getClassId())
|
|
|
|
subject.getSchoolId(), subject.getClassId())
|
|
|
|
.orElse(null);
|
|
|
|
.orElse(null);
|
|
|
|
if (corpActivated != null) {
|
|
|
|
if (corpActivated != null) {
|
|
|
|
|
|
|
|
// 【企业信息一致性校验】比对远程开立和临柜开立的企业信息是否一致
|
|
|
|
|
|
|
|
validateEnterpriseInfoConsistency(app, corpActivated);
|
|
|
|
|
|
|
|
|
|
|
|
// 复用临柜开立的钱包ID,不再生成新钱包
|
|
|
|
// 复用临柜开立的钱包ID,不再生成新钱包
|
|
|
|
app.activateWallet(corpActivated.getWalletId(), corpActivated.getActivateTime());
|
|
|
|
app.activateWallet(corpActivated.getWalletId(), corpActivated.getActivateTime());
|
|
|
|
repository.update(app);
|
|
|
|
repository.update(app);
|
|
|
|
@ -229,7 +304,8 @@ public class RemoteWalletService {
|
|
|
|
app.getWalletType(), "ACTIVATED", corpActivated.getActivateTime(), "已验证", "临柜开立");
|
|
|
|
app.getWalletType(), "ACTIVATED", corpActivated.getActivateTime(), "已验证", "临柜开立");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
String walletId = "CORP_" + app.getWalletDigestValue();
|
|
|
|
// 钱包ID基于统一社会信用代码生成,确保同一企业无论通过哪个渠道开立,钱包ID都一致
|
|
|
|
|
|
|
|
String walletId = "CORP_" + cryptography.sm3(app.getCreditCode());
|
|
|
|
String activateTime = app.getSignTime() != null ? app.getSignTime()
|
|
|
|
String activateTime = app.getSignTime() != null ? app.getSignTime()
|
|
|
|
: LocalDateTime.now().format(TIMESTAMP_FORMAT);
|
|
|
|
: LocalDateTime.now().format(TIMESTAMP_FORMAT);
|
|
|
|
|
|
|
|
|
|
|
|
@ -260,4 +336,108 @@ public class RemoteWalletService {
|
|
|
|
return "REMOTE_OPEN|" + corpName + "|" + creditCode + "|" + legalPerson + "|"
|
|
|
|
return "REMOTE_OPEN|" + corpName + "|" + creditCode + "|" + legalPerson + "|"
|
|
|
|
+ phone + "|" + applyTime;
|
|
|
|
+ phone + "|" + applyTime;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 获取远程开立所需的全部密钥信息:
|
|
|
|
|
|
|
|
* 央行第一密钥、商业银行第二密钥复用已有数据;
|
|
|
|
|
|
|
|
* 企业法人、数币操作员、数币复核员、监管机构密钥按需生成(若不存在则新增,已存在则复用)。
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
@Transactional
|
|
|
|
|
|
|
|
public java.util.List<RemoteWalletKeyResult> getAllKeys(InstitutionKeySubject subject, String operator) {
|
|
|
|
|
|
|
|
java.util.List<RemoteWalletKeyResult> results = new java.util.ArrayList<>();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 1. 央行第一密钥(复用 institution_sm2_key)
|
|
|
|
|
|
|
|
com.yau.digitalrmb.institutionidentity.application.InstitutionKeyPairResult centralKey =
|
|
|
|
|
|
|
|
institutionKeyService.centralBankKey(subject, operator);
|
|
|
|
|
|
|
|
results.add(buildKeyResult("央行第一私钥", "中国人民银行",
|
|
|
|
|
|
|
|
centralKey.getPrivateKey(), true, "REUSED"));
|
|
|
|
|
|
|
|
results.add(buildKeyResult("央行第一公钥", "中国人民银行",
|
|
|
|
|
|
|
|
centralKey.getPublicKey(), false, "REUSED"));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 商业银行第二密钥(复用 institution_sm2_key)
|
|
|
|
|
|
|
|
com.yau.digitalrmb.institutionidentity.application.InstitutionKeyPairResult bankKey =
|
|
|
|
|
|
|
|
institutionKeyService.commercialBankKey(subject, operator);
|
|
|
|
|
|
|
|
results.add(buildKeyResult("商业银行第二私钥", "中国银行",
|
|
|
|
|
|
|
|
bankKey.getPrivateKey(), true, "REUSED"));
|
|
|
|
|
|
|
|
results.add(buildKeyResult("商业银行第二公钥", "中国银行",
|
|
|
|
|
|
|
|
bankKey.getPublicKey(), false, "REUSED"));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 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"));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 4. 数币操作员密钥(按需生成)
|
|
|
|
|
|
|
|
String[] operatorPair = getOrCreateKeyPair(subject, "数币操作员私钥", "数币操作员公钥",
|
|
|
|
|
|
|
|
"王五");
|
|
|
|
|
|
|
|
results.add(buildKeyResult("数币操作员私钥", "王五", operatorPair[0], true, "NEW"));
|
|
|
|
|
|
|
|
results.add(buildKeyResult("数币操作员公钥", "王五", operatorPair[1], false, "NEW"));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 5. 数币复核员密钥(按需生成)
|
|
|
|
|
|
|
|
String[] reviewerPair = getOrCreateKeyPair(subject, "数币复核员私钥", "数币复核员公钥",
|
|
|
|
|
|
|
|
"赵六");
|
|
|
|
|
|
|
|
results.add(buildKeyResult("数币复核员私钥", "赵六", reviewerPair[0], true, "NEW"));
|
|
|
|
|
|
|
|
results.add(buildKeyResult("数币复核员公钥", "赵六", reviewerPair[1], false, "NEW"));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 6. 监管机构密钥(按需生成)
|
|
|
|
|
|
|
|
String[] regulatorPair = getOrCreateKeyPair(subject, "监管机构私钥", "监管机构公钥",
|
|
|
|
|
|
|
|
"民政部公益监管中心");
|
|
|
|
|
|
|
|
results.add(buildKeyResult("监管机构私钥", "民政部公益监管中心", regulatorPair[0], true, "NEW"));
|
|
|
|
|
|
|
|
results.add(buildKeyResult("监管机构公钥", "民政部公益监管中心", regulatorPair[1], false, "NEW"));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return results;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 按需获取密钥对:若数据库中已存在则直接返回,否则自动生成SM2密钥对并存入数据库。
|
|
|
|
|
|
|
|
* 返回 [privateKey, publicKey]
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
private String[] getOrCreateKeyPair(InstitutionKeySubject subject, String privateKeyType,
|
|
|
|
|
|
|
|
String publicKeyType, String holder) {
|
|
|
|
|
|
|
|
String privateKey = keyService.getOrCreatePrivateKey(subject, privateKeyType, holder);
|
|
|
|
|
|
|
|
String publicKey = keyService.getOrCreatePublicKey(subject, publicKeyType, holder);
|
|
|
|
|
|
|
|
return new String[]{privateKey, publicKey};
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private RemoteWalletKeyResult buildKeyResult(String keyType, String holder, String keyValue,
|
|
|
|
|
|
|
|
boolean isPrivateKey, String source) {
|
|
|
|
|
|
|
|
return new RemoteWalletKeyResult(keyType, holder, "SM2", keyValue, isPrivateKey, source);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 校验远程开立和临柜开立的企业信息一致性
|
|
|
|
|
|
|
|
* @param remoteApp 远程开立申请
|
|
|
|
|
|
|
|
* @param corpApp 临柜开立申请
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
private void validateEnterpriseInfoConsistency(RemoteWalletApplication remoteApp,
|
|
|
|
|
|
|
|
CorporateWalletApplication corpApp) {
|
|
|
|
|
|
|
|
java.util.List<String> errors = new java.util.ArrayList<>();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 1. 校验统一社会信用代码(最关键)
|
|
|
|
|
|
|
|
if (!remoteApp.getCreditCode().equals(corpApp.getCreditCode())) {
|
|
|
|
|
|
|
|
errors.add("统一社会信用代码不一致:远程开立为【" + remoteApp.getCreditCode() + "】,"
|
|
|
|
|
|
|
|
+ "临柜开立为【" + corpApp.getCreditCode() + "】");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 校验企业名称
|
|
|
|
|
|
|
|
if (!remoteApp.getCorpName().equals(corpApp.getCorpName())) {
|
|
|
|
|
|
|
|
errors.add("企业名称不一致:远程开立为【" + remoteApp.getCorpName() + "】,"
|
|
|
|
|
|
|
|
+ "临柜开立为【" + corpApp.getCorpName() + "】");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 校验法定代表人
|
|
|
|
|
|
|
|
if (!remoteApp.getLegalPerson().equals(corpApp.getLegalPerson())) {
|
|
|
|
|
|
|
|
errors.add("法定代表人不一致:远程开立为【" + remoteApp.getLegalPerson() + "】,"
|
|
|
|
|
|
|
|
+ "临柜开立为【" + corpApp.getLegalPerson() + "】");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 4. 如果有错误,抛出异常
|
|
|
|
|
|
|
|
if (!errors.isEmpty()) {
|
|
|
|
|
|
|
|
String errorMsg = "企业信息不一致,无法提交:\n" + String.join("\n", errors);
|
|
|
|
|
|
|
|
throw new BusinessException(ErrorCode.VALIDATION_ERROR, errorMsg);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|