You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
digital-rmb-backend/src/main/java/com/yau/digitalrmb/institutionidentity/application/QuotaControlBitService.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 + "次错误");
}
}