diff --git a/src/main/java/com/ycwl/basic/clickhouse/service/impl/ClickHouseStatsQueryServiceImpl.java b/src/main/java/com/ycwl/basic/clickhouse/service/impl/ClickHouseStatsQueryServiceImpl.java index 35dd1b27..5639babc 100644 --- a/src/main/java/com/ycwl/basic/clickhouse/service/impl/ClickHouseStatsQueryServiceImpl.java +++ b/src/main/java/com/ycwl/basic/clickhouse/service/impl/ClickHouseStatsQueryServiceImpl.java @@ -12,10 +12,13 @@ import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.stereotype.Service; import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.TimeZone; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; +import java.util.*; +import java.util.stream.Collectors; /** * ClickHouse 统计数据查询服务实现 @@ -366,12 +369,14 @@ public class ClickHouseStatsQueryServiceImpl implements StatsQueryService { sql.append("GROUP BY toStartOfHour(s.create_time) "); sql.append("ORDER BY toStartOfHour(s.create_time)"); - return getJdbcTemplate().query(sql.toString(), (rs, rowNum) -> { + List> rawData = getJdbcTemplate().query(sql.toString(), (rs, rowNum) -> { HashMap map = new HashMap<>(); map.put("t", rs.getString("t")); map.put("count", rs.getString("count")); return map; }); + + return fillHourSeries(rawData, query.getStartTime(), query.getEndTime()); } @Override @@ -389,12 +394,14 @@ public class ClickHouseStatsQueryServiceImpl implements StatsQueryService { sql.append("GROUP BY toStartOfDay(s.create_time) "); sql.append("ORDER BY toStartOfDay(s.create_time)"); - return getJdbcTemplate().query(sql.toString(), (rs, rowNum) -> { + List> rawData = getJdbcTemplate().query(sql.toString(), (rs, rowNum) -> { HashMap map = new HashMap<>(); map.put("t", rs.getString("t")); map.put("count", rs.getString("count")); return map; }); + + return fillDateSeries(rawData, query.getStartTime(), query.getEndTime()); } @Override @@ -418,12 +425,14 @@ public class ClickHouseStatsQueryServiceImpl implements StatsQueryService { sql.append("GROUP BY toStartOfHour(s.create_time) "); sql.append("ORDER BY toStartOfHour(s.create_time)"); - return getJdbcTemplate().query(sql.toString(), (rs, rowNum) -> { + List> rawData = getJdbcTemplate().query(sql.toString(), (rs, rowNum) -> { HashMap map = new HashMap<>(); map.put("t", rs.getString("t")); map.put("count", rs.getString("count")); return map; }); + + return fillHourSeries(rawData, query.getStartTime(), query.getEndTime()); } @Override @@ -447,11 +456,81 @@ public class ClickHouseStatsQueryServiceImpl implements StatsQueryService { sql.append("GROUP BY toStartOfDay(s.create_time) "); sql.append("ORDER BY toStartOfDay(s.create_time)"); - return getJdbcTemplate().query(sql.toString(), (rs, rowNum) -> { + List> rawData = getJdbcTemplate().query(sql.toString(), (rs, rowNum) -> { HashMap map = new HashMap<>(); map.put("t", rs.getString("t")); map.put("count", rs.getString("count")); return map; }); + + return fillDateSeries(rawData, query.getStartTime(), query.getEndTime()); + } + + /** + * 填充小时序列,确保每个小时都有数据(缺失的填充为0) + */ + private List> fillHourSeries(List> rawData, Date startTime, Date endTime) { + if (startTime == null || endTime == null) { + return rawData; + } + + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MM-dd HH"); + LocalDateTime start = startTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime().truncatedTo(ChronoUnit.HOURS); + LocalDateTime end = endTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime().truncatedTo(ChronoUnit.HOURS); + + // 将原始数据转为 Map 以便快速查找 + Map dataMap = rawData.stream() + .collect(Collectors.toMap( + m -> m.get("t"), + m -> m.get("count"), + (existing, replacement) -> existing + )); + + List> result = new ArrayList<>(); + LocalDateTime current = start; + while (!current.isAfter(end)) { + String timeKey = current.format(formatter); + HashMap item = new HashMap<>(); + item.put("t", timeKey); + item.put("count", dataMap.getOrDefault(timeKey, "0")); + result.add(item); + current = current.plusHours(1); + } + + return result; + } + + /** + * 填充日期序列,确保每天都有数据(缺失的填充为0) + */ + private List> fillDateSeries(List> rawData, Date startTime, Date endTime) { + if (startTime == null || endTime == null) { + return rawData; + } + + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MM-dd"); + LocalDate start = startTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); + LocalDate end = endTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); + + // 将原始数据转为 Map 以便快速查找 + Map dataMap = rawData.stream() + .collect(Collectors.toMap( + m -> m.get("t"), + m -> m.get("count"), + (existing, replacement) -> existing + )); + + List> result = new ArrayList<>(); + LocalDate current = start; + while (!current.isAfter(end)) { + String timeKey = current.format(formatter); + HashMap item = new HashMap<>(); + item.put("t", timeKey); + item.put("count", dataMap.getOrDefault(timeKey, "0")); + result.add(item); + current = current.plusDays(1); + } + + return result; } }