package cn.iocoder.yudao.module.bi.service.decision; import cn.iocoder.yudao.module.bi.dal.dataobject.decision.BiKpiDefinitionDO; import cn.iocoder.yudao.module.bi.dal.dataobject.decision.BiKpiValueHistoryDO; import cn.iocoder.yudao.module.bi.dal.mysql.decision.BiKpiDefinitionMapper; import cn.iocoder.yudao.module.bi.dal.mysql.decision.BiKpiValueHistoryMapper; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import java.math.BigDecimal; import java.math.RoundingMode; import java.time.LocalDateTime; import java.util.List; import java.util.Map; /** * KPI历史快照聚合任务 * * 定时扫描启用的 KPI 定义,执行查询 SQL 将当前值写入 bi_kpi_value_history, * 供趋势研判、同比环比与统计预测使用。同一 KPI 同一整点仅保留最新值。 * * @author 超级管理员 */ @Slf4j @Component public class KpiSnapshotTask { @Resource private BiKpiDefinitionMapper kpiMapper; @Resource private BiKpiValueHistoryMapper kpiValueHistoryMapper; @Resource private JdbcTemplate jdbcTemplate; /** * 每小时整点触发,默认 cron = 0 0 * * * *,可通过 bi.kpi.snapshot.cron 覆盖 */ @Scheduled(cron = "${bi.kpi.snapshot.cron:0 0 * * * *}") public void snapshotAllKpis() { log.debug("[snapshotAllKpis][开始 KPI 快照聚合]"); List kpis = kpiMapper.selectList(BiKpiDefinitionDO::getStatus, 1); if (kpis.isEmpty()) { log.debug("[snapshotAllKpis][无启用 KPI]"); return; } // 当前整点,保证同一 KPI 同整点幂等 LocalDateTime snapshotTime = LocalDateTime.now().withMinute(0).withSecond(0).withNano(0); for (BiKpiDefinitionDO kpi : kpis) { try { snapshotKpi(kpi, snapshotTime); } catch (Exception e) { log.error("[snapshotAllKpis][KPI {} 快照失败]", kpi.getCode(), e); } } } private void snapshotKpi(BiKpiDefinitionDO kpi, LocalDateTime snapshotTime) { BigDecimal value = executeKpiSql(kpi.getQuerySql()); if (value == null) { log.warn("[snapshotKpi][KPI {} 无值,跳过快照]", kpi.getCode()); return; } BiKpiValueHistoryDO exist = kpiValueHistoryMapper .selectByKpiCodeAndSnapshotTime(kpi.getCode(), snapshotTime); if (exist != null) { exist.setKpiValue(value); kpiValueHistoryMapper.updateById(exist); } else { BiKpiValueHistoryDO history = BiKpiValueHistoryDO.builder() .kpiId(kpi.getId()) .kpiCode(kpi.getCode()) .kpiName(kpi.getName()) .category(kpi.getCategory()) .kpiValue(value) .snapshotTime(snapshotTime) .periodType("hour") .dimensionValue("all") .build(); kpiValueHistoryMapper.insert(history); } } private BigDecimal executeKpiSql(String querySql) { if (querySql == null || querySql.isBlank()) { return null; } try { List> result = jdbcTemplate.queryForList(querySql); if (result.isEmpty()) { return null; } Object value = result.get(0).values().iterator().next(); if (value instanceof Number) { return BigDecimal.valueOf(((Number) value).doubleValue()); } return null; } catch (Exception e) { log.error("[executeKpiSql][SQL执行失败] {}", querySql, e); return null; } } }