From 74aaef9ad516f55f89cd5cbf1fb571d112b41aee Mon Sep 17 00:00:00 2001 From: fengyarnom Date: Thu, 15 May 2025 18:38:18 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E1=E5=88=86=E9=92=9F?= =?UTF-8?q?=E7=9A=84=E7=B2=92=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/dao/sensor.go | 100 +++++++++++++++++++++++++++++++++++++++++ static/index.html | 30 +++++++++++-- 2 files changed, 126 insertions(+), 4 deletions(-) diff --git a/internal/dao/sensor.go b/internal/dao/sensor.go index 6c28927..07a0743 100644 --- a/internal/dao/sensor.go +++ b/internal/dao/sensor.go @@ -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] +} diff --git a/static/index.html b/static/index.html index 366e65f..97a7a7a 100644 --- a/static/index.html +++ b/static/index.html @@ -128,9 +128,10 @@
@@ -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; }); }