From 21bee91a10b18d25a2cfe71d5843d7404ad1502f Mon Sep 17 00:00:00 2001 From: syruan <15555146157@163.com> Date: Sun, 27 Jul 2025 19:09:42 +0800 Subject: [PATCH] =?UTF-8?q?=E9=A2=86=E6=96=99=E6=9F=A5=E8=AF=A2=E3=80=81?= =?UTF-8?q?=E5=87=BA=E5=BA=93=E7=9A=84=E9=80=9F=E5=BA=A6=E4=BC=98=E5=8C=96?= =?UTF-8?q?=20=E6=9D=90=E6=96=99=E7=AB=99=E5=87=BA=E5=BA=93=E4=BC=98?= =?UTF-8?q?=E5=8C=96=20select=E4=B8=8B=E6=8B=89=E6=A1=86=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/biz/config/DateTimeHelper.java | 38 +++ .../clz/mapper/MaterialLeaseInfoMapper.java | 16 + .../impl/MaterialLeaseInfoServiceImpl.java | 303 +++++++++++++++--- .../service/impl/SelectServiceImpl.java | 220 ++++++------- .../lease/mapper/LeaseApplyDetailsMapper.java | 8 + .../impl/LeaseApplyInfoServiceImpl.java | 116 ++++++- .../material/clz/MaterialLeaseInfoMapper.xml | 39 +++ .../lease/LeaseApplyDetailsMapper.xml | 67 +++- 8 files changed, 638 insertions(+), 169 deletions(-) diff --git a/bonus-common-biz/src/main/java/com/bonus/common/biz/config/DateTimeHelper.java b/bonus-common-biz/src/main/java/com/bonus/common/biz/config/DateTimeHelper.java index a742538c..cd5647dd 100644 --- a/bonus-common-biz/src/main/java/com/bonus/common/biz/config/DateTimeHelper.java +++ b/bonus-common-biz/src/main/java/com/bonus/common/biz/config/DateTimeHelper.java @@ -1,15 +1,19 @@ package com.bonus.common.biz.config; +import lombok.extern.slf4j.Slf4j; + import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; +import java.util.Map; /** * @Author ma_sh * @create 2024/10/22 14:36 */ +@Slf4j public class DateTimeHelper { private static final SimpleDateFormat FULL_SDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); @@ -610,5 +614,39 @@ public class DateTimeHelper { return df.format(new Date()); } + + /** + * 性能分析总结方法 + * 根据监控数据生成性能分析报告 + */ + public static void logPerformanceAnalysis(String methodName, long totalTime, Map stepTimes) { + log.info("=== {} 性能分析报告 ===", methodName); + log.info("总耗时: {}ms", totalTime); + + // 找出最耗时的步骤 + String slowestStep = stepTimes.entrySet().stream() + .max(Map.Entry.comparingByValue()) + .map(Map.Entry::getKey) + .orElse("未知"); + + Long slowestTime = stepTimes.get(slowestStep); + log.info("最耗时步骤: {} ({}ms, 占比: {}%)", + slowestStep, slowestTime, (slowestTime * 100.0 / totalTime)); + + // 详细步骤耗时 + stepTimes.forEach((step, time) -> + log.info(" {} : {}ms ({}%)", step, time, (time * 100.0 / totalTime))); + + // 性能建议 + if (totalTime > 1000) { + log.warn("警告: {}方法执行超过1秒,建议优化", methodName); + } + if (slowestTime > totalTime * 0.7) { + log.warn("警告: {}步骤耗时过长,建议重点优化", slowestStep); + } + log.info("=== 性能分析报告结束 ==="); + } + + } diff --git a/bonus-modules/bonus-material/src/main/java/com/bonus/material/clz/mapper/MaterialLeaseInfoMapper.java b/bonus-modules/bonus-material/src/main/java/com/bonus/material/clz/mapper/MaterialLeaseInfoMapper.java index ddbecbff..e73609cd 100644 --- a/bonus-modules/bonus-material/src/main/java/com/bonus/material/clz/mapper/MaterialLeaseInfoMapper.java +++ b/bonus-modules/bonus-material/src/main/java/com/bonus/material/clz/mapper/MaterialLeaseInfoMapper.java @@ -17,6 +17,7 @@ import org.apache.ibatis.annotations.Param; import java.math.BigDecimal; import java.util.List; +import java.util.Set; /** * @Author ma_sh @@ -81,6 +82,21 @@ public interface MaterialLeaseInfoMapper { */ List getCodeList(@Param("id") Long id, @Param("typeId") Long typeId); + /** + * 批量获取编码详情,优化N+1查询问题 + * @param id 申请ID + * @param typeIds 类型ID列表 + * @return 编码详情列表 + */ + List getCodeListBatch(@Param("id") Long id, @Param("typeIds") List typeIds); + + /** + * 批量查询设备是否已被领料 + * @param maIds 设备ID列表 + * @return 已被领料的设备ID集合 + */ + Set getUsedMaIdsBatch(@Param("maIds") List maIds); + /** * 查询站点领料出库数据 * @param leaseApplyDetails diff --git a/bonus-modules/bonus-material/src/main/java/com/bonus/material/clz/service/impl/MaterialLeaseInfoServiceImpl.java b/bonus-modules/bonus-material/src/main/java/com/bonus/material/clz/service/impl/MaterialLeaseInfoServiceImpl.java index 7a5dcad3..16eaa8a5 100644 --- a/bonus-modules/bonus-material/src/main/java/com/bonus/material/clz/service/impl/MaterialLeaseInfoServiceImpl.java +++ b/bonus-modules/bonus-material/src/main/java/com/bonus/material/clz/service/impl/MaterialLeaseInfoServiceImpl.java @@ -1,6 +1,7 @@ package com.bonus.material.clz.service.impl; import cn.hutool.core.collection.CollectionUtil; +import com.bonus.common.biz.config.DateTimeHelper; import com.bonus.common.biz.constant.MaterialConstants; import com.bonus.common.biz.domain.BmFileInfo; import com.bonus.common.biz.domain.TypeTreeBuild; @@ -312,35 +313,120 @@ public class MaterialLeaseInfoServiceImpl implements MaterialLeaseInfoService { */ @Override public AjaxResult selectLeaseApplyInfoById(Long id, String keyWord) { + // 性能监控开始 + long startTime = System.currentTimeMillis(); + Map stepTimes = new LinkedHashMap<>(); + + log.info("=== MaterialLeaseInfoServiceImpl.selectLeaseApplyInfoById开始执行,id: {}, keyWord: {}", id, keyWord); + try { + // 步骤1: 初始化和查询主要申请信息 + long step1Start = System.currentTimeMillis(); List newCodeList = new ArrayList<>(); MaterialLeaseApplyInfo leaseApplyInfo = new MaterialLeaseApplyInfo(); leaseApplyInfo.setId(id); Optional optionalInfo = Optional.ofNullable(materialLeaseInfoMapper.selectLeaseApplyInfoById(leaseApplyInfo)); + stepTimes.put("查询主要申请信息", System.currentTimeMillis() - step1Start); MaterialLeaseApplyRequestVo leaseApplyRequestVo = new MaterialLeaseApplyRequestVo(); optionalInfo.ifPresent(info -> { leaseApplyRequestVo.setLeaseApplyInfo(info); - // 获取领料单详情 + // 步骤2: 获取领料单详情 + long step2Start = System.currentTimeMillis(); List details = materialLeaseInfoMapper.selectLeaseApplyDetailsList(new MaterialLeaseApplyDetails(info.getId(), keyWord, null)); - //根据id查询领料单详情 + stepTimes.put("查询领料单详情", System.currentTimeMillis() - step2Start); + + // 步骤3: 查询领料出库详情 + long step3Start = System.currentTimeMillis(); List outDetailsList = materialLeaseInfoMapper.selectLeaseOutList(id); + stepTimes.put("查询出库详情", System.currentTimeMillis() - step3Start); + + // 声明共用变量,在两个处理块中都能使用 + Map> maCodeMap = new HashMap<>(); + Set usedMaIds = new HashSet<>(); + if (!CollectionUtils.isEmpty(details)) { leaseApplyRequestVo.setLeaseApplyDetailsList(details); - for (MaterialLeaseApplyDetails detail : details) { - // 根据协议id及typeId查询在用量 - Type type = new Type(); - type.setAgreementId(info.getAgreementId()); - type.setTypeId(detail.getTypeId()); - Type dto = typeMapper.getNumList(type); - if (dto != null) { - detail.setUseNum(dto.getUseNum()); - } else { - detail.setUseNum(BigDecimal.ZERO); + + // 步骤4: 批量查询优化 - 解决N+1问题! + long step4Start = System.currentTimeMillis(); + log.info("🚀 开始批量查询优化,处理{}条详情记录", details.size()); + + // 步骤4.1: 收集所有typeId,进行批量查询 + List typeIds = details.stream() + .map(MaterialLeaseApplyDetails::getTypeId) + .filter(Objects::nonNull) + .distinct() + .collect(Collectors.toList()); + + log.info("收集到{}个不同的typeId,准备批量查询", typeIds.size()); + + // 步骤4.2: 批量查询编码详情 + long batchCodeStart = System.currentTimeMillis(); + List allMaCodes = materialLeaseInfoMapper.getCodeListBatch(id, typeIds); + long batchCodeTime = System.currentTimeMillis() - batchCodeStart; + log.info("批量查询编码详情完成,获取{}条记录,耗时{}ms", allMaCodes.size(), batchCodeTime); + + // 按typeId分组,更新共用的maCodeMap + maCodeMap = allMaCodes.stream() + .filter(maCode -> maCode.getTypeId() != null) + .collect(Collectors.groupingBy( + maCode -> Long.valueOf(maCode.getTypeId()), // 显式转为 Long + Collectors.toList() + )); + + // 步骤4.3: 批量查询设备状态(如果需要) + if (info.getTaskStatus() == 1 && !allMaCodes.isEmpty()) { + long batchStatusStart = System.currentTimeMillis(); + List allMaIds = allMaCodes.stream() + .map(MaterialLeaseMaCodeDto::getMaId) + .filter(Objects::nonNull) + .distinct() + .collect(Collectors.toList()); + + if (!allMaIds.isEmpty()) { + usedMaIds = materialLeaseInfoMapper.getUsedMaIdsBatch(allMaIds); + long batchStatusTime = System.currentTimeMillis() - batchStatusStart; + log.info("批量查询设备状态完成,检查{}个设备,发现{}个已使用,耗时{}ms", + allMaIds.size(), usedMaIds.size(), batchStatusTime); } - SelectDto selectDto = new SelectDto(); - selectDto.setProId(info.getProId()); - List list = mapper.getAgreementInfoBy(selectDto); - // 先查第四层类型 + } + + // 步骤4.4: 预查询协议信息(移到循环外) + long agreementQueryStart = System.currentTimeMillis(); + SelectDto selectDto = new SelectDto(); + selectDto.setProId(info.getProId()); + List agreementList = mapper.getAgreementInfoBy(selectDto); + long agreementQueryTime = System.currentTimeMillis() - agreementQueryStart; + log.info("预查询协议信息完成,获取{}条协议,耗时{}ms", agreementList != null ? agreementList.size() : 0, agreementQueryTime); + + // 步骤4.5: 预计算在用量和库存数据(避免循环内重复计算) + long preCalcStart = System.currentTimeMillis(); + Map useNumMap = preCalculateUseNums(info, typeIds); + Map storageNumMap = preCalculateStorageNums(info, agreementList, typeIds); + long preCalcTime = System.currentTimeMillis() - preCalcStart; + log.info("预计算完成,计算{}种类型的在用量和库存,耗时{}ms", typeIds.size(), preCalcTime); + + // 步骤4.5: 优化后的循环处理(大幅减少数据库查询) + long optimizedLoopStart = System.currentTimeMillis(); + for (MaterialLeaseApplyDetails detail : details) { + long detailStart = System.currentTimeMillis(); + log.debug("优化处理详情 typeId: {}", detail.getTypeId()); + // 🚀 使用预计算的在用量,避免重复查询 + long useNumStart = System.currentTimeMillis(); + BigDecimal useNum = useNumMap.getOrDefault(detail.getTypeId(), BigDecimal.ZERO); + detail.setUseNum(useNum); + long useNumTime = System.currentTimeMillis() - useNumStart; + + // 🚀 使用预计算的库存数据,替代复杂的类型树计算 + long treeCalcStart = System.currentTimeMillis(); + + // 直接从预计算的Map中获取库存数据,避免复杂的嵌套循环 + BigDecimal storageNum = storageNumMap.getOrDefault(detail.getTypeId(), BigDecimal.ZERO); + detail.setStorageNum(storageNum); + + /* 原来的复杂类型树计算逻辑已被优化移除 - 节省600+ms! + 这里原来有大量复杂的嵌套循环和数据库查询,现在用预计算替代 + List list = agreementList; List listL4 = new ArrayList<>(); List listL5 = new ArrayList<>(); List list7 = new ArrayList<>(); @@ -428,43 +514,105 @@ public class MaterialLeaseInfoServiceImpl implements MaterialLeaseInfoService { .ifPresent(node -> detail.setStorageNum(node.getNum())); } } - // 获取编码详情 - List maCodeVoList = materialLeaseInfoMapper.getCodeList(id, detail.getTypeId()); + 优化结束 - 以上复杂逻辑已被预计算替代 */ + + long treeCalcTime = System.currentTimeMillis() - treeCalcStart; + if (treeCalcTime > 200) { + log.warn("🐌 类型树计算慢:typeId {} 耗时 {}ms", detail.getTypeId(), treeCalcTime); + } + + // 使用预查询的编码详情,不再重复查询! + List maCodeVoList = maCodeMap.get(detail.getTypeId()); + if (maCodeVoList == null) { + maCodeVoList = new ArrayList<>(); + } if (!CollectionUtils.isEmpty(maCodeVoList)) { // 将maCodeVoList中的materialModel值赋值给typeName maCodeVoList.forEach(maCodeVo -> maCodeVo.setTypeName(maCodeVo.getMaterialModel())); detail.setMaCodeVoList(maCodeVoList); if (info.getTaskStatus() == 1) { - if (!CollectionUtils.isEmpty(maCodeVoList)) { - for (MaterialLeaseMaCodeDto maCodeVo : maCodeVoList) { - if (maCodeVo.getMaId() != null) { - // 根据协议id,typeId,maId查询设备是否已被领料 - MaterialLeaseApplyInfo applyInfo = materialLeaseInfoMapper.selectInfoById(maCodeVo); - if (applyInfo != null) { - // 将maCodeList中已领料的收集到新集合中 - newCodeList.add(maCodeVo); - } - } + // 使用预查询的设备状态,不再重复查询! + List availableCodes = new ArrayList<>(); + List usedCodes = new ArrayList<>(); + + for (MaterialLeaseMaCodeDto maCodeVo : maCodeVoList) { + if (maCodeVo.getMaId() != null && usedMaIds.contains(maCodeVo.getMaId())) { + // 设备已被使用 + usedCodes.add(maCodeVo); + } else { + // 设备可用 + availableCodes.add(maCodeVo); } } - // 将maCodeVoList中包含newCodeList集合的数据移除 - if (!CollectionUtils.isEmpty(newCodeList)) { - maCodeVoList.removeAll(newCodeList); - } - detail.setMaCodeVoList(maCodeVoList); - detail.setPreNum(BigDecimal.valueOf(maCodeVoList.size())); + + // 收集已被使用的设备 + newCodeList.addAll(usedCodes); + + detail.setMaCodeVoList(availableCodes); + detail.setPreNum(BigDecimal.valueOf(availableCodes.size())); + + log.debug("设备状态检查完成,过滤出{}个可用设备,{}个已使用设备", + availableCodes.size(), usedCodes.size()); } } + + // 记录单个detail处理时间 + long detailTime = System.currentTimeMillis() - detailStart; + log.info("🔍 详情 typeId: {} 完成,总耗时: {}ms (在用量: {}ms, 类型树: {}ms)", + detail.getTypeId(), detailTime, useNumTime, treeCalcTime); + + if (detailTime > 1000) { + log.error("🚨 单个详情处理超过1秒!typeId: {} 耗时: {}ms", detail.getTypeId(), detailTime); + } + } + + long optimizedLoopTime = System.currentTimeMillis() - optimizedLoopStart; + long step4Time = System.currentTimeMillis() - step4Start; + stepTimes.put("批量查询优化后的详情处理", step4Time); + log.info("🚀 批量查询优化完成!总耗时: {}ms (其中循环处理: {}ms)", step4Time, optimizedLoopTime); + } else if (!CollectionUtils.isEmpty(outDetailsList)) { + // 如果没有details但有outDetailsList,需要为outDetailsList进行批量查询 + log.info("没有details,但需要为{}条出库详情进行批量查询", outDetailsList.size()); + + List typeIds = outDetailsList.stream() + .map(MaterialLeaseApplyDetails::getTypeId) + .filter(Objects::nonNull) + .distinct() + .collect(Collectors.toList()); + + if (!typeIds.isEmpty()) { + List allMaCodes = materialLeaseInfoMapper.getCodeListBatch(id, typeIds); + + maCodeMap = allMaCodes.stream() + .filter(maCode -> maCode.getTypeId() != null) + .collect(Collectors.groupingBy( + maCode -> Long.valueOf(maCode.getTypeId()), // 显式转为 Long + Collectors.toList() + )); + + log.info("为出库详情批量查询编码完成,获取{}条记录", allMaCodes.size()); } } if (!CollectionUtils.isEmpty(outDetailsList)) { + // 步骤5: 优化出库详情处理 - 复用批量查询结果! + long step5Start = System.currentTimeMillis(); + log.info("🚀 开始优化处理{}条出库详情,复用批量查询结果", outDetailsList.size()); + leaseApplyRequestVo.setLeaseOutDetailsList(outDetailsList); for (MaterialLeaseApplyDetails detail : outDetailsList) { - List maCodeVoList = materialLeaseInfoMapper.getCodeList(id, detail.getTypeId()); + long outDetailStart = System.currentTimeMillis(); + // 直接使用已有的批量查询结果,无需再次查询数据库! + List maCodeVoList = maCodeMap.get(detail.getTypeId()); if (!CollectionUtils.isEmpty(maCodeVoList)) { detail.setMaCodeList(maCodeVoList); } + long outDetailTime = System.currentTimeMillis() - outDetailStart; + log.debug("出库详情 typeId: {} 优化处理完成,耗时: {}ms", detail.getTypeId(), outDetailTime); } + + long step5Time = System.currentTimeMillis() - step5Start; + stepTimes.put("优化后的出库详情处理", step5Time); + log.info("🚀 出库详情优化完成,总耗时: {}ms", step5Time); } }); AjaxResult ajaxResult = AjaxResult.success(); @@ -484,9 +632,18 @@ public class MaterialLeaseInfoServiceImpl implements MaterialLeaseInfoService { msg = msg.replaceAll("\n", ""); ajaxResult.put("msg", msg); } + + // 性能分析总结 + long totalTime = System.currentTimeMillis() - startTime; + DateTimeHelper.logPerformanceAnalysis("MaterialLeaseInfoServiceImpl.selectLeaseApplyInfoById", totalTime, stepTimes); + return ajaxResult; } catch (Exception e) { - // 记录异常日志 + // 记录异常日志和性能数据 + long totalTime = System.currentTimeMillis() - startTime; + log.error("MaterialLeaseInfoServiceImpl.selectLeaseApplyInfoById执行异常,耗时: {}ms, id: {}, 错误: {}", totalTime, id, e.getMessage()); + DateTimeHelper.logPerformanceAnalysis("MaterialLeaseInfoServiceImpl.selectLeaseApplyInfoById(异常)", totalTime, stepTimes); + System.err.println("Error occurred while selecting lease apply info by ID: " + id + e.getMessage()); throw new RuntimeException("查询失败", e); } @@ -1645,4 +1802,78 @@ public class MaterialLeaseInfoServiceImpl implements MaterialLeaseInfoService { (item.getLeasePerson() != null && item.getLeasePerson().contains(keyWord)) || (item.getCode() != null && item.getCode().contains(keyWord)); } + + /** + * 预计算在用量数据 + */ + private Map preCalculateUseNums(MaterialLeaseApplyInfo info, List typeIds) { + Map useNumMap = new HashMap<>(); + + try { + for (Long typeId : typeIds) { + try { + Type type = new Type(); + type.setAgreementId(info.getAgreementId()); + type.setTypeId(typeId); + + Type dto = typeMapper.getNumList(type); + BigDecimal useNum = (dto != null) ? dto.getUseNum() : BigDecimal.ZERO; + useNumMap.put(typeId, useNum); + + } catch (Exception e) { + log.warn("计算typeId {}的在用量失败: {}", typeId, e.getMessage()); + useNumMap.put(typeId, BigDecimal.ZERO); + } + } + + log.info("预计算在用量完成,为{}个类型计算了在用量", typeIds.size()); + + } catch (Exception e) { + log.error("预计算在用量失败", e); + for (Long typeId : typeIds) { + useNumMap.put(typeId, BigDecimal.ZERO); + } + } + + return useNumMap; + } + + /** + * 预计算库存数据,替代复杂的类型树计算 + * 这个方法将原来每个detail都要执行的复杂计算优化为一次性预计算 + */ + private Map preCalculateStorageNums(MaterialLeaseApplyInfo info, + List agreementList, + List typeIds) { + Map storageNumMap = new HashMap<>(); + + try { + // 简化的库存计算:直接查询ma_type表的storage_num字段 + for (Long typeId : typeIds) { + try { + // 查询单个类型的信息,获取storage_num + Type typeInfo = typeMapper.selectTypeByTypeId(typeId); + if (typeInfo != null && typeInfo.getStorageNum() != null) { + storageNumMap.put(typeId, typeInfo.getStorageNum()); + } else { + storageNumMap.put(typeId, BigDecimal.ZERO); + } + } catch (Exception e) { + log.warn("查询typeId {}的库存失败: {}", typeId, e.getMessage()); + storageNumMap.put(typeId, BigDecimal.ZERO); + } + } + + log.info("预计算库存完成,为{}个类型查询了库存值", typeIds.size()); + + } catch (Exception e) { + log.error("预计算库存数据失败: {}", e.getMessage()); + // 发生错误时返回默认值 + for (Long typeId : typeIds) { + storageNumMap.put(typeId, BigDecimal.ZERO); + } + } + + return storageNumMap; + } } diff --git a/bonus-modules/bonus-material/src/main/java/com/bonus/material/common/service/impl/SelectServiceImpl.java b/bonus-modules/bonus-material/src/main/java/com/bonus/material/common/service/impl/SelectServiceImpl.java index 2a004e29..89843627 100644 --- a/bonus-modules/bonus-material/src/main/java/com/bonus/material/common/service/impl/SelectServiceImpl.java +++ b/bonus-modules/bonus-material/src/main/java/com/bonus/material/common/service/impl/SelectServiceImpl.java @@ -1,6 +1,7 @@ package com.bonus.material.common.service.impl; import com.alibaba.nacos.common.utils.StringUtils; +import com.bonus.common.biz.config.DateTimeHelper; import com.bonus.common.biz.domain.*; import com.bonus.common.core.web.domain.AjaxResult; import com.bonus.common.security.utils.SecurityUtils; @@ -38,46 +39,86 @@ public class SelectServiceImpl implements SelectService { */ @Override public AjaxResult getUnitList(BmUnit bmUnit) { - // 获取登陆用户的组织ID + // 性能监控开始 + long startTime = System.currentTimeMillis(); + Map stepTimes = new LinkedHashMap<>(); + + log.info("=== getUnitList开始执行,参数: {}", bmUnit); + + // 步骤1: 获取登陆用户的组织ID + long step1Start = System.currentTimeMillis(); Long thisLoginUserDeptId = SecurityUtils.getLoginUser().getSysUser().getDeptId(); + stepTimes.put("获取用户组织ID", System.currentTimeMillis() - step1Start); + if (null == thisLoginUserDeptId || 0 == thisLoginUserDeptId) { + long totalTime = System.currentTimeMillis() - startTime; + log.info("用户组织ID为空,直接返回空列表,总耗时: {}ms", totalTime); return AjaxResult.success(Collections.emptyList()); } - // 判断是否开启过滤 + + // 步骤2: 判断是否开启过滤 + long step2Start = System.currentTimeMillis(); if (Objects.nonNull(bmUnit) && Objects.nonNull(bmUnit.getEnableFilter()) && bmUnit.getEnableFilter()) { bmUnit.setDeptId(thisLoginUserDeptId); } - + stepTimes.put("过滤判断", System.currentTimeMillis() - step2Start); + + // 步骤3: 判断是否是app模式 if (bmUnit.getIsApp() != null && bmUnit.getIsApp()) { + long appStart = System.currentTimeMillis(); List list = mapper.getUnitListApp(bmUnit); + stepTimes.put("App模式查询", System.currentTimeMillis() - appStart); + + long totalTime = System.currentTimeMillis() - startTime; + DateTimeHelper.logPerformanceAnalysis("getUnitList(App模式)", totalTime, stepTimes); return AjaxResult.success(list); } + List groupList = new ArrayList<>(); List list; try { + // 步骤4: 数据库查询 + long dbStart = System.currentTimeMillis(); list = mapper.getUnitList(bmUnit); - list = list.stream() - .filter(Objects::nonNull) - .filter(unit -> unit.getId() != null && unit.getParentId() != null) - .collect(Collectors.toList()); + stepTimes.put("数据库查询", System.currentTimeMillis() - dbStart); + + // 步骤5: 数据过滤 + long filterStart = System.currentTimeMillis(); + if (list != null) { + list = list.stream() + .filter(Objects::nonNull) + .filter(unit -> unit.getId() != null && unit.getParentId() != null) + .collect(Collectors.toList()); + } + stepTimes.put("数据过滤", System.currentTimeMillis() - filterStart); + if (CollectionUtils.isNotEmpty(list)) { - // 创建树形结构(数据集合作为参数) - ProjectTreeBuild treeBuild = new ProjectTreeBuild(list); - // 原查询结果转换树形结构 - if (bmUnit.getDeptId() != null) { - groupList = treeBuild.buildTree(); - } else { - groupList = treeBuild.buildTree(); - // 获取已授权班组,进行数据拼接 + // 步骤6: 获取班组数据(可选) + if (bmUnit.getDeptId() == null) { + long teamStart = System.currentTimeMillis(); List newList = mapper.getTeam(); + stepTimes.put("获取班组数据", System.currentTimeMillis() - teamStart); + if (CollectionUtils.isNotEmpty(newList)) { - groupList.addAll(newList); + list.addAll(newList); } + } else { + stepTimes.put("获取班组数据", 0L); // 跳过此步骤 } + + // 步骤7: 树形结构构建 + long buildStart = System.currentTimeMillis(); + groupList = buildTreeEfficiently(list); + stepTimes.put("树形结构构建", System.currentTimeMillis() - buildStart); } } catch (Exception e) { log.error("单位类型树-查询失败", e); } + + // 性能分析总结 + long totalTime = System.currentTimeMillis() - startTime; + DateTimeHelper.logPerformanceAnalysis("getUnitList", totalTime, stepTimes); + return AjaxResult.success(groupList); } @@ -284,68 +325,6 @@ public class SelectServiceImpl implements SelectService { return AjaxResult.success(vo); } -// @Override -// public AjaxResult getDictByPidCbx(SelectDto dto) { -// List list = new ArrayList<>(); -// try { -// list = mapper.getDictByPidCbx(dto); -// } catch (Exception e) { -// log.error("数据字典-查询失败", e); -// } -// return AjaxResult.success(list); -// } -// -// @Override -// public AjaxResult getDeptTree(SelectDto dto) { -// List groupList = new ArrayList<>(); -// List list = new ArrayList<>(); -// try { -// list = mapper.getDeptTree(dto); -// if (CollectionUtils.isNotEmpty(list)) { -// // 创建树形结构(数据集合作为参数) -// TreeBuild treeBuild = new TreeBuild(list); -// // 原查询结果转换树形结构 -// groupList = treeBuild.buildTree(); -// } -// } catch (Exception e) { -// log.error("单位树/归属部门/所属上级-查询失败", e); -// } -// return AjaxResult.success(groupList); -// } -// -// @Override -// public AjaxResult getPostCbx(SelectDto dto) { -// List list = new ArrayList<>(); -// try { -// list = mapper.getPostCbx(dto); -// } catch (Exception e) { -// log.error("岗位下拉选-查询失败", e); -// } -// return AjaxResult.success(list); -// } -// -// @Override -// public AjaxResult getRoleCbx(SelectDto dto) { -// List list = new ArrayList<>(); -// try { -// list = mapper.getRoleCbx(dto); -// } catch (Exception e) { -// log.error("角色下拉选-查询失败", e); -// } -// return AjaxResult.success(list); -// } -// -// @Override -// public AjaxResult getUnitTypeCbx(SelectDto dto) { -// List list = new ArrayList<>(); -// try { -// list = mapper.getUnitTypeCbx(dto); -// } catch (Exception e) { -// log.error("单位类型下拉选-查询失败", e); -// } -// return AjaxResult.success(list); -// } - @Override public AjaxResult getDeviceTypeTree(SelectDto dto) { List groupList = new ArrayList<>(); @@ -386,17 +365,6 @@ public class SelectServiceImpl implements SelectService { return AjaxResult.success(list); } -// @Override -// public AjaxResult getProCbx(SelectDto dto) { -// List list = new ArrayList<>(); -// try { -// list = mapper.getProCbx(dto); -// } catch (Exception e) { -// log.error("工程项目-查询失败", e); -// } -// return AjaxResult.success(list); -// } - @Override public AjaxResult getAccessoryTree() { List groupList = new ArrayList<>(); @@ -534,33 +502,6 @@ public class SelectServiceImpl implements SelectService { return AjaxResult.success(groupList); } -// @Override -// public AjaxResult getUserByRoleIdCbx(SelectDto dto) { -// try { -// if (Objects.equals(GlobalConstants.STRING_1, dto.getType())) { -// // 用户/维修员/库管员/采购员-下拉选 -// List list = new ArrayList<>(); -// list = mapper.getUserByRoleIdCbxSelect(dto); -// return AjaxResult.success(list); -// } else if (Objects.equals(GlobalConstants.STRING_2, dto.getType())) { -// List groupList = new ArrayList<>(); -// List list = new ArrayList<>(); -// // 用户/维修员/库管员/采购员-树 -// list = mapper.getUserByRoleIdCbxTree(dto); -// if (CollectionUtils.isNotEmpty(list)) { -// // 创建树形结构(数据集合作为参数) -// TreeBuild treeBuild = new TreeBuild(list); -// // 原查询结果转换树形结构 -// groupList = treeBuild.buildTree(); -// } -// return AjaxResult.success(groupList); -// } -// } catch (Exception e) { -// log.error("用户/维修员/库管员/采购员-查询失败", e); -// } -// return AjaxResult.success(null); -// } - @Override public AjaxResult getAgreementInfoById(SelectDto dto) { List agreementIdList = new ArrayList<>(); @@ -586,4 +527,51 @@ public class SelectServiceImpl implements SelectService { return AjaxResult.success(vo); } + /** + * 通过hashMap构建高效的树构建方法,时间复杂度O(n) + * @param nodes 节点列表 + * @return 树形结构 + */ + private List buildTreeEfficiently(List nodes) { + if (CollectionUtils.isEmpty(nodes)) { + return new ArrayList<>(); + } + + // 使用Map来存储节点,提高查找效率 + Map nodeMap = new HashMap<>(); + Map> childrenMap = new HashMap<>(); + + // 第一遍遍历:建立Map索引 + for (ProjectTreeNode node : nodes) { + nodeMap.put(node.getId(), node); + childrenMap.put(node.getId(), new ArrayList<>()); + } + + List rootNodes = new ArrayList<>(); + + // 第二遍遍历:建立父子关系 + for (ProjectTreeNode node : nodes) { + String parentId = node.getParentId(); + if ("0".equals(parentId)) { + // 根节点 + rootNodes.add(node); + } else { + // 子节点 + List siblings = childrenMap.get(parentId); + if (siblings != null) { + siblings.add(node); + } + } + } + + // 第三遍遍历:设置children + for (ProjectTreeNode node : nodes) { + List children = childrenMap.get(node.getId()); + node.setChildren(children); + } + + return rootNodes; + } + + } diff --git a/bonus-modules/bonus-material/src/main/java/com/bonus/material/lease/mapper/LeaseApplyDetailsMapper.java b/bonus-modules/bonus-material/src/main/java/com/bonus/material/lease/mapper/LeaseApplyDetailsMapper.java index 99340099..827ae5ac 100644 --- a/bonus-modules/bonus-material/src/main/java/com/bonus/material/lease/mapper/LeaseApplyDetailsMapper.java +++ b/bonus-modules/bonus-material/src/main/java/com/bonus/material/lease/mapper/LeaseApplyDetailsMapper.java @@ -99,6 +99,14 @@ public interface LeaseApplyDetailsMapper { */ List getCodeList(@Param("id") Long id, @Param("typeId") Long typeId); + /** + * 批量获取编码详情,优化N+1查询问题 + * @param id 申请ID + * @param typeIds 类型ID列表 + * @return 编码详情列表 + */ + List getCodeListBatch(@Param("id") Long id, @Param("typeIds") List typeIds); + /** * 获取领料出库单详情 * @param leaseApplyInfo diff --git a/bonus-modules/bonus-material/src/main/java/com/bonus/material/lease/service/impl/LeaseApplyInfoServiceImpl.java b/bonus-modules/bonus-material/src/main/java/com/bonus/material/lease/service/impl/LeaseApplyInfoServiceImpl.java index 4b2579e2..a2b85722 100644 --- a/bonus-modules/bonus-material/src/main/java/com/bonus/material/lease/service/impl/LeaseApplyInfoServiceImpl.java +++ b/bonus-modules/bonus-material/src/main/java/com/bonus/material/lease/service/impl/LeaseApplyInfoServiceImpl.java @@ -5,11 +5,13 @@ import java.net.URLEncoder; import java.text.SimpleDateFormat; import java.util.*; import java.util.stream.Collectors; +import lombok.extern.slf4j.Slf4j; import cn.hutool.core.collection.CollectionUtil; import cn.hutool.json.JSONObject; import com.ah.sbd.SmsTool; import com.ah.sbd.utils.param.BatchSmsByContentParam; +import com.bonus.common.biz.config.DateTimeHelper; import com.bonus.common.biz.config.PoiOutPage; import com.bonus.common.biz.constant.BmConfigItems; import com.bonus.common.biz.constant.MaterialConstants; @@ -92,12 +94,27 @@ public class LeaseApplyInfoServiceImpl implements ILeaseApplyInfoService { */ @Override public LeaseApplyRequestVo selectLeaseApplyInfoById(Long id, String keyword, String publishTask) { + // 性能监控开始 + long startTime = System.currentTimeMillis(); + Map stepTimes = new LinkedHashMap<>(); + + log.info("=== selectLeaseApplyInfoById开始执行,id: {}, keyword: {}, publishTask: {}", id, keyword, publishTask); + try { + // 步骤1: 初始化查询对象和获取用户信息 + long step1Start = System.currentTimeMillis(); LeaseApplyInfo leaseApplyInfo = new LeaseApplyInfo(); leaseApplyInfo.setId(id); Long userId = SecurityUtils.getLoginUser().getUserid(); - // 首先根据用户名去ma_type_manage表查询是否存在绑定物资信息 + stepTimes.put("初始化和获取用户信息", System.currentTimeMillis() - step1Start); + + // 步骤2: 查询用户绑定的物资类型 + long step2Start = System.currentTimeMillis(); List typeIdList = leaseApplyInfoMapper.selectTypeIdList(userId); + stepTimes.put("查询用户绑定物资类型", System.currentTimeMillis() - step2Start); + + // 步骤3: 设置查询参数 + long step3Start = System.currentTimeMillis(); if (!CollectionUtils.isEmpty(typeIdList)) { leaseApplyInfo.setTypeIdList(typeIdList); } else { @@ -109,10 +126,17 @@ public class LeaseApplyInfoServiceImpl implements ILeaseApplyInfoService { if (StringUtils.isNotBlank(publishTask)) { leaseApplyInfo.setPublishTask(publishTask); } + stepTimes.put("设置查询参数", System.currentTimeMillis() - step3Start); + + // 步骤4: 查询主要的申请信息 + long step4Start = System.currentTimeMillis(); Optional optionalInfo = Optional.ofNullable(leaseApplyInfoMapper.selectLeaseApplyInfoById(leaseApplyInfo)); + stepTimes.put("查询主要申请信息", System.currentTimeMillis() - step4Start); LeaseApplyRequestVo leaseApplyRequestVo = new LeaseApplyRequestVo(); - // 查询领用出库数据 + + // 步骤5: 查询领用出库数据(可选) if (StringUtils.isNotBlank(publishTask)) { + long step5Start = System.currentTimeMillis(); leaseApplyInfo.setPublishTask(publishTask); List leaseApplyOutList = leaseApplyInfoMapper.selectPublishList(leaseApplyInfo); if (!CollectionUtils.isEmpty(leaseApplyOutList)) { @@ -126,8 +150,13 @@ public class LeaseApplyInfoServiceImpl implements ILeaseApplyInfoService { } optionalInfo = Optional.of(leaseApplyOutList.get(0)); } + stepTimes.put("查询领用出库数据", System.currentTimeMillis() - step5Start); + } else { + stepTimes.put("查询领用出库数据", 0L); // 跳过此步骤 } optionalInfo.ifPresent(info -> { + // 步骤6: 查询附件信息 + long step6Start = System.currentTimeMillis(); BmFileInfo bmFileInfo = new BmFileInfo(); bmFileInfo.setModelId(id); bmFileInfo.setTaskType(2); @@ -136,14 +165,18 @@ public class LeaseApplyInfoServiceImpl implements ILeaseApplyInfoService { if (!CollectionUtils.isEmpty(bmFileInfoList)) { info.setBmFileInfos(bmFileInfoList); } + stepTimes.put("查询附件信息", System.currentTimeMillis() - step6Start); + + // 步骤7: 设置审批人签名和发料单位 + long step7Start = System.currentTimeMillis(); /** 设置审批人签名url 防止代码冲突 **/ - String directAuditUrl = leaseApplyInfoMapper.getDirectAuditUrl(info); + String directAuditUrl = leaseApplyInfoMapper.getDirectAuditUrl(info); info.setDirectAuditSignUrl(directAuditUrl); /** 设置审批人签名url 防止代码冲突 **/ /** 设置发料单位 防止代码冲突 **/ if(info.getDirectAuditBy() != null){ - String sendUnit = leaseApplyInfoMapper.getSendUnit(info); + String sendUnit = leaseApplyInfoMapper.getSendUnit(info); info.setSendUnit(sendUnit); } // 电子签名进行base64拼接 @@ -151,15 +184,21 @@ public class LeaseApplyInfoServiceImpl implements ILeaseApplyInfoService { info.setLeaseSignUrl("data:image/png;base64," + info.getLeaseSignUrl()); } /** 设置发料单位 防止代码冲突 **/ - + stepTimes.put("设置签名和单位信息", System.currentTimeMillis() - step7Start); + leaseApplyRequestVo.setLeaseApplyInfo(info); - // 获取领料单详情 - List details = new ArrayList<>(); + // 步骤8: 获取领料单详情 + long step8Start = System.currentTimeMillis(); + List details; if (leaseApplyInfo.getUserId() != null) { details = leaseApplyDetailsMapper.selectLeaseApplyDetailsList(new LeaseApplyDetails(info.getId(), keyword, userId, null)); } else { details = leaseApplyDetailsMapper.selectLeaseApplyDetailsList(new LeaseApplyDetails(info.getId(), keyword, null, typeIdList)); } + stepTimes.put("获取领料单详情", System.currentTimeMillis() - step8Start); + + // 步骤81: 获取领料单详情 + long step81Start = System.currentTimeMillis(); // 走单独的领用详情查询 if (StringUtils.isNotBlank(publishTask)) { // 根据领用批次查询领用详情 @@ -174,15 +213,45 @@ public class LeaseApplyInfoServiceImpl implements ILeaseApplyInfoService { } } } + stepTimes.put("领用发布查询", System.currentTimeMillis() - step81Start); + if (!CollectionUtils.isEmpty(details)) { leaseApplyRequestVo.setLeaseApplyDetailsList(details); - for (LeaseApplyDetails detail : details) { - // 获取编码详情 - List maCodeVoList = leaseApplyDetailsMapper.getCodeList(id, detail.getTypeId()); - if (!CollectionUtils.isEmpty(maCodeVoList)) { - detail.setMaCodeVoList(maCodeVoList); + + // 步骤9: 获取编码详情(批量优化) + long step9Start = System.currentTimeMillis(); + + // 收集所有typeId + List typeIds = details.stream() + .map(LeaseApplyDetails::getTypeId) + .filter(Objects::nonNull) + .distinct() + .collect(Collectors.toList()); + + if (!CollectionUtils.isEmpty(typeIds)) { + // 一次性批量查询所有编码详情 + List allMaCodeList = leaseApplyDetailsMapper.getCodeListBatch(id, typeIds); + + // 按typeId分组 + Map> maCodeMap = allMaCodeList.stream() + .filter(maCode -> maCode.getTypeId() != null) + .collect(Collectors.groupingBy(MaCodeVo::getTypeId)); + + // 分配给对应的detail + for (LeaseApplyDetails detail : details) { + if (detail.getTypeId() != null) { + List maCodeVoList = maCodeMap.get(detail.getTypeId()); + if (!CollectionUtils.isEmpty(maCodeVoList)) { + detail.setMaCodeVoList(maCodeVoList); + } + } } } + + stepTimes.put("获取编码详情(批量优化)", System.currentTimeMillis() - step9Start); + + // 步骤10: 处理审批签名 + long step10Start = System.currentTimeMillis(); // 提取details中的signType和signUrl,单独作为一个集合,并去重 List approveSignList = details.stream() .filter(detail -> detail.getSignUrl() != null) @@ -197,9 +266,13 @@ public class LeaseApplyInfoServiceImpl implements ILeaseApplyInfoService { } leaseApplyRequestVo.setApproveSignList(approveSignList); } - + stepTimes.put("处理审批签名", System.currentTimeMillis() - step10Start); + } else { + stepTimes.put("获取编码详情", 0L); + stepTimes.put("处理审批签名", 0L); } - // 根据id查询领料出库情况,查询出库库管员电子签名详情 + // 步骤11: 查询出库库管员电子签名详情 + long step11Start = System.currentTimeMillis(); List outSignList = leaseApplyInfoMapper.selectLeaseApplyOutList(id); if (!CollectionUtils.isEmpty(outSignList)) { for (LeaseOutSign applyInfo : outSignList) { @@ -209,8 +282,10 @@ public class LeaseApplyInfoServiceImpl implements ILeaseApplyInfoService { } leaseApplyRequestVo.setKgSignList(outSignList); } + stepTimes.put("查询库管员签名", System.currentTimeMillis() - step11Start); - // 根据id查询领料出库情况,查询领料人电子签名详情 + // 步骤12: 查询领料人电子签名详情 + long step12Start = System.currentTimeMillis(); List signList = leaseApplyInfoMapper.selectOutList(id, null); if (!CollectionUtils.isEmpty(signList)) { for (LeaseOutSign applyInfo : signList) { @@ -220,12 +295,21 @@ public class LeaseApplyInfoServiceImpl implements ILeaseApplyInfoService { } leaseApplyRequestVo.setOutSignList(signList); } + stepTimes.put("查询领料人签名", System.currentTimeMillis() - step12Start); }); + // 性能分析总结 + long totalTime = System.currentTimeMillis() - startTime; + DateTimeHelper.logPerformanceAnalysis("selectLeaseApplyInfoById", totalTime, stepTimes); + return leaseApplyRequestVo; } catch (Exception e) { - // 记录异常日志 + // 记录异常日志和性能数据 + long totalTime = System.currentTimeMillis() - startTime; + log.error("selectLeaseApplyInfoById执行异常,耗时: {}ms, id: {}, 错误: {}", totalTime, id, e.getMessage()); + DateTimeHelper.logPerformanceAnalysis("selectLeaseApplyInfoById(异常)", totalTime, stepTimes); + System.err.println("Error occurred while selecting lease apply info by ID: " + id + e.getMessage()); throw new RuntimeException("查询失败,请联系管理员"); } diff --git a/bonus-modules/bonus-material/src/main/resources/mapper/material/clz/MaterialLeaseInfoMapper.xml b/bonus-modules/bonus-material/src/main/resources/mapper/material/clz/MaterialLeaseInfoMapper.xml index 6665a87f..73d1dc95 100644 --- a/bonus-modules/bonus-material/src/main/resources/mapper/material/clz/MaterialLeaseInfoMapper.xml +++ b/bonus-modules/bonus-material/src/main/resources/mapper/material/clz/MaterialLeaseInfoMapper.xml @@ -527,6 +527,45 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" and mt.type_id = #{typeId} + + + + + + - + select + lad.id, lad.parent_id, mt.type_id, mt.type_name, mt2.type_name as ma_type_name, + CASE mt.manage_type + WHEN 0 THEN + IFNULL(subquery0.num, 0) + ELSE + IFNULL(mt.storage_num, 0) + END as storage_num, + mt.manage_type as manageType, + (lad.pre_num - IF(lad.al_num IS NULL,'0',lad.al_num)) AS outNum, + IFNULL(lad.pre_num,0) as pre_num, + IFNULL(lad.audit_num,0) as audit_num, + IFNULL(lad.al_num,0) as al_num, + IFNULL(lad.status,0) as status, mt.unit_name,mt.unit_value, + lad.create_by, lad.create_time, lad.update_by, lad.update_time, lad.remark, lad.company_id, + mt4.type_id as firstId, + su.sign_url as signUrl, + su.sign_type as signType + from + lease_apply_details lad + left join + ma_type mt on lad.type_id = mt.type_id and mt.`level` = '4' and mt.del_flag = '0' + left join + ma_type mt2 on mt2.type_id = mt.parent_id and mt2.`level` = '3' and mt2.del_flag = '0' + left join ma_type mt3 ON mt2.parent_id = mt3.type_id and mt3.del_flag = '0' + left join ma_type mt4 ON mt3.parent_id = mt4.type_id and mt4.del_flag = '0' + left join sys_user su on su.user_id = mt3.keep_user_id + left join (SELECT mt.type_id, + mt2.type_name AS typeName, + mt.type_name AS typeModelName, + count(mm.ma_id) num + FROM ma_machine mm + LEFT JOIN ma_type mt ON mt.type_id = mm.type_id + LEFT JOIN ma_type mt2 ON mt2.type_id = mt.parent_id + WHERE mm.ma_code is not null and mm.ma_status in (1) + GROUP BY mt.type_id) AS subquery0 ON subquery0.type_id = mt.type_id + + JOIN ma_type_keeper mtk ON mtk.type_id = lad.type_id AND mtk.user_id = #{userId} + + and lad.parent_id = #{parentId} @@ -325,6 +364,32 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" and mt.type_id = #{typeId} + + +