|
|
|
|
@ -0,0 +1,659 @@
|
|
|
|
|
package org.dromara.ems.report.service.impl;
|
|
|
|
|
|
|
|
|
|
import cn.hutool.core.collection.CollUtil;
|
|
|
|
|
import cn.hutool.core.util.StrUtil;
|
|
|
|
|
import lombok.RequiredArgsConstructor;
|
|
|
|
|
import org.dromara.common.core.exception.ServiceException;
|
|
|
|
|
import org.dromara.ems.record.domain.RecordIotenvInstant;
|
|
|
|
|
import org.dromara.ems.record.service.RecordIotenvPartitionService;
|
|
|
|
|
import org.dromara.ems.report.domain.bo.DisplacementBoardQueryBo;
|
|
|
|
|
import org.dromara.ems.report.domain.bo.VibrationBoardQueryBo;
|
|
|
|
|
import org.dromara.ems.report.domain.vo.displacementboard.DisplacementAdvancedPageVo;
|
|
|
|
|
import org.dromara.ems.report.domain.vo.displacementboard.DisplacementAnomalyPageVo;
|
|
|
|
|
import org.dromara.ems.report.domain.vo.displacementboard.DisplacementComparisonPageVo;
|
|
|
|
|
import org.dromara.ems.report.domain.vo.displacementboard.DisplacementDistributionPageVo;
|
|
|
|
|
import org.dromara.ems.report.domain.vo.displacementboard.DisplacementOverviewPageVo;
|
|
|
|
|
import org.dromara.ems.report.domain.vo.displacementboard.DisplacementQualityPageVo;
|
|
|
|
|
import org.dromara.ems.report.domain.vo.displacementboard.DisplacementTrendPageVo;
|
|
|
|
|
import org.dromara.ems.report.domain.vo.vibrationboard.VibrationAdvancedPageVo;
|
|
|
|
|
import org.dromara.ems.report.domain.vo.vibrationboard.VibrationAnomalyPageVo;
|
|
|
|
|
import org.dromara.ems.report.domain.vo.vibrationboard.VibrationComparisonPageVo;
|
|
|
|
|
import org.dromara.ems.report.domain.vo.vibrationboard.VibrationDistributionPageVo;
|
|
|
|
|
import org.dromara.ems.report.domain.vo.vibrationboard.VibrationOverviewPageVo;
|
|
|
|
|
import org.dromara.ems.report.domain.vo.vibrationboard.VibrationQualityPageVo;
|
|
|
|
|
import org.dromara.ems.report.domain.vo.vibrationboard.VibrationTrendPageVo;
|
|
|
|
|
import org.dromara.ems.report.mapper.VibrationBoardMapper;
|
|
|
|
|
import org.dromara.ems.report.service.IDisplacementBoardService;
|
|
|
|
|
import org.dromara.ems.report.service.IVibrationBoardService;
|
|
|
|
|
import org.springframework.stereotype.Service;
|
|
|
|
|
|
|
|
|
|
import java.math.BigDecimal;
|
|
|
|
|
import java.math.RoundingMode;
|
|
|
|
|
import java.time.LocalDateTime;
|
|
|
|
|
import java.time.ZoneId;
|
|
|
|
|
import java.time.format.DateTimeFormatter;
|
|
|
|
|
import java.time.format.DateTimeParseException;
|
|
|
|
|
import java.util.ArrayList;
|
|
|
|
|
import java.util.Collections;
|
|
|
|
|
import java.util.Comparator;
|
|
|
|
|
import java.util.Date;
|
|
|
|
|
import java.util.LinkedHashSet;
|
|
|
|
|
import java.util.List;
|
|
|
|
|
import java.util.Set;
|
|
|
|
|
import java.util.regex.Pattern;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 位移专属看板服务实现。
|
|
|
|
|
*
|
|
|
|
|
* <p>职责边界:
|
|
|
|
|
* 1. 振动通用能力仍由 {@link IVibrationBoardService} 提供
|
|
|
|
|
* 2. 位移专属统计口径、结果裁剪和质量页分母逻辑全部收口在本类
|
|
|
|
|
* 3. 不再把位移专属实现塞回 VibrationBoardServiceImpl,避免污染振动多指标兼容能力</p>
|
|
|
|
|
*/
|
|
|
|
|
@Service
|
|
|
|
|
@RequiredArgsConstructor
|
|
|
|
|
public class DisplacementBoardServiceImpl implements IDisplacementBoardService {
|
|
|
|
|
|
|
|
|
|
private static final String DISPLACEMENT_FIELD = "vibrationDisplacement";
|
|
|
|
|
private static final String DISPLACEMENT_LABEL = "位移";
|
|
|
|
|
private static final String DISPLACEMENT_UNIT = "um";
|
|
|
|
|
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
|
|
|
|
private static final Pattern TABLE_NAME_PATTERN = Pattern.compile("^record_iotenv_instant_\\d{8}$");
|
|
|
|
|
private static final int MAX_QUERY_DAYS = 90;
|
|
|
|
|
private static final long MAX_ESTIMATED_QUERY_ROWS = 500_000L;
|
|
|
|
|
private static final int MAX_UNION_TABLES = 31;
|
|
|
|
|
|
|
|
|
|
private final IVibrationBoardService vibrationBoardService;
|
|
|
|
|
private final VibrationBoardMapper vibrationBoardMapper;
|
|
|
|
|
private final RecordIotenvPartitionService recordIotenvPartitionService;
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public DisplacementOverviewPageVo listOverviewData(DisplacementBoardQueryBo bo) {
|
|
|
|
|
VibrationBoardQueryBo query = normalizeDisplacementQueryBo(bo);
|
|
|
|
|
VibrationOverviewPageVo overview = vibrationBoardService.listOverviewData(query);
|
|
|
|
|
DisplacementQualityPageVo quality = listQualityData(bo);
|
|
|
|
|
|
|
|
|
|
VibrationOverviewPageVo result = overview == null ? new VibrationOverviewPageVo() : overview;
|
|
|
|
|
List<VibrationOverviewPageVo.MetricCardItem> displacementCards = (result.getMetricCards() == null ? Collections.<VibrationOverviewPageVo.MetricCardItem>emptyList() : result.getMetricCards())
|
|
|
|
|
.stream()
|
|
|
|
|
.filter(item -> DISPLACEMENT_FIELD.equals(item.getField()))
|
|
|
|
|
.toList();
|
|
|
|
|
return buildDisplacementOverviewVo(result, quality, displacementCards);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public DisplacementTrendPageVo listTrendData(DisplacementBoardQueryBo bo) {
|
|
|
|
|
VibrationTrendPageVo trend = vibrationBoardService.listTrendData(normalizeDisplacementQueryBo(bo));
|
|
|
|
|
if (trend == null) {
|
|
|
|
|
return new DisplacementTrendPageVo();
|
|
|
|
|
}
|
|
|
|
|
DisplacementTrendPageVo result = new DisplacementTrendPageVo();
|
|
|
|
|
result.setMetricField(DISPLACEMENT_FIELD);
|
|
|
|
|
result.setMetricLabel(DISPLACEMENT_LABEL);
|
|
|
|
|
result.setUnit(DISPLACEMENT_UNIT);
|
|
|
|
|
result.setMultiDevice(trend.getMultiDevice());
|
|
|
|
|
if (Boolean.FALSE.equals(trend.getMultiDevice())) {
|
|
|
|
|
result.setSeries((trend.getSeries() == null ? Collections.<VibrationTrendPageVo.TrendSeriesItem>emptyList() : trend.getSeries())
|
|
|
|
|
.stream()
|
|
|
|
|
.filter(item -> DISPLACEMENT_FIELD.equals(item.getField()))
|
|
|
|
|
.map(this::toDisplacementTrendSeriesItem)
|
|
|
|
|
.toList());
|
|
|
|
|
} else {
|
|
|
|
|
result.setSeries((trend.getSeries() == null ? Collections.<VibrationTrendPageVo.TrendSeriesItem>emptyList() : trend.getSeries())
|
|
|
|
|
.stream()
|
|
|
|
|
.map(this::toDisplacementTrendSeriesItem)
|
|
|
|
|
.toList());
|
|
|
|
|
}
|
|
|
|
|
result.setHourlyItems((trend.getHourlyItems() == null ? Collections.<VibrationTrendPageVo.HourlyItem>emptyList() : trend.getHourlyItems())
|
|
|
|
|
.stream()
|
|
|
|
|
.map(this::toDisplacementHourlyItem)
|
|
|
|
|
.toList());
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public DisplacementComparisonPageVo listComparisonData(DisplacementBoardQueryBo bo) {
|
|
|
|
|
VibrationComparisonPageVo comparison = vibrationBoardService.listComparisonData(normalizeDisplacementQueryBo(bo));
|
|
|
|
|
DisplacementComparisonPageVo result = new DisplacementComparisonPageVo();
|
|
|
|
|
if (comparison == null) {
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
result.setMetricField(DISPLACEMENT_FIELD);
|
|
|
|
|
result.setMetricLabel(DISPLACEMENT_LABEL);
|
|
|
|
|
result.setUnit(DISPLACEMENT_UNIT);
|
|
|
|
|
result.setRankItems((comparison.getRankItems() == null ? Collections.<VibrationComparisonPageVo.RankItem>emptyList() : comparison.getRankItems())
|
|
|
|
|
.stream()
|
|
|
|
|
.map(this::toDisplacementRankItem)
|
|
|
|
|
.toList());
|
|
|
|
|
result.setScatterItems((comparison.getScatterItems() == null ? Collections.<VibrationComparisonPageVo.ScatterItem>emptyList() : comparison.getScatterItems())
|
|
|
|
|
.stream()
|
|
|
|
|
.map(this::toDisplacementScatterItem)
|
|
|
|
|
.toList());
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public DisplacementQualityPageVo listQualityData(DisplacementBoardQueryBo bo) {
|
|
|
|
|
List<RecordIotenvInstant> rows = listQualityPageData(bo);
|
|
|
|
|
DisplacementQualityPageVo vo = new DisplacementQualityPageVo();
|
|
|
|
|
if (CollUtil.isEmpty(rows)) {
|
|
|
|
|
vo.setSampleCount(0);
|
|
|
|
|
vo.setDeviceCount(0);
|
|
|
|
|
vo.setCoverageRate(zeroRate());
|
|
|
|
|
vo.setValidCount(0);
|
|
|
|
|
vo.setInvalidCount(0);
|
|
|
|
|
vo.setValidRate(zeroRate());
|
|
|
|
|
vo.setInvalidRate(zeroRate());
|
|
|
|
|
vo.setMetricQualityItems(Collections.emptyList());
|
|
|
|
|
return vo;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int validCount = countValidDisplacementRows(rows);
|
|
|
|
|
int sampleCount = rows.size();
|
|
|
|
|
int invalidCount = sampleCount - validCount;
|
|
|
|
|
BigDecimal validRate = divideRate(validCount, sampleCount);
|
|
|
|
|
vo.setSampleCount(sampleCount);
|
|
|
|
|
vo.setDeviceCount(countDistinctMonitors(rows));
|
|
|
|
|
vo.setCoverageRate(validRate);
|
|
|
|
|
vo.setValidCount(validCount);
|
|
|
|
|
vo.setInvalidCount(invalidCount);
|
|
|
|
|
vo.setValidRate(validRate);
|
|
|
|
|
vo.setInvalidRate(divideRate(invalidCount, sampleCount));
|
|
|
|
|
vo.setMetricQualityItems(List.of(buildDisplacementQualityItem(validCount, sampleCount)));
|
|
|
|
|
return vo;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public DisplacementDistributionPageVo listDistributionData(DisplacementBoardQueryBo bo) {
|
|
|
|
|
VibrationDistributionPageVo distribution = vibrationBoardService.listDistributionData(normalizeDisplacementQueryBo(bo));
|
|
|
|
|
DisplacementDistributionPageVo result = new DisplacementDistributionPageVo();
|
|
|
|
|
if (distribution == null) {
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
result.setMetricField(DISPLACEMENT_FIELD);
|
|
|
|
|
result.setMetricLabel(DISPLACEMENT_LABEL);
|
|
|
|
|
result.setUnit(DISPLACEMENT_UNIT);
|
|
|
|
|
result.setIntervalBuckets((distribution.getIntervalBuckets() == null ? Collections.<VibrationDistributionPageVo.IntervalBucketItem>emptyList() : distribution.getIntervalBuckets())
|
|
|
|
|
.stream()
|
|
|
|
|
.map(this::toDisplacementIntervalBucketItem)
|
|
|
|
|
.toList());
|
|
|
|
|
result.setHistogramBuckets((distribution.getHistogramBuckets() == null ? Collections.<VibrationDistributionPageVo.HistogramBucketItem>emptyList() : distribution.getHistogramBuckets())
|
|
|
|
|
.stream()
|
|
|
|
|
.map(this::toDisplacementHistogramBucketItem)
|
|
|
|
|
.toList());
|
|
|
|
|
result.setCalendarHeatmap((distribution.getCalendarHeatmap() == null ? Collections.<VibrationDistributionPageVo.CalendarHeatmapItem>emptyList() : distribution.getCalendarHeatmap())
|
|
|
|
|
.stream()
|
|
|
|
|
.map(this::toDisplacementCalendarHeatmapItem)
|
|
|
|
|
.toList());
|
|
|
|
|
result.setHourlyHeatmap((distribution.getHourlyHeatmap() == null ? Collections.<VibrationDistributionPageVo.HourlyHeatmapItem>emptyList() : distribution.getHourlyHeatmap())
|
|
|
|
|
.stream()
|
|
|
|
|
.map(this::toDisplacementHourlyHeatmapItem)
|
|
|
|
|
.toList());
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public DisplacementAnomalyPageVo listAnomalyData(DisplacementBoardQueryBo bo) {
|
|
|
|
|
VibrationAnomalyPageVo anomaly = vibrationBoardService.listAnomalyData(normalizeDisplacementQueryBo(bo));
|
|
|
|
|
DisplacementAnomalyPageVo result = new DisplacementAnomalyPageVo();
|
|
|
|
|
if (anomaly == null) {
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
result.setMetricField(DISPLACEMENT_FIELD);
|
|
|
|
|
result.setMetricLabel(DISPLACEMENT_LABEL);
|
|
|
|
|
result.setUnit(DISPLACEMENT_UNIT);
|
|
|
|
|
result.setHighThreshold(anomaly.getHighThreshold());
|
|
|
|
|
result.setWarningThreshold(anomaly.getWarningThreshold());
|
|
|
|
|
result.setRapidRiseThreshold(anomaly.getRapidRiseThreshold());
|
|
|
|
|
result.setStddevThreshold(anomaly.getStddevThreshold());
|
|
|
|
|
result.setMinContinuousSamples(anomaly.getMinContinuousSamples());
|
|
|
|
|
result.setHighEventCount(anomaly.getHighEventCount());
|
|
|
|
|
result.setContinuousEventCount(anomaly.getContinuousEventCount());
|
|
|
|
|
result.setRapidRiseEventCount(anomaly.getRapidRiseEventCount());
|
|
|
|
|
result.setJitterEventCount(anomaly.getJitterEventCount());
|
|
|
|
|
result.setHighEvents((anomaly.getHighEvents() == null ? Collections.<VibrationAnomalyPageVo.HighEventItem>emptyList() : anomaly.getHighEvents())
|
|
|
|
|
.stream()
|
|
|
|
|
.map(this::toDisplacementHighEventItem)
|
|
|
|
|
.toList());
|
|
|
|
|
result.setContinuousEvents((anomaly.getContinuousEvents() == null ? Collections.<VibrationAnomalyPageVo.ContinuousEventItem>emptyList() : anomaly.getContinuousEvents())
|
|
|
|
|
.stream()
|
|
|
|
|
.map(this::toDisplacementContinuousEventItem)
|
|
|
|
|
.toList());
|
|
|
|
|
result.setRapidRiseEvents((anomaly.getRapidRiseEvents() == null ? Collections.<VibrationAnomalyPageVo.RapidRiseEventItem>emptyList() : anomaly.getRapidRiseEvents())
|
|
|
|
|
.stream()
|
|
|
|
|
.map(this::toDisplacementRapidRiseEventItem)
|
|
|
|
|
.toList());
|
|
|
|
|
result.setJitterEvents((anomaly.getJitterEvents() == null ? Collections.<VibrationAnomalyPageVo.JitterEventItem>emptyList() : anomaly.getJitterEvents())
|
|
|
|
|
.stream()
|
|
|
|
|
.map(this::toDisplacementJitterEventItem)
|
|
|
|
|
.toList());
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public DisplacementAdvancedPageVo listAdvancedData(DisplacementBoardQueryBo bo) {
|
|
|
|
|
VibrationAdvancedPageVo advanced = vibrationBoardService.listAdvancedData(normalizeDisplacementQueryBo(bo));
|
|
|
|
|
if (advanced == null) {
|
|
|
|
|
return new DisplacementAdvancedPageVo();
|
|
|
|
|
}
|
|
|
|
|
DisplacementAdvancedPageVo result = new DisplacementAdvancedPageVo();
|
|
|
|
|
result.setMetricField(DISPLACEMENT_FIELD);
|
|
|
|
|
result.setMetricLabel(DISPLACEMENT_LABEL);
|
|
|
|
|
result.setUnit(DISPLACEMENT_UNIT);
|
|
|
|
|
result.setLowBandUpper(advanced.getLowBandUpper());
|
|
|
|
|
result.setFocusBandUpper(advanced.getFocusBandUpper());
|
|
|
|
|
result.setSankeyNodes((advanced.getSankeyNodes() == null ? Collections.<VibrationAdvancedPageVo.SankeyNodeItem>emptyList() : advanced.getSankeyNodes())
|
|
|
|
|
.stream()
|
|
|
|
|
.map(this::toDisplacementSankeyNodeItem)
|
|
|
|
|
.toList());
|
|
|
|
|
result.setSankeyLinks((advanced.getSankeyLinks() == null ? Collections.<VibrationAdvancedPageVo.SankeyLinkItem>emptyList() : advanced.getSankeyLinks())
|
|
|
|
|
.stream()
|
|
|
|
|
.map(this::toDisplacementSankeyLinkItem)
|
|
|
|
|
.toList());
|
|
|
|
|
result.setTreemapItems((advanced.getTreemapItems() == null ? Collections.<VibrationAdvancedPageVo.TreemapItem>emptyList() : advanced.getTreemapItems())
|
|
|
|
|
.stream()
|
|
|
|
|
.map(this::toDisplacementTreemapItem)
|
|
|
|
|
.toList());
|
|
|
|
|
// 位移专属高级页只保留风险分带分析,不再输出四指标平行坐标画像。
|
|
|
|
|
result.setParallelAxes(Collections.emptyList());
|
|
|
|
|
result.setParallelSeries(Collections.emptyList());
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 质量页底层查询:只在本类内部使用,避免把位移专属分母口径继续混进振动通用服务。
|
|
|
|
|
*/
|
|
|
|
|
private List<RecordIotenvInstant> listQualityPageData(DisplacementBoardQueryBo bo) {
|
|
|
|
|
if (bo == null) {
|
|
|
|
|
throw new ServiceException("查询参数不能为空");
|
|
|
|
|
}
|
|
|
|
|
Date beginTime = parseDateTime(bo.getBeginRecordTime(), "开始记录时间");
|
|
|
|
|
Date endTime = parseDateTime(bo.getEndRecordTime(), "结束记录时间");
|
|
|
|
|
if (beginTime.after(endTime)) {
|
|
|
|
|
throw new ServiceException("开始记录时间不能晚于结束记录时间");
|
|
|
|
|
}
|
|
|
|
|
validateQuerySpan(beginTime, endTime);
|
|
|
|
|
|
|
|
|
|
List<String> monitorIds = normalizeMonitorIds(bo);
|
|
|
|
|
if (CollUtil.isEmpty(monitorIds)) {
|
|
|
|
|
throw new ServiceException("请选择至少一个振动设备");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
VibrationBoardQueryBo query = normalizeDisplacementQueryBo(bo);
|
|
|
|
|
query.setMonitorId(monitorIds.size() == 1 ? monitorIds.get(0) : null);
|
|
|
|
|
query.setMonitorIds(monitorIds.size() > 1 ? monitorIds : null);
|
|
|
|
|
validateEstimatedQueryRows(beginTime, endTime, monitorIds.size(), query.getSamplingInterval());
|
|
|
|
|
|
|
|
|
|
List<String> tableNames = recordIotenvPartitionService.resolveTables(beginTime, endTime);
|
|
|
|
|
if (CollUtil.isEmpty(tableNames)) {
|
|
|
|
|
return Collections.emptyList();
|
|
|
|
|
}
|
|
|
|
|
validateResolvedTableNames(tableNames);
|
|
|
|
|
return queryQualityDataByTableBatches(tableNames, query);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private List<RecordIotenvInstant> queryQualityDataByTableBatches(List<String> tableNames, VibrationBoardQueryBo query) {
|
|
|
|
|
if (tableNames.size() <= MAX_UNION_TABLES) {
|
|
|
|
|
return executeSingleBatchQualityQuery(tableNames, query);
|
|
|
|
|
}
|
|
|
|
|
List<RecordIotenvInstant> mergedRows = new ArrayList<>();
|
|
|
|
|
for (int index = 0; index < tableNames.size(); index += MAX_UNION_TABLES) {
|
|
|
|
|
int endIndex = Math.min(tableNames.size(), index + MAX_UNION_TABLES);
|
|
|
|
|
List<String> batchTableNames = tableNames.subList(index, endIndex);
|
|
|
|
|
List<RecordIotenvInstant> batchRows = executeSingleBatchQualityQuery(batchTableNames, query);
|
|
|
|
|
if (CollUtil.isNotEmpty(batchRows)) {
|
|
|
|
|
mergedRows.addAll(batchRows);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
mergedRows.sort(Comparator.comparing(RecordIotenvInstant::getMonitorId, Comparator.nullsFirst(Comparator.naturalOrder()))
|
|
|
|
|
.thenComparing(RecordIotenvInstant::getRecodeTime, Comparator.nullsFirst(Comparator.naturalOrder()))
|
|
|
|
|
.thenComparing(RecordIotenvInstant::getObjid, Comparator.nullsFirst(Comparator.naturalOrder())));
|
|
|
|
|
return mergedRows;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private List<RecordIotenvInstant> executeSingleBatchQualityQuery(List<String> batchTableNames, VibrationBoardQueryBo query) {
|
|
|
|
|
if (query.getSamplingInterval() != null && query.getSamplingInterval() > 1) {
|
|
|
|
|
return vibrationBoardMapper.selectQualitySampledData(batchTableNames, query);
|
|
|
|
|
}
|
|
|
|
|
return vibrationBoardMapper.selectQualityRawData(batchTableNames, query);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private VibrationBoardQueryBo normalizeDisplacementQueryBo(DisplacementBoardQueryBo source) {
|
|
|
|
|
if (source == null) {
|
|
|
|
|
throw new ServiceException("查询参数不能为空");
|
|
|
|
|
}
|
|
|
|
|
VibrationBoardQueryBo query = new VibrationBoardQueryBo();
|
|
|
|
|
query.setMonitorId(source.getMonitorId());
|
|
|
|
|
query.setMonitorIds(source.getMonitorIds());
|
|
|
|
|
query.setBeginRecordTime(source.getBeginRecordTime());
|
|
|
|
|
query.setEndRecordTime(source.getEndRecordTime());
|
|
|
|
|
query.setSamplingInterval(normalizeSamplingInterval(source.getSamplingInterval()));
|
|
|
|
|
// 位移专属接口在入参层强制收口,避免错误传参被悄悄转成其它振动指标。
|
|
|
|
|
query.setVibrationParam(DISPLACEMENT_FIELD);
|
|
|
|
|
query.setHighThreshold(source.getHighThreshold());
|
|
|
|
|
query.setWarningThreshold(source.getWarningThreshold());
|
|
|
|
|
query.setMinContinuousSamples(source.getMinContinuousSamples());
|
|
|
|
|
query.setRapidRiseThreshold(source.getRapidRiseThreshold());
|
|
|
|
|
query.setStddevThreshold(source.getStddevThreshold());
|
|
|
|
|
return query;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private int countDistinctMonitors(List<RecordIotenvInstant> rows) {
|
|
|
|
|
return (int) rows.stream()
|
|
|
|
|
.map(RecordIotenvInstant::getMonitorId)
|
|
|
|
|
.filter(StrUtil::isNotBlank)
|
|
|
|
|
.distinct()
|
|
|
|
|
.count();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private int countValidDisplacementRows(List<RecordIotenvInstant> rows) {
|
|
|
|
|
return (int) rows.stream()
|
|
|
|
|
.map(RecordIotenvInstant::getVibrationDisplacement)
|
|
|
|
|
.filter(value -> value != null && value.compareTo(BigDecimal.ZERO) > 0)
|
|
|
|
|
.count();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private BigDecimal zeroRate() {
|
|
|
|
|
return BigDecimal.ZERO.setScale(4, RoundingMode.HALF_UP);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private BigDecimal divideRate(int numerator, int denominator) {
|
|
|
|
|
if (denominator <= 0) {
|
|
|
|
|
return zeroRate();
|
|
|
|
|
}
|
|
|
|
|
return BigDecimal.valueOf(numerator).divide(BigDecimal.valueOf(denominator), 4, RoundingMode.HALF_UP);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private DisplacementQualityPageVo.MetricQualityItem buildDisplacementQualityItem(int validCount, int sampleCount) {
|
|
|
|
|
DisplacementQualityPageVo.MetricQualityItem item = new DisplacementQualityPageVo.MetricQualityItem();
|
|
|
|
|
item.setField(DISPLACEMENT_FIELD);
|
|
|
|
|
item.setLabel(DISPLACEMENT_LABEL);
|
|
|
|
|
item.setUnit(DISPLACEMENT_UNIT);
|
|
|
|
|
item.setValidCount(validCount);
|
|
|
|
|
item.setValidRate(divideRate(validCount, sampleCount));
|
|
|
|
|
return item;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private List<DisplacementOverviewPageVo.GaugeItem> buildDisplacementGaugeItems(
|
|
|
|
|
List<VibrationOverviewPageVo.MetricCardItem> metricCards,
|
|
|
|
|
VibrationOverviewPageVo.PrimaryMetricStats primaryMetricStats,
|
|
|
|
|
Integer deviceCount
|
|
|
|
|
) {
|
|
|
|
|
if (CollUtil.isNotEmpty(metricCards)) {
|
|
|
|
|
VibrationOverviewPageVo.MetricCardItem card = metricCards.get(0);
|
|
|
|
|
BigDecimal value = deviceCount != null && deviceCount > 1 ? card.getAvg() : card.getLatest();
|
|
|
|
|
String name = deviceCount != null && deviceCount > 1 ? "群组均值" : card.getLabel();
|
|
|
|
|
return List.of(buildGaugeItem(name, value, card.getMax(), card.getUnit()));
|
|
|
|
|
}
|
|
|
|
|
if (primaryMetricStats == null) {
|
|
|
|
|
return Collections.emptyList();
|
|
|
|
|
}
|
|
|
|
|
BigDecimal value = deviceCount != null && deviceCount > 1 ? primaryMetricStats.getAvg() : primaryMetricStats.getLatest();
|
|
|
|
|
String name = deviceCount != null && deviceCount > 1 ? "群组均值" : DISPLACEMENT_LABEL;
|
|
|
|
|
return List.of(buildGaugeItem(name, value, primaryMetricStats.getMax(), DISPLACEMENT_UNIT));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private DisplacementOverviewPageVo buildDisplacementOverviewVo(
|
|
|
|
|
VibrationOverviewPageVo overview,
|
|
|
|
|
DisplacementQualityPageVo quality,
|
|
|
|
|
List<VibrationOverviewPageVo.MetricCardItem> displacementCards
|
|
|
|
|
) {
|
|
|
|
|
DisplacementOverviewPageVo result = new DisplacementOverviewPageVo();
|
|
|
|
|
result.setMetricField(DISPLACEMENT_FIELD);
|
|
|
|
|
result.setMetricLabel(DISPLACEMENT_LABEL);
|
|
|
|
|
result.setUnit(DISPLACEMENT_UNIT);
|
|
|
|
|
result.setSampleCount(quality.getSampleCount());
|
|
|
|
|
result.setDeviceCount(quality.getDeviceCount());
|
|
|
|
|
result.setCoverageRate(quality.getCoverageRate());
|
|
|
|
|
result.setMetricCards(displacementCards.stream().map(this::toDisplacementMetricCardItem).toList());
|
|
|
|
|
result.setPrimaryMetricStats(toDisplacementPrimaryMetricStats(overview.getPrimaryMetricStats()));
|
|
|
|
|
result.setDeviceRanks((overview.getDeviceRanks() == null ? Collections.<VibrationOverviewPageVo.DeviceRankItem>emptyList() : overview.getDeviceRanks())
|
|
|
|
|
.stream()
|
|
|
|
|
.map(this::toDisplacementDeviceRankItem)
|
|
|
|
|
.toList());
|
|
|
|
|
result.setGaugeItems(buildDisplacementGaugeItems(displacementCards, overview.getPrimaryMetricStats(), quality.getDeviceCount()));
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private DisplacementOverviewPageVo.GaugeItem buildGaugeItem(String name, BigDecimal value, BigDecimal maxValue, String unit) {
|
|
|
|
|
DisplacementOverviewPageVo.GaugeItem item = new DisplacementOverviewPageVo.GaugeItem();
|
|
|
|
|
item.setName(name);
|
|
|
|
|
item.setValue(value);
|
|
|
|
|
item.setMaxValue(maxValue == null ? BigDecimal.ONE : maxValue.multiply(BigDecimal.valueOf(1.2D)).setScale(2, RoundingMode.HALF_UP));
|
|
|
|
|
item.setUnit(unit);
|
|
|
|
|
return item;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private DisplacementOverviewPageVo.MetricCardItem toDisplacementMetricCardItem(VibrationOverviewPageVo.MetricCardItem source) {
|
|
|
|
|
DisplacementOverviewPageVo.MetricCardItem item = new DisplacementOverviewPageVo.MetricCardItem();
|
|
|
|
|
item.setField(source.getField());
|
|
|
|
|
item.setLabel(source.getLabel());
|
|
|
|
|
item.setUnit(source.getUnit());
|
|
|
|
|
item.setLatest(source.getLatest());
|
|
|
|
|
item.setAvg(source.getAvg());
|
|
|
|
|
item.setMax(source.getMax());
|
|
|
|
|
return item;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private DisplacementOverviewPageVo.PrimaryMetricStats toDisplacementPrimaryMetricStats(VibrationOverviewPageVo.PrimaryMetricStats source) {
|
|
|
|
|
DisplacementOverviewPageVo.PrimaryMetricStats stats = new DisplacementOverviewPageVo.PrimaryMetricStats();
|
|
|
|
|
if (source == null) {
|
|
|
|
|
return stats;
|
|
|
|
|
}
|
|
|
|
|
stats.setLatest(source.getLatest());
|
|
|
|
|
stats.setMin(source.getMin());
|
|
|
|
|
stats.setAvg(source.getAvg());
|
|
|
|
|
stats.setMax(source.getMax());
|
|
|
|
|
return stats;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private DisplacementOverviewPageVo.DeviceRankItem toDisplacementDeviceRankItem(VibrationOverviewPageVo.DeviceRankItem source) {
|
|
|
|
|
DisplacementOverviewPageVo.DeviceRankItem item = new DisplacementOverviewPageVo.DeviceRankItem();
|
|
|
|
|
item.setMonitorId(source.getMonitorId());
|
|
|
|
|
item.setMonitorName(source.getMonitorName());
|
|
|
|
|
item.setAvg(source.getAvg());
|
|
|
|
|
item.setLatest(source.getLatest());
|
|
|
|
|
item.setMax(source.getMax());
|
|
|
|
|
item.setSampleCount(source.getSampleCount());
|
|
|
|
|
return item;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private DisplacementTrendPageVo.TrendSeriesItem toDisplacementTrendSeriesItem(VibrationTrendPageVo.TrendSeriesItem source) {
|
|
|
|
|
DisplacementTrendPageVo.TrendSeriesItem item = new DisplacementTrendPageVo.TrendSeriesItem();
|
|
|
|
|
item.setName(source.getName());
|
|
|
|
|
item.setField(source.getField());
|
|
|
|
|
item.setUnit(source.getUnit());
|
|
|
|
|
item.setPoints((source.getPoints() == null ? Collections.<VibrationTrendPageVo.TrendPointItem>emptyList() : source.getPoints())
|
|
|
|
|
.stream()
|
|
|
|
|
.map(this::toDisplacementTrendPointItem)
|
|
|
|
|
.toList());
|
|
|
|
|
return item;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private DisplacementTrendPageVo.TrendPointItem toDisplacementTrendPointItem(VibrationTrendPageVo.TrendPointItem source) {
|
|
|
|
|
DisplacementTrendPageVo.TrendPointItem item = new DisplacementTrendPageVo.TrendPointItem();
|
|
|
|
|
item.setTime(source.getTime());
|
|
|
|
|
item.setValue(source.getValue());
|
|
|
|
|
return item;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private DisplacementTrendPageVo.HourlyItem toDisplacementHourlyItem(VibrationTrendPageVo.HourlyItem source) {
|
|
|
|
|
DisplacementTrendPageVo.HourlyItem item = new DisplacementTrendPageVo.HourlyItem();
|
|
|
|
|
item.setHour(source.getHour());
|
|
|
|
|
item.setAvgValue(source.getAvgValue());
|
|
|
|
|
return item;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private DisplacementComparisonPageVo.RankItem toDisplacementRankItem(VibrationComparisonPageVo.RankItem source) {
|
|
|
|
|
DisplacementComparisonPageVo.RankItem item = new DisplacementComparisonPageVo.RankItem();
|
|
|
|
|
item.setMonitorId(source.getMonitorId());
|
|
|
|
|
item.setMonitorName(source.getMonitorName());
|
|
|
|
|
item.setAvg(source.getAvg());
|
|
|
|
|
item.setLatest(source.getLatest());
|
|
|
|
|
return item;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private DisplacementComparisonPageVo.ScatterItem toDisplacementScatterItem(VibrationComparisonPageVo.ScatterItem source) {
|
|
|
|
|
DisplacementComparisonPageVo.ScatterItem item = new DisplacementComparisonPageVo.ScatterItem();
|
|
|
|
|
item.setMonitorId(source.getMonitorId());
|
|
|
|
|
item.setMonitorName(source.getMonitorName());
|
|
|
|
|
item.setAvg(source.getAvg());
|
|
|
|
|
item.setMax(source.getMax());
|
|
|
|
|
item.setSampleCount(source.getSampleCount());
|
|
|
|
|
return item;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private DisplacementDistributionPageVo.IntervalBucketItem toDisplacementIntervalBucketItem(VibrationDistributionPageVo.IntervalBucketItem source) {
|
|
|
|
|
DisplacementDistributionPageVo.IntervalBucketItem item = new DisplacementDistributionPageVo.IntervalBucketItem();
|
|
|
|
|
item.setLabel(source.getLabel());
|
|
|
|
|
item.setCount(source.getCount());
|
|
|
|
|
return item;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private DisplacementDistributionPageVo.HistogramBucketItem toDisplacementHistogramBucketItem(VibrationDistributionPageVo.HistogramBucketItem source) {
|
|
|
|
|
DisplacementDistributionPageVo.HistogramBucketItem item = new DisplacementDistributionPageVo.HistogramBucketItem();
|
|
|
|
|
item.setStartValue(source.getStartValue());
|
|
|
|
|
item.setEndValue(source.getEndValue());
|
|
|
|
|
item.setCount(source.getCount());
|
|
|
|
|
return item;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private DisplacementDistributionPageVo.CalendarHeatmapItem toDisplacementCalendarHeatmapItem(VibrationDistributionPageVo.CalendarHeatmapItem source) {
|
|
|
|
|
DisplacementDistributionPageVo.CalendarHeatmapItem item = new DisplacementDistributionPageVo.CalendarHeatmapItem();
|
|
|
|
|
item.setStatDate(source.getStatDate());
|
|
|
|
|
item.setAvgValue(source.getAvgValue());
|
|
|
|
|
return item;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private DisplacementDistributionPageVo.HourlyHeatmapItem toDisplacementHourlyHeatmapItem(VibrationDistributionPageVo.HourlyHeatmapItem source) {
|
|
|
|
|
DisplacementDistributionPageVo.HourlyHeatmapItem item = new DisplacementDistributionPageVo.HourlyHeatmapItem();
|
|
|
|
|
item.setStatDate(source.getStatDate());
|
|
|
|
|
item.setStatHour(source.getStatHour());
|
|
|
|
|
item.setAvgValue(source.getAvgValue());
|
|
|
|
|
return item;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private DisplacementAnomalyPageVo.HighEventItem toDisplacementHighEventItem(VibrationAnomalyPageVo.HighEventItem source) {
|
|
|
|
|
DisplacementAnomalyPageVo.HighEventItem item = new DisplacementAnomalyPageVo.HighEventItem();
|
|
|
|
|
item.setMonitorId(source.getMonitorId());
|
|
|
|
|
item.setMonitorName(source.getMonitorName());
|
|
|
|
|
item.setValue(source.getValue());
|
|
|
|
|
item.setRecodeTime(source.getRecodeTime());
|
|
|
|
|
return item;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private DisplacementAnomalyPageVo.ContinuousEventItem toDisplacementContinuousEventItem(VibrationAnomalyPageVo.ContinuousEventItem source) {
|
|
|
|
|
DisplacementAnomalyPageVo.ContinuousEventItem item = new DisplacementAnomalyPageVo.ContinuousEventItem();
|
|
|
|
|
item.setMonitorId(source.getMonitorId());
|
|
|
|
|
item.setMonitorName(source.getMonitorName());
|
|
|
|
|
item.setStartTime(source.getStartTime());
|
|
|
|
|
item.setEndTime(source.getEndTime());
|
|
|
|
|
item.setMaxValue(source.getMaxValue());
|
|
|
|
|
item.setSampleCount(source.getSampleCount());
|
|
|
|
|
return item;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private DisplacementAnomalyPageVo.RapidRiseEventItem toDisplacementRapidRiseEventItem(VibrationAnomalyPageVo.RapidRiseEventItem source) {
|
|
|
|
|
DisplacementAnomalyPageVo.RapidRiseEventItem item = new DisplacementAnomalyPageVo.RapidRiseEventItem();
|
|
|
|
|
item.setMonitorId(source.getMonitorId());
|
|
|
|
|
item.setMonitorName(source.getMonitorName());
|
|
|
|
|
item.setDiff(source.getDiff());
|
|
|
|
|
item.setRecodeTime(source.getRecodeTime());
|
|
|
|
|
return item;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private DisplacementAnomalyPageVo.JitterEventItem toDisplacementJitterEventItem(VibrationAnomalyPageVo.JitterEventItem source) {
|
|
|
|
|
DisplacementAnomalyPageVo.JitterEventItem item = new DisplacementAnomalyPageVo.JitterEventItem();
|
|
|
|
|
item.setMonitorId(source.getMonitorId());
|
|
|
|
|
item.setMonitorName(source.getMonitorName());
|
|
|
|
|
item.setHourBucket(source.getHourBucket());
|
|
|
|
|
item.setStddev(source.getStddev());
|
|
|
|
|
item.setSampleCount(source.getSampleCount());
|
|
|
|
|
return item;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private DisplacementAdvancedPageVo.SankeyNodeItem toDisplacementSankeyNodeItem(VibrationAdvancedPageVo.SankeyNodeItem source) {
|
|
|
|
|
DisplacementAdvancedPageVo.SankeyNodeItem item = new DisplacementAdvancedPageVo.SankeyNodeItem();
|
|
|
|
|
item.setName(source.getName());
|
|
|
|
|
return item;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private DisplacementAdvancedPageVo.SankeyLinkItem toDisplacementSankeyLinkItem(VibrationAdvancedPageVo.SankeyLinkItem source) {
|
|
|
|
|
DisplacementAdvancedPageVo.SankeyLinkItem item = new DisplacementAdvancedPageVo.SankeyLinkItem();
|
|
|
|
|
item.setSource(source.getSource());
|
|
|
|
|
item.setTarget(source.getTarget());
|
|
|
|
|
item.setValue(source.getValue());
|
|
|
|
|
return item;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private DisplacementAdvancedPageVo.TreemapItem toDisplacementTreemapItem(VibrationAdvancedPageVo.TreemapItem source) {
|
|
|
|
|
DisplacementAdvancedPageVo.TreemapItem item = new DisplacementAdvancedPageVo.TreemapItem();
|
|
|
|
|
item.setName(source.getName());
|
|
|
|
|
item.setValue(source.getValue());
|
|
|
|
|
item.setLevelTag(source.getLevelTag());
|
|
|
|
|
return item;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private List<String> normalizeMonitorIds(VibrationBoardQueryBo bo) {
|
|
|
|
|
Set<String> monitorIdSet = new LinkedHashSet<>();
|
|
|
|
|
if (StrUtil.isNotBlank(bo.getMonitorId())) {
|
|
|
|
|
monitorIdSet.add(bo.getMonitorId().trim());
|
|
|
|
|
}
|
|
|
|
|
if (CollUtil.isNotEmpty(bo.getMonitorIds())) {
|
|
|
|
|
for (String monitorId : bo.getMonitorIds()) {
|
|
|
|
|
if (StrUtil.isNotBlank(monitorId)) {
|
|
|
|
|
monitorIdSet.add(monitorId.trim());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return new ArrayList<>(monitorIdSet);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Integer normalizeSamplingInterval(Integer samplingInterval) {
|
|
|
|
|
if (samplingInterval == null || samplingInterval < 1) {
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
return Math.min(samplingInterval, 1440);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Date parseDateTime(String value, String fieldName) {
|
|
|
|
|
if (StrUtil.isBlank(value)) {
|
|
|
|
|
throw new ServiceException(fieldName + "不能为空");
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
LocalDateTime localDateTime = LocalDateTime.parse(value.trim(), DATE_TIME_FORMATTER);
|
|
|
|
|
return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
|
|
|
|
|
} catch (DateTimeParseException ex) {
|
|
|
|
|
throw new ServiceException(fieldName + "格式不正确,请使用 yyyy-MM-dd HH:mm:ss");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void validateQuerySpan(Date beginTime, Date endTime) {
|
|
|
|
|
long diffMs = endTime.getTime() - beginTime.getTime();
|
|
|
|
|
long diffDays = diffMs / (24L * 3600L * 1000L);
|
|
|
|
|
if (diffDays > MAX_QUERY_DAYS) {
|
|
|
|
|
throw new ServiceException("查询跨度不能超过" + MAX_QUERY_DAYS + "天,请缩小时间范围");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void validateEstimatedQueryRows(Date beginTime, Date endTime, int monitorCount, Integer samplingInterval) {
|
|
|
|
|
int effectiveSamplingInterval = normalizeSamplingInterval(samplingInterval);
|
|
|
|
|
long diffMinutes = Math.max(1L, (endTime.getTime() - beginTime.getTime()) / (60L * 1000L) + 1L);
|
|
|
|
|
long rowsPerMonitor = (long) Math.ceil((double) diffMinutes / effectiveSamplingInterval);
|
|
|
|
|
long estimatedRows = rowsPerMonitor * Math.max(1, monitorCount);
|
|
|
|
|
if (estimatedRows > MAX_ESTIMATED_QUERY_ROWS) {
|
|
|
|
|
long recommendedSamplingInterval = Math.max(1L, (long) Math.ceil((double) diffMinutes * Math.max(1, monitorCount) / MAX_ESTIMATED_QUERY_ROWS));
|
|
|
|
|
throw new ServiceException("当前查询预计返回约" + estimatedRows
|
|
|
|
|
+ "条记录,超过系统上限" + MAX_ESTIMATED_QUERY_ROWS
|
|
|
|
|
+ "条,请将抽样间隔至少调整为" + recommendedSamplingInterval + "分钟,或缩小时间范围/设备范围");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void validateResolvedTableNames(List<String> tableNames) {
|
|
|
|
|
for (String tableName : tableNames) {
|
|
|
|
|
if (!TABLE_NAME_PATTERN.matcher(tableName).matches()) {
|
|
|
|
|
throw new ServiceException("非法分表名称:" + tableName);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|