feat:发行货币的功能开发
parent
c6f2a24293
commit
f92cb83566
@ -0,0 +1,29 @@
|
|||||||
|
package com.yau.digitalrmb.institutionidentity.application;
|
||||||
|
|
||||||
|
import com.yau.digitalrmb.institutionidentity.domain.InstitutionIdentifierApplication;
|
||||||
|
|
||||||
|
public class CommercialBankInstitutionIdentifierResult {
|
||||||
|
private final long applicationId;
|
||||||
|
private final String applicationStatus;
|
||||||
|
private final int status;
|
||||||
|
private final String statusText;
|
||||||
|
private final String institutionIdentifier;
|
||||||
|
private final String fullInstitutionIdentifier;
|
||||||
|
|
||||||
|
public CommercialBankInstitutionIdentifierResult(InstitutionIdentifierApplication application) {
|
||||||
|
boolean feedbacked = application.getStatus() == InstitutionIdentifierApplication.Status.FEEDBACKED;
|
||||||
|
this.applicationId = application.getId();
|
||||||
|
this.applicationStatus = application.getStatus().name();
|
||||||
|
this.status = feedbacked ? 2 : 1;
|
||||||
|
this.statusText = feedbacked ? "已确认" : "待确认";
|
||||||
|
this.institutionIdentifier = feedbacked ? application.getInstitutionIdentifier() : null;
|
||||||
|
this.fullInstitutionIdentifier = feedbacked ? application.getDigest() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getApplicationId() { return applicationId; }
|
||||||
|
public String getApplicationStatus() { return applicationStatus; }
|
||||||
|
public int getStatus() { return status; }
|
||||||
|
public String getStatusText() { return statusText; }
|
||||||
|
public String getInstitutionIdentifier() { return institutionIdentifier; }
|
||||||
|
public String getFullInstitutionIdentifier() { return fullInstitutionIdentifier; }
|
||||||
|
}
|
||||||
@ -0,0 +1,78 @@
|
|||||||
|
package com.yau.digitalrmb.institutionidentity.application;
|
||||||
|
|
||||||
|
import com.yau.digitalrmb.institutionidentity.domain.InstitutionIdentifierApplication;
|
||||||
|
import com.yau.digitalrmb.institutionidentity.domain.InstitutionIdentifierRepository;
|
||||||
|
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;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class InstitutionIdentifierFeedbackService {
|
||||||
|
@Resource
|
||||||
|
private InstitutionIdentifierRepository repository;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private InstitutionTrainingScoreService scoreService;
|
||||||
|
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public InstitutionIdentifierFinalResult finalIdentifier(InstitutionKeySubject subject) {
|
||||||
|
InstitutionIdentifierApplication application = requireLatest(subject);
|
||||||
|
if (application.getStatus() != InstitutionIdentifierApplication.Status.ISSUED
|
||||||
|
&& application.getStatus() != InstitutionIdentifierApplication.Status.FEEDBACKED) {
|
||||||
|
throw new BusinessException(ErrorCode.VALIDATION_ERROR,
|
||||||
|
"央行尚未完成签名确权,不能查看最终机构标识,当前状态:"
|
||||||
|
+ application.getStatus().name());
|
||||||
|
}
|
||||||
|
return new InstitutionIdentifierFinalResult(application);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public InstitutionIdentifierFinalResult feedback(InstitutionKeySubject subject, String operator) {
|
||||||
|
InstitutionIdentifierApplication application = requireLatest(subject);
|
||||||
|
if (application.getStatus() != InstitutionIdentifierApplication.Status.ISSUED) {
|
||||||
|
throw new BusinessException(ErrorCode.VALIDATION_ERROR,
|
||||||
|
"当前没有待反馈的最终机构标识,当前状态:" + application.getStatus().name());
|
||||||
|
}
|
||||||
|
InstitutionIdentifierApplication.Status expectedStatus = application.getStatus();
|
||||||
|
invokeDomain(application::feedback);
|
||||||
|
repository.update(application, expectedStatus, "FEEDBACK",
|
||||||
|
"央行将最终机构标识同步反馈到商业银行端", operator);
|
||||||
|
scoreService.markStepCompleted(subject.getUserId(), 1);
|
||||||
|
return new InstitutionIdentifierFinalResult(application);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public CommercialBankInstitutionIdentifierResult commercialBankResult(InstitutionKeySubject subject) {
|
||||||
|
InstitutionIdentifierApplication application = requireLatest(subject);
|
||||||
|
if (!isVisibleToCommercialBank(application.getStatus())) {
|
||||||
|
throw new BusinessException(ErrorCode.RESOURCE_NOT_FOUND,
|
||||||
|
"商业银行第二私钥签名成功后才会出现待确认数据");
|
||||||
|
}
|
||||||
|
return new CommercialBankInstitutionIdentifierResult(application);
|
||||||
|
}
|
||||||
|
|
||||||
|
private InstitutionIdentifierApplication requireLatest(InstitutionKeySubject subject) {
|
||||||
|
return repository.findLatestBySubject(subject.getUserId(), subject.getSchoolId(), subject.getClassId())
|
||||||
|
.orElseThrow(() -> new BusinessException(
|
||||||
|
ErrorCode.RESOURCE_NOT_FOUND, "当前没有机构标识申请数据"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isVisibleToCommercialBank(InstitutionIdentifierApplication.Status status) {
|
||||||
|
return status == InstitutionIdentifierApplication.Status.SIGNED
|
||||||
|
|| status == InstitutionIdentifierApplication.Status.PACKAGED
|
||||||
|
|| status == InstitutionIdentifierApplication.Status.VERIFIED
|
||||||
|
|| status == InstitutionIdentifierApplication.Status.ISSUED
|
||||||
|
|| status == InstitutionIdentifierApplication.Status.FEEDBACKED;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void invokeDomain(Runnable operation) {
|
||||||
|
try {
|
||||||
|
operation.run();
|
||||||
|
} catch (IllegalArgumentException | IllegalStateException exception) {
|
||||||
|
throw new BusinessException(ErrorCode.VALIDATION_ERROR, exception.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
package com.yau.digitalrmb.institutionidentity.application;
|
||||||
|
|
||||||
|
import com.yau.digitalrmb.institutionidentity.domain.InstitutionIdentifierApplication;
|
||||||
|
|
||||||
|
public class InstitutionIdentifierFinalResult {
|
||||||
|
private final long applicationId;
|
||||||
|
private final String applicationStatus;
|
||||||
|
private final int feedbackStatus;
|
||||||
|
private final String feedbackStatusText;
|
||||||
|
private final String institutionIdentifier;
|
||||||
|
private final String fullInstitutionIdentifier;
|
||||||
|
private final String centralBankSignature;
|
||||||
|
|
||||||
|
public InstitutionIdentifierFinalResult(InstitutionIdentifierApplication application) {
|
||||||
|
boolean feedbacked = application.getStatus() == InstitutionIdentifierApplication.Status.FEEDBACKED;
|
||||||
|
this.applicationId = application.getId();
|
||||||
|
this.applicationStatus = application.getStatus().name();
|
||||||
|
this.feedbackStatus = feedbacked ? 2 : 1;
|
||||||
|
this.feedbackStatusText = feedbacked ? "已反馈" : "待反馈";
|
||||||
|
this.institutionIdentifier = application.getInstitutionIdentifier();
|
||||||
|
this.fullInstitutionIdentifier = application.getDigest();
|
||||||
|
this.centralBankSignature = application.getCentralBankSignature();
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getApplicationId() { return applicationId; }
|
||||||
|
public String getApplicationStatus() { return applicationStatus; }
|
||||||
|
public int getFeedbackStatus() { return feedbackStatus; }
|
||||||
|
public String getFeedbackStatusText() { return feedbackStatusText; }
|
||||||
|
public String getInstitutionIdentifier() { return institutionIdentifier; }
|
||||||
|
public String getFullInstitutionIdentifier() { return fullInstitutionIdentifier; }
|
||||||
|
public String getCentralBankSignature() { return centralBankSignature; }
|
||||||
|
}
|
||||||
@ -0,0 +1,117 @@
|
|||||||
|
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.infrastructure.StuModuleScoreDetailsEntity;
|
||||||
|
import com.yau.digitalrmb.institutionidentity.infrastructure.StuModuleScoreDetailsMapper;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.RoundingMode;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将本实验的错误次数、完成进度和项目得分同步到平台统一成绩明细表。
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class InstitutionTrainingScoreService {
|
||||||
|
public static final String MODULE_NAME = "中国人民银行生成数字货币";
|
||||||
|
private static final int TRAINING_SERIAL_NUMBER = 3;
|
||||||
|
private static final double TOTAL_SCORE = 100.0;
|
||||||
|
private static final int TOTAL_TRAINING_MODULES = 9;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private StuModuleScoreDetailsMapper scoreDetailsMapper;
|
||||||
|
|
||||||
|
@Value("${institution-training.score.wrong-deduct:0.50}")
|
||||||
|
private BigDecimal wrongDeduct = new BigDecimal("0.50");
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public void recordError(String userId, int errorCount) {
|
||||||
|
if (userId == null || userId.trim().isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
StuModuleScoreDetailsEntity detail = getOrCreate(userId);
|
||||||
|
detail.setCompletionStatus(String.valueOf(errorCount));
|
||||||
|
detail.setScoreProject(score(errorCount, progress(detail.getSchedule())));
|
||||||
|
scoreDetailsMapper.updateById(detail);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public void markStepCompleted(String userId, int completedStep) {
|
||||||
|
if (userId == null || userId.trim().isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
StuModuleScoreDetailsEntity detail = getOrCreate(userId);
|
||||||
|
int errorCount = errorCount(detail.getCompletionStatus());
|
||||||
|
int normalizedStep = Math.max(0, Math.min(TOTAL_TRAINING_MODULES, completedStep));
|
||||||
|
double schedule = BigDecimal.valueOf(normalizedStep)
|
||||||
|
.multiply(BigDecimal.valueOf(TOTAL_SCORE))
|
||||||
|
.divide(BigDecimal.valueOf(TOTAL_TRAINING_MODULES), 2, RoundingMode.HALF_UP)
|
||||||
|
.doubleValue();
|
||||||
|
detail.setCompletionStatus(String.valueOf(errorCount));
|
||||||
|
detail.setSchedule(schedule);
|
||||||
|
detail.setScoreProject(score(errorCount, schedule));
|
||||||
|
scoreDetailsMapper.updateById(detail);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public StuModuleScoreDetailsEntity detail(String userId) {
|
||||||
|
if (userId == null || userId.trim().isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return scoreDetailsMapper.selectOne(new LambdaQueryWrapper<StuModuleScoreDetailsEntity>()
|
||||||
|
.eq(StuModuleScoreDetailsEntity::getUserId, userId)
|
||||||
|
.eq(StuModuleScoreDetailsEntity::getMoudule, MODULE_NAME)
|
||||||
|
.eq(StuModuleScoreDetailsEntity::getSerialNumber, TRAINING_SERIAL_NUMBER)
|
||||||
|
.last("LIMIT 1"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private StuModuleScoreDetailsEntity getOrCreate(String userId) {
|
||||||
|
StuModuleScoreDetailsEntity existing = detail(userId);
|
||||||
|
if (existing != null) {
|
||||||
|
return existing;
|
||||||
|
}
|
||||||
|
StuModuleScoreDetailsEntity created = new StuModuleScoreDetailsEntity();
|
||||||
|
created.setId(UUID.randomUUID().toString());
|
||||||
|
created.setMoudule(MODULE_NAME);
|
||||||
|
created.setLearningProjects("实验实训");
|
||||||
|
created.setAssessmentItems("三步流程错误次数");
|
||||||
|
created.setScoringCriteria(wrongDeduct.stripTrailingZeros().toPlainString());
|
||||||
|
created.setCompletionStatus("0");
|
||||||
|
created.setScoreProject(0.0);
|
||||||
|
created.setUserId(userId);
|
||||||
|
created.setTotalScore(TOTAL_SCORE);
|
||||||
|
created.setSerialNumber(TRAINING_SERIAL_NUMBER);
|
||||||
|
created.setSchedule(0.0);
|
||||||
|
scoreDetailsMapper.insert(created);
|
||||||
|
return created;
|
||||||
|
}
|
||||||
|
|
||||||
|
private double score(int errorCount, double schedule) {
|
||||||
|
BigDecimal base = BigDecimal.valueOf(TOTAL_SCORE)
|
||||||
|
.subtract(wrongDeduct.multiply(BigDecimal.valueOf(Math.max(0, errorCount))))
|
||||||
|
.max(BigDecimal.ZERO);
|
||||||
|
return base.multiply(BigDecimal.valueOf(schedule))
|
||||||
|
.divide(BigDecimal.valueOf(TOTAL_SCORE), 2, RoundingMode.HALF_UP)
|
||||||
|
.doubleValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
private double progress(Double schedule) {
|
||||||
|
return schedule == null ? 0.0 : schedule;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int errorCount(String completionStatus) {
|
||||||
|
if (completionStatus == null || completionStatus.trim().isEmpty()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return Math.max(0, Integer.parseInt(completionStatus));
|
||||||
|
} catch (NumberFormatException exception) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,48 @@
|
|||||||
|
package com.yau.digitalrmb.institutionidentity.application;
|
||||||
|
|
||||||
|
import com.yau.digitalrmb.institutionidentity.domain.TransactionInformationIdentifier;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
public class TransactionInformationIdentifierResult {
|
||||||
|
private final long id;
|
||||||
|
private final long verificationId;
|
||||||
|
private final long requestId;
|
||||||
|
private final long identifierApplicationId;
|
||||||
|
private final BigDecimal amount;
|
||||||
|
private final String institutionIdentifier;
|
||||||
|
private final String transactionTimestamp;
|
||||||
|
private final String originalText;
|
||||||
|
private final String digestAlgorithm;
|
||||||
|
private final String digest;
|
||||||
|
private final String transactionIdentifier;
|
||||||
|
private final String status;
|
||||||
|
|
||||||
|
public TransactionInformationIdentifierResult(TransactionInformationIdentifier value) {
|
||||||
|
this.id = value.getId();
|
||||||
|
this.verificationId = value.getVerificationId();
|
||||||
|
this.requestId = value.getRequestId();
|
||||||
|
this.identifierApplicationId = value.getIdentifierApplicationId();
|
||||||
|
this.amount = value.getAmount();
|
||||||
|
this.institutionIdentifier = value.getInstitutionIdentifier();
|
||||||
|
this.transactionTimestamp = value.getTransactionTimestamp();
|
||||||
|
this.originalText = value.getOriginalText();
|
||||||
|
this.digestAlgorithm = value.getDigest() == null ? null : "SM3";
|
||||||
|
this.digest = value.getDigest();
|
||||||
|
this.transactionIdentifier = value.getTransactionIdentifier();
|
||||||
|
this.status = value.getStatus().name();
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getId() { return id; }
|
||||||
|
public long getVerificationId() { return verificationId; }
|
||||||
|
public long getRequestId() { return requestId; }
|
||||||
|
public long getIdentifierApplicationId() { return identifierApplicationId; }
|
||||||
|
public BigDecimal getAmount() { return amount; }
|
||||||
|
public String getInstitutionIdentifier() { return institutionIdentifier; }
|
||||||
|
public String getTransactionTimestamp() { return transactionTimestamp; }
|
||||||
|
public String getOriginalText() { return originalText; }
|
||||||
|
public String getDigestAlgorithm() { return digestAlgorithm; }
|
||||||
|
public String getDigest() { return digest; }
|
||||||
|
public String getTransactionIdentifier() { return transactionIdentifier; }
|
||||||
|
public String getStatus() { return status; }
|
||||||
|
}
|
||||||
@ -0,0 +1,175 @@
|
|||||||
|
package com.yau.digitalrmb.institutionidentity.application;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.yau.digitalrmb.institutionidentity.domain.InstitutionIdentityCryptography;
|
||||||
|
import com.yau.digitalrmb.institutionidentity.domain.TransactionInformationIdentifier;
|
||||||
|
import com.yau.digitalrmb.institutionidentity.domain.TransactionInformationIdentifierRepository;
|
||||||
|
import com.yau.digitalrmb.institutionidentity.infrastructure.CurrencyGenerationRequestEntity;
|
||||||
|
import com.yau.digitalrmb.institutionidentity.infrastructure.CurrencyGenerationRequestMapper;
|
||||||
|
import com.yau.digitalrmb.institutionidentity.infrastructure.CurrencyGenerationVerificationEntity;
|
||||||
|
import com.yau.digitalrmb.institutionidentity.infrastructure.CurrencyGenerationVerificationMapper;
|
||||||
|
import com.yau.digitalrmb.institutionidentity.infrastructure.InstitutionIdentifierApplicationEntity;
|
||||||
|
import com.yau.digitalrmb.institutionidentity.infrastructure.InstitutionIdentifierApplicationMapper;
|
||||||
|
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.time.Clock;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.ZoneOffset;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class TransactionInformationIdentifierService {
|
||||||
|
private static final DateTimeFormatter TIMESTAMP = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private CurrencyGenerationVerificationMapper verificationMapper;
|
||||||
|
@Resource
|
||||||
|
private CurrencyGenerationRequestMapper requestMapper;
|
||||||
|
@Resource
|
||||||
|
private InstitutionIdentifierApplicationMapper applicationMapper;
|
||||||
|
@Resource
|
||||||
|
private TransactionInformationIdentifierRepository repository;
|
||||||
|
@Resource
|
||||||
|
private InstitutionIdentityCryptography cryptography;
|
||||||
|
@Resource
|
||||||
|
private InstitutionTrainingScoreService scoreService;
|
||||||
|
|
||||||
|
private final Clock clock = Clock.systemUTC();
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public TransactionInformationIdentifierResult prepare(InstitutionKeySubject subject, String operator) {
|
||||||
|
CurrencyGenerationVerificationEntity verification = requireCompletedVerification(subject);
|
||||||
|
CurrencyGenerationRequestEntity request = requireCompletedRequest(verification.getRequestId(), subject);
|
||||||
|
InstitutionIdentifierApplicationEntity application = requireCompletedIdentifier(
|
||||||
|
request.getIdentifierApplicationId(), subject);
|
||||||
|
|
||||||
|
TransactionInformationIdentifier existing = repository.findLatestByVerificationAndSubject(
|
||||||
|
verification.getId(), subject.getUserId(), subject.getSchoolId(), subject.getClassId()).orElse(null);
|
||||||
|
if (existing != null) {
|
||||||
|
throw new BusinessException(ErrorCode.VALIDATION_ERROR,
|
||||||
|
"当前步骤三结果已生成模块四记录,不能重复准备");
|
||||||
|
}
|
||||||
|
|
||||||
|
String timestamp = LocalDateTime.ofInstant(clock.instant(), ZoneOffset.UTC).format(TIMESTAMP);
|
||||||
|
TransactionInformationIdentifier value = invokeResult(() -> TransactionInformationIdentifier.prepare(
|
||||||
|
verification.getId(), request.getId(), application.getId(), request.getAmount(),
|
||||||
|
application.getInstitutionIdentifier(), timestamp));
|
||||||
|
long id = repository.save(value, subject.getUserId(), subject.getSchoolId(), subject.getClassId(), operator);
|
||||||
|
return new TransactionInformationIdentifierResult(TransactionInformationIdentifier.restore(id,
|
||||||
|
value.getVerificationId(), value.getRequestId(), value.getIdentifierApplicationId(),
|
||||||
|
value.getAmount(), value.getInstitutionIdentifier(), value.getTransactionTimestamp(),
|
||||||
|
value.getOriginalText(), null, null, value.getStatus()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public TransactionInformationIdentifierResult digest(InstitutionKeySubject subject, String operator) {
|
||||||
|
TransactionInformationIdentifier value = requireStatus(subject,
|
||||||
|
TransactionInformationIdentifier.Status.PREPARED, "请先获取前序数据并准备交易信息标识");
|
||||||
|
TransactionInformationIdentifier.Status expected = value.getStatus();
|
||||||
|
String digest = cryptography.sm3(value.getOriginalText());
|
||||||
|
invoke(() -> value.recordDigest(digest));
|
||||||
|
repository.update(value, expected, "DIGEST", "使用SM3计算交易信息标识摘要", operator);
|
||||||
|
return new TransactionInformationIdentifierResult(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public TransactionInformationIdentifierResult assemble(InstitutionKeySubject subject, String operator) {
|
||||||
|
TransactionInformationIdentifier value = requireStatus(subject,
|
||||||
|
TransactionInformationIdentifier.Status.DIGESTED, "请先使用SM3生成摘要");
|
||||||
|
TransactionInformationIdentifier.Status expected = value.getStatus();
|
||||||
|
invoke(value::assemble);
|
||||||
|
repository.update(value, expected, "ASSEMBLE", "将固定前缀TXN_与SM3摘要组装为交易信息标识", operator);
|
||||||
|
scoreService.markStepCompleted(subject.getUserId(), 4);
|
||||||
|
return new TransactionInformationIdentifierResult(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public TransactionInformationIdentifierResult detail(InstitutionKeySubject subject) {
|
||||||
|
TransactionInformationIdentifier value = repository.findLatestBySubject(subject.getUserId(),
|
||||||
|
subject.getSchoolId(), subject.getClassId()).orElseThrow(() ->
|
||||||
|
new BusinessException(ErrorCode.RESOURCE_NOT_FOUND, "当前没有交易信息标识记录"));
|
||||||
|
return new TransactionInformationIdentifierResult(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private CurrencyGenerationVerificationEntity requireCompletedVerification(InstitutionKeySubject subject) {
|
||||||
|
CurrencyGenerationVerificationEntity value = verificationMapper.selectOne(
|
||||||
|
new LambdaQueryWrapper<CurrencyGenerationVerificationEntity>()
|
||||||
|
.eq(CurrencyGenerationVerificationEntity::getUserId, subject.getUserId())
|
||||||
|
.eq(CurrencyGenerationVerificationEntity::getSchoolId, subject.getSchoolId())
|
||||||
|
.eq(CurrencyGenerationVerificationEntity::getClassId, subject.getClassId())
|
||||||
|
.eq(CurrencyGenerationVerificationEntity::getStatus, "RESPONSE_RETURNED")
|
||||||
|
.eq(CurrencyGenerationVerificationEntity::getDeleted, false)
|
||||||
|
.orderByDesc(CurrencyGenerationVerificationEntity::getCreatedAt)
|
||||||
|
.last("LIMIT 1"));
|
||||||
|
if (value == null) {
|
||||||
|
throw new BusinessException(ErrorCode.VALIDATION_ERROR,
|
||||||
|
"步骤三尚未完成确认报文返回,不能进入模块四");
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private CurrencyGenerationRequestEntity requireCompletedRequest(long requestId, InstitutionKeySubject subject) {
|
||||||
|
CurrencyGenerationRequestEntity value = requestMapper.selectOne(
|
||||||
|
new LambdaQueryWrapper<CurrencyGenerationRequestEntity>()
|
||||||
|
.eq(CurrencyGenerationRequestEntity::getId, requestId)
|
||||||
|
.eq(CurrencyGenerationRequestEntity::getUserId, subject.getUserId())
|
||||||
|
.eq(CurrencyGenerationRequestEntity::getSchoolId, subject.getSchoolId())
|
||||||
|
.eq(CurrencyGenerationRequestEntity::getClassId, subject.getClassId())
|
||||||
|
.eq(CurrencyGenerationRequestEntity::getStatus, "RECEIVED")
|
||||||
|
.eq(CurrencyGenerationRequestEntity::getDeleted, false));
|
||||||
|
if (value == null) {
|
||||||
|
throw new BusinessException(ErrorCode.VALIDATION_ERROR,
|
||||||
|
"步骤二尚未完成央行接收,不能进入模块四");
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private InstitutionIdentifierApplicationEntity requireCompletedIdentifier(long applicationId,
|
||||||
|
InstitutionKeySubject subject) {
|
||||||
|
InstitutionIdentifierApplicationEntity value = applicationMapper.selectOne(
|
||||||
|
new LambdaQueryWrapper<InstitutionIdentifierApplicationEntity>()
|
||||||
|
.eq(InstitutionIdentifierApplicationEntity::getId, applicationId)
|
||||||
|
.eq(InstitutionIdentifierApplicationEntity::getUserId, subject.getUserId())
|
||||||
|
.eq(InstitutionIdentifierApplicationEntity::getSchoolId, subject.getSchoolId())
|
||||||
|
.eq(InstitutionIdentifierApplicationEntity::getClassId, subject.getClassId())
|
||||||
|
.eq(InstitutionIdentifierApplicationEntity::getStatus, "FEEDBACKED")
|
||||||
|
.eq(InstitutionIdentifierApplicationEntity::getDeleted, false));
|
||||||
|
if (value == null) {
|
||||||
|
throw new BusinessException(ErrorCode.VALIDATION_ERROR,
|
||||||
|
"步骤一尚未完成机构标识反馈,不能进入模块四");
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TransactionInformationIdentifier requireStatus(InstitutionKeySubject subject,
|
||||||
|
TransactionInformationIdentifier.Status status,
|
||||||
|
String message) {
|
||||||
|
return repository.findLatestBySubjectAndStatus(subject.getUserId(), subject.getSchoolId(),
|
||||||
|
subject.getClassId(), status).orElseThrow(() ->
|
||||||
|
new BusinessException(ErrorCode.VALIDATION_ERROR, message));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void invoke(Runnable operation) {
|
||||||
|
try {
|
||||||
|
operation.run();
|
||||||
|
} catch (IllegalArgumentException | IllegalStateException exception) {
|
||||||
|
throw new BusinessException(ErrorCode.VALIDATION_ERROR, exception.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <T> T invokeResult(Supplier<T> supplier) {
|
||||||
|
try {
|
||||||
|
return supplier.get();
|
||||||
|
} catch (IllegalArgumentException | IllegalStateException exception) {
|
||||||
|
throw new BusinessException(ErrorCode.VALIDATION_ERROR, exception.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private interface Supplier<T> {
|
||||||
|
T get();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
package com.yau.digitalrmb.institutionidentity.domain;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public interface TransactionInformationIdentifierRepository {
|
||||||
|
long save(TransactionInformationIdentifier value, String userId, long schoolId, long classId, String operator);
|
||||||
|
|
||||||
|
Optional<TransactionInformationIdentifier> findByIdAndSubject(long id, String userId,
|
||||||
|
long schoolId, long classId);
|
||||||
|
|
||||||
|
Optional<TransactionInformationIdentifier> findLatestByVerificationAndSubject(long verificationId,
|
||||||
|
String userId,
|
||||||
|
long schoolId,
|
||||||
|
long classId);
|
||||||
|
|
||||||
|
Optional<TransactionInformationIdentifier> findLatestBySubject(String userId, long schoolId, long classId);
|
||||||
|
|
||||||
|
Optional<TransactionInformationIdentifier> findLatestBySubjectAndStatus(
|
||||||
|
String userId, long schoolId, long classId, TransactionInformationIdentifier.Status status);
|
||||||
|
|
||||||
|
void update(TransactionInformationIdentifier value,
|
||||||
|
TransactionInformationIdentifier.Status expectedStatus,
|
||||||
|
String operation, String detail, String operator);
|
||||||
|
}
|
||||||
@ -0,0 +1,152 @@
|
|||||||
|
package com.yau.digitalrmb.institutionidentity.infrastructure;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||||
|
import com.yau.digitalrmb.institutionidentity.domain.TransactionInformationIdentifier;
|
||||||
|
import com.yau.digitalrmb.institutionidentity.domain.TransactionInformationIdentifierRepository;
|
||||||
|
import com.yau.digitalrmb.shared.api.ErrorCode;
|
||||||
|
import com.yau.digitalrmb.shared.exception.BusinessException;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public class MybatisTransactionInformationIdentifierRepository
|
||||||
|
implements TransactionInformationIdentifierRepository {
|
||||||
|
@Resource
|
||||||
|
private TransactionInformationIdentifierMapper mapper;
|
||||||
|
@Resource
|
||||||
|
private TransactionInformationIdentifierOperationLogMapper logMapper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long save(TransactionInformationIdentifier value, String userId,
|
||||||
|
long schoolId, long classId, String operator) {
|
||||||
|
TransactionInformationIdentifierEntity entity = toEntity(value);
|
||||||
|
entity.setUserId(userId);
|
||||||
|
entity.setSchoolId(schoolId);
|
||||||
|
entity.setClassId(classId);
|
||||||
|
entity.setCreatedAt(LocalDateTime.now());
|
||||||
|
entity.setUpdatedAt(LocalDateTime.now());
|
||||||
|
entity.setCreatedBy(operator);
|
||||||
|
entity.setUpdatedBy(operator);
|
||||||
|
entity.setDeleted(false);
|
||||||
|
mapper.insert(entity);
|
||||||
|
insertLog(entity.getId(), "PREPARE", null, value.getStatus().name(),
|
||||||
|
"从已完成的步骤一至步骤三复制金额和机构标识并生成交易时间戳", operator);
|
||||||
|
return entity.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<TransactionInformationIdentifier> findByIdAndSubject(long id, String userId,
|
||||||
|
long schoolId, long classId) {
|
||||||
|
return Optional.ofNullable(mapper.selectOne(new LambdaQueryWrapper<TransactionInformationIdentifierEntity>()
|
||||||
|
.eq(TransactionInformationIdentifierEntity::getId, id)
|
||||||
|
.eq(TransactionInformationIdentifierEntity::getUserId, userId)
|
||||||
|
.eq(TransactionInformationIdentifierEntity::getSchoolId, schoolId)
|
||||||
|
.eq(TransactionInformationIdentifierEntity::getClassId, classId)
|
||||||
|
.eq(TransactionInformationIdentifierEntity::getDeleted, false)))
|
||||||
|
.map(this::toDomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<TransactionInformationIdentifier> findLatestByVerificationAndSubject(
|
||||||
|
long verificationId, String userId, long schoolId, long classId) {
|
||||||
|
return Optional.ofNullable(mapper.selectOne(new LambdaQueryWrapper<TransactionInformationIdentifierEntity>()
|
||||||
|
.eq(TransactionInformationIdentifierEntity::getVerificationId, verificationId)
|
||||||
|
.eq(TransactionInformationIdentifierEntity::getUserId, userId)
|
||||||
|
.eq(TransactionInformationIdentifierEntity::getSchoolId, schoolId)
|
||||||
|
.eq(TransactionInformationIdentifierEntity::getClassId, classId)
|
||||||
|
.eq(TransactionInformationIdentifierEntity::getDeleted, false)
|
||||||
|
.orderByDesc(TransactionInformationIdentifierEntity::getCreatedAt)
|
||||||
|
.last("LIMIT 1")))
|
||||||
|
.map(this::toDomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<TransactionInformationIdentifier> findLatestBySubject(String userId, long schoolId, long classId) {
|
||||||
|
return Optional.ofNullable(mapper.selectOne(new LambdaQueryWrapper<TransactionInformationIdentifierEntity>()
|
||||||
|
.eq(TransactionInformationIdentifierEntity::getUserId, userId)
|
||||||
|
.eq(TransactionInformationIdentifierEntity::getSchoolId, schoolId)
|
||||||
|
.eq(TransactionInformationIdentifierEntity::getClassId, classId)
|
||||||
|
.eq(TransactionInformationIdentifierEntity::getDeleted, false)
|
||||||
|
.orderByDesc(TransactionInformationIdentifierEntity::getCreatedAt)
|
||||||
|
.last("LIMIT 1")))
|
||||||
|
.map(this::toDomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<TransactionInformationIdentifier> findLatestBySubjectAndStatus(
|
||||||
|
String userId, long schoolId, long classId, TransactionInformationIdentifier.Status status) {
|
||||||
|
return Optional.ofNullable(mapper.selectOne(new LambdaQueryWrapper<TransactionInformationIdentifierEntity>()
|
||||||
|
.eq(TransactionInformationIdentifierEntity::getUserId, userId)
|
||||||
|
.eq(TransactionInformationIdentifierEntity::getSchoolId, schoolId)
|
||||||
|
.eq(TransactionInformationIdentifierEntity::getClassId, classId)
|
||||||
|
.eq(TransactionInformationIdentifierEntity::getStatus, status.name())
|
||||||
|
.eq(TransactionInformationIdentifierEntity::getDeleted, false)
|
||||||
|
.orderByDesc(TransactionInformationIdentifierEntity::getCreatedAt)
|
||||||
|
.last("LIMIT 1")))
|
||||||
|
.map(this::toDomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update(TransactionInformationIdentifier value,
|
||||||
|
TransactionInformationIdentifier.Status expectedStatus,
|
||||||
|
String operation, String detail, String operator) {
|
||||||
|
LambdaUpdateWrapper<TransactionInformationIdentifierEntity> update =
|
||||||
|
new LambdaUpdateWrapper<TransactionInformationIdentifierEntity>()
|
||||||
|
.eq(TransactionInformationIdentifierEntity::getId, value.getId())
|
||||||
|
.eq(TransactionInformationIdentifierEntity::getStatus, expectedStatus.name())
|
||||||
|
.eq(TransactionInformationIdentifierEntity::getDeleted, false)
|
||||||
|
.set(TransactionInformationIdentifierEntity::getDigest, value.getDigest())
|
||||||
|
.set(TransactionInformationIdentifierEntity::getTransactionIdentifier,
|
||||||
|
value.getTransactionIdentifier())
|
||||||
|
.set(TransactionInformationIdentifierEntity::getStatus, value.getStatus().name())
|
||||||
|
.set(TransactionInformationIdentifierEntity::getUpdatedAt, LocalDateTime.now())
|
||||||
|
.set(TransactionInformationIdentifierEntity::getUpdatedBy, operator);
|
||||||
|
if (mapper.update(null, update) != 1) {
|
||||||
|
throw new BusinessException(ErrorCode.VALIDATION_ERROR,
|
||||||
|
"交易信息标识状态已变化,不能重复或越级操作");
|
||||||
|
}
|
||||||
|
insertLog(value.getId(), operation, expectedStatus.name(), value.getStatus().name(), detail, operator);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TransactionInformationIdentifierEntity toEntity(TransactionInformationIdentifier value) {
|
||||||
|
TransactionInformationIdentifierEntity entity = new TransactionInformationIdentifierEntity();
|
||||||
|
entity.setId(value.getId());
|
||||||
|
entity.setVerificationId(value.getVerificationId());
|
||||||
|
entity.setRequestId(value.getRequestId());
|
||||||
|
entity.setIdentifierApplicationId(value.getIdentifierApplicationId());
|
||||||
|
entity.setAmount(value.getAmount());
|
||||||
|
entity.setInstitutionIdentifier(value.getInstitutionIdentifier());
|
||||||
|
entity.setTransactionTimestamp(value.getTransactionTimestamp());
|
||||||
|
entity.setOriginalText(value.getOriginalText());
|
||||||
|
entity.setDigest(value.getDigest());
|
||||||
|
entity.setTransactionIdentifier(value.getTransactionIdentifier());
|
||||||
|
entity.setStatus(value.getStatus().name());
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TransactionInformationIdentifier toDomain(TransactionInformationIdentifierEntity entity) {
|
||||||
|
return TransactionInformationIdentifier.restore(entity.getId(), entity.getVerificationId(),
|
||||||
|
entity.getRequestId(), entity.getIdentifierApplicationId(), entity.getAmount(),
|
||||||
|
entity.getInstitutionIdentifier(), entity.getTransactionTimestamp(), entity.getOriginalText(),
|
||||||
|
entity.getDigest(), entity.getTransactionIdentifier(),
|
||||||
|
TransactionInformationIdentifier.Status.valueOf(entity.getStatus()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void insertLog(Long id, String operation, String fromStatus, String toStatus,
|
||||||
|
String detail, String operator) {
|
||||||
|
TransactionInformationIdentifierOperationLogEntity log =
|
||||||
|
new TransactionInformationIdentifierOperationLogEntity();
|
||||||
|
log.setTransactionIdentifierId(id);
|
||||||
|
log.setOperation(operation);
|
||||||
|
log.setFromStatus(fromStatus);
|
||||||
|
log.setToStatus(toStatus);
|
||||||
|
log.setOperationDetail(detail);
|
||||||
|
log.setCreatedAt(LocalDateTime.now());
|
||||||
|
log.setCreatedBy(operator);
|
||||||
|
logMapper.insert(log);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,44 @@
|
|||||||
|
package com.yau.digitalrmb.institutionidentity.infrastructure;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@TableName("stu_module_score_details")
|
||||||
|
public class StuModuleScoreDetailsEntity {
|
||||||
|
@TableId(type = IdType.ASSIGN_UUID)
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
private String moudule;
|
||||||
|
|
||||||
|
@TableField("learning_projects")
|
||||||
|
private String learningProjects;
|
||||||
|
|
||||||
|
@TableField("assessment_items")
|
||||||
|
private String assessmentItems;
|
||||||
|
|
||||||
|
@TableField("scoring_criteria")
|
||||||
|
private String scoringCriteria;
|
||||||
|
|
||||||
|
@TableField("completion_status")
|
||||||
|
private String completionStatus;
|
||||||
|
|
||||||
|
@TableField("score_project")
|
||||||
|
private Double scoreProject;
|
||||||
|
|
||||||
|
@TableField("user_id")
|
||||||
|
private String userId;
|
||||||
|
|
||||||
|
@TableField("total_score")
|
||||||
|
private Double totalScore;
|
||||||
|
|
||||||
|
@TableField("serial_number")
|
||||||
|
private Integer serialNumber;
|
||||||
|
|
||||||
|
private Double schedule;
|
||||||
|
}
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
package com.yau.digitalrmb.institutionidentity.infrastructure;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface StuModuleScoreDetailsMapper extends BaseMapper<StuModuleScoreDetailsEntity> {
|
||||||
|
}
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
package com.yau.digitalrmb.institutionidentity.infrastructure;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import com.yau.digitalrmb.shared.infrastructure.persistence.AuditableEntity;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@TableName("transaction_information_identifier")
|
||||||
|
public class TransactionInformationIdentifierEntity extends AuditableEntity {
|
||||||
|
@TableField("verification_id") private Long verificationId;
|
||||||
|
@TableField("request_id") private Long requestId;
|
||||||
|
@TableField("identifier_application_id") private Long identifierApplicationId;
|
||||||
|
private BigDecimal amount;
|
||||||
|
@TableField("institution_identifier") private String institutionIdentifier;
|
||||||
|
@TableField("transaction_timestamp") private String transactionTimestamp;
|
||||||
|
@TableField("original_text") private String originalText;
|
||||||
|
private String digest;
|
||||||
|
@TableField("transaction_identifier") private String transactionIdentifier;
|
||||||
|
private String status;
|
||||||
|
@TableField("user_id") private String userId;
|
||||||
|
@TableField("school_id") private Long schoolId;
|
||||||
|
@TableField("class_id") private Long classId;
|
||||||
|
}
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
package com.yau.digitalrmb.institutionidentity.infrastructure;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface TransactionInformationIdentifierMapper extends BaseMapper<TransactionInformationIdentifierEntity> {
|
||||||
|
}
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
package com.yau.digitalrmb.institutionidentity.infrastructure;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableLogic;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@TableName("transaction_information_identifier_operation_log")
|
||||||
|
public class TransactionInformationIdentifierOperationLogEntity {
|
||||||
|
@TableId(type = IdType.ASSIGN_ID)
|
||||||
|
private Long id;
|
||||||
|
@TableField("transaction_identifier_id") private Long transactionIdentifierId;
|
||||||
|
private String operation;
|
||||||
|
@TableField("from_status") private String fromStatus;
|
||||||
|
@TableField("to_status") private String toStatus;
|
||||||
|
@TableField("operation_detail") private String operationDetail;
|
||||||
|
@TableField("created_at") private LocalDateTime createdAt;
|
||||||
|
@TableField("created_by") private String createdBy;
|
||||||
|
@TableLogic
|
||||||
|
private Boolean deleted = false;
|
||||||
|
}
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
package com.yau.digitalrmb.institutionidentity.infrastructure;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface TransactionInformationIdentifierOperationLogMapper
|
||||||
|
extends BaseMapper<TransactionInformationIdentifierOperationLogEntity> {
|
||||||
|
}
|
||||||
@ -1,11 +1,8 @@
|
|||||||
package com.yau.digitalrmb.institutionidentity.interfaces.dto;
|
package com.yau.digitalrmb.institutionidentity.interfaces.dto;
|
||||||
|
|
||||||
import javax.validation.constraints.NotBlank;
|
|
||||||
|
|
||||||
public class ConfirmInstitutionIdentifierRequest {
|
public class ConfirmInstitutionIdentifierRequest {
|
||||||
@NotBlank(message = "签名密钥不能为空")
|
private String privateKey;
|
||||||
private String signingKeyId;
|
|
||||||
|
|
||||||
public String getSigningKeyId() { return signingKeyId; }
|
public String getPrivateKey() { return privateKey; }
|
||||||
public void setSigningKeyId(String signingKeyId) { this.signingKeyId = signingKeyId; }
|
public void setPrivateKey(String privateKey) { this.privateKey = privateKey; }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,8 @@
|
|||||||
package com.yau.digitalrmb.institutionidentity.interfaces.dto;
|
package com.yau.digitalrmb.institutionidentity.interfaces.dto;
|
||||||
|
|
||||||
import javax.validation.constraints.Size;
|
|
||||||
|
|
||||||
public class InstitutionIdentifierStepRequest {
|
public class InstitutionIdentifierStepRequest {
|
||||||
@Size(max = 64, message = "密钥标识长度不能超过64个字符")
|
private String privateKey;
|
||||||
private String keyId;
|
|
||||||
|
|
||||||
public String getKeyId() { return keyId; }
|
public String getPrivateKey() { return privateKey; }
|
||||||
public void setKeyId(String keyId) { this.keyId = keyId; }
|
public void setPrivateKey(String privateKey) { this.privateKey = privateKey; }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,11 @@
|
|||||||
|
package com.yau.digitalrmb.platformintegration.config;
|
||||||
|
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@ConditionalOnProperty(prefix = "platform-integration", name = "enabled", havingValue = "true")
|
||||||
|
@EnableConfigurationProperties(PlatformIntegrationProperties.class)
|
||||||
|
public class PlatformIntegrationConfig {
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
-- 为中国人民银行生成数字货币实验的过程记录表补齐逻辑删除字段。
|
||||||
|
-- 已存在的历史记录默认视为未删除。
|
||||||
|
|
||||||
|
-- 本脚本为一次性升级脚本,请勿重复执行。
|
||||||
|
ALTER TABLE institution_identifier_operation_log
|
||||||
|
ADD COLUMN deleted TINYINT(1) NOT NULL DEFAULT 0,
|
||||||
|
ADD INDEX idx_identifier_operation_application_deleted (application_id, deleted);
|
||||||
|
|
||||||
|
ALTER TABLE institution_identifier_training_error
|
||||||
|
ADD COLUMN deleted TINYINT(1) NOT NULL DEFAULT 0,
|
||||||
|
ADD INDEX idx_identifier_error_application_deleted (application_id, deleted);
|
||||||
|
|
||||||
|
ALTER TABLE institution_sm2_key_audit
|
||||||
|
ADD COLUMN deleted TINYINT(1) NOT NULL DEFAULT 0,
|
||||||
|
ADD INDEX idx_institution_key_audit_record_deleted (key_record_id, deleted);
|
||||||
|
|
||||||
|
ALTER TABLE currency_generation_request_operation_log
|
||||||
|
ADD COLUMN deleted TINYINT(1) NOT NULL DEFAULT 0,
|
||||||
|
ADD INDEX idx_currency_request_operation_deleted (request_id, deleted);
|
||||||
|
|
||||||
|
ALTER TABLE currency_generation_verification_operation_log
|
||||||
|
ADD COLUMN deleted TINYINT(1) NOT NULL DEFAULT 0,
|
||||||
|
ADD INDEX idx_currency_verification_operation_deleted (verification_id, deleted);
|
||||||
@ -0,0 +1,49 @@
|
|||||||
|
-- 模块四“生成交易信息标识”一次性 MySQL 升级脚本。
|
||||||
|
-- 适用于已经存在步骤一至步骤三业务表的数据库;执行前请先备份。
|
||||||
|
|
||||||
|
CREATE TABLE transaction_information_identifier (
|
||||||
|
id BIGINT PRIMARY KEY,
|
||||||
|
verification_id BIGINT NOT NULL,
|
||||||
|
request_id BIGINT NOT NULL,
|
||||||
|
identifier_application_id BIGINT NOT NULL,
|
||||||
|
amount DECIMAL(18, 2) NOT NULL,
|
||||||
|
institution_identifier VARCHAR(32) NOT NULL,
|
||||||
|
transaction_timestamp CHAR(14) NOT NULL,
|
||||||
|
original_text VARCHAR(256) NOT NULL,
|
||||||
|
digest CHAR(64),
|
||||||
|
transaction_identifier VARCHAR(80),
|
||||||
|
status VARCHAR(32) NOT NULL,
|
||||||
|
user_id VARCHAR(36) NOT NULL,
|
||||||
|
school_id BIGINT NOT NULL,
|
||||||
|
class_id BIGINT NOT NULL,
|
||||||
|
created_at TIMESTAMP NOT NULL,
|
||||||
|
updated_at TIMESTAMP NOT NULL,
|
||||||
|
created_by VARCHAR(64) NOT NULL,
|
||||||
|
updated_by VARCHAR(64) NOT NULL,
|
||||||
|
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
CONSTRAINT uk_transaction_identifier_verification UNIQUE
|
||||||
|
(verification_id, user_id, school_id, class_id),
|
||||||
|
CONSTRAINT fk_transaction_identifier_verification FOREIGN KEY (verification_id)
|
||||||
|
REFERENCES currency_generation_verification(id),
|
||||||
|
CONSTRAINT fk_transaction_identifier_request FOREIGN KEY (request_id)
|
||||||
|
REFERENCES currency_generation_request(id),
|
||||||
|
CONSTRAINT fk_transaction_identifier_application FOREIGN KEY (identifier_application_id)
|
||||||
|
REFERENCES institution_identifier_application(id),
|
||||||
|
INDEX idx_transaction_identifier_subject
|
||||||
|
(user_id, school_id, class_id, status, deleted)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='模块四交易信息标识';
|
||||||
|
|
||||||
|
CREATE TABLE transaction_information_identifier_operation_log (
|
||||||
|
id BIGINT PRIMARY KEY,
|
||||||
|
transaction_identifier_id BIGINT NOT NULL,
|
||||||
|
operation VARCHAR(32) NOT NULL,
|
||||||
|
from_status VARCHAR(32),
|
||||||
|
to_status VARCHAR(32) NOT NULL,
|
||||||
|
operation_detail VARCHAR(1024) NOT NULL,
|
||||||
|
created_at TIMESTAMP NOT NULL,
|
||||||
|
created_by VARCHAR(64) NOT NULL,
|
||||||
|
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
CONSTRAINT fk_transaction_identifier_operation FOREIGN KEY (transaction_identifier_id)
|
||||||
|
REFERENCES transaction_information_identifier(id),
|
||||||
|
INDEX idx_transaction_identifier_operation (transaction_identifier_id, created_at)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='模块四交易信息标识操作日志';
|
||||||
@ -0,0 +1,266 @@
|
|||||||
|
package com.yau.digitalrmb.institutionidentity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.yau.digitalrmb.institutionidentity.application.InstitutionKeyService;
|
||||||
|
import com.yau.digitalrmb.institutionidentity.application.InstitutionKeySubject;
|
||||||
|
import com.yau.digitalrmb.institutionidentity.domain.CurrencyGenerationRequest;
|
||||||
|
import com.yau.digitalrmb.institutionidentity.domain.CurrencyGenerationVerification;
|
||||||
|
import com.yau.digitalrmb.institutionidentity.infrastructure.CurrencyGenerationRequestEntity;
|
||||||
|
import com.yau.digitalrmb.institutionidentity.infrastructure.CurrencyGenerationRequestMapper;
|
||||||
|
import com.yau.digitalrmb.institutionidentity.infrastructure.CurrencyGenerationVerificationEntity;
|
||||||
|
import com.yau.digitalrmb.institutionidentity.infrastructure.CurrencyGenerationVerificationMapper;
|
||||||
|
import com.yau.digitalrmb.institutionidentity.infrastructure.InstitutionIdentifierApplicationEntity;
|
||||||
|
import com.yau.digitalrmb.institutionidentity.infrastructure.InstitutionIdentifierApplicationMapper;
|
||||||
|
import com.yau.digitalrmb.institutionidentity.infrastructure.StuModuleScoreDetailsEntity;
|
||||||
|
import com.yau.digitalrmb.institutionidentity.infrastructure.StuModuleScoreDetailsMapper;
|
||||||
|
import com.yau.digitalrmb.institutionidentity.infrastructure.TransactionInformationIdentifierEntity;
|
||||||
|
import com.yau.digitalrmb.institutionidentity.infrastructure.TransactionInformationIdentifierMapper;
|
||||||
|
import com.yau.digitalrmb.institutionidentity.infrastructure.TransactionInformationIdentifierOperationLogEntity;
|
||||||
|
import com.yau.digitalrmb.institutionidentity.infrastructure.TransactionInformationIdentifierOperationLogMapper;
|
||||||
|
import com.yau.digitalrmb.security.application.JwtTokenService;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
|
import org.springframework.test.context.ActiveProfiles;
|
||||||
|
import org.springframework.test.web.servlet.MockMvc;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||||
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||||
|
|
||||||
|
@SpringBootTest
|
||||||
|
@AutoConfigureMockMvc
|
||||||
|
@ActiveProfiles("test")
|
||||||
|
class TransactionInformationIdentifierControllerTest {
|
||||||
|
private static final String BASE_PATH =
|
||||||
|
"/api/v1/institution-identifiers/transaction-information-identifiers";
|
||||||
|
private static final String USER_ID = "00000000-0000-0000-0000-000000009004";
|
||||||
|
private static final long SCHOOL_ID = 1001L;
|
||||||
|
private static final long CLASS_ID = 2001L;
|
||||||
|
|
||||||
|
@Autowired private MockMvc mvc;
|
||||||
|
@Autowired private JwtTokenService jwtTokenService;
|
||||||
|
@Autowired private JdbcTemplate jdbcTemplate;
|
||||||
|
@Autowired private InstitutionIdentifierApplicationMapper applicationMapper;
|
||||||
|
@Autowired private CurrencyGenerationRequestMapper requestMapper;
|
||||||
|
@Autowired private CurrencyGenerationVerificationMapper verificationMapper;
|
||||||
|
@Autowired private TransactionInformationIdentifierMapper transactionMapper;
|
||||||
|
@Autowired private TransactionInformationIdentifierOperationLogMapper transactionLogMapper;
|
||||||
|
@Autowired private StuModuleScoreDetailsMapper scoreMapper;
|
||||||
|
|
||||||
|
private String authorization;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
jdbcTemplate.update("DELETE FROM transaction_information_identifier_operation_log");
|
||||||
|
jdbcTemplate.update("DELETE FROM transaction_information_identifier");
|
||||||
|
jdbcTemplate.update("DELETE FROM currency_generation_verification_operation_log");
|
||||||
|
jdbcTemplate.update("DELETE FROM currency_generation_verification");
|
||||||
|
jdbcTemplate.update("DELETE FROM currency_generation_request_operation_log");
|
||||||
|
jdbcTemplate.update("DELETE FROM currency_generation_request");
|
||||||
|
jdbcTemplate.update("DELETE FROM institution_identifier_training_error");
|
||||||
|
jdbcTemplate.update("DELETE FROM institution_identifier_operation_log");
|
||||||
|
jdbcTemplate.update("DELETE FROM institution_identifier_application");
|
||||||
|
jdbcTemplate.update("DELETE FROM institution_sm2_key_audit");
|
||||||
|
jdbcTemplate.update("DELETE FROM institution_sm2_key");
|
||||||
|
jdbcTemplate.update("DELETE FROM stu_module_score_details");
|
||||||
|
authorization = "Bearer " + jwtTokenService.issueFor(USER_ID, SCHOOL_ID, CLASS_ID,
|
||||||
|
"module-four-tester", Collections.singleton("STUDENT")).accessToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void rejectsModuleFourWhenStepThreeHasNotReturnedResponse() throws Exception {
|
||||||
|
mvc.perform(post(BASE_PATH + "/steps/prepare").header("Authorization", authorization))
|
||||||
|
.andExpect(status().isBadRequest())
|
||||||
|
.andExpect(jsonPath("$.message").value(org.hamcrest.Matchers.containsString(
|
||||||
|
"步骤三尚未完成确认报文返回")));
|
||||||
|
assertThat(transactionMapper.selectList(null)).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void flowsCompletedPreviousDataAndPersistsEveryModuleFourStep() throws Exception {
|
||||||
|
PrecedingData preceding = insertCompletedPrecedingData();
|
||||||
|
|
||||||
|
mvc.perform(post(BASE_PATH + "/steps/prepare").header("Authorization", authorization))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.data.status").value("PREPARED"))
|
||||||
|
.andExpect(jsonPath("$.data.verificationId").value(preceding.verificationId))
|
||||||
|
.andExpect(jsonPath("$.data.requestId").value(preceding.requestId))
|
||||||
|
.andExpect(jsonPath("$.data.identifierApplicationId").value(preceding.applicationId))
|
||||||
|
.andExpect(jsonPath("$.data.amount").value(1589.85))
|
||||||
|
.andExpect(jsonPath("$.data.institutionIdentifier").value("ORG_3A4B5C6D7E8F"))
|
||||||
|
.andExpect(jsonPath("$.data.transactionTimestamp")
|
||||||
|
.value(org.hamcrest.Matchers.matchesPattern("\\d{14}")))
|
||||||
|
.andExpect(jsonPath("$.data.originalText")
|
||||||
|
.value(org.hamcrest.Matchers.matchesPattern(
|
||||||
|
"1589\\.85\\|ORG_3A4B5C6D7E8F\\|\\d{14}")))
|
||||||
|
.andExpect(jsonPath("$.data.digest").value(org.hamcrest.Matchers.nullValue()))
|
||||||
|
.andExpect(jsonPath("$.data.transactionIdentifier")
|
||||||
|
.value(org.hamcrest.Matchers.nullValue()));
|
||||||
|
|
||||||
|
mvc.perform(post(BASE_PATH + "/steps/assemble").header("Authorization", authorization))
|
||||||
|
.andExpect(status().isBadRequest())
|
||||||
|
.andExpect(jsonPath("$.message").value(org.hamcrest.Matchers.containsString(
|
||||||
|
"请先使用SM3生成摘要")));
|
||||||
|
|
||||||
|
mvc.perform(post(BASE_PATH + "/steps/digest").header("Authorization", authorization))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.data.status").value("DIGESTED"))
|
||||||
|
.andExpect(jsonPath("$.data.digestAlgorithm").value("SM3"))
|
||||||
|
.andExpect(jsonPath("$.data.digest")
|
||||||
|
.value(org.hamcrest.Matchers.matchesPattern("[0-9A-F]{64}")));
|
||||||
|
|
||||||
|
mvc.perform(post(BASE_PATH + "/steps/assemble").header("Authorization", authorization))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.data.status").value("ASSEMBLED"))
|
||||||
|
.andExpect(jsonPath("$.data.transactionIdentifier")
|
||||||
|
.value(org.hamcrest.Matchers.matchesPattern("TXN_[0-9A-F]{64}")));
|
||||||
|
|
||||||
|
mvc.perform(get(BASE_PATH).header("Authorization", authorization))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.data.status").value("ASSEMBLED"))
|
||||||
|
.andExpect(jsonPath("$.data.transactionIdentifier")
|
||||||
|
.value(org.hamcrest.Matchers.matchesPattern("TXN_[0-9A-F]{64}")));
|
||||||
|
|
||||||
|
List<TransactionInformationIdentifierEntity> records = transactionMapper.selectList(null);
|
||||||
|
assertThat(records).hasSize(1);
|
||||||
|
TransactionInformationIdentifierEntity persisted = records.get(0);
|
||||||
|
assertThat(persisted.getVerificationId()).isEqualTo(preceding.verificationId);
|
||||||
|
assertThat(persisted.getRequestId()).isEqualTo(preceding.requestId);
|
||||||
|
assertThat(persisted.getIdentifierApplicationId()).isEqualTo(preceding.applicationId);
|
||||||
|
assertThat(persisted.getAmount()).isEqualByComparingTo("1589.85");
|
||||||
|
assertThat(persisted.getInstitutionIdentifier()).isEqualTo("ORG_3A4B5C6D7E8F");
|
||||||
|
assertThat(persisted.getOriginalText()).isEqualTo("1589.85|ORG_3A4B5C6D7E8F|"
|
||||||
|
+ persisted.getTransactionTimestamp());
|
||||||
|
assertThat(persisted.getDigest()).matches("[0-9A-F]{64}");
|
||||||
|
assertThat(persisted.getTransactionIdentifier()).isEqualTo("TXN_" + persisted.getDigest());
|
||||||
|
assertThat(persisted.getStatus()).isEqualTo("ASSEMBLED");
|
||||||
|
|
||||||
|
assertThat(transactionLogMapper.selectList(null))
|
||||||
|
.extracting(TransactionInformationIdentifierOperationLogEntity::getOperation)
|
||||||
|
.containsExactly("PREPARE", "DIGEST", "ASSEMBLE");
|
||||||
|
|
||||||
|
StuModuleScoreDetailsEntity score = scoreMapper.selectOne(
|
||||||
|
new LambdaQueryWrapper<StuModuleScoreDetailsEntity>()
|
||||||
|
.eq(StuModuleScoreDetailsEntity::getUserId, USER_ID)
|
||||||
|
.eq(StuModuleScoreDetailsEntity::getSerialNumber, 3));
|
||||||
|
assertThat(score).isNotNull();
|
||||||
|
assertThat(score.getSchedule()).isEqualTo(44.44);
|
||||||
|
assertThat(score.getScoreProject()).isEqualTo(44.44);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void preventsDuplicatePreparationForSameCompletedVerification() throws Exception {
|
||||||
|
insertCompletedPrecedingData();
|
||||||
|
mvc.perform(post(BASE_PATH + "/steps/prepare").header("Authorization", authorization))
|
||||||
|
.andExpect(status().isOk());
|
||||||
|
mvc.perform(post(BASE_PATH + "/steps/prepare").header("Authorization", authorization))
|
||||||
|
.andExpect(status().isBadRequest())
|
||||||
|
.andExpect(jsonPath("$.message").value(org.hamcrest.Matchers.containsString(
|
||||||
|
"不能重复准备")));
|
||||||
|
assertThat(transactionMapper.selectList(null)).hasSize(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private PrecedingData insertCompletedPrecedingData() {
|
||||||
|
LocalDateTime now = LocalDateTime.now();
|
||||||
|
InstitutionIdentifierApplicationEntity application = new InstitutionIdentifierApplicationEntity();
|
||||||
|
application.setBankCode("BKCHCNBJ40001");
|
||||||
|
application.setUserId(USER_ID);
|
||||||
|
application.setSchoolId(SCHOOL_ID);
|
||||||
|
application.setClassId(CLASS_ID);
|
||||||
|
application.setRequestTimestamp("20260805180000");
|
||||||
|
application.setDigest("3A4B5C6D7E8F0000000000000000000000000000000000000000000000000000");
|
||||||
|
application.setBankSignature("3044AABB");
|
||||||
|
application.setStatus("FEEDBACKED");
|
||||||
|
application.setVerificationKeyId(InstitutionKeyService.BANK_SECOND_KEY);
|
||||||
|
application.setSignatureValid(true);
|
||||||
|
application.setDigestMatches(true);
|
||||||
|
application.setSigningKeyId(InstitutionKeyService.CENTRAL_FIRST_KEY);
|
||||||
|
application.setInstitutionIdentifier("ORG_3A4B5C6D7E8F");
|
||||||
|
application.setCentralBankSignature("3044CCDD");
|
||||||
|
application.setTrainingRound(1);
|
||||||
|
application.setScoringCriteria(0);
|
||||||
|
application.setCreatedAt(now);
|
||||||
|
application.setUpdatedAt(now);
|
||||||
|
application.setCreatedBy("module-four-tester");
|
||||||
|
application.setUpdatedBy("module-four-tester");
|
||||||
|
application.setDeleted(false);
|
||||||
|
applicationMapper.insert(application);
|
||||||
|
|
||||||
|
CurrencyGenerationRequestEntity request = new CurrencyGenerationRequestEntity();
|
||||||
|
request.setIdentifierApplicationId(application.getId());
|
||||||
|
request.setInstitutionIdentifier(application.getInstitutionIdentifier());
|
||||||
|
request.setFullInstitutionIdentifier(application.getDigest());
|
||||||
|
request.setAmount(new BigDecimal("1589.85"));
|
||||||
|
request.setDeliveryNodeCode("SYS_DC_001");
|
||||||
|
request.setRequestTimestamp("20260805180100");
|
||||||
|
request.setRequestOriginalText("ORG_3A4B5C6D7E8F|1589.85|SYS_DC_001|20260805180100");
|
||||||
|
request.setRequestDigest("7A8B9C0D1E2F3A4B5C6D7E8F9A0B1C2D3E4F5A6B7C8D9E0F1A2B3C4D5E6F");
|
||||||
|
request.setBankSignature("3044AABB");
|
||||||
|
request.setSigningKeyId(InstitutionKeyService.BANK_SECOND_KEY);
|
||||||
|
request.setRequestMessage("{request:true}");
|
||||||
|
request.setStatus(CurrencyGenerationRequest.Status.RECEIVED.name());
|
||||||
|
request.setUserId(USER_ID);
|
||||||
|
request.setSchoolId(SCHOOL_ID);
|
||||||
|
request.setClassId(CLASS_ID);
|
||||||
|
request.setCreatedAt(now);
|
||||||
|
request.setUpdatedAt(now);
|
||||||
|
request.setCreatedBy("module-four-tester");
|
||||||
|
request.setUpdatedBy("module-four-tester");
|
||||||
|
request.setDeleted(false);
|
||||||
|
requestMapper.insert(request);
|
||||||
|
|
||||||
|
CurrencyGenerationVerificationEntity verification = new CurrencyGenerationVerificationEntity();
|
||||||
|
verification.setRequestId(request.getId());
|
||||||
|
verification.setRequestMessage(request.getRequestMessage());
|
||||||
|
verification.setRequestOriginalText(request.getRequestOriginalText());
|
||||||
|
verification.setRequestDigest(request.getRequestDigest());
|
||||||
|
verification.setBankSignature(request.getBankSignature());
|
||||||
|
verification.setBankKeyId(request.getSigningKeyId());
|
||||||
|
verification.setAmount(request.getAmount());
|
||||||
|
verification.setSignatureValid(true);
|
||||||
|
verification.setRecomputedDigest(request.getRequestDigest());
|
||||||
|
verification.setDigestMatches(true);
|
||||||
|
verification.setTotalQuota(new BigDecimal("500000000.00"));
|
||||||
|
verification.setUsedQuota(new BigDecimal("495000000.00"));
|
||||||
|
verification.setRemainingQuota(new BigDecimal("5000000.00"));
|
||||||
|
verification.setQuotaSufficient(true);
|
||||||
|
verification.setCentralBankKeyId(InstitutionKeyService.CENTRAL_FIRST_KEY);
|
||||||
|
verification.setCentralBankSignature("3044CCDD");
|
||||||
|
verification.setConfirmationTime("20260805180200");
|
||||||
|
verification.setConfirmationMessage("{confirmed:true}");
|
||||||
|
verification.setStatus(CurrencyGenerationVerification.Status.RESPONSE_RETURNED.name());
|
||||||
|
verification.setUserId(USER_ID);
|
||||||
|
verification.setSchoolId(SCHOOL_ID);
|
||||||
|
verification.setClassId(CLASS_ID);
|
||||||
|
verification.setCreatedAt(now);
|
||||||
|
verification.setUpdatedAt(now);
|
||||||
|
verification.setCreatedBy("module-four-tester");
|
||||||
|
verification.setUpdatedBy("module-four-tester");
|
||||||
|
verification.setDeleted(false);
|
||||||
|
verificationMapper.insert(verification);
|
||||||
|
return new PrecedingData(application.getId(), request.getId(), verification.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class PrecedingData {
|
||||||
|
private final long applicationId;
|
||||||
|
private final long requestId;
|
||||||
|
private final long verificationId;
|
||||||
|
|
||||||
|
private PrecedingData(long applicationId, long requestId, long verificationId) {
|
||||||
|
this.applicationId = applicationId;
|
||||||
|
this.requestId = requestId;
|
||||||
|
this.verificationId = verificationId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
package com.yau.digitalrmb.platformintegration.config;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
class PlatformIntegrationConfigTest {
|
||||||
|
private final ApplicationContextRunner contextRunner =
|
||||||
|
new ApplicationContextRunner().withUserConfiguration(PlatformIntegrationConfig.class);
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void doesNotBindOrValidatePropertiesWhenIntegrationIsDisabled() {
|
||||||
|
contextRunner
|
||||||
|
.withPropertyValues("platform-integration.enabled=false")
|
||||||
|
.run(context -> {
|
||||||
|
assertThat(context).hasNotFailed();
|
||||||
|
assertThat(context).doesNotHaveBean(PlatformIntegrationProperties.class);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void validatesRequiredPropertiesWhenIntegrationIsEnabled() {
|
||||||
|
contextRunner
|
||||||
|
.withPropertyValues("platform-integration.enabled=true")
|
||||||
|
.run(context -> {
|
||||||
|
assertThat(context).hasFailed();
|
||||||
|
assertThat(context.getStartupFailure()).hasMessageContaining("platform-integration");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,43 @@
|
|||||||
|
package com.yau.digitalrmb.shared;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.test.context.ActiveProfiles;
|
||||||
|
import org.springframework.test.context.TestPropertySource;
|
||||||
|
import org.springframework.test.web.servlet.MockMvc;
|
||||||
|
|
||||||
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||||
|
|
||||||
|
@SpringBootTest
|
||||||
|
@AutoConfigureMockMvc
|
||||||
|
@ActiveProfiles("test")
|
||||||
|
@TestPropertySource(properties = {
|
||||||
|
"springdoc.swagger-ui.enabled=true",
|
||||||
|
"knife4j.enable=true"
|
||||||
|
})
|
||||||
|
class SwaggerDocumentationTest {
|
||||||
|
@Autowired
|
||||||
|
private MockMvc mvc;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void docHtmlAndOpenApiJsonArePubliclyAvailable() throws Exception {
|
||||||
|
mvc.perform(get("/doc.html"))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(content().contentTypeCompatibleWith("text/html"))
|
||||||
|
.andExpect(content().string(org.hamcrest.Matchers.containsString("knife4j")));
|
||||||
|
|
||||||
|
mvc.perform(get("/v3/api-docs"))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(content().contentTypeCompatibleWith("application/json"))
|
||||||
|
.andExpect(jsonPath("$.info.title").value("数字人民币教学仿真后端"))
|
||||||
|
.andExpect(jsonPath("$.components.securitySchemes.Authorization.type").value("apiKey"))
|
||||||
|
.andExpect(jsonPath("$.components.securitySchemes.Authorization.in").value("header"))
|
||||||
|
.andExpect(jsonPath("$.components.securitySchemes.Authorization.name").value("Authorization"))
|
||||||
|
.andExpect(jsonPath("$.security[0].Authorization").isArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue