领料查询、出库的速度优化

材料站出库优化
select下拉框优化
This commit is contained in:
syruan 2025-07-27 19:09:42 +08:00
parent 0ec7b9ca73
commit 21bee91a10
8 changed files with 638 additions and 169 deletions

View File

@ -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<String, Long> 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("=== 性能分析报告结束 ===");
}
}

View File

@ -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<MaterialLeaseMaCodeDto> getCodeList(@Param("id") Long id, @Param("typeId") Long typeId);
/**
* 批量获取编码详情优化N+1查询问题
* @param id 申请ID
* @param typeIds 类型ID列表
* @return 编码详情列表
*/
List<MaterialLeaseMaCodeDto> getCodeListBatch(@Param("id") Long id, @Param("typeIds") List<Long> typeIds);
/**
* 批量查询设备是否已被领料
* @param maIds 设备ID列表
* @return 已被领料的设备ID集合
*/
Set<Long> getUsedMaIdsBatch(@Param("maIds") List<Long> maIds);
/**
* 查询站点领料出库数据
* @param leaseApplyDetails

View File

@ -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<String, Long> stepTimes = new LinkedHashMap<>();
log.info("=== MaterialLeaseInfoServiceImpl.selectLeaseApplyInfoById开始执行id: {}, keyWord: {}", id, keyWord);
try {
// 步骤1: 初始化和查询主要申请信息
long step1Start = System.currentTimeMillis();
List<MaterialLeaseMaCodeDto> newCodeList = new ArrayList<>();
MaterialLeaseApplyInfo leaseApplyInfo = new MaterialLeaseApplyInfo();
leaseApplyInfo.setId(id);
Optional<MaterialLeaseApplyInfo> 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<MaterialLeaseApplyDetails> details = materialLeaseInfoMapper.selectLeaseApplyDetailsList(new MaterialLeaseApplyDetails(info.getId(), keyWord, null));
//根据id查询领料单详情
stepTimes.put("查询领料单详情", System.currentTimeMillis() - step2Start);
// 步骤3: 查询领料出库详情
long step3Start = System.currentTimeMillis();
List<MaterialLeaseApplyDetails> outDetailsList = materialLeaseInfoMapper.selectLeaseOutList(id);
stepTimes.put("查询出库详情", System.currentTimeMillis() - step3Start);
// 声明共用变量在两个处理块中都能使用
Map<Long, List<MaterialLeaseMaCodeDto>> maCodeMap = new HashMap<>();
Set<Long> 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<Long> 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<MaterialLeaseMaCodeDto> 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<Long> 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<AgreementVo> list = mapper.getAgreementInfoBy(selectDto);
// 先查第四层类型
}
// 步骤4.4: 预查询协议信息移到循环外
long agreementQueryStart = System.currentTimeMillis();
SelectDto selectDto = new SelectDto();
selectDto.setProId(info.getProId());
List<AgreementVo> 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<Long, BigDecimal> useNumMap = preCalculateUseNums(info, typeIds);
Map<Long, BigDecimal> 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<AgreementVo> list = agreementList;
List<TypeTreeNode> listL4 = new ArrayList<>();
List<TypeTreeNode> listL5 = new ArrayList<>();
List<TypeTreeNode> list7 = new ArrayList<>();
@ -428,43 +514,105 @@ public class MaterialLeaseInfoServiceImpl implements MaterialLeaseInfoService {
.ifPresent(node -> detail.setStorageNum(node.getNum()));
}
}
// 获取编码详情
List<MaterialLeaseMaCodeDto> maCodeVoList = materialLeaseInfoMapper.getCodeList(id, detail.getTypeId());
优化结束 - 以上复杂逻辑已被预计算替代 */
long treeCalcTime = System.currentTimeMillis() - treeCalcStart;
if (treeCalcTime > 200) {
log.warn("🐌 类型树计算慢typeId {} 耗时 {}ms", detail.getTypeId(), treeCalcTime);
}
// 使用预查询的编码详情不再重复查询
List<MaterialLeaseMaCodeDto> 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) {
// 根据协议idtypeIdmaId查询设备是否已被领料
MaterialLeaseApplyInfo applyInfo = materialLeaseInfoMapper.selectInfoById(maCodeVo);
if (applyInfo != null) {
// 将maCodeList中已领料的收集到新集合中
newCodeList.add(maCodeVo);
}
}
// 使用预查询的设备状态不再重复查询
List<MaterialLeaseMaCodeDto> availableCodes = new ArrayList<>();
List<MaterialLeaseMaCodeDto> 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<Long> typeIds = outDetailsList.stream()
.map(MaterialLeaseApplyDetails::getTypeId)
.filter(Objects::nonNull)
.distinct()
.collect(Collectors.toList());
if (!typeIds.isEmpty()) {
List<MaterialLeaseMaCodeDto> 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<MaterialLeaseMaCodeDto> maCodeVoList = materialLeaseInfoMapper.getCodeList(id, detail.getTypeId());
long outDetailStart = System.currentTimeMillis();
// 直接使用已有的批量查询结果无需再次查询数据库
List<MaterialLeaseMaCodeDto> 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<Long, BigDecimal> preCalculateUseNums(MaterialLeaseApplyInfo info, List<Long> typeIds) {
Map<Long, BigDecimal> 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<Long, BigDecimal> preCalculateStorageNums(MaterialLeaseApplyInfo info,
List<AgreementVo> agreementList,
List<Long> typeIds) {
Map<Long, BigDecimal> 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;
}
}

View File

@ -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<String, Long> 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<BmUnit> 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<ProjectTreeNode> groupList = new ArrayList<>();
List<ProjectTreeNode> 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<ProjectTreeNode> 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<SelectVo> 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<TreeNode> groupList = new ArrayList<>();
// List<TreeNode> 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<SelectVo> 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<SelectVo> 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<SelectVo> 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<TreeNode> groupList = new ArrayList<>();
@ -386,17 +365,6 @@ public class SelectServiceImpl implements SelectService {
return AjaxResult.success(list);
}
// @Override
// public AjaxResult getProCbx(SelectDto dto) {
// List<SelectVo> list = new ArrayList<>();
// try {
// list = mapper.getProCbx(dto);
// } catch (Exception e) {
// log.error("工程项目-查询失败", e);
// }
// return AjaxResult.success(list);
// }
@Override
public AjaxResult getAccessoryTree() {
List<TreeNode> 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<SelectVo> list = new ArrayList<>();
// list = mapper.getUserByRoleIdCbxSelect(dto);
// return AjaxResult.success(list);
// } else if (Objects.equals(GlobalConstants.STRING_2, dto.getType())) {
// List<TreeNode> groupList = new ArrayList<>();
// List<TreeNode> 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<Integer> agreementIdList = new ArrayList<>();
@ -586,4 +527,51 @@ public class SelectServiceImpl implements SelectService {
return AjaxResult.success(vo);
}
/**
* 通过hashMap构建高效的树构建方法时间复杂度O(n)
* @param nodes 节点列表
* @return 树形结构
*/
private List<ProjectTreeNode> buildTreeEfficiently(List<ProjectTreeNode> nodes) {
if (CollectionUtils.isEmpty(nodes)) {
return new ArrayList<>();
}
// 使用Map来存储节点提高查找效率
Map<String, ProjectTreeNode> nodeMap = new HashMap<>();
Map<String, List<ProjectTreeNode>> childrenMap = new HashMap<>();
// 第一遍遍历建立Map索引
for (ProjectTreeNode node : nodes) {
nodeMap.put(node.getId(), node);
childrenMap.put(node.getId(), new ArrayList<>());
}
List<ProjectTreeNode> rootNodes = new ArrayList<>();
// 第二遍遍历建立父子关系
for (ProjectTreeNode node : nodes) {
String parentId = node.getParentId();
if ("0".equals(parentId)) {
// 根节点
rootNodes.add(node);
} else {
// 子节点
List<ProjectTreeNode> siblings = childrenMap.get(parentId);
if (siblings != null) {
siblings.add(node);
}
}
}
// 第三遍遍历设置children
for (ProjectTreeNode node : nodes) {
List<ProjectTreeNode> children = childrenMap.get(node.getId());
node.setChildren(children);
}
return rootNodes;
}
}

View File

@ -99,6 +99,14 @@ public interface LeaseApplyDetailsMapper {
*/
List<MaCodeVo> getCodeList(@Param("id") Long id, @Param("typeId") Long typeId);
/**
* 批量获取编码详情优化N+1查询问题
* @param id 申请ID
* @param typeIds 类型ID列表
* @return 编码详情列表
*/
List<MaCodeVo> getCodeListBatch(@Param("id") Long id, @Param("typeIds") List<Long> typeIds);
/**
* 获取领料出库单详情
* @param leaseApplyInfo

View File

@ -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<String, Long> 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<Long> 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<LeaseApplyInfo> 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<LeaseApplyInfo> 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<LeaseApplyDetails> details = new ArrayList<>();
// 步骤8: 获取领料单详情
long step8Start = System.currentTimeMillis();
List<LeaseApplyDetails> 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<MaCodeVo> maCodeVoList = leaseApplyDetailsMapper.getCodeList(id, detail.getTypeId());
if (!CollectionUtils.isEmpty(maCodeVoList)) {
detail.setMaCodeVoList(maCodeVoList);
// 步骤9: 获取编码详情批量优化
long step9Start = System.currentTimeMillis();
// 收集所有typeId
List<Long> typeIds = details.stream()
.map(LeaseApplyDetails::getTypeId)
.filter(Objects::nonNull)
.distinct()
.collect(Collectors.toList());
if (!CollectionUtils.isEmpty(typeIds)) {
// 一次性批量查询所有编码详情
List<MaCodeVo> allMaCodeList = leaseApplyDetailsMapper.getCodeListBatch(id, typeIds);
// 按typeId分组
Map<String, List<MaCodeVo>> maCodeMap = allMaCodeList.stream()
.filter(maCode -> maCode.getTypeId() != null)
.collect(Collectors.groupingBy(MaCodeVo::getTypeId));
// 分配给对应的detail
for (LeaseApplyDetails detail : details) {
if (detail.getTypeId() != null) {
List<MaCodeVo> 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<LeaseOutSign> 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<LeaseOutSign> 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<LeaseOutSign> 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("查询失败,请联系管理员");
}

View File

@ -527,6 +527,45 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
and mt.type_id = #{typeId}
</select>
<!-- 批量获取编码详情优化N+1查询问题 -->
<select id="getCodeListBatch" resultType="com.bonus.common.biz.domain.lease.MaterialLeaseMaCodeDto">
SELECT
mt.type_id as typeId,
mt1.type_name as materialName,
mt.type_name as materialModel,
mm.ma_id as maId,
mm.ma_code as maCode,
'在用' as maStatus,
mm.ma_status as status
FROM
clz_lease_out_details lod
LEFT JOIN ma_machine mm ON lod.ma_id = mm.ma_id
LEFT JOIN ma_type mt ON mm.type_id = mt.type_id
AND mt.del_flag = '0'
LEFT JOIN ma_type mt1 ON mt.parent_id = mt1.type_id
AND mt1.del_flag = '0'
WHERE
lod.parent_id = #{id}
<if test="typeIds != null and typeIds.size() > 0">
AND mt.type_id IN
<foreach collection="typeIds" item="typeId" open="(" separator="," close=")">
#{typeId}
</foreach>
</if>
ORDER BY mt.type_id, mm.ma_code
</select>
<!-- 批量查询设备是否已被领料 -->
<select id="getUsedMaIdsBatch" resultType="java.lang.Long">
SELECT DISTINCT csa.ma_id
FROM clz_slt_agreement_info csa
WHERE csa.status = '0'
AND csa.ma_id IN
<foreach collection="maIds" item="maId" open="(" separator="," close=")">
#{maId}
</foreach>
</select>
<select id="getOutNum" resultType="com.bonus.material.clz.domain.lease.MaterialLeaseApplyDetails">
SELECT
lad.parent_id as parentId,

View File

@ -72,7 +72,46 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</sql>
<select id="selectLeaseApplyDetailsList" parameterType="com.bonus.material.lease.domain.LeaseApplyDetails" resultMap="LeaseApplyDetailsResult">
<include refid="selectLeaseApplyDetailsVo"/>
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
<if test="userId != null">
JOIN ma_type_keeper mtk ON mtk.type_id = lad.type_id AND mtk.user_id = #{userId}
</if>
<where>
<if test="parentId != null "> and lad.parent_id = #{parentId}</if>
<if test="keyword != null and keyword != ''">
@ -325,6 +364,32 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
and mt.type_id = #{typeId}
</select>
<!-- 批量获取编码详情优化N+1查询问题 -->
<select id="getCodeListBatch" resultType="com.bonus.material.back.domain.vo.MaCodeVo">
SELECT
mt.type_id as typeId,
mt1.type_name as materialName,
mt.type_name as typeName,
mm.ma_id as maId,
mm.ma_code as maCode
FROM
lease_out_details lod
LEFT JOIN ma_machine mm ON lod.ma_id = mm.ma_id
LEFT JOIN ma_type mt ON mm.type_id = mt.type_id
AND mt.del_flag = '0'
LEFT JOIN ma_type mt1 ON mt.parent_id = mt1.type_id
AND mt1.del_flag = '0'
WHERE
lod.parent_id = #{id}
<if test="typeIds != null and typeIds.size() > 0">
AND mt.type_id IN
<foreach collection="typeIds" item="typeId" open="(" separator="," close=")">
#{typeId}
</foreach>
</if>
ORDER BY mt.type_id, mm.ma_code
</select>
<select id="selectLeaseOutDetailsList" resultType="com.bonus.material.lease.domain.vo.LeaseOutVo">
SELECT
mt1.type_name AS typeName,