package com.battery.business.service.impl; import java.math.BigDecimal; import java.util.Date; import java.util.List; import java.util.Locale; import com.alibaba.fastjson2.JSON; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.battery.business.domain.TTest; import com.battery.business.domain.TTestWarningException; import com.battery.business.domain.TTestWarningThreshold; import com.battery.business.mapper.TTestMapper; /** 分别记录测量超限与接收中的离线事实;缺失测量不参与阈值判断。 */ @Service public class TestWarningExceptionService { private final TTestMapper mapper; public TestWarningExceptionService(TTestMapper mapper) { this.mapper = mapper; } /** 调用方须持有测试事务锁;仅在接收中记录当前绑定来源的离线事实。 */ public void recordOfflineSources(TTest test, Date occurredAt) { if (test == null || !"RUNNING".equals(test.getTestStatus()) || !"RECEIVING".equals(test.getReceptionStatus())) return; for (var source : mapper.selectOfflineTestSources(test.getTestId())) { mapper.upsertOfflineException(test.getTestId(), source.sourceType(), source.sourceIdentity(), occurredAt); } } /** 在持有测试事务锁时,对接收中的成功实时测量聚合开放异常,不受采样入库周期限制。 */ public void evaluate(TTest test, String sourceType, String sourceIdentity, String metricCode, BigDecimal value, String unit, Date occurredAt) { for (TTestWarningThreshold rule : exceededRules(test, metricCode, value)) { mapper.upsertWarningException(test.getTestId(), rule.getThresholdId(), sourceType, sourceIdentity, metricCode, value, unit, JSON.toJSONString(rule), rule.getSeverity(), occurredAt == null ? new Date() : occurredAt); } } static List exceededRules(TTest test, String metricCode, BigDecimal value) { if (value == null || test == null || test.getThresholds() == null || !"RUNNING".equals(test.getTestStatus()) || !"RECEIVING".equals(test.getReceptionStatus())) return List.of(); return test.getThresholds().stream().filter(rule -> rule != null && matchesMetric(rule.getMetricCode(), metricCode) && outside(value, rule.getMinValue(), rule.getMaxValue())).toList(); } /** 由测试负责人关闭开放异常,处理内容保存在异常事实中。 */ @Transactional(rollbackFor = Exception.class) public int close(Long exceptionId, Long actorId, String actorName, String handlingNote) { TTestWarningException exception = mapper.selectWarningExceptionById(exceptionId); if (exception == null) return 0; int rows = mapper.closeWarningException(exceptionId, actorId, actorName, new Date(), handlingNote); return rows; } private static boolean outside(BigDecimal value, BigDecimal min, BigDecimal max) { return (min != null && value.compareTo(min) < 0) || (max != null && value.compareTo(max) > 0); } static boolean matchesCollectorMetric(String configured, String actual) { if (configured == null || actual == null) return false; String role = configured.toUpperCase(Locale.ROOT), metric = actual.toUpperCase(Locale.ROOT); return role.equals(metric) || List.of("TEMPERATURE", "POSITIVE_POTENTIAL", "NEGATIVE_POTENTIAL").stream() .anyMatch(code -> metric.equals(code) && role.contains(code)); } static boolean matchesMetric(String configured, String actual) { if (configured == null || actual == null) return false; if (configured.equalsIgnoreCase(actual)) return true; String c = configured.toUpperCase(Locale.ROOT), a = actual.toUpperCase(Locale.ROOT); return (c.contains("VOLTAGE") && a.contains("VOLTAGE")) || (c.contains("CURRENT") && a.contains("CURRENT")) || (c.contains("CAPACITY") && a.contains("CAPACITY")) || (c.contains("ENERGY") && a.contains("ENERGY")) || (c.contains("TEMPERATURE") && a.contains("TEMPERATURE")) || (c.contains("POSITIVE_POTENTIAL") && a.contains("POSITIVE_POTENTIAL")) || (c.contains("NEGATIVE_POTENTIAL") && a.contains("NEGATIVE_POTENTIAL")); } }