feat(TempBoard温度报表/高级分析): 优化主题河流图查询性能并支持动态粒度

- 添加动态粒度参数支持,根据查询时间跨度自动选择分钟/15分钟/小时级聚合
- 重构SQL查询逻辑,使用DATEADD替代FORMAT提升性能
- 添加90天查询时间范围硬限制
- 使用线程安全的DateTimeFormatter替代SimpleDateFormat
main
zangch@mesnac.com 3 months ago
parent 4ccf639cd2
commit edfafe9cc4

@ -240,10 +240,11 @@ public interface TempBoardMapper {
@Param("startTime") String startTime,
@Param("endTime") String endTime);
/** 主题河流图数据 */
/** 主题河流图数据(支持动态粒度) */
List<TempBoardAdvancedVo> selectThemeRiverData(@Param("tableNames") List<String> tableNames,
@Param("startTime") String startTime,
@Param("endTime") String endTime);
@Param("endTime") String endTime,
@Param("granularity") String granularity);
/** 矩形树图数据 */
List<TempBoardAdvancedVo> selectTreemapData(@Param("tableNames") List<String> tableNames,

@ -10,6 +10,10 @@ import org.dromara.ems.report.service.ITempBoardService;
import org.springframework.stereotype.Service;
import java.text.SimpleDateFormat;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
@ -33,15 +37,28 @@ public class TempBoardServiceImpl implements ITempBoardService {
private static final String DATETIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
/** 线程安全的静态格式化器,替代每次 new SimpleDateFormat */
private static final DateTimeFormatter FMT = DateTimeFormatter.ofPattern(DATETIME_FORMAT);
/** 最大查询天数(硬限制,防止灾难性查询) */
private static final int MAX_QUERY_DAYS = 90;
// ==================== 工具方法 ====================
/**
*
*
*/
private List<String> resolveTables(TempBoardQueryBo query) {
if (query.getStartTime() == null || query.getEndTime() == null) {
throw new ServiceException("请选择时间范围");
}
// 时间范围硬限制校验
long diffMs = query.getEndTime().getTime() - query.getStartTime().getTime();
long diffDays = diffMs / (24 * 3600 * 1000);
if (diffDays > MAX_QUERY_DAYS) {
throw new ServiceException("查询跨度不能超过" + MAX_QUERY_DAYS + "天,请缩小范围");
}
List<String> tables = partitionService.resolveTables(query.getStartTime(), query.getEndTime());
if (tables.isEmpty()) {
throw new ServiceException("所选时间范围内无数据表");
@ -49,8 +66,27 @@ public class TempBoardServiceImpl implements ITempBoardService {
return tables;
}
/** Date → 格式化字符串(兼容旧接口) */
private String fmt(Date date) {
return new SimpleDateFormat(DATETIME_FORMAT).format(date);
return FMT.format(date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime());
}
/**
*
* <= 1
* > 1 <= 7 15
* > 7
*/
private String resolveGranularity(TempBoardQueryBo query) {
long diffMs = query.getEndTime().getTime() - query.getStartTime().getTime();
long diffHours = diffMs / (3600 * 1000);
if (diffHours <= 24) {
return "MINUTE";
} else if (diffHours <= 168) { // 7 天
return "FIFTEEN_MINUTE";
} else {
return "HOUR";
}
}
// ==================== A. 温度总览 ====================
@ -343,7 +379,7 @@ public class TempBoardServiceImpl implements ITempBoardService {
return tempBoardMapper.selectMonitorActivity(tables, fmt(query.getStartTime()), fmt(query.getEndTime()));
}
// ==================== H. 高级分析 ====================
// ==================== H. 高级分析(已优化) ====================
@Override
public List<TempBoardAdvancedVo> getSankeyData(TempBoardQueryBo query) {
@ -354,7 +390,10 @@ public class TempBoardServiceImpl implements ITempBoardService {
@Override
public List<TempBoardAdvancedVo> getThemeRiverData(TempBoardQueryBo query) {
List<String> tables = resolveTables(query);
return tempBoardMapper.selectThemeRiverData(tables, fmt(query.getStartTime()), fmt(query.getEndTime()));
// 根据时间跨度自动选择聚合粒度
String granularity = resolveGranularity(query);
return tempBoardMapper.selectThemeRiverData(
tables, fmt(query.getStartTime()), fmt(query.getEndTime()), granularity);
}
@Override

@ -918,38 +918,33 @@
<!-- ==================== H. 高级分析 ==================== -->
<!-- H1. 桑基图数据(温区按时间段流转) -->
<!-- H1. 桑基图数据(温区按时间段流转)
优化:每表内部先做 DISTINCT(monitorId+桶) 缩小数据量,再 UNION ALL 小结果集做全局自关联 -->
<select id="selectSankeyData" resultType="org.dromara.ems.report.domain.vo.tempboard.TempBoardAdvancedVo">
WITH base AS (
<foreach collection="tableNames" item="tableName" separator=" UNION ALL ">
SELECT t.monitorId, t.temperature, t.collectTime
FROM ${tableName} t
<include refid="baseJoin"/>
<where>
<include refid="timeFilter"/>
<include refid="tempFilter"/>
</where>
</foreach>
),
bucketed AS (
SELECT monitorId, temperature, collectTime,
CASE
WHEN DATEPART(HOUR, collectTime) &lt; 6 THEN 'T1'
WHEN DATEPART(HOUR, collectTime) &lt; 12 THEN 'T2'
WHEN DATEPART(HOUR, collectTime) &lt; 18 THEN 'T3'
ELSE 'T4'
END AS timeBucket,
CASE
WHEN temperature &lt; 15 THEN '&lt;15'
WHEN temperature &lt; 20 THEN '15-20'
WHEN temperature &lt; 25 THEN '20-25'
ELSE '>=25'
END AS tempBucket
FROM base
),
stage AS (
SELECT DISTINCT monitorId, timeBucket, tempBucket
FROM bucketed
WITH stage AS (
SELECT DISTINCT monitorId, timeBucket, tempBucket FROM (
<foreach collection="tableNames" item="tableName" separator=" UNION ALL ">
SELECT DISTINCT t.monitorId,
CASE
WHEN DATEPART(HOUR, t.collectTime) &lt; 6 THEN 'T1'
WHEN DATEPART(HOUR, t.collectTime) &lt; 12 THEN 'T2'
WHEN DATEPART(HOUR, t.collectTime) &lt; 18 THEN 'T3'
ELSE 'T4'
END AS timeBucket,
CASE
WHEN t.temperature &lt; 15 THEN '&lt;15'
WHEN t.temperature &lt; 20 THEN '15-20'
WHEN t.temperature &lt; 25 THEN '20-25'
ELSE '>=25'
END AS tempBucket
FROM ${tableName} t
<include refid="baseJoin"/>
<where>
<include refid="timeFilter"/>
<include refid="tempFilter"/>
</where>
</foreach>
) raw_stage
),
flow AS (
SELECT
@ -969,27 +964,56 @@
ORDER BY fromNode, toNode
</select>
<!-- H2. 主题河流图数据 -->
<!-- H2. 主题河流图数据
优化GROUP BY 使用 DATEADD 时间桶(可走索引),外层 CONVERT 格式化输出(仅对聚合后小结果集);
支持动态粒度MINUTE / FIFTEEN_MINUTE / HOUR -->
<select id="selectThemeRiverData" resultType="org.dromara.ems.report.domain.vo.tempboard.TempBoardAdvancedVo">
<foreach collection="tableNames" item="tableName" separator=" UNION ALL ">
SELECT
FORMAT(t.collectTime, 'yyyy-MM-dd HH:mm:00') AS statTime,
t.monitorId,
COALESCE(ebmi.monitor_name, t.monitorId) AS monitorName,
ROUND(AVG(t.temperature), 2) AS avgTemp
FROM ${tableName} t
<include refid="baseJoin"/>
<where>
<include refid="timeFilter"/>
<include refid="tempFilter"/>
</where>
GROUP BY FORMAT(t.collectTime, 'yyyy-MM-dd HH:mm:00'), t.monitorId,
COALESCE(ebmi.monitor_name, t.monitorId)
</foreach>
SELECT
CONVERT(VARCHAR(19), statBucket, 120) AS statTime,
monitorId, monitorName, avgTemp
FROM (
<foreach collection="tableNames" item="tableName" separator=" UNION ALL ">
SELECT
<choose>
<when test="granularity == 'HOUR'">
DATEADD(HOUR, DATEDIFF(HOUR, 0, t.collectTime), 0)
</when>
<when test="granularity == 'FIFTEEN_MINUTE'">
DATEADD(MINUTE, (DATEDIFF(MINUTE, 0, t.collectTime) / 15) * 15, 0)
</when>
<otherwise>
DATEADD(MINUTE, DATEDIFF(MINUTE, 0, t.collectTime), 0)
</otherwise>
</choose> AS statBucket,
t.monitorId,
COALESCE(ebmi.monitor_name, t.monitorId) AS monitorName,
ROUND(AVG(t.temperature), 2) AS avgTemp
FROM ${tableName} t
<include refid="baseJoin"/>
<where>
<include refid="timeFilter"/>
<include refid="tempFilter"/>
</where>
GROUP BY
<choose>
<when test="granularity == 'HOUR'">
DATEADD(HOUR, DATEDIFF(HOUR, 0, t.collectTime), 0)
</when>
<when test="granularity == 'FIFTEEN_MINUTE'">
DATEADD(MINUTE, (DATEDIFF(MINUTE, 0, t.collectTime) / 15) * 15, 0)
</when>
<otherwise>
DATEADD(MINUTE, DATEDIFF(MINUTE, 0, t.collectTime), 0)
</otherwise>
</choose>,
t.monitorId, COALESCE(ebmi.monitor_name, t.monitorId)
</foreach>
) sub
ORDER BY statTime, monitorId
</select>
<!-- H3. 矩形树图数据(按测点平均温度+样本数) -->
<!-- H3. 矩形树图数据(按测点平均温度+样本数)
每张日表独立聚合UNION ALL 直接输出 -->
<select id="selectTreemapData" resultType="org.dromara.ems.report.domain.vo.tempboard.TempBoardAdvancedVo">
<foreach collection="tableNames" item="tableName" separator=" UNION ALL ">
SELECT t.monitorId,
@ -1007,7 +1031,8 @@
ORDER BY avgTemp DESC
</select>
<!-- H4. 旭日图数据(温区→测点层级) -->
<!-- H4. 旭日图数据(温区→测点层级)
每张日表独立聚合UNION ALL 直接输出 -->
<select id="selectSunburstData" resultType="org.dromara.ems.report.domain.vo.tempboard.TempBoardAdvancedVo">
<foreach collection="tableNames" item="tableName" separator=" UNION ALL ">
SELECT
@ -1040,7 +1065,8 @@
ORDER BY tempBucket, monitorId
</select>
<!-- H5. 平行坐标图数据(多维温度画像) -->
<!-- H5. 平行坐标图数据(多维温度画像)
每张日表独立聚合UNION ALL 直接输出 -->
<select id="selectParallelData" resultType="org.dromara.ems.report.domain.vo.tempboard.TempBoardAdvancedVo">
<foreach collection="tableNames" item="tableName" separator=" UNION ALL ">
SELECT t.monitorId,

Loading…
Cancel
Save