feat(wms/material-coil): 优化钢卷囤积统计查询性能

1. 重构囤积统计方法:将原Java循环计算逻辑替换为单SQL聚合查询,通过JSON_EXTRACT解析二维码步骤创建时间,一次性计算平均囤积周期与成本
2. 移除原低效实现:删除getHoardingStatistics方法中的批量查询与循环解析代码,消除N+1性能问题
3. 新增Mapper方法与XML映射:添加selectHoardingStatistics接口及对应SQL,支持与分页查询相同的条件筛选

调整前,统计需先查询钢卷列表再批量获取二维码并循环解析,存在性能瓶颈;调整后,通过单SQL完成所有聚合计算,大幅提升查询效率,支持大规模数据统计。
This commit is contained in:
2026-06-04 17:06:08 +08:00
parent 29c21fd64f
commit 705d929d6e
3 changed files with 69 additions and 117 deletions

View File

@@ -484,6 +484,34 @@ public class WmsMaterialCoilServiceImpl implements IWmsMaterialCoilService {
return selectMaterialCoilStatistics(qw);
}
/**
* 统计已发货钢卷的平均囤积周期和平均囤积成本
* 一次SQL完成聚合高性能
*/
@Override
public Map<String, Object> getHoardingStatistics(WmsMaterialCoilBo bo) {
QueryWrapper<WmsMaterialCoil> qw = buildQueryWrapperPlus(bo);
qw.isNotNull("mc.export_time");
Map<String, Object> stats = baseMapper.selectHoardingStatistics(qw);
Map<String, Object> result = new HashMap<>();
if (stats != null) {
Object count = stats.get("total_count");
Object avgDays = stats.get("avg_hoarding_days");
Object avgCost = stats.get("avg_hoarding_cost");
result.put("totalCount", count != null ? new BigDecimal(count.toString()) : BigDecimal.ZERO);
result.put("avgHoardingDays", avgDays != null ? new BigDecimal(avgDays.toString()).setScale(2, RoundingMode.HALF_UP) : BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP));
result.put("avgHoardingCost", avgCost != null ? new BigDecimal(avgCost.toString()).setScale(2, RoundingMode.HALF_UP) : BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP));
} else {
result.put("totalCount", BigDecimal.ZERO);
result.put("avgHoardingDays", BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP));
result.put("avgHoardingCost", BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP));
}
return result;
}
private Page<WmsMaterialCoilVo> queryMaterialCoilPage(WmsMaterialCoilBo bo, PageQuery pageQuery) {
QueryWrapper<WmsMaterialCoil> qw = buildQueryWrapperPlus(bo);
Page<WmsMaterialCoilVo> result;
@@ -5862,123 +5890,6 @@ public class WmsMaterialCoilServiceImpl implements IWmsMaterialCoilService {
* - totalCount: 符合条件的钢卷总数
*/
@Override
public Map<String, Object> getHoardingStatistics(WmsMaterialCoilBo bo) {
// 构建查询条件
QueryWrapper<WmsMaterialCoil> qw = buildQueryWrapperPlus(bo);
// 查询符合条件的钢卷列表
List<WmsMaterialCoilVo> list = baseMapper.selectVoListWithDynamicJoin(qw);
// 初始化返回结果,设置默认值
Map<String, Object> result = new HashMap<>();
result.put("avgHoardingDays", BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP));
result.put("avgHoardingCost", BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP));
result.put("totalCount", 0);
// 如果没有钢卷数据,直接返回默认值
if (list == null || list.isEmpty()) {
return result;
}
// 收集所有钢卷的二维码记录ID用于批量查询二维码信息
Set<Long> qrcodeIds = list.stream()
.map(WmsMaterialCoilVo::getQrcodeRecordId)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
// 批量查询二维码记录建立ID到二维码对象的映射
Map<Long, WmsGenerateRecordVo> qrcodeMap = new HashMap<>();
if (!qrcodeIds.isEmpty()) {
List<WmsGenerateRecordVo> qrcodeList = generateRecordMapper.selectVoBatchIds(new ArrayList<>(qrcodeIds));
if (qrcodeList != null) {
for (WmsGenerateRecordVo qrcode : qrcodeList) {
if (qrcode != null && qrcode.getRecordId() != null) {
qrcodeMap.put(qrcode.getRecordId(), qrcode);
}
}
}
}
// 初始化统计变量
ObjectMapper objectMapper = new ObjectMapper();
BigDecimal totalHoardingDays = BigDecimal.ZERO;
BigDecimal totalHoardingCost = BigDecimal.ZERO;
int validCount = 0;
// 遍历每个钢卷,计算囤积天数和成本
for (WmsMaterialCoilVo vo : list) {
Date exportTime = vo.getExportTime();
if (exportTime == null) {
exportTime = vo.getUpdateTime(); // 没有发货时间则用更新时间
}
Long qrcodeRecordId = vo.getQrcodeRecordId();
if (qrcodeRecordId == null) {
continue; // 没有二维码记录,跳过
}
WmsGenerateRecordVo qrcode = qrcodeMap.get(qrcodeRecordId);
if (qrcode == null) {
continue; // 二维码记录不存在,跳过
}
try {
// 解析二维码内容,获取操作步骤
@SuppressWarnings("unchecked")
Map<String, Object> contentMap = objectMapper.readValue(qrcode.getContent(), Map.class);
@SuppressWarnings("unchecked")
List<Map<String, Object>> steps = (List<Map<String, Object>>) contentMap.get("steps");
if (steps == null || steps.isEmpty()) {
continue; // 没有操作步骤,跳过
}
// 获取第一步操作的时间(钢卷创建时间)
Object createTimeObj = steps.get(0).get("create_time");
if (createTimeObj == null) {
continue; // 没有创建时间,跳过
}
Date firstCreateTime = parseDateFromObject(createTimeObj);
if (firstCreateTime == null) {
continue; // 时间解析失败,跳过
}
// 计算囤积天数:发货时间 - 创建时间
long diffMs = exportTime.getTime() - firstCreateTime.getTime();
BigDecimal days = BigDecimal.valueOf(diffMs)
.divide(BigDecimal.valueOf(1000L * 60 * 60 * 24), 2, RoundingMode.HALF_UP);
totalHoardingDays = totalHoardingDays.add(days);
// 计算囤积成本:囤积天数 × 钢卷净重
BigDecimal netWeight = vo.getNetWeight() != null ? vo.getNetWeight() : BigDecimal.ZERO;
BigDecimal cost = days.multiply(netWeight);
totalHoardingCost = totalHoardingCost.add(cost);
validCount++; // 有效数据计数
} catch (Exception e) {
log.warn("计算囤积统计时解析二维码失败, coilId={}", vo.getCoilId(), e);
}
}
// 计算平均值
if (validCount > 0) {
result.put("avgHoardingDays", totalHoardingDays.divide(BigDecimal.valueOf(validCount), 2, RoundingMode.HALF_UP));
result.put("avgHoardingCost", totalHoardingCost.divide(BigDecimal.valueOf(validCount), 2, RoundingMode.HALF_UP));
result.put("totalCount", validCount);
}
return result;
}
/**
* 查询分页列表并填充二维码记录
* 在普通分页查询的基础上,额外填充每个钢卷的二维码详细信息
*
* @param bo 查询条件对象
* @param pageQuery 分页查询参数
* @return 包含二维码信息的分页结果
*/
@Override
public TableDataInfo<WmsMaterialCoilVo> queryPageListWithQrcode(WmsMaterialCoilBo bo, PageQuery pageQuery) {
// 先执行普通分页查询
TableDataInfo<WmsMaterialCoilVo> result = queryPageList(bo, pageQuery);