A:优化网络数据聚合策略,增加5分钟级聚合提升查询性能

- 新增GlobalNetworkMinutely模型和相关数据表,实现5分钟级数据聚合
- 调整数据聚合流程:原始数据→5分钟→小时→天→周,减少数据冗余
- 优化数据清理策略:6小时后删除原始数据,3天后删除5分钟级聚合数据
- 改进NetworkController时间范围检测逻辑,基于实际数据范围智能判断
- 增强NetworkApiHelper,支持多级数据回退和动态聚合间隔调整

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
jimmyandClaude committed 2025-08-19 16:25:26 +08:00
1 parent 65672f8299
commit e3e7911117
6 files changed
+503 -98

No files matched your search

@@ -482,15 +482,19 @@ public class DatabaseWriteManager : IDisposable
{
Log.Information($"开始处理网卡: {networkCardGuid}");
// 第一步:从原始数据聚合到小时数据
// 第一步:从原始数据聚合到5分钟数据
Log.Information($"为网卡 {networkCardGuid} 聚合5分钟数据");
await AggregateGlobalNetworkMinutelyAsync(networkCardGuid);
// 第二步:从5分钟数据聚合到小时数据
Log.Information($"为网卡 {networkCardGuid} 聚合小时数据");
await AggregateGlobalNetworkHourlyAsync(networkCardGuid);
// 第二步:从小时数据聚合到天数据
// 第三步:从小时数据聚合到天数据
Log.Information($"为网卡 {networkCardGuid} 聚合天数据");
await AggregateGlobalNetworkDailyAsync(networkCardGuid, threeDaysAgo);
// 第三步:从天数据聚合到周数据
// 第四步:从天数据聚合到周数据
Log.Information($"为网卡 {networkCardGuid} 聚合周数据");
await AggregateGlobalNetworkWeeklyAsync(networkCardGuid, oneWeekAgo);
}
@@ -794,14 +798,14 @@ public class DatabaseWriteManager : IDisposable
}
/// <summary>
/// 按小时聚合全局网络数据
/// 按5分钟聚合全局网络数据
/// </summary>
private async Task AggregateGlobalNetworkHourlyAsync(string networkCardGuid)
private async Task AggregateGlobalNetworkMinutelyAsync(string networkCardGuid)
{
try
{
var hourSeconds = 3600;
Log.Information($"聚合网卡 {networkCardGuid} 的小时数据");
var minuteSeconds = 300; // 5分钟 = 300秒
Log.Information($"聚合网卡 {networkCardGuid} 的5分钟数据");
var connection = _dataService.Networks.GetType()
.GetField("_context",
@@ -818,7 +822,7 @@ public class DatabaseWriteManager : IDisposable
string sql = @"
SELECT
(Timestep / @HourSeconds) * @HourSeconds as HourTimestamp,
(Timestep / @MinuteSeconds) * @MinuteSeconds as MinuteTimestamp,
SUM(Upload) as TotalUpload,
SUM(Download) as TotalDownload,
AVG(Upload) as AvgUpload,
@@ -828,7 +832,100 @@ public class DatabaseWriteManager : IDisposable
COUNT(*) as RecordCount
FROM GlobalNetwork
WHERE NetworkCardGuid = @NetworkCardGuid
GROUP BY (Timestep / @HourSeconds)
GROUP BY (Timestep / @MinuteSeconds)
ORDER BY MinuteTimestamp";
var parameters = new { NetworkCardGuid = networkCardGuid, MinuteSeconds = minuteSeconds };
var aggregatedData = await contextConnection.QueryAsync(sql, parameters);
Log.Information($"查询到 {aggregatedData.Count()} 条5分钟聚合数据");
int savedCount = 0;
int skippedCount = 0;
foreach (dynamic data in aggregatedData)
{
var minuteTimestamp = (long)data.MinuteTimestamp;
var totalUpload = (long)data.TotalUpload;
var totalDownload = (long)data.TotalDownload;
var avgUpload = (long)data.AvgUpload;
var avgDownload = (long)data.AvgDownload;
var maxUpload = (long)data.MaxUpload;
var maxDownload = (long)data.MaxDownload;
var recordCount = (int)data.RecordCount;
bool exists =
await _dataService.Networks.GlobalNetworkMinutelyExistsAsync(networkCardGuid, minuteTimestamp);
if (!exists)
{
var minutelyData = new GlobalNetworkMinutely
{
NetworkCardGuid = networkCardGuid,
MinuteTimestamp = minuteTimestamp,
TotalUpload = totalUpload,
TotalDownload = totalDownload,
AvgUpload = avgUpload,
AvgDownload = avgDownload,
MaxUpload = maxUpload,
MaxDownload = maxDownload,
RecordCount = recordCount,
CreatedTimestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
};
await _dataService.Networks.SaveGlobalNetworkMinutelyAsync(minutelyData);
savedCount++;
}
else
{
skippedCount++;
}
}
Log.Information($"网卡 {networkCardGuid} 的5分钟聚合完成,保存了 {savedCount} 条新数据,跳过了 {skippedCount} 条已存在数据");
}
catch (Exception ex)
{
Log.Error(ex, $"聚合网卡 {networkCardGuid} 的5分钟数据失败");
}
}
/// <summary>
/// 按小时聚合全局网络数据(从5分钟数据聚合)
/// </summary>
private async Task AggregateGlobalNetworkHourlyAsync(string networkCardGuid)
{
try
{
var hourSeconds = 3600;
Log.Information($"聚合网卡 {networkCardGuid} 的小时数据,从5分钟数据聚合");
var connection = _dataService.Networks.GetType()
.GetField("_context",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)
?.GetValue(_dataService.Networks);
var contextConnection =
connection?.GetType().GetProperty("Connection")?.GetValue(connection) as System.Data.IDbConnection;
if (contextConnection == null)
{
Log.Warning("无法获取数据库连接");
return;
}
string sql = @"
SELECT
(MinuteTimestamp / @HourSeconds) * @HourSeconds as HourTimestamp,
SUM(TotalUpload) as TotalUpload,
SUM(TotalDownload) as TotalDownload,
AVG(AvgUpload) as AvgUpload,
AVG(AvgDownload) as AvgDownload,
MAX(MaxUpload) as MaxUpload,
MAX(MaxDownload) as MaxDownload,
SUM(RecordCount) as RecordCount
FROM GlobalNetworkMinutely
WHERE NetworkCardGuid = @NetworkCardGuid
GROUP BY (MinuteTimestamp / @HourSeconds)
ORDER BY HourTimestamp";
var parameters = new { NetworkCardGuid = networkCardGuid, HourSeconds = hourSeconds };
@@ -1103,13 +1200,21 @@ public class DatabaseWriteManager : IDisposable
{
Log.Information("开始清理全局网络数据");
// 删除超过3天的原始数据(已聚合到周级数据)
var deletedOldData = await _dataService.Networks.DeleteOldGlobalNetworkDataAsync(threeDaysAgo);
Log.Information($"删除了 {deletedOldData} 条超过3天的全局网络原始数据");
var currentTimestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
var sixHoursAgo = currentTimestamp - 21600; // 6小时前
var threeDaysAgoMinutely = currentTimestamp - 259200; // 3天前
// 删除超过6小时的原始数据(已聚合到5分钟级数据)
var deletedRawData = await _dataService.Networks.DeleteOldGlobalNetworkDataAsync(sixHoursAgo);
Log.Information($"删除了 {deletedRawData} 条超过6小时的全局网络原始数据");
// 删除超过3天的5分钟级聚合数据(已聚合到小时级数据)
var deletedMinutelyData = await _dataService.Networks.DeleteGlobalNetworkMinutelyBeforeAsync(threeDaysAgoMinutely);
Log.Information($"删除了 {deletedMinutelyData} 条超过3天的全局网络5分钟级聚合数据");
// 删除超过1周的周级聚合数据(已聚合到月级数据)
var deletedMonthlyData = await _dataService.Networks.DeleteGlobalNetworkWeeklyBeforeAsync(oneWeekAgo);
Log.Information($"删除了 {deletedMonthlyData} 条超过1周的全局网络周级聚合数据");
var deletedWeeklyData = await _dataService.Networks.DeleteGlobalNetworkWeeklyBeforeAsync(oneWeekAgo);
Log.Information($"删除了 {deletedWeeklyData} 条超过1周的全局网络周级聚合数据");
Log.Information("全局网络数据清理完成");
}
@@ -53,6 +53,17 @@ public abstract class GlobalNetworkAggregatedBase
public long CreatedTimestamp { get; set; }
}
/// <summary>
/// 全局网络5分钟聚合数据(用于1小时视图)
/// </summary>
public class GlobalNetworkMinutely : GlobalNetworkAggregatedBase
{
/// <summary>
/// 分钟时间戳(Unix时间戳,精确到5分钟)
/// </summary>
public long MinuteTimestamp { get; set; }
}
/// <summary>
/// 全局网络小时聚合数据
/// </summary>
@@ -173,6 +173,23 @@ public class NetVizorDbContext : IDisposable
UNIQUE(AppId, MonthTimestamp)
)");
// 创建全局网络聚合数据表 - 按5分钟聚合(用于1小时视图)
await connection.ExecuteAsync(@"
CREATE TABLE IF NOT EXISTS GlobalNetworkMinutely (
Id INTEGER PRIMARY KEY AUTOINCREMENT,
NetworkCardGuid TEXT NOT NULL,
MinuteTimestamp INTEGER NOT NULL,
TotalUpload INTEGER DEFAULT 0,
TotalDownload INTEGER DEFAULT 0,
AvgUpload INTEGER DEFAULT 0,
AvgDownload INTEGER DEFAULT 0,
MaxUpload INTEGER DEFAULT 0,
MaxDownload INTEGER DEFAULT 0,
RecordCount INTEGER DEFAULT 0,
CreatedTimestamp INTEGER NOT NULL,
UNIQUE(NetworkCardGuid, MinuteTimestamp)
)");
// 创建全局网络聚合数据表 - 按小时聚合
await connection.ExecuteAsync(@"
CREATE TABLE IF NOT EXISTS GlobalNetworkHourly (
@@ -264,6 +281,8 @@ public class NetVizorDbContext : IDisposable
"CREATE INDEX IF NOT EXISTS idx_appnetwork_monthly_timestamp ON AppNetworkMonthly(MonthTimestamp)");
// 全局网络聚合表索引
await connection.ExecuteAsync(
"CREATE INDEX IF NOT EXISTS idx_globalnetwork_minutely_timestamp ON GlobalNetworkMinutely(MinuteTimestamp)");
await connection.ExecuteAsync(
"CREATE INDEX IF NOT EXISTS idx_globalnetwork_hourly_timestamp ON GlobalNetworkHourly(HourTimestamp)");
await connection.ExecuteAsync(
@@ -31,6 +31,7 @@ public interface INetworkRepository
Task<IEnumerable<AppNetworkSummary>> GetAppNetworkByMonthAsync(string appId, int months = 12);
// 全局网络按时间单位聚合查询
Task<IEnumerable<GlobalNetworkSummary>> GetGlobalNetworkByMinuteAsync(string networkCardGuid, int minutes = 60);
Task<IEnumerable<GlobalNetworkSummary>> GetGlobalNetworkByHourAsync(string networkCardGuid, int hours = 24);
Task<IEnumerable<GlobalNetworkSummary>> GetGlobalNetworkByDayAsync(string networkCardGuid, int days = 7);
Task<IEnumerable<GlobalNetworkSummary>> GetGlobalNetworkByMonthAsync(string networkCardGuid, int months = 12);
@@ -51,6 +52,7 @@ public interface INetworkRepository
Task<int> SaveAppNetworkMonthlyAsync(AppNetworkMonthly monthly);
// 全局网络聚合数据操作
Task<int> SaveGlobalNetworkMinutelyAsync(GlobalNetworkMinutely minutely);
Task<int> SaveGlobalNetworkHourlyAsync(GlobalNetworkHourly hourly);
Task<int> SaveGlobalNetworkDailyAsync(GlobalNetworkDaily daily);
Task<int> SaveGlobalNetworkWeeklyAsync(GlobalNetworkWeekly weekly);
@@ -62,6 +64,7 @@ public interface INetworkRepository
Task<bool> AppNetworkWeeklyExistsAsync(string appId, long weekTimestamp);
Task<bool> AppNetworkMonthlyExistsAsync(string appId, long monthTimestamp);
Task<bool> GlobalNetworkMinutelyExistsAsync(string networkCardGuid, long minuteTimestamp);
Task<bool> GlobalNetworkHourlyExistsAsync(string networkCardGuid, long hourTimestamp);
Task<bool> GlobalNetworkDailyExistsAsync(string networkCardGuid, long dayTimestamp);
Task<bool> GlobalNetworkWeeklyExistsAsync(string networkCardGuid, long weekTimestamp);
@@ -72,6 +75,7 @@ public interface INetworkRepository
Task<int> DeleteAppNetworkDailyBeforeAsync(long beforeTimestamp);
Task<int> DeleteAppNetworkWeeklyBeforeAsync(long beforeTimestamp);
Task<int> DeleteGlobalNetworkMinutelyBeforeAsync(long beforeTimestamp);
Task<int> DeleteGlobalNetworkHourlyBeforeAsync(long beforeTimestamp);
Task<int> DeleteGlobalNetworkDailyBeforeAsync(long beforeTimestamp);
Task<int> DeleteGlobalNetworkWeeklyBeforeAsync(long beforeTimestamp);
@@ -347,6 +351,52 @@ public class NetworkRepository : INetworkRepository
#region 全局网络按时间单位聚合查询
public async Task<IEnumerable<GlobalNetworkSummary>> GetGlobalNetworkByMinuteAsync(string networkCardGuid, int minutes = 60)
{
var startTime = DateTimeOffset.UtcNow.AddMinutes(-minutes).ToUnixTimeSeconds();
string sql;
object parameters;
if (string.IsNullOrEmpty(networkCardGuid))
{
// 获取所有网卡数据
sql = @"
SELECT
MinuteTimestamp,
SUM(TotalUpload) as TotalUpload,
SUM(TotalDownload) as TotalDownload,
AVG(AvgUpload) as AvgUpload,
AVG(AvgDownload) as AvgDownload,
MAX(MaxUpload) as MaxUpload,
MAX(MaxDownload) as MaxDownload
FROM GlobalNetworkMinutely
WHERE MinuteTimestamp >= @StartTime
GROUP BY MinuteTimestamp
ORDER BY MinuteTimestamp DESC";
parameters = new { StartTime = startTime };
}
else
{
// 获取指定网卡数据
sql = @"
SELECT
MinuteTimestamp,
TotalUpload,
TotalDownload,
AvgUpload,
AvgDownload,
MaxUpload,
MaxDownload
FROM GlobalNetworkMinutely
WHERE NetworkCardGuid = @NetworkCardGuid AND MinuteTimestamp >= @StartTime
ORDER BY MinuteTimestamp DESC";
parameters = new { NetworkCardGuid = networkCardGuid, StartTime = startTime };
}
return await _context.Connection.QueryAsync<GlobalNetworkSummary>(sql, parameters);
}
public async Task<IEnumerable<GlobalNetworkSummary>> GetGlobalNetworkByHourAsync(string networkCardGuid,
int hours = 24)
{
@@ -621,6 +671,15 @@ public class NetworkRepository : INetworkRepository
#region 全局网络聚合数据操作
public async Task<int> SaveGlobalNetworkMinutelyAsync(GlobalNetworkMinutely minutely)
{
const string sql = @"
INSERT INTO GlobalNetworkMinutely (NetworkCardGuid, MinuteTimestamp, TotalUpload, TotalDownload, AvgUpload, AvgDownload, MaxUpload, MaxDownload, RecordCount, CreatedTimestamp)
VALUES (@NetworkCardGuid, @MinuteTimestamp, @TotalUpload, @TotalDownload, @AvgUpload, @AvgDownload, @MaxUpload, @MaxDownload, @RecordCount, @CreatedTimestamp)";
return await _context.Connection.ExecuteAsync(sql, minutely);
}
public async Task<int> SaveGlobalNetworkHourlyAsync(GlobalNetworkHourly hourly)
{
const string sql = @"
@@ -696,6 +755,15 @@ public class NetworkRepository : INetworkRepository
return count > 0;
}
public async Task<bool> GlobalNetworkMinutelyExistsAsync(string networkCardGuid, long minuteTimestamp)
{
const string sql =
"SELECT COUNT(1) FROM GlobalNetworkMinutely WHERE NetworkCardGuid = @NetworkCardGuid AND MinuteTimestamp = @MinuteTimestamp";
var count = await _context.Connection.QuerySingleAsync<int>(sql,
new { NetworkCardGuid = networkCardGuid, MinuteTimestamp = minuteTimestamp });
return count > 0;
}
public async Task<bool> GlobalNetworkHourlyExistsAsync(string networkCardGuid, long hourTimestamp)
{
const string sql =
@@ -754,6 +822,12 @@ public class NetworkRepository : INetworkRepository
return await _context.Connection.ExecuteAsync(sql, new { BeforeTimestamp = beforeTimestamp });
}
public async Task<int> DeleteGlobalNetworkMinutelyBeforeAsync(long beforeTimestamp)
{
const string sql = "DELETE FROM GlobalNetworkMinutely WHERE MinuteTimestamp < @BeforeTimestamp";
return await _context.Connection.ExecuteAsync(sql, new { BeforeTimestamp = beforeTimestamp });
}
public async Task<int> DeleteGlobalNetworkHourlyBeforeAsync(long beforeTimestamp)
{
const string sql = "DELETE FROM GlobalNetworkHourly WHERE HourTimestamp < @BeforeTimestamp";
@@ -790,6 +864,7 @@ public class AppNetworkSummary
public class GlobalNetworkSummary
{
public long MinuteTimestamp { get; set; }
public long HourTimestamp { get; set; }
public long DayTimestamp { get; set; }
public long MonthTimestamp { get; set; }
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using Application.Utils;
@@ -11,6 +12,7 @@ using Data.Models;
using Data.Repositories;
using Shell.Utils;
using Shell.Services;
using Dapper;
namespace Shell.Controllers;
@@ -113,73 +115,83 @@ public class NetworkController : BaseController
var availableRanges = new List<object>();
var now = DateTimeOffset.UtcNow;
var hasHourlyData =
await NetworkApiHelper.HasNetworkDataInTimeRangeAsync("hourly", now.AddHours(-1).ToUnixTimeSeconds());
if (hasHourlyData)
// 获取最早和最新的数据时间戳来确定实际数据范围
var (earliestTime, latestTime) = await GetDataTimeRangeAsync();
if (earliestTime == 0 || latestTime == 0)
{
// 没有数据时返回空数组
await WriteJsonResponseAsync(context, new ResponseModel<object>
{
Success = true,
Data = availableRanges,
Message = "暂无网络数据"
});
return;
}
var dataSpanSeconds = latestTime - earliestTime;
var oneHour = 3600;
var oneDay = 86400;
var oneWeek = 604800;
var oneMonth = 2592000;
// 1小时:至少有1分钟的数据
if (dataSpanSeconds >= 60)
{
availableRanges.Add(new
{
type = "hour",
name = "1小时",
available = true,
startTime = now.AddHours(-1).ToString("yyyy-MM-ddTHH:mm:ssZ")
startTime = DateTimeOffset.FromUnixTimeSeconds(Math.Max(earliestTime, latestTime - oneHour)).ToString("yyyy-MM-ddTHH:mm:ssZ"),
actualDataSpan = Math.Min(dataSpanSeconds, oneHour)
});
}
var hasDailyData =
await NetworkApiHelper.HasNetworkDataInTimeRangeAsync("daily", now.AddDays(-1).ToUnixTimeSeconds());
if (hasDailyData)
// 24小时:至少有1小时的数据
if (dataSpanSeconds >= oneHour)
{
availableRanges.Add(new
{
type = "day",
name = "1天",
name = "24小时",
available = true,
startTime = now.AddDays(-1).ToString("yyyy-MM-ddTHH:mm:ssZ")
startTime = DateTimeOffset.FromUnixTimeSeconds(Math.Max(earliestTime, latestTime - oneDay)).ToString("yyyy-MM-ddTHH:mm:ssZ"),
actualDataSpan = Math.Min(dataSpanSeconds, oneDay)
});
}
var hasWeeklyData =
await NetworkApiHelper.HasNetworkDataInTimeRangeAsync("daily", now.AddDays(-7).ToUnixTimeSeconds());
if (hasWeeklyData)
// 7天:至少有6小时的数据
if (dataSpanSeconds >= oneHour * 6)
{
availableRanges.Add(new
{
type = "week",
name = "1周",
name = "7天",
available = true,
startTime = now.AddDays(-7).ToString("yyyy-MM-ddTHH:mm:ssZ")
startTime = DateTimeOffset.FromUnixTimeSeconds(Math.Max(earliestTime, latestTime - oneWeek)).ToString("yyyy-MM-ddTHH:mm:ssZ"),
actualDataSpan = Math.Min(dataSpanSeconds, oneWeek)
});
}
var hasMonthlyData =
await NetworkApiHelper.HasNetworkDataInTimeRangeAsync("daily", now.AddDays(-30).ToUnixTimeSeconds());
if (hasMonthlyData)
// 30天:至少有1天的数据
if (dataSpanSeconds >= oneDay)
{
availableRanges.Add(new
{
type = "month",
name = "1个月",
name = "30天",
available = true,
startTime = now.AddDays(-30).ToString("yyyy-MM-ddTHH:mm:ssZ")
});
}
if (!availableRanges.Any())
{
availableRanges.Add(new
{
type = "hour",
name = "1小时",
available = true,
startTime = now.ToString("yyyy-MM-ddTHH:mm:ssZ")
startTime = DateTimeOffset.FromUnixTimeSeconds(Math.Max(earliestTime, latestTime - oneMonth)).ToString("yyyy-MM-ddTHH:mm:ssZ"),
actualDataSpan = Math.Min(dataSpanSeconds, oneMonth)
});
}
await WriteJsonResponseAsync(context, new ResponseModel<object>
{
Success = true,
Data = availableRanges.First(),
Data = availableRanges,
Message = "获取可用时间范围成功"
});
}
@@ -189,6 +201,45 @@ public class NetworkController : BaseController
}
}
/// <summary>
/// 获取数据库中的实际数据时间范围
/// </summary>
private async Task<(long earliestTime, long latestTime)> GetDataTimeRangeAsync()
{
try
{
// 查询全局网络数据的时间范围
var globalData = await DatabaseManager.Instance.Networks.GetGlobalNetworkHistoryAsync("", 1);
if (!globalData.Any())
{
return (0, 0);
}
// 获取更精确的时间范围 - 通过反射获取NetworkRepository的连接
var networkRepo = DatabaseManager.Instance.Networks;
var contextField = networkRepo.GetType().GetField("_context",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
var context = contextField?.GetValue(networkRepo);
var connection = context?.GetType().GetProperty("Connection")?.GetValue(context) as System.Data.IDbConnection;
if (connection == null)
{
return (0, 0);
}
var earliestTime = await connection.QuerySingleOrDefaultAsync<long?>(
"SELECT MIN(Timestep) FROM GlobalNetwork") ?? 0;
var latestTime = await connection.QuerySingleOrDefaultAsync<long?>(
"SELECT MAX(Timestep) FROM GlobalNetwork") ?? 0;
return (earliestTime, latestTime);
}
catch
{
return (0, 0);
}
}
public async Task ClearCacheAsync(HttpContext context)
{
try
@@ -1,9 +1,11 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using Data;
using Data.Models;
using Dapper;
namespace Shell.Utils;
@@ -69,17 +71,30 @@ public static class NetworkApiHelper
{
try
{
var currentTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
switch (dataType)
{
case "hourly":
var hourlyData = await DatabaseManager.Instance.Networks.GetGlobalNetworkByHourAsync("", 1);
return hourlyData.Any();
// 1小时视图:检查原始数据(临时方案,直到数据库迁移完成)
var rawData = await DatabaseManager.Instance.Networks.GetGlobalNetworkByTimeRangeAsync("", startTime, currentTime);
return rawData.Any();
case "daily":
var dailyData = await DatabaseManager.Instance.Networks.GetGlobalNetworkByDayAsync("", 1);
return dailyData.Any();
// 24小时视图:优先检查小时级聚合数据,其次检查5分钟级和原始数据
var hourlyData = await DatabaseManager.Instance.Networks.GetGlobalNetworkByHourAsync("", 24);
if (hourlyData.Any(h => h.HourTimestamp >= startTime)) return true;
var minutelyDataDaily = await DatabaseManager.Instance.Networks.GetGlobalNetworkByMinuteAsync("", 1440); // 24小时 = 1440分钟
if (minutelyDataDaily.Any(m => m.MinuteTimestamp >= startTime)) return true;
var rawDataDaily = await DatabaseManager.Instance.Networks.GetGlobalNetworkByTimeRangeAsync("", startTime, currentTime);
return rawDataDaily.Any();
default:
var networkData = await DatabaseManager.Instance.Networks.GetGlobalNetworkHistoryAsync("", 1);
return networkData.Any();
// 7天/30天视图:检查天级聚合数据
var dailyData = await DatabaseManager.Instance.Networks.GetGlobalNetworkByDayAsync("", 32);
return dailyData.Any(d => d.DayTimestamp >= startTime);
}
}
catch
@@ -94,82 +109,177 @@ public static class NetworkApiHelper
try
{
// 获取实际数据的时间范围
var (actualStartTime, actualEndTime) = await GetActualDataRangeAsync(startTime, endTime);
if (actualStartTime == 0 || actualEndTime == 0)
{
return points; // 返回空数据
}
// 智能调整查询范围:使用实际数据范围与请求范围的交集
var queryStartTime = Math.Max(startTime, actualStartTime);
var queryEndTime = Math.Min(endTime, actualEndTime);
// 计算实际数据跨度来动态调整聚合间隔
var actualDataSpan = queryEndTime - queryStartTime;
if (timeRange == "hour")
{
// 1小时视图:动态调整聚合间隔
int intervalSeconds;
if (actualDataSpan <= 300) // 5分钟内的数据
{
intervalSeconds = 5; // 5秒聚合
}
else if (actualDataSpan <= 1800) // 30分钟内的数据
{
intervalSeconds = 10; // 10秒聚合
}
else
{
intervalSeconds = 30; // 30秒聚合
}
if (interfaceId == "all")
{
var allData = await DatabaseManager.Instance.Networks
.GetGlobalNetworkByTimeRangeAsync("", startTime, endTime);
var groupedData = allData.GroupBy(d => d.Timestep).Select(g => new
{
timestamp = g.Key.ToString(),
uploadSpeed = g.Sum(x => x.Upload),
downloadSpeed = g.Sum(x => x.Download)
});
.GetGlobalNetworkByTimeRangeAsync("", queryStartTime, queryEndTime);
var groupedData = allData.GroupBy(d => (d.Timestep / intervalSeconds) * intervalSeconds)
.Select(g => new
{
timestamp = g.Key.ToString(),
uploadSpeed = (long)g.Sum(x => x.Upload),
downloadSpeed = (long)g.Sum(x => x.Download)
})
.OrderBy(x => long.Parse(x.timestamp));
points.AddRange(groupedData);
}
else
{
var data = await DatabaseManager.Instance.Networks.GetGlobalNetworkByTimeRangeAsync(interfaceId,
startTime, endTime);
points.AddRange(data.Select(d => new
{
timestamp = d.Timestep.ToString(),
uploadSpeed = d.Upload,
downloadSpeed = d.Download
}));
var data = await DatabaseManager.Instance.Networks
.GetGlobalNetworkByTimeRangeAsync(interfaceId, queryStartTime, queryEndTime);
var groupedData = data.GroupBy(d => (d.Timestep / intervalSeconds) * intervalSeconds)
.Select(g => new
{
timestamp = g.Key.ToString(),
uploadSpeed = (long)g.Average(x => x.Upload),
downloadSpeed = (long)g.Average(x => x.Download)
})
.OrderBy(x => long.Parse(x.timestamp));
points.AddRange(groupedData);
}
}
else if (timeRange == "day")
{
// 24小时视图:动态调整聚合间隔
int intervalSeconds;
if (actualDataSpan <= 1800) // 30分钟内的数据
{
intervalSeconds = 30; // 30秒聚合
}
else if (actualDataSpan <= 7200) // 2小时内的数据
{
intervalSeconds = 60; // 1分钟聚合
}
else
{
intervalSeconds = 300; // 5分钟聚合
}
if (interfaceId == "all")
{
var allData = await DatabaseManager.Instance.Networks.GetGlobalNetworkByHourAsync("", 24);
var groupedData = allData.GroupBy(d => d.HourTimestamp).Select(g => new
{
timestamp = g.Key.ToString(),
uploadSpeed = g.Sum(x => x.AvgUpload),
downloadSpeed = g.Sum(x => x.AvgDownload)
});
var allData = await DatabaseManager.Instance.Networks
.GetGlobalNetworkByTimeRangeAsync("", queryStartTime, queryEndTime);
var groupedData = allData.GroupBy(d => (d.Timestep / intervalSeconds) * intervalSeconds)
.Select(g => new
{
timestamp = g.Key.ToString(),
uploadSpeed = (long)g.Sum(x => x.Upload),
downloadSpeed = (long)g.Sum(x => x.Download)
})
.OrderBy(x => long.Parse(x.timestamp));
points.AddRange(groupedData);
}
else
{
var data = await DatabaseManager.Instance.Networks.GetGlobalNetworkByHourAsync(interfaceId, 24);
points.AddRange(data.Select(d => new
{
timestamp = d.HourTimestamp.ToString(),
uploadSpeed = d.AvgUpload,
downloadSpeed = d.AvgDownload
}));
var data = await DatabaseManager.Instance.Networks
.GetGlobalNetworkByTimeRangeAsync(interfaceId, queryStartTime, queryEndTime);
var groupedData = data.GroupBy(d => (d.Timestep / intervalSeconds) * intervalSeconds)
.Select(g => new
{
timestamp = g.Key.ToString(),
uploadSpeed = (long)g.Average(x => x.Upload),
downloadSpeed = (long)g.Average(x => x.Download)
})
.OrderBy(x => long.Parse(x.timestamp));
points.AddRange(groupedData);
}
}
else
{
int days = timeRange == "week" ? 7 : 30;
// 7天/30天视图:智能调整聚合间隔
int intervalSeconds;
if (timeRange == "week")
{
// 7天视图:根据实际数据跨度动态调整
if (actualDataSpan <= 7200) // 2小时内数据
{
intervalSeconds = 300; // 5分钟聚合
}
else if (actualDataSpan <= 86400) // 1天内数据
{
intervalSeconds = 900; // 15分钟聚合
}
else
{
intervalSeconds = 1800; // 30分钟聚合
}
}
else
{
// 30天视图:根据实际数据跨度动态调整
if (actualDataSpan <= 86400) // 1天内数据
{
intervalSeconds = 1800; // 30分钟聚合
}
else if (actualDataSpan <= 604800) // 1周内数据
{
intervalSeconds = 3600; // 1小时聚合
}
else
{
intervalSeconds = 7200; // 2小时聚合
}
}
if (interfaceId == "all")
{
var allData = await DatabaseManager.Instance.Networks.GetGlobalNetworkByDayAsync("", days);
var groupedData = allData.GroupBy(d => d.DayTimestamp).Select(g => new
{
timestamp = g.Key.ToString(),
uploadSpeed = g.Sum(x => x.AvgUpload),
downloadSpeed = g.Sum(x => x.AvgDownload)
});
var allData = await DatabaseManager.Instance.Networks
.GetGlobalNetworkByTimeRangeAsync("", queryStartTime, queryEndTime);
var groupedData = allData.GroupBy(d => (d.Timestep / intervalSeconds) * intervalSeconds)
.Select(g => new
{
timestamp = g.Key.ToString(),
uploadSpeed = (long)g.Sum(x => x.Upload),
downloadSpeed = (long)g.Sum(x => x.Download)
})
.OrderBy(x => long.Parse(x.timestamp));
points.AddRange(groupedData);
}
else
{
var data = await DatabaseManager.Instance.Networks.GetGlobalNetworkByDayAsync(interfaceId, days);
points.AddRange(data.Select(d => new
{
timestamp = d.DayTimestamp.ToString(),
uploadSpeed = d.AvgUpload,
downloadSpeed = d.AvgDownload
}));
var data = await DatabaseManager.Instance.Networks
.GetGlobalNetworkByTimeRangeAsync(interfaceId, queryStartTime, queryEndTime);
var groupedData = data.GroupBy(d => (d.Timestep / intervalSeconds) * intervalSeconds)
.Select(g => new
{
timestamp = g.Key.ToString(),
uploadSpeed = (long)g.Average(x => x.Upload),
downloadSpeed = (long)g.Average(x => x.Download)
})
.OrderBy(x => long.Parse(x.timestamp));
points.AddRange(groupedData);
}
}
}
@@ -181,6 +291,40 @@ public static class NetworkApiHelper
return points;
}
/// <summary>
/// 获取指定时间范围内的实际数据范围
/// </summary>
private static async Task<(long actualStartTime, long actualEndTime)> GetActualDataRangeAsync(long startTime, long endTime)
{
try
{
// 查询指定时间范围内的最早和最晚时间戳 - 通过反射获取NetworkRepository的连接
var networkRepo = DatabaseManager.Instance.Networks;
var contextField = networkRepo.GetType().GetField("_context",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
var context = contextField?.GetValue(networkRepo);
var connection = context?.GetType().GetProperty("Connection")?.GetValue(context) as System.Data.IDbConnection;
if (connection == null)
{
return (0, 0);
}
var actualStartTime = await connection.QuerySingleOrDefaultAsync<long?>(
"SELECT MIN(Timestep) FROM GlobalNetwork WHERE Timestep >= @StartTime AND Timestep <= @EndTime",
new { StartTime = startTime, EndTime = endTime }) ?? 0;
var actualEndTime = await connection.QuerySingleOrDefaultAsync<long?>(
"SELECT MAX(Timestep) FROM GlobalNetwork WHERE Timestep >= @StartTime AND Timestep <= @EndTime",
new { StartTime = startTime, EndTime = endTime }) ?? 0;
return (actualStartTime, actualEndTime);
}
catch
{
return (0, 0);
}
}
public static string GetPortServiceName(int port)
{
return port switch