From 1b811181a8ebe8847bdbb4d9566b55878ec69b92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=B3=89=E4=BD=B3?= <1107186916@qq.com> Date: Thu, 5 Nov 2026 14:59:27 +0800 Subject: [PATCH] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E6=AD=A3=E5=BC=8F?= =?UTF-8?q?=E7=BB=93=E7=AE=97=E5=8D=95=E5=88=86=E9=A1=B5=E6=9F=A5=E8=AF=A2?= =?UTF-8?q?N+1=E5=AF=BC=E8=87=B4=E6=8E=A5=E5=8F=A3=E8=B6=85=E6=97=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../impl/FormalSettlementServiceImpl.java | 106 ++++++++++++++++-- 1 file changed, 98 insertions(+), 8 deletions(-) diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/FormalSettlementServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/FormalSettlementServiceImpl.java index c1ddc927..84410256 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/FormalSettlementServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/FormalSettlementServiceImpl.java @@ -11,6 +11,7 @@ package org.springblade.transport.service.impl; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springblade.core.log.exception.ServiceException; @@ -221,19 +222,108 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl result = page(page, wrapper.orderByDesc(FormalSettlement::getCreateTime)); - // 本页应付结算单的项目预付核销合计一次查询,避免逐行 toVO 重算汇总时的 N+1 - Map writeOffAmounts = this.projectAdvanceWriteOffService.sumBySettlementIds( - TransportBusiConstant.SETTLEMENT_BILL_FORMAL, - result.getRecords().stream() - .filter(item -> "payable".equals(item.getSettlementType())) - .map(FormalSettlement::getId).toList()); - IPage voPage = result.convert(item -> this.toVO(item, writeOffAmounts)); + Page voPage = new Page<>(result.getCurrent(), result.getSize(), result.getTotal()); + // 收款汇总与预结算单号按本页批量查询后内存分发,避免逐行 toVO 的 N+1(约 500 次 SQL → 常数次) + voPage.setRecords(this.toVOList(result.getRecords())); fillAttachmentSyncStatus(voPage.getRecords()); fillKingdeeLockStatus(voPage.getRecords()); fillKingdeeAssignStatus(voPage.getRecords()); return voPage; } + /** + * 列表页批量 Entity → VO:收款汇总各数据源与预结算单号按本页一次批量查询后内存分发, + * 计算口径与单笔版 toVO 完全一致(复用 calculatePaymentSummary 纯计算重载), + * 固定约 6 次查询替代逐行 toVO 的 4~5 次/行(100 行约 500 次 SQL)。 + * + * @param entities 本页结算单 + * @return 本页 VO 列表 + */ + private List toVOList(List entities) { + if (Func.isEmpty(entities)) { + return new ArrayList<>(); + } + List ids = entities.stream().map(FormalSettlement::getId).toList(); + boolean hasPayable = entities.stream().anyMatch(item -> "payable".equals(item.getSettlementType())); + boolean hasReceivable = entities.stream().anyMatch(item -> "receivable".equals(item.getSettlementType())); + // 结算-付款申请关系(全量) + Map> relationsBySettlement = paymentApplicationSettlementMapper.selectList( + Wrappers.lambdaQuery() + .in(PaymentApplicationSettlement::getFormalSettlementId, ids) + .eq(PaymentApplicationSettlement::getIsDeleted, 0)).stream() + .collect(Collectors.groupingBy(PaymentApplicationSettlement::getFormalSettlementId)); + // 付款申请(按结算单直挂,payment_type=结算) + Map> applicationsBySettlement = paymentApplicationMapper.selectList( + Wrappers.lambdaQuery() + .in(PaymentApplication::getSettlementId, ids) + .eq(PaymentApplication::getPaymentType, TransportBusiConstant.PAYMENT_TYPE_SETTLEMENT) + .eq(PaymentApplication::getIsDeleted, 0)).stream() + .collect(Collectors.groupingBy(PaymentApplication::getSettlementId)); + // 关系表引用的付款申请状态(全量一次) + Set relationApplicationIds = relationsBySettlement.values().stream() + .flatMap(List::stream).map(PaymentApplicationSettlement::getPaymentApplicationId) + .filter(Objects::nonNull).collect(Collectors.toSet()); + Map relationStatuses = relationApplicationIds.isEmpty() ? Map.of() + : paymentApplicationMapper.selectList(Wrappers.lambdaQuery() + .select(PaymentApplication::getId, PaymentApplication::getApprovalStatus) + .in(PaymentApplication::getId, relationApplicationIds) + .eq(PaymentApplication::getIsDeleted, 0)).stream() + .collect(Collectors.toMap(PaymentApplication::getId, PaymentApplication::getApprovalStatus, + (first, second) -> first)); + // 来源行(预结算单号展示不过滤删除;收款汇总计算时内存过滤 isDeleted=0,与单笔版口径一致) + Map> sourcesBySettlement = sourceMapper.selectList( + Wrappers.lambdaQuery() + .in(FormalSettlementSource::getFormalSettlementId, ids)).stream() + .collect(Collectors.groupingBy(FormalSettlementSource::getFormalSettlementId)); + // 收款认领分摊聚合(仅应收结算单需要) + Map receiptClaimPaidBySettlement = hasReceivable + ? receiptClaimSettlementMapper.selectList(Wrappers.lambdaQuery() + .in(ReceiptClaimSettlement::getFormalSettlementId, ids) + .eq(ReceiptClaimSettlement::getStatus, 1) + .eq(ReceiptClaimSettlement::getIsDeleted, 0)).stream() + .collect(Collectors.groupingBy(ReceiptClaimSettlement::getFormalSettlementId, + Collectors.reducing(BigDecimal.ZERO, + item -> this.money(item.getAllocatedReceiptAmount()), BigDecimal::add))) + : Map.of(); + // 项目预付核销合计(仅应付结算单需要,一次 group-by 避免逐单查询) + Map writeOffAmountsBySettlement = hasPayable + ? this.projectAdvanceWriteOffService.sumBySettlementIds( + TransportBusiConstant.SETTLEMENT_BILL_FORMAL, + entities.stream().filter(item -> "payable".equals(item.getSettlementType())) + .map(FormalSettlement::getId).toList()) + : Map.of(); + + List voList = new ArrayList<>(entities.size()); + for (FormalSettlement entity : entities) { + FormalSettlementVO vo = FormalSettlementWrapper.build().entityVO(entity); + vo.setInvoiceStatus(this.invoiceStatus(entity.getInvoiceAmount(), entity.getSettlementAmount())); + List relations = relationsBySettlement + .getOrDefault(entity.getId(), List.of()); + // 直挂付款申请需排除该结算单关系表已引用的申请(与单笔版 notIn 语义一致) + Set ownRelationApplicationIds = relations.stream() + .map(PaymentApplicationSettlement::getPaymentApplicationId) + .filter(Objects::nonNull).collect(Collectors.toSet()); + List directApplications = applicationsBySettlement + .getOrDefault(entity.getId(), List.of()).stream() + .filter(item -> !ownRelationApplicationIds.contains(item.getId())) + .toList(); + PaymentSummary summary = this.calculatePaymentSummary(entity, new PaymentSummaryData( + relations, directApplications, relationStatuses, + sourcesBySettlement.getOrDefault(entity.getId(), List.of()).stream() + .filter(item -> Objects.equals(item.getIsDeleted(), 0)).toList(), + receiptClaimPaidBySettlement.getOrDefault(entity.getId(), BigDecimal.ZERO), + writeOffAmountsBySettlement)); + vo.setAppliedPaymentAmount(summary.appliedAmount()); + vo.setPaidAmount(summary.paidAmount()); + vo.setRemainingPayableAmount(this.money(entity.getSettlementAmount()).subtract(summary.paidAmount()) + .max(BigDecimal.ZERO)); + vo.setPreSettlementNos(sourcesBySettlement.getOrDefault(entity.getId(), List.of()).stream() + .map(FormalSettlementSource::getPreSettlementNo).collect(Collectors.joining(","))); + voList.add(vo); + } + return voList; + } + /** * 填充已指定应付聚合态(仅付款结算单):按本页结算单一次批量查认领关系与收票登记,避免 N+1。 * 聚合规则:任一指定失败 → failed;全部已指定 → assigned;其余(有认领但未指定/未推送)→ unspecified; @@ -2517,7 +2607,7 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl writeOffAmounts) { FormalSettlementVO vo = FormalSettlementWrapper.build().entityVO(entity);