Browse Source

2026-04-24

销售报价【切换版本】性能优化
master
fengyuan_yang 3 months ago
parent
commit
a95245605a
  1. 45
      src/main/java/com/spring/modules/quote/service/impl/QuoteDetailBomTreeServiceImpl.java
  2. 52
      src/main/java/com/spring/modules/quote/service/impl/QuoteDetailServiceImpl.java

45
src/main/java/com/spring/modules/quote/service/impl/QuoteDetailBomTreeServiceImpl.java

@ -104,7 +104,8 @@ public class QuoteDetailBomTreeServiceImpl extends ServiceImpl<QuoteDetailBomTre
public long initQuoteDetailBomTree(QuoteDetail detail, Long parentId, Integer level) {
// 阶段一事务外获取 IFS 连接工厂并递归收集数据含并行 IFS 成本查询
Supplier<Server> ifsConFactory = resolveIfsConFactory();
BomNodeData rootNode = collectBomData(detail, parentId, level, ifsConFactory);
Map<String, BigDecimal> costCache = new ConcurrentHashMap<>();
BomNodeData rootNode = collectBomData(detail, parentId, level, ifsConFactory, costCache);
if (rootNode == null) {
return 0;
}
@ -128,7 +129,7 @@ public class QuoteDetailBomTreeServiceImpl extends ServiceImpl<QuoteDetailBomTre
*
* @param ifsConFactory IFS 连接工厂每个并发线程调用 get() 获取独立连接避免共享连接线程安全问题
*/
private BomNodeData collectBomData(QuoteDetail detail, Long parentId, Integer level, Supplier<Server> ifsConFactory) {
private BomNodeData collectBomData(QuoteDetail detail, Long parentId, Integer level, Supplier<Server> ifsConFactory, Map<String, BigDecimal> costCache) {
// 确定 BOM 类型
if (parentId.equals(0L)) {
detail.setBomType("Manufacturing");
@ -189,7 +190,7 @@ public class QuoteDetailBomTreeServiceImpl extends ServiceImpl<QuoteDetailBomTre
if (Objects.nonNull(childBomCheck) && "Y".equals(component.getBomFlag())) {
log.debug("[BOM_PROCESS] 递归处理半成品 - PartNo: {}", component.getComponentPart());
// 递归收集子节点parentId 占位 -1写入时由 doSaveBomDataRecursive 覆盖
BomNodeData childNode = collectBomData(childDetail, -1L, level + 1, ifsConFactory);
BomNodeData childNode = collectBomData(childDetail, -1L, level + 1, ifsConFactory, costCache);
if (childNode != null) {
nodeData.children.add(childNode);
// 半成品bomId 在写入阶段填充先清零价格IFS 并行阶段会覆盖
@ -231,7 +232,7 @@ public class QuoteDetailBomTreeServiceImpl extends ServiceImpl<QuoteDetailBomTre
Server conn = null;
try {
conn = connectionPool.take();
getFinalPartCost(component, conn);
getFinalPartCost(component, conn, costCache);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.warn("[BOM_PROCESS] 并行成本查询线程被中断 - PartNo: {}", component.getComponentPart());
@ -336,6 +337,9 @@ public class QuoteDetailBomTreeServiceImpl extends ServiceImpl<QuoteDetailBomTre
@Override
public void changeQuoteDetailBomTree(QuoteDetailBomTree tree) {
long startTime = System.currentTimeMillis();
log.info("========== [PERF_LOG] START changeQuoteDetailBomTree for QuoteDetailId: {} ==========", tree.getQuoteDetailId());
QuoteDetail detail = null;
BomNodeData nodeData = null;
Long newParentId = 0L;
@ -353,10 +357,13 @@ public class QuoteDetailBomTreeServiceImpl extends ServiceImpl<QuoteDetailBomTre
detail.setAlternativeNo(tree.getAlternativeNo());
// 先在事务外完成所有 IFS 调用和数据收集避免 IFS 阻塞期间持有数据库行锁
long collectStartTime = System.currentTimeMillis();
Supplier<Server> ifsConFactory = resolveIfsConFactory();
newParentId = bomTree.getParentId();
Integer newLevel = Optional.ofNullable(tree.getLevel()).orElse(0);
nodeData = collectBomData(detail, newParentId, newLevel, ifsConFactory);
Map<String, BigDecimal> costCache = new ConcurrentHashMap<>();
nodeData = collectBomData(detail, newParentId, newLevel, ifsConFactory, costCache);
log.info("[PERF_LOG] Phase 1: collectBomData (including IFS calls) took {} ms", (System.currentTimeMillis() - collectStartTime));
} else {
detail = new QuoteDetail();
detail.setQuoteId(tree.getQuoteId());
@ -371,8 +378,11 @@ public class QuoteDetailBomTreeServiceImpl extends ServiceImpl<QuoteDetailBomTre
detail.setAlternativeNo(tree.getAlternativeNo());
// 先收集数据再写入
long collectStartTime = System.currentTimeMillis();
Supplier<Server> ifsConFactory = resolveIfsConFactory();
nodeData = collectBomData(detail, 0L, 0, ifsConFactory);
Map<String, BigDecimal> costCache = new ConcurrentHashMap<>();
nodeData = collectBomData(detail, 0L, 0, ifsConFactory, costCache);
log.info("[PERF_LOG] Phase 1: collectBomData (including IFS calls) took {} ms", (System.currentTimeMillis() - collectStartTime));
}
// 数据已就绪开启短事务执行纯 DB 写入和成本计算
@ -381,6 +391,7 @@ public class QuoteDetailBomTreeServiceImpl extends ServiceImpl<QuoteDetailBomTre
final Long finalNewParentId = newParentId;
final QuoteDetailBomTree finalBomTree = bomTree;
long transactionStartTime = System.currentTimeMillis();
transactionTemplate.execute(status -> {
// 1. 执行 BOM 结构切换的 DB 写入
if (Objects.nonNull(tree.getId())) {
@ -400,24 +411,30 @@ public class QuoteDetailBomTreeServiceImpl extends ServiceImpl<QuoteDetailBomTre
.update();
} else {
if (finalNodeData != null) {
lambdaUpdate().eq(QuoteDetailBomTree::getQuoteDetailId, finalDetail.getId()).remove();
quoteDetailBomService.lambdaUpdate().eq(QuoteDetailBom::getQuoteDetailId, finalDetail.getId()).remove();
quoteDetailRoutingService.lambdaUpdate().eq(QuoteDetailRouting::getQuoteDetailId, finalDetail.getId()).remove();
doSaveBomDataRecursive(finalNodeData, finalDetail, 0L);
}
}
return null;
});
log.info("[PERF_LOG] Phase 2: DB Transaction (Delete old tree & Save new tree) took {} ms", (System.currentTimeMillis() - transactionStartTime));
// 2. 重新计算成本 (如果开启)
// 移出事务外执行避免长事务锁表导致其他用户无法查看/编辑条目明细
if (Boolean.TRUE.equals(tree.getRecalculateCost()) && finalDetail != null) {
log.info("[BOM_SWITCH] Recalculate cost is enabled, executing cost calculation for QuoteDetailId: {}", finalDetail.getId());
long calcCostStartTime = System.currentTimeMillis();
try {
quoteDetailService.queryQuoteDetailCost(finalDetail);
log.info("[BOM_SWITCH] Cost calculation completed successfully for QuoteDetailId: {}", finalDetail.getId());
log.info("[PERF_LOG] Phase 3: Cost calculation (queryQuoteDetailCost) took {} ms", (System.currentTimeMillis() - calcCostStartTime));
} catch (Exception e) {
log.error("[BOM_SWITCH] Cost calculation failed for QuoteDetailId: {}, Error: {}", finalDetail.getId(), e.getMessage(), e);
throw new RuntimeException("BOM切换成功,但成本重新计算失败: " + e.getMessage());
}
}
log.info("========== [PERF_LOG] END changeQuoteDetailBomTree. Total time: {} ms ==========", (System.currentTimeMillis() - startTime));
}
// =========================================================================
@ -694,13 +711,23 @@ public class QuoteDetailBomTreeServiceImpl extends ServiceImpl<QuoteDetailBomTre
/**
* 获取物料成本带重试机制
*/
private void getFinalPartCost(QuoteDetailBom component, Server ifsServer) {
private void getFinalPartCost(QuoteDetailBom component, Server ifsServer, Map<String, BigDecimal> costCache) {
final int MAX_RETRY_COUNT = 2;
final long RETRY_INTERVAL_MS = 500;
String partNo = component.getComponentPart();
String site = component.getSite();
String cacheKey = site + "_" + partNo;
BigDecimal cachedCost = costCache.get(cacheKey);
if (cachedCost != null) {
component.setUnitPrice(cachedCost);
component.setActualPrice(cachedCost);
component.setQuotePrice(cachedCost);
log.info("[COST_QUERY] 从缓存读取成功 - PartNo: {}, Site: {}, UnitCost: {}", partNo, site, cachedCost);
return;
}
log.info("[COST_QUERY] 开始查询物料成本 - PartNo: {}, Site: {}", partNo, site);
PartInformationEntity part = new PartInformationEntity();
@ -723,6 +750,7 @@ public class QuoteDetailBomTreeServiceImpl extends ServiceImpl<QuoteDetailBomTre
component.setUnitPrice(unitCost);
component.setActualPrice(unitCost);
component.setQuotePrice(unitCost);
costCache.put(cacheKey, unitCost);
log.info("[COST_QUERY] 查询成功 - PartNo: {}, Site: {}, UnitCost: {}, 尝试次数: {}",
partNo, site, unitCost, retryCount);
@ -779,6 +807,7 @@ public class QuoteDetailBomTreeServiceImpl extends ServiceImpl<QuoteDetailBomTre
component.setUnitPrice(BigDecimal.ZERO);
component.setActualPrice(BigDecimal.ZERO);
component.setQuotePrice(BigDecimal.ZERO);
costCache.put(cacheKey, BigDecimal.ZERO);
}
}

52
src/main/java/com/spring/modules/quote/service/impl/QuoteDetailServiceImpl.java

@ -202,10 +202,16 @@ public class QuoteDetailServiceImpl extends ServiceImpl<QuoteDetailMapper, Quote
@Override
public Map<String, Object> queryQuoteDetailCost(QuoteDetail detail) {
log.info("========== [COST_CALC] START - QuoteDetailId: {} ==========", detail.getId());
long perfTotalStartTime = System.currentTimeMillis();
log.info("========== [COST_CALC] [PERF_LOG] START - QuoteDetailId: {} ==========", detail.getId());
QuoteDetail quoteDetail = getById(detail.getId());
Map<String, Object> map = new HashMap<>();
// 用于缓存IFS接口获取的物料预估成本避免在循环中重复调用降低性能
Map<String, BigDecimal> estimatedCostCache = new HashMap<>();
long perfAttrStartTime = System.currentTimeMillis();
// 1工具成本
BigDecimal toolCost = computedToolCost(quoteDetail.getId());
// 2其他成本
@ -218,9 +224,15 @@ public class QuoteDetailServiceImpl extends ServiceImpl<QuoteDetailMapper, Quote
// 2-2运输成本
BigDecimal shippingCost = BigDecimal.ZERO;
shippingCost = computedShippingCost(attributeList);
QuoteDetailBomTree one = quoteDetailBomTreeService.lambdaQuery()
log.info("[PERF_LOG] Fetching Attributes and Tools took {} ms", (System.currentTimeMillis() - perfAttrStartTime));
long perfTreeStartTime = System.currentTimeMillis();
List<QuoteDetailBomTree> roots = quoteDetailBomTreeService.lambdaQuery()
.eq(QuoteDetailBomTree::getQuoteDetailId, quoteDetail.getId())
.eq(QuoteDetailBomTree::getParentId, 0L).one();
.eq(QuoteDetailBomTree::getParentId, 0L)
.orderByDesc(QuoteDetailBomTree::getId)
.list();
QuoteDetailBomTree one = roots.isEmpty() ? null : roots.get(0);
// 3工艺成本
BigDecimal labourCost = BigDecimal.ZERO;// 人工成本
@ -237,12 +249,13 @@ public class QuoteDetailServiceImpl extends ServiceImpl<QuoteDetailMapper, Quote
.orderByDesc(QuoteDetailBomTree::getId)
.list();
log.info("[COST_CALC] Total BomTree nodes: {}, Root node exists: {}", list.size(), one != null);
log.info("[COST_CALC] [PERF_LOG] Fetching {} BomTree nodes took {} ms", list.size(), (System.currentTimeMillis() - perfTreeStartTime));
// 判断 报价BomTree是否存在
if (Objects.nonNull(one)) {
log.info("[COST_CALC] Root node - PartNo: {}, BomType: {}, TreeId: {}", one.getPartNo(), one.getBomType(), one.getId());
long perfRoutingStartTime = System.currentTimeMillis();
// 查询 工艺
QuoteDetailRouting queryRouting = new QuoteDetailRouting();
queryRouting.setQuoteDetailId(quoteDetail.getId());
@ -250,8 +263,9 @@ public class QuoteDetailServiceImpl extends ServiceImpl<QuoteDetailMapper, Quote
queryRouting.setTreeId(one.getId());
List<QuoteDetailRouting> routingList = quoteDetailRoutingService.queryQuoteDetailRouting(queryRouting);
Map<Long,List<QuoteDetailRouting>> routingMap = routingList.stream().collect(Collectors.groupingBy(QuoteDetailRouting::getTreeId));
log.info("[COST_CALC] Total routing records loaded: {}", routingList.size());
log.info("[COST_CALC] [PERF_LOG] Fetching and grouping {} Routing records took {} ms", routingList.size(), (System.currentTimeMillis() - perfRoutingStartTime));
long perfBomStartTime = System.currentTimeMillis();
// 查询 BOM
QuoteDetailBom quoteDetailBom = new QuoteDetailBom();
quoteDetailBom.setQuoteDetailId(quoteDetail.getId());
@ -259,10 +273,14 @@ public class QuoteDetailServiceImpl extends ServiceImpl<QuoteDetailMapper, Quote
quoteDetailBom.setTreeId(one.getId());
List<QuoteDetailBom> bomList = quoteDetailBomService.queryQuoteDetailBom(quoteDetailBom);
Map<Long,List<QuoteDetailBom>> bomMap = bomList.stream().collect(Collectors.groupingBy(QuoteDetailBom::getTreeId));
log.info("[COST_CALC] Total BOM component records loaded: {}", bomList.size());
log.info("[COST_CALC] [PERF_LOG] Fetching and grouping {} BOM component records took {} ms", bomList.size(), (System.currentTimeMillis() - perfBomStartTime));
long treeId = 0;
int nodeIndex = 0;
long perfConvolutionStartTime = System.currentTimeMillis();
long totalUpdateDbTime = 0;
// 第一层BOM
for (QuoteDetailBomTree bomTree : list) {
nodeIndex++;
@ -307,8 +325,13 @@ public class QuoteDetailServiceImpl extends ServiceImpl<QuoteDetailMapper, Quote
}
// 如果半成品的BOM类型是Purchase则加上该物料的预估材料成本(estimated_material_cost)
if ("Purchase".equals(bomTree.getBomType())) {
BigDecimal estimatedMaterialCost = quoteDetailBomTreeService.queryEstimatedMaterialCost(
bomTree.getSite(), bomTree.getPartNo());
String cacheKey = bomTree.getSite() + "_" + bomTree.getPartNo();
BigDecimal estimatedMaterialCost = estimatedCostCache.computeIfAbsent(cacheKey, k -> {
long ifsStart = System.currentTimeMillis();
BigDecimal cost = quoteDetailBomTreeService.queryEstimatedMaterialCost(bomTree.getSite(), bomTree.getPartNo());
log.info("[COST_CALC] [PERF_LOG] Fetched estimated material cost for {} from IFS/DB in {} ms", bomTree.getPartNo(), (System.currentTimeMillis() - ifsStart));
return cost == null ? BigDecimal.ZERO : cost;
});
bomQuotePrice = bomQuotePrice.add(estimatedMaterialCost);
log.info("[COST_CALC] Semi-finished product {} is Purchase type, add estimated material cost {}, accumulated cost is {}",
bomTree.getPartNo(), estimatedMaterialCost, bomQuotePrice);
@ -353,12 +376,11 @@ public class QuoteDetailServiceImpl extends ServiceImpl<QuoteDetailMapper, Quote
bom.setQuoteUnitPrice(decimal);
bom.setActualQuotePrice(decimal);
bom.setAllQuotePrice(decimal);
quoteDetailBomService.lambdaUpdate()
.set(QuoteDetailBom::getQuotePrice,bom.getQuotePrice())
.set(QuoteDetailBom::getActualPrice,bom.getActualPrice())
.set(QuoteDetailBom::getUnitPrice,bom.getUnitPrice())
.eq(QuoteDetailBom::getId,bom.getId())
.update();
}
if (!parentBomList.isEmpty()) {
long dbUpdateStart = System.currentTimeMillis();
quoteDetailBomService.updateBatchById(parentBomList);
totalUpdateDbTime += (System.currentTimeMillis() - dbUpdateStart);
}
BigDecimal qtyPerAssembly = BigDecimal.ONE;
@ -418,7 +440,7 @@ public class QuoteDetailServiceImpl extends ServiceImpl<QuoteDetailMapper, Quote
actualQuotePrice = actualQuotePrice.add(bom.getActualQuotePrice());
}
}
log.info("[COST_CALC] [PERF_LOG] Main convolution loop took {} ms, out of which DB updates took {} ms", (System.currentTimeMillis() - perfConvolutionStartTime), totalUpdateDbTime);
}
// 测试
BigDecimal testCost = BigDecimal.ZERO;

Loading…
Cancel
Save