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() .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() .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() .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() .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() .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() .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().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() .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() .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 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 + "次错误"); } }