From 73fe422495ca92d658b6c6eb969cdf52a18608a9 Mon Sep 17 00:00:00 2001 From: chenyuan Date: Wed, 12 Aug 2026 22:49:19 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8A=A0=E5=9B=BA=E5=8F=91=E8=A1=8C=E4=B8=8E?= =?UTF-8?q?=E5=85=91=E6=8D=A2=E6=8E=A5=E5=8F=A3=E5=BC=82=E5=B8=B8=E5=8F=8A?= =?UTF-8?q?=E5=B9=B6=E5=8F=91=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/ExchangeApplicationService.java | 37 +++++++++++++--- .../repository/ExchangeOrderRepository.java | 3 ++ .../JdbcExchangeOrderRepository.java | 12 +++++- .../interfaces/dto/CreateExchangeRequest.java | 3 +- ...mercialBankIssuanceApplicationService.java | 43 ++++++++++++------- .../repository/IssuanceRequestRepository.java | 4 ++ .../mapper/IssuanceRequestMapper.java | 4 ++ .../MybatisIssuanceRequestRepository.java | 12 +++++- .../interfaces/dto/CreateIssuanceRequest.java | 4 +- .../dto/DenominationItemRequest.java | 7 +++ .../dto/DenominationPlanRequest.java | 7 +++ .../CommercialBankIssuanceController.java | 14 +++++- .../shared/web/GlobalExceptionHandler.java | 10 +++++ 13 files changed, 132 insertions(+), 28 deletions(-) diff --git a/src/main/java/com/yau/digitalrmb/exchange/application/service/ExchangeApplicationService.java b/src/main/java/com/yau/digitalrmb/exchange/application/service/ExchangeApplicationService.java index 9f8fadc..8510076 100644 --- a/src/main/java/com/yau/digitalrmb/exchange/application/service/ExchangeApplicationService.java +++ b/src/main/java/com/yau/digitalrmb/exchange/application/service/ExchangeApplicationService.java @@ -21,6 +21,8 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.beans.factory.annotation.Autowired; +import java.math.BigDecimal; +import java.math.RoundingMode; import java.time.Clock; import java.time.Instant; import java.time.ZoneOffset; @@ -55,6 +57,8 @@ public class ExchangeApplicationService { @Transactional public ExchangeOrderView create(CreateExchangeCommand command, ExchangeActor actor) { + if (command == null) throw validation("兑换请求不能为空"); + BigDecimal amount = amount(command.getAmount()); ExchangeContext context = resourceRepository.loadContext(actor); if (!context.getWalletId().equals(command.getWalletId()) || !context.getBankAccountId().equals(command.getBankAccountId())) { throw validation("钱包或银行卡不属于当前用户,或尚未完成绑定"); @@ -63,13 +67,13 @@ public class ExchangeApplicationService { UUID id = UUID.randomUUID(); String timestamp = TIMESTAMP.format(now); String requestNo = "WITHDRAW_REQ_" + timestamp + "_" + id.toString().substring(0, 8).toUpperCase(); - String message = "WITHDRAW|" + context.getWalletId() + "|" + command.getAmount().setScale(2).toPlainString() + String message = "WITHDRAW|" + context.getWalletId() + "|" + amount.toPlainString() + "|" + context.getBankCardNumber().replace(" ", "") + "|" + timestamp; - String signingOriginal = "SIGN|" + requestNo + "|" + command.getAmount().setScale(2).toPlainString() + String signingOriginal = "SIGN|" + requestNo + "|" + amount.toPlainString() + "|" + context.getBankCardLast4() + "|" + timestamp; ExchangeOrder order = ExchangeOrder.create(new ExchangeOrderId(id), requestNo, actor.getUserId(), context.getWalletId(), context.getBankAccountId(), context.getBankCode(), context.getOrganizationId(), - command.getAmount(), timestamp, message, signingOriginal, cryptography.sm3(signingOriginal), now); + amount, timestamp, message, signingOriginal, cryptography.sm3(signingOriginal), now); orderRepository.save(order, actor.getUsername()); resourceRepository.appendStepLog(order, "02", "生成取币请求报文", message, actor); return ExchangeOrderView.from(order, null); @@ -77,7 +81,7 @@ public class ExchangeApplicationService { @Transactional public ExchangeOrderView sign(UUID id, ExchangeActor actor) { - ExchangeOrder order = owned(id, actor); + ExchangeOrder order = ownedForUpdate(id, actor); if (order.getStatus() != ExchangeStatus.MESSAGE_PREPARED) { if (order.getStatus().ordinal() > ExchangeStatus.MESSAGE_PREPARED.ordinal()) return current(order); throw validation("当前兑换订单尚不能签名"); @@ -90,7 +94,7 @@ public class ExchangeApplicationService { @Transactional public ExchangeOrderView bankProcess(UUID id, ExchangeActor actor) { - ExchangeOrder order = owned(id, actor); + ExchangeOrder order = ownedForUpdate(id, actor); if (order.getStatus() != ExchangeStatus.SIGNED) { if (order.getStatus().ordinal() > ExchangeStatus.SIGNED.ordinal()) return current(order); throw validation("请先完成钱包签名"); @@ -106,7 +110,7 @@ public class ExchangeApplicationService { @Transactional public ExchangeOrderView reserveCoins(UUID id, ExchangeActor actor) { - ExchangeOrder order = owned(id, actor); + ExchangeOrder order = ownedForUpdate(id, actor); if (order.getStatus() != ExchangeStatus.BANK_PROCESSED) { if (order.getStatus().ordinal() > ExchangeStatus.BANK_PROCESSED.ordinal()) return current(order); throw validation("请先完成商业银行验签和扣款"); @@ -119,7 +123,7 @@ public class ExchangeApplicationService { @Transactional public ExchangeOrderView confirmOwnership(UUID id, ExchangeActor actor) { - ExchangeOrder order = owned(id, actor); + ExchangeOrder order = ownedForUpdate(id, actor); if (order.getStatus() == ExchangeStatus.SUCCESS) return current(order); if (order.getStatus() != ExchangeStatus.COINS_RESERVED) throw validation("请先锁定等额商业银行币串"); List coins = resourceRepository.findReservedCoins(order); @@ -164,9 +168,28 @@ public class ExchangeApplicationService { return order; } + private ExchangeOrder ownedForUpdate(UUID id, ExchangeActor actor) { + ExchangeOrder order = orderRepository.findByIdForUpdate(new ExchangeOrderId(id)) + .orElseThrow(() -> new BusinessException(ErrorCode.RESOURCE_NOT_FOUND, "数字货币兑换订单不存在")); + try { order.requireOwnedBy(actor.getUserId()); } + catch (SecurityException exception) { throw new BusinessException(ErrorCode.FORBIDDEN, exception.getMessage()); } + return order; + } + private ExchangeOrderView current(ExchangeOrder order) { return ExchangeOrderView.from(order, resourceRepository.findReservedCoins(order)); } private BusinessException validation(String message) { return new BusinessException(ErrorCode.VALIDATION_ERROR, message); } + + private BigDecimal amount(BigDecimal value) { + if (value == null) throw validation("兑换金额不能为空"); + try { + BigDecimal normalized = value.setScale(2, RoundingMode.UNNECESSARY); + if (normalized.signum() <= 0) throw validation("兑换金额必须大于零"); + return normalized; + } catch (ArithmeticException exception) { + throw validation("兑换金额最多保留两位小数"); + } + } } diff --git a/src/main/java/com/yau/digitalrmb/exchange/domain/repository/ExchangeOrderRepository.java b/src/main/java/com/yau/digitalrmb/exchange/domain/repository/ExchangeOrderRepository.java index 774fdbd..847b7b2 100644 --- a/src/main/java/com/yau/digitalrmb/exchange/domain/repository/ExchangeOrderRepository.java +++ b/src/main/java/com/yau/digitalrmb/exchange/domain/repository/ExchangeOrderRepository.java @@ -8,4 +8,7 @@ import java.util.Optional; public interface ExchangeOrderRepository { void save(ExchangeOrder order, String operator); Optional findById(ExchangeOrderId id); + default Optional findByIdForUpdate(ExchangeOrderId id) { + return findById(id); + } } diff --git a/src/main/java/com/yau/digitalrmb/exchange/infrastructure/persistence/JdbcExchangeOrderRepository.java b/src/main/java/com/yau/digitalrmb/exchange/infrastructure/persistence/JdbcExchangeOrderRepository.java index c147f3b..f3b7f2a 100644 --- a/src/main/java/com/yau/digitalrmb/exchange/infrastructure/persistence/JdbcExchangeOrderRepository.java +++ b/src/main/java/com/yau/digitalrmb/exchange/infrastructure/persistence/JdbcExchangeOrderRepository.java @@ -56,7 +56,17 @@ public class JdbcExchangeOrderRepository implements ExchangeOrderRepository { @Override public Optional findById(ExchangeOrderId id) { - List values = jdbc.query("SELECT * FROM currency_exchange_order WHERE id=?", (rs, row) -> + return findById(id, false); + } + + @Override + public Optional findByIdForUpdate(ExchangeOrderId id) { + return findById(id, true); + } + + private Optional findById(ExchangeOrderId id, boolean forUpdate) { + String sql = "SELECT * FROM currency_exchange_order WHERE id=?" + (forUpdate ? " FOR UPDATE" : ""); + List values = jdbc.query(sql, (rs, row) -> ExchangeOrder.rehydrate(new ExchangeOrderId(java.util.UUID.fromString(rs.getString("id"))), rs.getString("request_no"), rs.getString("user_id"), rs.getString("wallet_id"), rs.getString("bank_account_id"), rs.getString("bank_code"), rs.getString("organization_id"), diff --git a/src/main/java/com/yau/digitalrmb/exchange/interfaces/dto/CreateExchangeRequest.java b/src/main/java/com/yau/digitalrmb/exchange/interfaces/dto/CreateExchangeRequest.java index e02d4b2..7474d6d 100644 --- a/src/main/java/com/yau/digitalrmb/exchange/interfaces/dto/CreateExchangeRequest.java +++ b/src/main/java/com/yau/digitalrmb/exchange/interfaces/dto/CreateExchangeRequest.java @@ -1,6 +1,7 @@ package com.yau.digitalrmb.exchange.interfaces.dto; import javax.validation.constraints.DecimalMin; +import javax.validation.constraints.Digits; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; import java.math.BigDecimal; @@ -8,7 +9,7 @@ import java.math.BigDecimal; public class CreateExchangeRequest { @NotBlank private String walletId; @NotBlank private String bankAccountId; - @NotNull @DecimalMin("0.01") private BigDecimal amount; + @NotNull @DecimalMin("0.01") @Digits(integer = 18, fraction = 2) private BigDecimal amount; public String getWalletId() { return walletId; } public void setWalletId(String walletId) { this.walletId = walletId; } diff --git a/src/main/java/com/yau/digitalrmb/issuance/application/service/CommercialBankIssuanceApplicationService.java b/src/main/java/com/yau/digitalrmb/issuance/application/service/CommercialBankIssuanceApplicationService.java index 6dad920..850fa83 100644 --- a/src/main/java/com/yau/digitalrmb/issuance/application/service/CommercialBankIssuanceApplicationService.java +++ b/src/main/java/com/yau/digitalrmb/issuance/application/service/CommercialBankIssuanceApplicationService.java @@ -97,7 +97,7 @@ public class CommercialBankIssuanceApplicationService { command.getDenominations(), auditActor(auditActor).getUserId()); repository.save(request, auditActor); return IssuanceRequestView.from(request); - } catch (IllegalArgumentException | IllegalStateException | NullPointerException exception) { + } catch (IllegalArgumentException | IllegalStateException | ArithmeticException | NullPointerException exception) { throw validationError(exception); } } @@ -109,11 +109,11 @@ public class CommercialBankIssuanceApplicationService { @Transactional public IssuanceRequestView update(UUID id, UpdateIssuanceRequestCommand command, IssuanceAuditActor auditActor) { try { - IssuanceRequest request = requireOwnedRequest(id, auditActor); + IssuanceRequest request = requireOwnedRequestForUpdate(id, auditActor); request.updateDraft(command.getTotalAmount(), command.getCurrency(), command.getDenominations()); repository.save(request, auditActor(auditActor)); return IssuanceRequestView.from(request); - } catch (IllegalArgumentException | IllegalStateException | NullPointerException exception) { + } catch (IllegalArgumentException | IllegalStateException | ArithmeticException | NullPointerException exception) { throw validationError(exception); } } @@ -125,11 +125,11 @@ public class CommercialBankIssuanceApplicationService { @Transactional public IssuanceRequestView captureTimestamp(UUID id, IssuanceAuditActor auditActor) { try { - IssuanceRequest request = requireOwnedRequest(id, auditActor); + IssuanceRequest request = requireOwnedRequestForUpdate(id, auditActor); request.captureTimestamp(LocalDateTime.now().format(TIMESTAMP_FORMAT)); repository.save(request, auditActor(auditActor)); return IssuanceRequestView.from(request); - } catch (IllegalArgumentException | IllegalStateException | NullPointerException exception) { + } catch (IllegalArgumentException | IllegalStateException | ArithmeticException | NullPointerException exception) { throw validationError(exception); } } @@ -141,13 +141,13 @@ public class CommercialBankIssuanceApplicationService { @Transactional public IssuanceRequestView prepareMessage(UUID id, IssuanceAuditActor auditActor) { try { - IssuanceRequest request = requireOwnedRequest(id, auditActor); + IssuanceRequest request = requireOwnedRequestForUpdate(id, auditActor); String message = messageComposer.compose(request.getBankCode(), request.getOrganizationId(), request.getTotalAmount(), request.getDenominations(), request.getCurrency(), request.getRequestTimestamp()); request.prepareMessage(message); repository.save(request, auditActor(auditActor)); return IssuanceRequestView.from(request); - } catch (IllegalArgumentException | IllegalStateException | NullPointerException exception) { + } catch (IllegalArgumentException | IllegalStateException | ArithmeticException | NullPointerException exception) { throw validationError(exception); } } @@ -159,11 +159,11 @@ public class CommercialBankIssuanceApplicationService { @Transactional public IssuanceRequestView digest(UUID id, IssuanceAuditActor auditActor) { try { - IssuanceRequest request = requireOwnedRequest(id, auditActor); + IssuanceRequest request = requireOwnedRequestForUpdate(id, auditActor); request.recordDigest(signatureService.digest(request.getMessageText())); repository.save(request, auditActor(auditActor)); return IssuanceRequestView.from(request); - } catch (IllegalArgumentException | IllegalStateException | NullPointerException exception) { + } catch (IllegalArgumentException | IllegalStateException | ArithmeticException | NullPointerException exception) { throw validationError(exception); } } @@ -175,7 +175,7 @@ public class CommercialBankIssuanceApplicationService { @Transactional public IssuanceRequestView sign(UUID id, IssuanceAuditActor auditActor, InstitutionKeySubject subject) { try { - IssuanceRequest request = requireOwnedRequest(id, auditActor); + IssuanceRequest request = requireOwnedRequestForUpdate(id, auditActor); if (generationGateway == null) { throw new BusinessException(ErrorCode.INTERNAL_ERROR, "生成数字货币模块密钥服务未配置"); } @@ -185,7 +185,7 @@ public class CommercialBankIssuanceApplicationService { return IssuanceRequestView.from(request); } catch (BusinessException exception) { throw exception; - } catch (IllegalArgumentException | IllegalStateException | NullPointerException exception) { + } catch (IllegalArgumentException | IllegalStateException | ArithmeticException | NullPointerException exception) { throw validationError(exception); } } @@ -193,11 +193,11 @@ public class CommercialBankIssuanceApplicationService { @Transactional public IssuanceRequestView packagePayload(UUID id, IssuanceAuditActor auditActor) { try { - IssuanceRequest request = requireOwnedRequest(id, auditActor); + IssuanceRequest request = requireOwnedRequestForUpdate(id, auditActor); request.packagePayload(payloadFor(request)); repository.save(request, auditActor(auditActor)); return IssuanceRequestView.from(request); - } catch (IllegalArgumentException | IllegalStateException | NullPointerException exception) { + } catch (IllegalArgumentException | IllegalStateException | ArithmeticException | NullPointerException exception) { throw validationError(exception); } } @@ -209,13 +209,13 @@ public class CommercialBankIssuanceApplicationService { @Transactional public IssuanceRequestView send(UUID id, IssuanceAuditActor auditActor) { try { - IssuanceRequest request = requireOwnedRequest(id, auditActor); + IssuanceRequest request = requireOwnedRequestForUpdate(id, auditActor); request.sendToCentralBank(Instant.now()); receiptRepository.saveIfAbsent(new CentralBankIssuanceReceipt(request.getId().value(), request.getRequestNo(), request.getPayloadJson(), request.getCentralBankReceivedAt(), auditActor.getUserId(), auditActor.getUsername())); repository.save(request, auditActor(auditActor)); return IssuanceRequestView.from(request); - } catch (IllegalArgumentException | IllegalStateException | NullPointerException exception) { + } catch (IllegalArgumentException | IllegalStateException | ArithmeticException | NullPointerException exception) { throw validationError(exception); } } @@ -301,6 +301,19 @@ public class CommercialBankIssuanceApplicationService { return request; } + private IssuanceRequest requireOwnedRequestForUpdate(UUID id, IssuanceAuditActor auditActor) { + if (id == null) { + throw new BusinessException(ErrorCode.VALIDATION_ERROR, "发行申请标识不能为空"); + } + IssuanceRequest request = repository.findByIdForUpdate(new IssuanceApplicationId(id)).orElseThrow(() -> + new BusinessException(ErrorCode.RESOURCE_NOT_FOUND, "发行申请不存在")); + IssuanceAuditActor effectiveActor = auditActor(auditActor); + if (!request.getCreatedByUserId().equals(effectiveActor.getUserId())) { + throw new BusinessException(ErrorCode.UNAUTHORIZED, "无权操作该发行申请"); + } + return request; + } + private static IssuanceAuditActor legacyAuditActor(String username) { return new IssuanceAuditActor("SYSTEM", username); } diff --git a/src/main/java/com/yau/digitalrmb/issuance/domain/repository/IssuanceRequestRepository.java b/src/main/java/com/yau/digitalrmb/issuance/domain/repository/IssuanceRequestRepository.java index 090e3e1..b39cebf 100644 --- a/src/main/java/com/yau/digitalrmb/issuance/domain/repository/IssuanceRequestRepository.java +++ b/src/main/java/com/yau/digitalrmb/issuance/domain/repository/IssuanceRequestRepository.java @@ -16,5 +16,9 @@ public interface IssuanceRequestRepository { Optional findById(IssuanceApplicationId id); + default Optional findByIdForUpdate(IssuanceApplicationId id) { + return findById(id); + } + Optional findBuiltInInventory(); } diff --git a/src/main/java/com/yau/digitalrmb/issuance/infrastructure/persistence/mapper/IssuanceRequestMapper.java b/src/main/java/com/yau/digitalrmb/issuance/infrastructure/persistence/mapper/IssuanceRequestMapper.java index 3a2a58b..6fe57a3 100644 --- a/src/main/java/com/yau/digitalrmb/issuance/infrastructure/persistence/mapper/IssuanceRequestMapper.java +++ b/src/main/java/com/yau/digitalrmb/issuance/infrastructure/persistence/mapper/IssuanceRequestMapper.java @@ -3,7 +3,11 @@ package com.yau.digitalrmb.issuance.infrastructure.persistence.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.yau.digitalrmb.issuance.infrastructure.persistence.entity.IssuanceRequestEntity; import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; @Mapper public interface IssuanceRequestMapper extends BaseMapper { + @Select("SELECT * FROM issuance_request WHERE id = #{id} AND deleted = FALSE FOR UPDATE") + IssuanceRequestEntity selectByIdForUpdate(@Param("id") String id); } diff --git a/src/main/java/com/yau/digitalrmb/issuance/infrastructure/persistence/repository/MybatisIssuanceRequestRepository.java b/src/main/java/com/yau/digitalrmb/issuance/infrastructure/persistence/repository/MybatisIssuanceRequestRepository.java index cba9ec7..97ae14a 100644 --- a/src/main/java/com/yau/digitalrmb/issuance/infrastructure/persistence/repository/MybatisIssuanceRequestRepository.java +++ b/src/main/java/com/yau/digitalrmb/issuance/infrastructure/persistence/repository/MybatisIssuanceRequestRepository.java @@ -87,8 +87,18 @@ public class MybatisIssuanceRequestRepository implements IssuanceRequestReposito @Override public Optional findById(IssuanceApplicationId id) { + return findById(id, false); + } + + @Override + public Optional findByIdForUpdate(IssuanceApplicationId id) { + return findById(id, true); + } + + private Optional findById(IssuanceApplicationId id, boolean forUpdate) { String requestId = id.value().toString(); - IssuanceRequestEntity requestEntity = requestMapper.selectById(requestId); + IssuanceRequestEntity requestEntity = forUpdate + ? requestMapper.selectByIdForUpdate(requestId) : requestMapper.selectById(requestId); if (requestEntity == null) { return Optional.empty(); } diff --git a/src/main/java/com/yau/digitalrmb/issuance/interfaces/dto/CreateIssuanceRequest.java b/src/main/java/com/yau/digitalrmb/issuance/interfaces/dto/CreateIssuanceRequest.java index 3a49f1f..84b2c0c 100644 --- a/src/main/java/com/yau/digitalrmb/issuance/interfaces/dto/CreateIssuanceRequest.java +++ b/src/main/java/com/yau/digitalrmb/issuance/interfaces/dto/CreateIssuanceRequest.java @@ -3,6 +3,7 @@ package com.yau.digitalrmb.issuance.interfaces.dto; import io.swagger.v3.oas.annotations.media.Schema; import javax.validation.Valid; import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Digits; import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotNull; import javax.validation.constraints.Positive; @@ -13,7 +14,8 @@ import java.util.List; public class CreateIssuanceRequest { @NotBlank @Schema(description = "商业银行机构代码", example = "BKCHCNBJ00001") private String bankCode; @NotBlank @Schema(description = "机构标识", example = "ORG_3A4B5C6D7E8F") private String organizationId; - @NotNull @Positive @Schema(description = "发行总金额", example = "50000.00") private BigDecimal totalAmount; + @NotNull @Positive @Digits(integer = 18, fraction = 2) + @Schema(description = "发行总金额", example = "50000.00") private BigDecimal totalAmount; @NotBlank @Schema(description = "币种", example = "DC") private String currency; @Valid @NotEmpty @Schema(description = "面额明细") private List denominations; public String getBankCode(){return bankCode;} public void setBankCode(String v){bankCode=v;} diff --git a/src/main/java/com/yau/digitalrmb/issuance/interfaces/dto/DenominationItemRequest.java b/src/main/java/com/yau/digitalrmb/issuance/interfaces/dto/DenominationItemRequest.java index 8a8e226..747053f 100644 --- a/src/main/java/com/yau/digitalrmb/issuance/interfaces/dto/DenominationItemRequest.java +++ b/src/main/java/com/yau/digitalrmb/issuance/interfaces/dto/DenominationItemRequest.java @@ -2,13 +2,20 @@ package com.yau.digitalrmb.issuance.interfaces.dto; import io.swagger.v3.oas.annotations.media.Schema; +import javax.validation.constraints.Digits; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Positive; +import javax.validation.constraints.PositiveOrZero; + import java.math.BigDecimal; @Schema(description = "数字货币面额明细") public class DenominationItemRequest { @Schema(description = "面额", example = "100") + @NotNull @Positive @Digits(integer = 18, fraction = 2) private BigDecimal denomination; @Schema(description = "数量", example = "400") + @NotNull @PositiveOrZero private Integer quantity; public BigDecimal getDenomination() { return denomination; } public void setDenomination(BigDecimal denomination) { this.denomination = denomination; } diff --git a/src/main/java/com/yau/digitalrmb/issuance/interfaces/dto/DenominationPlanRequest.java b/src/main/java/com/yau/digitalrmb/issuance/interfaces/dto/DenominationPlanRequest.java index 349e011..d49266e 100644 --- a/src/main/java/com/yau/digitalrmb/issuance/interfaces/dto/DenominationPlanRequest.java +++ b/src/main/java/com/yau/digitalrmb/issuance/interfaces/dto/DenominationPlanRequest.java @@ -1,10 +1,17 @@ package com.yau.digitalrmb.issuance.interfaces.dto; +import javax.validation.Valid; +import javax.validation.constraints.Digits; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Positive; import java.math.BigDecimal; import java.util.List; public class DenominationPlanRequest { + @NotNull @Positive @Digits(integer = 18, fraction = 2) private BigDecimal totalAmount; + @Valid @NotEmpty private List items; public BigDecimal getTotalAmount() { return totalAmount; } diff --git a/src/main/java/com/yau/digitalrmb/issuance/interfaces/rest/CommercialBankIssuanceController.java b/src/main/java/com/yau/digitalrmb/issuance/interfaces/rest/CommercialBankIssuanceController.java index b2ca26b..1c8a15b 100644 --- a/src/main/java/com/yau/digitalrmb/issuance/interfaces/rest/CommercialBankIssuanceController.java +++ b/src/main/java/com/yau/digitalrmb/issuance/interfaces/rest/CommercialBankIssuanceController.java @@ -76,7 +76,7 @@ public class CommercialBankIssuanceController { @PostMapping("/denomination-plan/validate") @Operation(summary = "校验定制面额结构", description = "自动计算小计、总张数和总金额,并返回是否等于申请总额") public ApiResponse validateDenominationPlan( - @RequestBody DenominationPlanRequest request) { + @Valid @RequestBody DenominationPlanRequest request) { return ok(denominationPlanService.validate(request.getTotalAmount(), request.getItems())); } @@ -155,9 +155,19 @@ public class CommercialBankIssuanceController { } private List items(List input) { + if (input == null || input.isEmpty()) { + throw new BusinessException(ErrorCode.VALIDATION_ERROR, "面额明细不能为空"); + } List result = new ArrayList<>(); for (DenominationItemRequest item : input) { - result.add(new DenominationItem(item.getDenomination(), item.getQuantity())); + if (item == null || item.getDenomination() == null || item.getQuantity() == null) { + throw new BusinessException(ErrorCode.VALIDATION_ERROR, "面额和数量不能为空"); + } + try { + result.add(new DenominationItem(item.getDenomination(), item.getQuantity())); + } catch (IllegalArgumentException | ArithmeticException exception) { + throw new BusinessException(ErrorCode.VALIDATION_ERROR, exception.getMessage()); + } } return result; } diff --git a/src/main/java/com/yau/digitalrmb/shared/web/GlobalExceptionHandler.java b/src/main/java/com/yau/digitalrmb/shared/web/GlobalExceptionHandler.java index 4868a3b..518d206 100644 --- a/src/main/java/com/yau/digitalrmb/shared/web/GlobalExceptionHandler.java +++ b/src/main/java/com/yau/digitalrmb/shared/web/GlobalExceptionHandler.java @@ -11,9 +11,12 @@ import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.MissingServletRequestParameterException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; +import org.springframework.http.converter.HttpMessageNotReadableException; import java.util.UUID; @@ -26,6 +29,13 @@ public class GlobalExceptionHandler { return ApiResponse.failure(ErrorCode.VALIDATION_ERROR, "请求参数校验失败", traceId()); } + @ExceptionHandler({HttpMessageNotReadableException.class, MethodArgumentTypeMismatchException.class, + MissingServletRequestParameterException.class}) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ApiResponse handleInvalidRequest(Exception exception) { + return ApiResponse.failure(ErrorCode.VALIDATION_ERROR, "请求参数格式或取值无效", traceId()); + } + @ExceptionHandler(BusinessException.class) public ResponseEntity> handleBusiness(BusinessException exception) { ApiResponse response = ApiResponse.failure(exception.getErrorCode(), exception.getMessage(), traceId());