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:
1 parent
65672f8299
commit
e3e7911117
6 files changed
+494
-89
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
|
||||
.GetGlobalNetworkByTimeRangeAsync("", queryStartTime, queryEndTime);
|
||||
var groupedData = allData.GroupBy(d => (d.Timestep / intervalSeconds) * intervalSeconds)
|
||||
.Select(g => new
|
||||
{
|
||||
timestamp = g.Key.ToString(),
|
||||
uploadSpeed = g.Sum(x => x.Upload),
|
||||
downloadSpeed = g.Sum(x => x.Download)
|
||||
});
|
||||
|
||||
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
|
||||
var data = await DatabaseManager.Instance.Networks
|
||||
.GetGlobalNetworkByTimeRangeAsync(interfaceId, queryStartTime, queryEndTime);
|
||||
var groupedData = data.GroupBy(d => (d.Timestep / intervalSeconds) * intervalSeconds)
|
||||
.Select(g => new
|
||||
{
|
||||
timestamp = d.Timestep.ToString(),
|
||||
uploadSpeed = d.Upload,
|
||||
downloadSpeed = d.Download
|
||||
}));
|
||||
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
|
||||
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 = g.Sum(x => x.AvgUpload),
|
||||
downloadSpeed = g.Sum(x => x.AvgDownload)
|
||||
});
|
||||
|
||||
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
|
||||
var data = await DatabaseManager.Instance.Networks
|
||||
.GetGlobalNetworkByTimeRangeAsync(interfaceId, queryStartTime, queryEndTime);
|
||||
var groupedData = data.GroupBy(d => (d.Timestep / intervalSeconds) * intervalSeconds)
|
||||
.Select(g => new
|
||||
{
|
||||
timestamp = d.HourTimestamp.ToString(),
|
||||
uploadSpeed = d.AvgUpload,
|
||||
downloadSpeed = d.AvgDownload
|
||||
}));
|
||||
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
|
||||
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 = g.Sum(x => x.AvgUpload),
|
||||
downloadSpeed = g.Sum(x => x.AvgDownload)
|
||||
});
|
||||
|
||||
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
|
||||
var data = await DatabaseManager.Instance.Networks
|
||||
.GetGlobalNetworkByTimeRangeAsync(interfaceId, queryStartTime, queryEndTime);
|
||||
var groupedData = data.GroupBy(d => (d.Timestep / intervalSeconds) * intervalSeconds)
|
||||
.Select(g => new
|
||||
{
|
||||
timestamp = d.DayTimestamp.ToString(),
|
||||
uploadSpeed = d.AvgUpload,
|
||||
downloadSpeed = d.AvgDownload
|
||||
}));
|
||||
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
|
||||
|
||||
Reference in new issue
Block a user