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<BiKpiDefinitionDO> 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<Map<String, Object>> 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;
|
}
|
}
|
|
}
|