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.
302 lines
18 KiB
Java
302 lines
18 KiB
Java
package com.yau.digitalrmb.institutionidentity.application;
|
|
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
|
import com.yau.digitalrmb.institutionidentity.domain.InstitutionIdentityCryptography;
|
|
import com.yau.digitalrmb.institutionidentity.infrastructure.ControlSystemSignatureEntity;
|
|
import com.yau.digitalrmb.institutionidentity.infrastructure.ControlSystemSignatureMapper;
|
|
import com.yau.digitalrmb.institutionidentity.infrastructure.QuotaControlBitEntity;
|
|
import com.yau.digitalrmb.institutionidentity.infrastructure.QuotaControlBitMapper;
|
|
import com.yau.digitalrmb.shared.api.ErrorCode;
|
|
import com.yau.digitalrmb.shared.exception.BusinessException;
|
|
import org.springframework.stereotype.Service;
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
|
|
import javax.annotation.Resource;
|
|
import java.security.SecureRandom;
|
|
import java.time.LocalDateTime;
|
|
import java.time.format.DateTimeFormatter;
|
|
|
|
@Service
|
|
public class QuotaControlBitService {
|
|
private static final DateTimeFormatter DATE = DateTimeFormatter.ofPattern("yyyyMMdd");
|
|
private static final DateTimeFormatter TIMESTAMP = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
|
|
private static final char[] RANDOM_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".toCharArray();
|
|
private static final SecureRandom RANDOM = new SecureRandom();
|
|
|
|
@Resource private ControlSystemSignatureMapper controlSignatureMapper;
|
|
@Resource private QuotaControlBitMapper mapper;
|
|
@Resource private InstitutionIdentityCryptography cryptography;
|
|
@Resource private InstitutionKeyService keyService;
|
|
@Resource private InstitutionTrainingErrorRecorder errorRecorder;
|
|
@Resource private InstitutionTrainingScoreService scoreService;
|
|
|
|
@Transactional
|
|
public QuotaControlStaticSegmentResult staticSegment(InstitutionKeySubject subject, String operator) {
|
|
ControlSystemSignatureEntity source = requireStepSeven(subject);
|
|
QuotaControlBitEntity current = latest(subject);
|
|
if (current != null) {
|
|
if (!current.getControlSignatureId().equals(source.getId())) {
|
|
throw new BusinessException(ErrorCode.VALIDATION_ERROR,
|
|
"当前实训已有步骤八记录,如需重新开始请先调用重新实训接口");
|
|
}
|
|
String originalText = source.getAmount().setScale(2).toPlainString() + "|"
|
|
+ source.getInstitutionIdentifier() + "|" + source.getTransactionIdentifier();
|
|
String segment = "STATIC_" + cryptography.sm3(originalText);
|
|
update(current, current.getStatus(), "STATIC_READY", operator,
|
|
new LambdaUpdateWrapper<QuotaControlBitEntity>()
|
|
.set(QuotaControlBitEntity::getAmount, source.getAmount().setScale(2))
|
|
.set(QuotaControlBitEntity::getInstitutionIdentifier, source.getInstitutionIdentifier())
|
|
.set(QuotaControlBitEntity::getTransactionIdentifier, source.getTransactionIdentifier())
|
|
.set(QuotaControlBitEntity::getStaticOriginalText, originalText)
|
|
.set(QuotaControlBitEntity::getStaticSegment, segment)
|
|
.set(QuotaControlBitEntity::getRandomValue, null)
|
|
.set(QuotaControlBitEntity::getSegmentTimestamp, null)
|
|
.set(QuotaControlBitEntity::getDynamicSegment, null)
|
|
.set(QuotaControlBitEntity::getCentralBankSignatureSegment, null)
|
|
.set(QuotaControlBitEntity::getCompleteControlBit, null)
|
|
.set(QuotaControlBitEntity::getSigningKeyId, null)
|
|
.set(QuotaControlBitEntity::getHashAlgorithm, null)
|
|
.set(QuotaControlBitEntity::getBankSignature, null)
|
|
.set(QuotaControlBitEntity::getRequestMessage, null)
|
|
.set(QuotaControlBitEntity::getReceiveStatus, 1));
|
|
return new QuotaControlStaticSegmentResult(segment);
|
|
}
|
|
QuotaControlBitEntity value = new QuotaControlBitEntity();
|
|
value.setControlSignatureId(source.getId());
|
|
value.setQuotaId("QC_" + LocalDateTime.now().format(DATE) + "_" + String.format("%03d", source.getId() % 1000));
|
|
value.setAmount(source.getAmount().setScale(2));
|
|
value.setInstitutionIdentifier(source.getInstitutionIdentifier());
|
|
value.setTransactionIdentifier(source.getTransactionIdentifier());
|
|
value.setStaticOriginalText(value.getAmount().toPlainString() + "|" + value.getInstitutionIdentifier()
|
|
+ "|" + value.getTransactionIdentifier());
|
|
value.setStaticSegment("STATIC_" + cryptography.sm3(value.getStaticOriginalText()));
|
|
value.setReceiveStatus(1);
|
|
value.setStatus("STATIC_READY");
|
|
value.setUserId(subject.getUserId()); value.setSchoolId(subject.getSchoolId()); value.setClassId(subject.getClassId());
|
|
value.setCreatedAt(LocalDateTime.now()); value.setUpdatedAt(value.getCreatedAt());
|
|
value.setCreatedBy(operator); value.setUpdatedBy(operator); value.setDeleted(false);
|
|
mapper.insert(value);
|
|
return new QuotaControlStaticSegmentResult(value.getStaticSegment());
|
|
}
|
|
|
|
@Transactional
|
|
public QuotaControlDynamicSegmentResult dynamicSegment(InstitutionKeySubject subject, String operator) {
|
|
QuotaControlBitEntity value = requireLatest(subject);
|
|
if (value.getStaticSegment() == null) {
|
|
throw new BusinessException(ErrorCode.VALIDATION_ERROR, "请先生成静态段");
|
|
}
|
|
String randomValue = randomValue();
|
|
String timestamp = LocalDateTime.now().format(TIMESTAMP);
|
|
String segment = "DYN_" + randomValue + "_" + timestamp;
|
|
update(value, value.getStatus(), "DYNAMIC_READY", operator,
|
|
new LambdaUpdateWrapper<QuotaControlBitEntity>()
|
|
.set(QuotaControlBitEntity::getRandomValue, randomValue)
|
|
.set(QuotaControlBitEntity::getSegmentTimestamp, timestamp)
|
|
.set(QuotaControlBitEntity::getDynamicSegment, segment)
|
|
.set(QuotaControlBitEntity::getCentralBankSignatureSegment, null)
|
|
.set(QuotaControlBitEntity::getCompleteControlBit, null)
|
|
.set(QuotaControlBitEntity::getSigningKeyId, null)
|
|
.set(QuotaControlBitEntity::getHashAlgorithm, null)
|
|
.set(QuotaControlBitEntity::getBankSignature, null)
|
|
.set(QuotaControlBitEntity::getRequestMessage, null)
|
|
.set(QuotaControlBitEntity::getReceiveStatus, 1));
|
|
return new QuotaControlDynamicSegmentResult(segment);
|
|
}
|
|
|
|
@Transactional
|
|
public CentralBankSignatureSegmentResult readCentralBankSignature(InstitutionKeySubject subject, String operator) {
|
|
QuotaControlBitEntity value = requireLatest(subject);
|
|
requireResult(value.getDynamicSegment(), "请先生成动态段");
|
|
ControlSystemSignatureEntity source = requireStepSeven(subject);
|
|
if (!source.getId().equals(value.getControlSignatureId()) || source.getControlSignature() == null) {
|
|
throw new BusinessException(ErrorCode.VALIDATION_ERROR, "步骤七央行控制系统签名数据不完整");
|
|
}
|
|
update(value, value.getStatus(), "CB_SIGNATURE_READY", operator,
|
|
new LambdaUpdateWrapper<QuotaControlBitEntity>()
|
|
.set(QuotaControlBitEntity::getCentralBankSignatureSegment,
|
|
"CB_CTRL_" + source.getControlSignature())
|
|
.set(QuotaControlBitEntity::getCompleteControlBit, null)
|
|
.set(QuotaControlBitEntity::getSigningKeyId, null)
|
|
.set(QuotaControlBitEntity::getHashAlgorithm, null)
|
|
.set(QuotaControlBitEntity::getBankSignature, null)
|
|
.set(QuotaControlBitEntity::getRequestMessage, null)
|
|
.set(QuotaControlBitEntity::getReceiveStatus, 1));
|
|
return new CentralBankSignatureSegmentResult(
|
|
requireLatest(subject).getCentralBankSignatureSegment());
|
|
}
|
|
|
|
@Transactional
|
|
public QuotaControlBitResult assemble(InstitutionKeySubject subject, String operator) {
|
|
QuotaControlBitEntity value = requireLatest(subject);
|
|
requireResult(value.getStaticSegment(), "请先生成静态段");
|
|
requireResult(value.getDynamicSegment(), "请先生成动态段");
|
|
requireResult(value.getCentralBankSignatureSegment(), "请先读取央行签名段");
|
|
String complete = value.getStaticSegment() + "|" + value.getDynamicSegment() + "|"
|
|
+ value.getCentralBankSignatureSegment();
|
|
update(value, value.getStatus(), "ASSEMBLED", operator,
|
|
new LambdaUpdateWrapper<QuotaControlBitEntity>()
|
|
.set(QuotaControlBitEntity::getCompleteControlBit, complete)
|
|
.set(QuotaControlBitEntity::getSigningKeyId, null)
|
|
.set(QuotaControlBitEntity::getHashAlgorithm, null)
|
|
.set(QuotaControlBitEntity::getBankSignature, null)
|
|
.set(QuotaControlBitEntity::getRequestMessage, null)
|
|
.set(QuotaControlBitEntity::getReceiveStatus, 1));
|
|
return new QuotaControlBitResult(requireLatest(subject));
|
|
}
|
|
|
|
@Transactional
|
|
public QuotaControlBitResult sign(String privateKey, String completeControlBit, String algorithm,
|
|
InstitutionKeySubject subject, String operator) {
|
|
QuotaControlBitEntity value = requireLatest(subject);
|
|
requireResult(value.getStaticSegment(), "请先生成静态段");
|
|
requireResult(value.getDynamicSegment(), "请先生成动态段");
|
|
requireResult(value.getCentralBankSignatureSegment(), "请先读取央行签名段");
|
|
String expectedControlBit = value.getStaticSegment() + "|" + value.getDynamicSegment() + "|"
|
|
+ value.getCentralBankSignatureSegment();
|
|
String submittedControlBit = completeControlBit == null ? null : completeControlBit.trim();
|
|
if (!expectedControlBit.equals(submittedControlBit)) {
|
|
throw keyError(subject, "完整额度控制位与静态段、动态段及央行签名段拼接结果不一致");
|
|
}
|
|
if (!"SM2".equalsIgnoreCase(algorithm == null ? "" : algorithm.trim())) {
|
|
throw keyError(subject, "签名算法必须选择SM2");
|
|
}
|
|
String signature = keyService.signCommercialBank(subject, privateKey, submittedControlBit);
|
|
if (signature == null) {
|
|
throw keyError(subject, "签名必须使用当前实训主体的商业银行第二私钥");
|
|
}
|
|
update(value, value.getStatus(), "SIGNED", operator,
|
|
new LambdaUpdateWrapper<QuotaControlBitEntity>()
|
|
.set(QuotaControlBitEntity::getCompleteControlBit, submittedControlBit)
|
|
.set(QuotaControlBitEntity::getSigningKeyId, InstitutionKeyService.BANK_SECOND_KEY)
|
|
.set(QuotaControlBitEntity::getHashAlgorithm, "SM2")
|
|
.set(QuotaControlBitEntity::getBankSignature, signature)
|
|
.set(QuotaControlBitEntity::getRequestMessage, null)
|
|
.set(QuotaControlBitEntity::getReceiveStatus, 1));
|
|
return new QuotaControlBitResult(requireLatest(subject));
|
|
}
|
|
|
|
@Transactional
|
|
public QuotaControlBitResult packageRequest(InstitutionKeySubject subject, String operator) {
|
|
QuotaControlBitEntity value = requireLatest(subject);
|
|
requireResult(value.getBankSignature(), "请先完成商业银行签名确认");
|
|
String message = "{\"quotaId\":\"" + value.getQuotaId() + "\",\"staticSegment\":\""
|
|
+ value.getStaticSegment() + "\",\"dynamicSegment\":\"" + value.getDynamicSegment()
|
|
+ "\",\"cbSignatureSegment\":\"" + value.getCentralBankSignatureSegment()
|
|
+ "\",\"bankSignature\":\"" + value.getBankSignature() + "\"}";
|
|
update(value, value.getStatus(), "PACKAGED", operator,
|
|
new LambdaUpdateWrapper<QuotaControlBitEntity>()
|
|
.set(QuotaControlBitEntity::getRequestMessage, message)
|
|
.set(QuotaControlBitEntity::getReceiveStatus, 1));
|
|
return new QuotaControlBitResult(requireLatest(subject));
|
|
}
|
|
|
|
@Transactional
|
|
public QuotaControlBitResult send(InstitutionKeySubject subject, String operator) {
|
|
QuotaControlBitEntity value = requireLatest(subject);
|
|
requireResult(value.getRequestMessage(), "请先打包额度控制位数据包报文");
|
|
update(value, value.getStatus(), "RECEIVED", operator,
|
|
new LambdaUpdateWrapper<QuotaControlBitEntity>().set(QuotaControlBitEntity::getReceiveStatus, 2));
|
|
scoreService.markStepCompleted(subject.getUserId(), 8);
|
|
return new QuotaControlBitResult(requireLatest(subject));
|
|
}
|
|
|
|
@Transactional(readOnly = true)
|
|
public CentralBankQuotaControlBitResult centralBankMessage(InstitutionKeySubject subject) {
|
|
return new CentralBankQuotaControlBitResult(latest(subject));
|
|
}
|
|
|
|
@Transactional(readOnly = true)
|
|
public QuotaControlStaticSegmentResult queryStaticSegment(InstitutionKeySubject subject) {
|
|
String staticSegment = requireLatest(subject).getStaticSegment();
|
|
if (staticSegment == null) {
|
|
throw new BusinessException(ErrorCode.RESOURCE_NOT_FOUND, "当前尚未生成步骤八静态段");
|
|
}
|
|
return new QuotaControlStaticSegmentResult(staticSegment);
|
|
}
|
|
|
|
@Transactional(readOnly = true)
|
|
public QuotaControlDynamicSegmentResult queryDynamicSegment(InstitutionKeySubject subject) {
|
|
String dynamicSegment = requireLatest(subject).getDynamicSegment();
|
|
if (dynamicSegment == null) {
|
|
throw new BusinessException(ErrorCode.RESOURCE_NOT_FOUND, "当前尚未生成步骤八动态段");
|
|
}
|
|
return new QuotaControlDynamicSegmentResult(dynamicSegment);
|
|
}
|
|
|
|
@Transactional(readOnly = true)
|
|
public QuotaControlBitResult detail(InstitutionKeySubject subject) {
|
|
return new QuotaControlBitResult(requireLatest(subject));
|
|
}
|
|
|
|
private String randomValue() {
|
|
StringBuilder value = new StringBuilder(8);
|
|
for (int index = 0; index < 8; index++) {
|
|
value.append(RANDOM_CHARS[RANDOM.nextInt(RANDOM_CHARS.length)]);
|
|
}
|
|
return value.toString();
|
|
}
|
|
|
|
private void requireResult(String value, String message) {
|
|
if (value == null || value.trim().isEmpty()) {
|
|
throw new BusinessException(ErrorCode.VALIDATION_ERROR, message);
|
|
}
|
|
}
|
|
|
|
private ControlSystemSignatureEntity requireStepSeven(InstitutionKeySubject subject) {
|
|
ControlSystemSignatureEntity value = controlSignatureMapper.selectOne(
|
|
new LambdaQueryWrapper<ControlSystemSignatureEntity>()
|
|
.eq(ControlSystemSignatureEntity::getUserId, subject.getUserId())
|
|
.eq(ControlSystemSignatureEntity::getSchoolId, subject.getSchoolId())
|
|
.eq(ControlSystemSignatureEntity::getClassId, subject.getClassId())
|
|
.eq(ControlSystemSignatureEntity::getStatus, "SENT")
|
|
.eq(ControlSystemSignatureEntity::getDeleted, false)
|
|
.orderByDesc(ControlSystemSignatureEntity::getCreatedAt).last("LIMIT 1"));
|
|
if (value == null) throw new BusinessException(ErrorCode.VALIDATION_ERROR,
|
|
"步骤七尚未发送控制系统签名反馈,不能进入步骤八");
|
|
return value;
|
|
}
|
|
|
|
private QuotaControlBitEntity requireStatus(InstitutionKeySubject subject, String status, String message) {
|
|
QuotaControlBitEntity value = requireLatest(subject);
|
|
if (!status.equals(value.getStatus())) {
|
|
throw new BusinessException(ErrorCode.VALIDATION_ERROR, message + ",当前状态:" + value.getStatus());
|
|
}
|
|
return value;
|
|
}
|
|
|
|
private QuotaControlBitEntity requireLatest(InstitutionKeySubject subject) {
|
|
QuotaControlBitEntity value = latest(subject);
|
|
if (value == null) throw new BusinessException(ErrorCode.RESOURCE_NOT_FOUND, "当前没有步骤八额度控制位记录");
|
|
return value;
|
|
}
|
|
|
|
private QuotaControlBitEntity latest(InstitutionKeySubject subject) {
|
|
return mapper.selectOne(new LambdaQueryWrapper<QuotaControlBitEntity>()
|
|
.eq(QuotaControlBitEntity::getUserId, subject.getUserId())
|
|
.eq(QuotaControlBitEntity::getSchoolId, subject.getSchoolId())
|
|
.eq(QuotaControlBitEntity::getClassId, subject.getClassId())
|
|
.eq(QuotaControlBitEntity::getDeleted, false)
|
|
.orderByDesc(QuotaControlBitEntity::getCreatedAt).last("LIMIT 1"));
|
|
}
|
|
|
|
private void update(QuotaControlBitEntity value, String from, String to, String operator,
|
|
LambdaUpdateWrapper<QuotaControlBitEntity> fields) {
|
|
fields.eq(QuotaControlBitEntity::getId, value.getId())
|
|
.eq(QuotaControlBitEntity::getStatus, from)
|
|
.eq(QuotaControlBitEntity::getDeleted, false)
|
|
.set(QuotaControlBitEntity::getStatus, to)
|
|
.set(QuotaControlBitEntity::getUpdatedAt, LocalDateTime.now())
|
|
.set(QuotaControlBitEntity::getUpdatedBy, operator);
|
|
if (mapper.update(null, fields) != 1) {
|
|
throw new BusinessException(ErrorCode.VALIDATION_ERROR, "步骤八状态已变化,不能重复或越级操作");
|
|
}
|
|
}
|
|
|
|
private BusinessException keyError(InstitutionKeySubject subject, String message) {
|
|
int sequence = errorRecorder.recordScoreError(subject.getUserId());
|
|
return new BusinessException(ErrorCode.VALIDATION_ERROR,
|
|
message + ",本次实训第" + sequence + "次错误");
|
|
}
|
|
}
|