Merge salary signing fix

chore/issuance-comments-zh
chenyuan 4 weeks ago
commit 5af0e857b8

@ -73,14 +73,16 @@ public class SalaryBatchService {
@Transactional @Transactional
public SalaryBlockchainResult fetchBlockchain(InstitutionKeySubject subject, String operator) { public SalaryBlockchainResult fetchBlockchain(InstitutionKeySubject subject, String operator) {
SalaryBatch batch = findOrCreate(subject);
// 付款钱包从步骤一(对公钱包开立)获取 // 付款钱包从步骤一(对公钱包开立)获取
String corpWalletId = findCorporateWalletId(subject); String corpWalletId = findCorporateWalletId(subject);
if (corpWalletId == null) { if (corpWalletId == null) {
throw new BusinessException(ErrorCode.VALIDATION_ERROR, "请先完成对公钱包开立(步骤一)"); throw new BusinessException(ErrorCode.VALIDATION_ERROR, "请先完成对公钱包开立(步骤一)");
} }
// 每次调取区块信息都代表一个新的工资批次,避免复用上一批次的摘要、签名和状态。
SalaryBatch batch = repository.save(
SalaryBatch.create(subject.getUserId(), subject.getSchoolId(), subject.getClassId()));
// 批次号随机生成 // 批次号随机生成
String batchId = generateBatchId(); String batchId = generateBatchId();
// 时间戳 // 时间戳
@ -161,8 +163,8 @@ public class SalaryBatchService {
throw new BusinessException(ErrorCode.VALIDATION_ERROR, "数币操作员私钥不正确,错误次数+1"); throw new BusinessException(ErrorCode.VALIDATION_ERROR, "数币操作员私钥不正确,错误次数+1");
} }
// 使用数币操作员私钥对摘要进行SM2签名 // 按原型要求,使用数币操作员私钥对SM3摘要值进行SM2签名。
String signature = cryptography.sign(storedPrivateKey, batch.getConcatenatedMessage()); String signature = cryptography.sign(storedPrivateKey, batch.getDigestValue());
batch.sign(signature); batch.sign(signature);
repository.update(batch); repository.update(batch);
@ -516,12 +518,6 @@ public class SalaryBatchService {
// ==================== 内部方法 ==================== // ==================== 内部方法 ====================
private SalaryBatch findOrCreate(InstitutionKeySubject subject) {
return repository.findLatest(subject.getUserId(), subject.getSchoolId(), subject.getClassId())
.orElseGet(() -> repository.save(
SalaryBatch.create(subject.getUserId(), subject.getSchoolId(), subject.getClassId())));
}
private SalaryBatch findRequired(InstitutionKeySubject subject) { private SalaryBatch findRequired(InstitutionKeySubject subject) {
return repository.findLatest(subject.getUserId(), subject.getSchoolId(), subject.getClassId()) return repository.findLatest(subject.getUserId(), subject.getSchoolId(), subject.getClassId())
.orElseThrow(() -> new BusinessException(ErrorCode.RESOURCE_NOT_FOUND, .orElseThrow(() -> new BusinessException(ErrorCode.RESOURCE_NOT_FOUND,

@ -30,6 +30,7 @@ public class MybatisSalaryBatchRepository implements SalaryBatchRepository {
.eq(SalaryBatchEntity::getClassId, classId) .eq(SalaryBatchEntity::getClassId, classId)
.eq(SalaryBatchEntity::getDeleted, false) .eq(SalaryBatchEntity::getDeleted, false)
.orderByDesc(SalaryBatchEntity::getCreatedAt) .orderByDesc(SalaryBatchEntity::getCreatedAt)
.orderByDesc(SalaryBatchEntity::getId)
.last("LIMIT 1")); .last("LIMIT 1"));
return entity == null ? Optional.empty() : Optional.of(toDomain(entity)); return entity == null ? Optional.empty() : Optional.of(toDomain(entity));
} }

@ -0,0 +1,90 @@
package com.yau.digitalrmb.corporatewallet.application;
import com.yau.digitalrmb.corporatewallet.domain.CorporateWalletApplication;
import com.yau.digitalrmb.corporatewallet.domain.CorporateWalletApplicationRepository;
import com.yau.digitalrmb.corporatewallet.domain.SalaryBatch;
import com.yau.digitalrmb.corporatewallet.domain.SalaryBatchRepository;
import com.yau.digitalrmb.institutionidentity.application.InstitutionKeySubject;
import com.yau.digitalrmb.institutionidentity.domain.InstitutionIdentityCryptography;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class SalaryBatchServiceTest {
private static final InstitutionKeySubject SUBJECT =
new InstitutionKeySubject("salary-user", 1001L, 2001L);
private static final String PRIVATE_KEY = "OPERATOR_PRIVATE_KEY";
@Mock private SalaryBatchRepository repository;
@Mock private CorporateWalletApplicationRepository corporateWalletRepository;
@Mock private InstitutionIdentityCryptography cryptography;
@Mock private SalaryScoreService scoreService;
@Mock private CorporateWalletKeyService keyService;
@InjectMocks private SalaryBatchService service;
@Test
void startsFreshBatchAndSignsCurrentDigestWhenWorkflowRunsAgain() {
AtomicReference<SalaryBatch> latest = new AtomicReference<>();
AtomicLong ids = new AtomicLong(10L);
when(repository.save(any(SalaryBatch.class))).thenAnswer(invocation -> {
SalaryBatch batch = invocation.getArgument(0);
batch.setId(ids.incrementAndGet());
latest.set(batch);
return batch;
});
when(repository.findLatest(SUBJECT.getUserId(), SUBJECT.getSchoolId(), SUBJECT.getClassId()))
.thenAnswer(invocation -> Optional.ofNullable(latest.get()));
CorporateWalletApplication wallet = CorporateWalletApplication.create(
SUBJECT.getUserId(), SUBJECT.getSchoolId(), SUBJECT.getClassId());
wallet.setWalletId("CORP_WALLET_001");
when(corporateWalletRepository.findLatest(
SUBJECT.getUserId(), SUBJECT.getSchoolId(), SUBJECT.getClassId()))
.thenReturn(Optional.of(wallet));
when(cryptography.sm3(any(String.class))).thenReturn("DIGEST_ONE", "DIGEST_TWO");
when(keyService.getOrCreatePrivateKey(
any(InstitutionKeySubject.class), eq("数币操作员私钥"), eq("王五")))
.thenReturn(PRIVATE_KEY);
when(cryptography.sign(PRIVATE_KEY, "DIGEST_ONE")).thenReturn("SIGNATURE_ONE");
when(cryptography.sign(PRIVATE_KEY, "DIGEST_TWO")).thenReturn("SIGNATURE_TWO");
service.fetchBlockchain(SUBJECT, "tester");
service.concatenate(SUBJECT);
SalaryDigestResult firstDigest = service.computeDigest(SUBJECT);
SalaryBatch firstBatch = latest.get();
assertThat(firstBatch.getStatus()).isEqualTo(SalaryBatch.Status.DIGESTED);
SalarySignatureResult firstSignature = service.sign(SUBJECT, PRIVATE_KEY);
service.fetchBlockchain(SUBJECT, "tester");
service.concatenate(SUBJECT);
SalaryDigestResult secondDigest = service.computeDigest(SUBJECT);
SalaryBatch secondBatch = latest.get();
assertThat(secondBatch.getStatus()).isEqualTo(SalaryBatch.Status.DIGESTED);
SalarySignatureResult secondSignature = service.sign(SUBJECT, PRIVATE_KEY);
assertThat(secondBatch).isNotSameAs(firstBatch);
assertThat(secondBatch.getId()).isNotEqualTo(firstBatch.getId());
assertThat(firstDigest.getDigestValue()).isEqualTo("DIGEST_ONE");
assertThat(secondDigest.getDigestValue()).isEqualTo("DIGEST_TWO");
assertThat(firstSignature.getSignature()).isEqualTo("SIGNATURE_ONE");
assertThat(secondSignature.getSignature()).isEqualTo("SIGNATURE_TWO");
verify(repository, times(2)).save(any(SalaryBatch.class));
verify(cryptography).sign(PRIVATE_KEY, "DIGEST_ONE");
verify(cryptography).sign(PRIVATE_KEY, "DIGEST_TWO");
}
}
Loading…
Cancel
Save