feat: 新增1分钟的粒度
This commit is contained in:
parent
a673267fd3
commit
74aaef9ad5
@ -2,6 +2,8 @@ package dao
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"math"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"go_rain_dtu/internal/model"
|
||||
@ -46,6 +48,8 @@ func (dao *SensorDAO) GetAggregatedData(start, end time.Time, interval string) (
|
||||
var intervalMinutes int
|
||||
|
||||
switch interval {
|
||||
case "1min":
|
||||
intervalMinutes = 1
|
||||
case "5min":
|
||||
intervalMinutes = 5
|
||||
case "30min":
|
||||
@ -116,6 +120,102 @@ func (dao *SensorDAO) GetAggregatedData(start, end time.Time, interval string) (
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 如果是1分钟粒度,且数据不足,可能需要进行插值
|
||||
if intervalMinutes == 1 && len(result) > 0 {
|
||||
result = fillMissingMinutes(result, start, end)
|
||||
}
|
||||
|
||||
logger.Logger.Printf("聚合查询成功: 返回%d条记录 (间隔=%s)", len(result), interval)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// fillMissingMinutes 填充缺失的分钟数据点
|
||||
func fillMissingMinutes(data []model.AggregatedData, start, end time.Time) []model.AggregatedData {
|
||||
// 只有在数据点少于应有数量的一半时才进行插值
|
||||
expectedPoints := int(end.Sub(start).Minutes()) + 1
|
||||
if len(data) > expectedPoints/2 {
|
||||
return data // 数据点足够多,不需要插值
|
||||
}
|
||||
|
||||
// 创建一个映射,用于快速查找已有数据点
|
||||
dataMap := make(map[int64]model.AggregatedData)
|
||||
for _, point := range data {
|
||||
// 将时间转换为Unix时间戳(秒),便于比较
|
||||
key := point.Timestamp.Unix()
|
||||
dataMap[key] = point
|
||||
}
|
||||
|
||||
// 初始化结果数组,预分配足够的空间
|
||||
result := make([]model.AggregatedData, 0, expectedPoints)
|
||||
|
||||
// 创建按时间排序的索引
|
||||
var timestamps []int64
|
||||
for k := range dataMap {
|
||||
timestamps = append(timestamps, k)
|
||||
}
|
||||
sort.Slice(timestamps, func(i, j int) bool {
|
||||
return timestamps[i] < timestamps[j]
|
||||
})
|
||||
|
||||
// 如果没有数据点,直接返回
|
||||
if len(timestamps) == 0 {
|
||||
return data
|
||||
}
|
||||
|
||||
// 获取第一个和最后一个数据点
|
||||
firstPointTime := time.Unix(timestamps[0], 0)
|
||||
lastPointTime := time.Unix(timestamps[len(timestamps)-1], 0)
|
||||
|
||||
// 调整开始和结束时间,确保它们不会超出实际数据范围
|
||||
if start.Before(firstPointTime) {
|
||||
start = firstPointTime
|
||||
}
|
||||
if end.After(lastPointTime) {
|
||||
end = lastPointTime
|
||||
}
|
||||
|
||||
// 将start向下取整到整分钟
|
||||
start = time.Date(start.Year(), start.Month(), start.Day(), start.Hour(), start.Minute(), 0, 0, start.Location())
|
||||
|
||||
// 按分钟迭代时间范围
|
||||
for t := start; !t.After(end); t = t.Add(time.Minute) {
|
||||
key := t.Unix()
|
||||
if point, exists := dataMap[key]; exists {
|
||||
// 使用现有数据点
|
||||
result = append(result, point)
|
||||
} else {
|
||||
// 使用最近的有效数据点进行插值
|
||||
nearestPoint := findNearestPoint(t, timestamps, dataMap)
|
||||
if !nearestPoint.Timestamp.IsZero() {
|
||||
// 创建新的数据点
|
||||
newPoint := nearestPoint
|
||||
newPoint.Timestamp = t
|
||||
result = append(result, newPoint)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// findNearestPoint 查找最接近指定时间的数据点
|
||||
func findNearestPoint(t time.Time, timestamps []int64, dataMap map[int64]model.AggregatedData) model.AggregatedData {
|
||||
target := t.Unix()
|
||||
nearest := int64(0)
|
||||
minDiff := int64(math.MaxInt64)
|
||||
|
||||
for _, ts := range timestamps {
|
||||
diff := int64(math.Abs(float64(ts - target)))
|
||||
if diff < minDiff {
|
||||
minDiff = diff
|
||||
nearest = ts
|
||||
}
|
||||
}
|
||||
|
||||
// 如果最近的点超过10分钟,返回空值
|
||||
if minDiff > 10*60 {
|
||||
return model.AggregatedData{}
|
||||
}
|
||||
|
||||
return dataMap[nearest]
|
||||
}
|
||||
|
||||
@ -128,9 +128,10 @@
|
||||
<div class="control-group">
|
||||
<label for="interval">数据粒度:</label>
|
||||
<select id="interval">
|
||||
<option value="5min">5分钟</option>
|
||||
<option value="1min">1分钟</option>
|
||||
<option value="5min" selected>5分钟</option>
|
||||
<option value="30min">30分钟</option>
|
||||
<option value="1hour" selected>1小时</option>
|
||||
<option value="1hour">1小时</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
@ -255,20 +256,39 @@
|
||||
function queryLatestData() {
|
||||
const interval = document.getElementById('interval').value;
|
||||
|
||||
// 计算最近1小时的时间范围
|
||||
// 根据粒度调整查询范围
|
||||
let hours = 1;
|
||||
if (interval === "1min") {
|
||||
hours = 0.5; // 30分钟
|
||||
} else if (interval === "5min") {
|
||||
hours = 1; // 1小时
|
||||
} else if (interval === "30min") {
|
||||
hours = 6; // 6小时
|
||||
} else {
|
||||
hours = 24; // 1天
|
||||
}
|
||||
|
||||
// 计算最近时间范围 - 使用当天0点到现在
|
||||
const endTime = new Date();
|
||||
const startTime = new Date(endTime.getTime() - (1 * 60 * 60 * 1000)); // 1小时前
|
||||
const startTime = new Date(endTime);
|
||||
startTime.setHours(0, 0, 0, 0); // 设置为当天0点
|
||||
|
||||
// 确保时间格式符合后端要求
|
||||
const startDateTime = startTime.toISOString();
|
||||
const endDateTime = endTime.toISOString();
|
||||
|
||||
// 加载状态指示
|
||||
document.getElementById('mainChart').style.opacity = 0.5;
|
||||
|
||||
fetch(`/api/latest?interval=${interval}&start=${startDateTime}&end=${endDateTime}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
updateChart(data);
|
||||
updateTable(data);
|
||||
|
||||
// 恢复正常显示
|
||||
document.getElementById('mainChart').style.opacity = 1;
|
||||
|
||||
// 自动更新日期选择器为最近查询的时间范围
|
||||
document.getElementById('startDate').value = formatDateTime(startTime);
|
||||
document.getElementById('endDate').value = formatDateTime(endTime);
|
||||
@ -276,6 +296,8 @@
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
alert('获取最新数据失败,请检查网络连接');
|
||||
// 恢复正常显示
|
||||
document.getElementById('mainChart').style.opacity = 1;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user