A:增加消息记录功能

This commit is contained in:
jimmy committed 2025-08-01 16:57:59 +08:00
1 parent 2f6e912c58
commit 2f54037836
14 files changed
+1975 -121

No files matched your search

-102
View File
@@ -1,102 +0,0 @@
namespace Data;
public class Class1
{
}
/*
软件信息的记录:
名字、路径、版本、公司、ICON等
归档与清理任务的调度
使用 BackgroundWorker / Timer / Task 定期执行:
按月归档
删除旧数据
合并压缩历史数据(比如求平均/最大/最小)
异常崩溃后的恢复
建议使用 WAL 模式 + 定期备份数据库文件。
常见的异常事件类型:
连接异常
连接失败(如 TCP 三次握手失败)
连接超时、断开异常
流量异常
突然流量暴增或骤降
单个 IP/端口流量异常(过高或过低)
安全告警
访问黑名单 IP 或域名
非法端口访问(比如非标准端口被访问)
端口扫描行为检测
异常登录/连接尝试
软件异常
某进程网络异常终止
网络接口断开或重连
系统网络错误(如 DNS 解析失败)
事件日志的记录结构建议
| 字段 | 说明 |
| -------- | --------------------------- |
| 事件ID | 唯一标识 |
| 时间戳 | 事件发生时间 |
| 事件类型 | 如“连接失败”、“异常流量”、“黑名单访问”等 |
| 相关进程/应用 | 关联的进程路径或名称(如果有) |
| 本地 IP/端口 | 事件关联的本地地址 |
| 远程 IP/端口 | 事件关联的远程地址 |
| 事件描述 | 详细描述,如“连接 8.8.8.8:443 超过阈值” |
| 严重等级 | Info/Warning/Error/Critical |
| 额外数据 | 可 JSON 格式存储其他细节信息 |
CREATE TABLE event_log (
event_id INTEGER PRIMARY KEY AUTOINCREMENT,
timestamp DATETIME NOT NULL,
event_type TEXT NOT NULL,
process_path TEXT,
local_ip TEXT,
local_port INTEGER,
remote_ip TEXT,
remote_port INTEGER,
severity TEXT NOT NULL,
description TEXT,
extra_data TEXT -- JSON 格式可选
);
端口扫描行为如何检测?
检测思路
短时间内对多个端口发起连接尝试:
同一源 IP 在短时间内向同一目标 IP 扫描多个端口。
大量连接失败:
多个端口连接失败,说明可能是扫描。
连接速率异常:
短时间内连接速率远高于正常业务连接。
实现方法
滑动时间窗口统计:记录单位时间内访问某目标IP的端口次数。
阈值判断:超过阈值触发告警。
结合连接失败率:如果大部分端口连接失败,更可能是扫描。
黑名单过滤:过滤已知安全扫描器IP。
第一个版本只记录和分析数据,不进行异常信息监控和警告
*/
@@ -0,0 +1,433 @@
using System.Collections.Concurrent;
using System.Threading.Channels;
using Common.Logger;
using Data.Models;
using Data.Services;
namespace Data.Core;
/// <summary>
/// 数据库写入操作类型
/// </summary>
public enum DbOperationType
{
SaveAppInfo,
SaveAppNetwork,
SaveGlobalNetwork,
AggregateData,
CleanupOldData
}
/// <summary>
/// 数据库操作请求
/// </summary>
public class DbOperationRequest
{
public DbOperationType Type { get; set; }
public object Data { get; set; }
public TaskCompletionSource<bool> CompletionSource { get; set; }
public DateTime RequestTime { get; set; } = DateTime.UtcNow;
}
/// <summary>
/// 专用的数据库写入线程管理器
/// 所有数据库写入操作都通过这个管理器进行,保证SQLite的最佳性能
/// </summary>
public class DatabaseWriteManager : IDisposable
{
private readonly Channel<DbOperationRequest> _operationChannel;
private readonly ChannelWriter<DbOperationRequest> _writer;
private readonly ChannelReader<DbOperationRequest> _reader;
private readonly CancellationTokenSource _cancellationTokenSource;
private readonly Task _processingTask;
private readonly IDataService _dataService;
// 批处理缓存
private readonly List<AppNetwork> _appNetworkBatch = new();
private readonly List<GlobalNetwork> _globalNetworkBatch = new();
private readonly SemaphoreSlim _batchSemaphore = new(1, 1);
// 性能计数器
private long _totalOperations = 0;
private long _successfulOperations = 0;
private DateTime _lastStatsReport = DateTime.UtcNow;
public DatabaseWriteManager(IDataService dataService)
{
_dataService = dataService;
// 创建高性能的Channel用于操作队列
var options = new BoundedChannelOptions(10000)
{
FullMode = BoundedChannelFullMode.Wait,
SingleReader = true,
SingleWriter = false
};
_operationChannel = Channel.CreateBounded<DbOperationRequest>(options);
_writer = _operationChannel.Writer;
_reader = _operationChannel.Reader;
_cancellationTokenSource = new CancellationTokenSource();
// 启动写入处理线程
_processingTask = Task.Run(ProcessOperationsAsync, _cancellationTokenSource.Token);
Log.Information("数据库写入线程管理器已启动");
}
/// <summary>
/// 异步保存应用信息
/// </summary>
public async Task<bool> SaveAppInfoAsync(AppInfo appInfo)
{
var request = new DbOperationRequest
{
Type = DbOperationType.SaveAppInfo,
Data = appInfo,
CompletionSource = new TaskCompletionSource<bool>()
};
if (await _writer.WaitToWriteAsync(_cancellationTokenSource.Token))
{
await _writer.WriteAsync(request, _cancellationTokenSource.Token);
return await request.CompletionSource.Task;
}
return false;
}
/// <summary>
/// 异步保存应用网络记录(支持批处理)
/// </summary>
public async Task<bool> SaveAppNetworkAsync(AppNetwork appNetwork)
{
await _batchSemaphore.WaitAsync();
try
{
_appNetworkBatch.Add(appNetwork);
// 当批处理达到一定数量或时间间隔时,触发批量写入
if (_appNetworkBatch.Count >= 50)
{
await FlushAppNetworkBatchAsync();
}
}
finally
{
_batchSemaphore.Release();
}
return true;
}
/// <summary>
/// 异步保存全局网络记录(支持批处理)
/// </summary>
public async Task<bool> SaveGlobalNetworkAsync(GlobalNetwork globalNetwork)
{
await _batchSemaphore.WaitAsync();
try
{
_globalNetworkBatch.Add(globalNetwork);
// 当批处理达到一定数量时,触发批量写入
if (_globalNetworkBatch.Count >= 20)
{
await FlushGlobalNetworkBatchAsync();
}
}
finally
{
_batchSemaphore.Release();
}
return true;
}
/// <summary>
/// 强制刷新所有批处理缓存
/// </summary>
public async Task FlushAllBatchesAsync()
{
await _batchSemaphore.WaitAsync();
try
{
await FlushAppNetworkBatchAsync();
await FlushGlobalNetworkBatchAsync();
}
finally
{
_batchSemaphore.Release();
}
}
/// <summary>
/// 触发数据聚合操作
/// </summary>
public async Task<bool> TriggerDataAggregationAsync()
{
var request = new DbOperationRequest
{
Type = DbOperationType.AggregateData,
Data = DateTime.UtcNow,
CompletionSource = new TaskCompletionSource<bool>()
};
if (await _writer.WaitToWriteAsync(_cancellationTokenSource.Token))
{
await _writer.WriteAsync(request, _cancellationTokenSource.Token);
return await request.CompletionSource.Task;
}
return false;
}
/// <summary>
/// 触发数据清理操作
/// </summary>
public async Task<bool> TriggerDataCleanupAsync()
{
var request = new DbOperationRequest
{
Type = DbOperationType.CleanupOldData,
Data = DateTime.UtcNow,
CompletionSource = new TaskCompletionSource<bool>()
};
if (await _writer.WaitToWriteAsync(_cancellationTokenSource.Token))
{
await _writer.WriteAsync(request, _cancellationTokenSource.Token);
return await request.CompletionSource.Task;
}
return false;
}
/// <summary>
/// 主要的操作处理循环
/// </summary>
private async Task ProcessOperationsAsync()
{
Log.Information("数据库写入处理线程已启动");
try
{
await foreach (var request in _reader.ReadAllAsync(_cancellationTokenSource.Token))
{
Interlocked.Increment(ref _totalOperations);
try
{
bool success = await ProcessSingleOperationAsync(request);
if (success)
{
Interlocked.Increment(ref _successfulOperations);
}
request.CompletionSource.SetResult(success);
}
catch (Exception ex)
{
Log.Error(ex, $"处理数据库操作时发生错误: {request.Type}");
request.CompletionSource.SetException(ex);
}
// 定期报告统计信息
if (DateTime.UtcNow - _lastStatsReport > TimeSpan.FromMinutes(5))
{
ReportStatistics();
_lastStatsReport = DateTime.UtcNow;
}
}
}
catch (OperationCanceledException)
{
Log.Information("数据库写入线程已取消");
}
catch (Exception ex)
{
Log.Error(ex, "数据库写入线程发生未处理异常");
}
}
/// <summary>
/// 处理单个数据库操作
/// </summary>
private async Task<bool> ProcessSingleOperationAsync(DbOperationRequest request)
{
switch (request.Type)
{
case DbOperationType.SaveAppInfo:
var appInfo = (AppInfo)request.Data;
await _dataService.AppInfos.SaveAppAsync(appInfo);
return true;
case DbOperationType.AggregateData:
return await PerformDataAggregationAsync();
case DbOperationType.CleanupOldData:
return await PerformDataCleanupAsync();
default:
Log.Warning($"未知的数据库操作类型: {request.Type}");
return false;
}
}
/// <summary>
/// 刷新应用网络批处理
/// </summary>
private async Task FlushAppNetworkBatchAsync()
{
if (_appNetworkBatch.Count == 0) return;
try
{
var batch = _appNetworkBatch.ToList();
_appNetworkBatch.Clear();
foreach (var item in batch)
{
await _dataService.Networks.SaveAppNetworkAsync(item);
}
Log.Debug($"批量写入 {batch.Count} 条应用网络记录");
}
catch (Exception ex)
{
Log.Error(ex, "批量写入应用网络记录失败");
}
}
/// <summary>
/// 刷新全局网络批处理
/// </summary>
private async Task FlushGlobalNetworkBatchAsync()
{
if (_globalNetworkBatch.Count == 0) return;
try
{
var batch = _globalNetworkBatch.ToList();
_globalNetworkBatch.Clear();
foreach (var item in batch)
{
await _dataService.Networks.SaveGlobalNetworkAsync(item);
}
Log.Debug($"批量写入 {batch.Count} 条全局网络记录");
}
catch (Exception ex)
{
Log.Error(ex, "批量写入全局网络记录失败");
}
}
/// <summary>
/// 执行数据聚合
/// </summary>
private async Task<bool> PerformDataAggregationAsync()
{
try
{
Log.Information("开始执行数据聚合任务");
// 先刷新所有批处理数据
await FlushAllBatchesAsync();
// TODO: 实现数据聚合逻辑
// 这里会实现按小时、天的数据聚合
Log.Information("数据聚合任务完成");
return true;
}
catch (Exception ex)
{
Log.Error(ex, "数据聚合任务失败");
return false;
}
}
/// <summary>
/// 执行数据清理
/// </summary>
private async Task<bool> PerformDataCleanupAsync()
{
try
{
Log.Information("开始执行数据清理任务");
// TODO: 实现数据清理逻辑
// 删除过期的原始数据
Log.Information("数据清理任务完成");
return true;
}
catch (Exception ex)
{
Log.Error(ex, "数据清理任务失败");
return false;
}
}
/// <summary>
/// 报告统计信息
/// </summary>
private void ReportStatistics()
{
var total = Interlocked.Read(ref _totalOperations);
var successful = Interlocked.Read(ref _successfulOperations);
var successRate = total > 0 ? (double)successful / total * 100 : 0;
Log.Information($"数据库写入统计: 总操作数={total}, 成功数={successful}, 成功率={successRate:F1}%");
}
/// <summary>
/// 检查应用信息是否存在
/// </summary>
public async Task<bool> AppExistsAsync(string appId)
{
var existingApp = await _dataService.AppInfos.GetAppByAppIdAsync(appId);
return existingApp != null;
}
public void Dispose()
{
Log.Information("正在关闭数据库写入线程管理器...");
// 先刷新所有批处理数据
try
{
FlushAllBatchesAsync().Wait(TimeSpan.FromSeconds(10));
}
catch (Exception ex)
{
Log.Warning("关闭时刷新批处理数据失败", ex);
}
// 关闭Channel
_writer.TryComplete();
// 取消处理线程
_cancellationTokenSource.Cancel();
// 等待处理线程完成
try
{
_processingTask.Wait(TimeSpan.FromSeconds(5));
}
catch (Exception ex)
{
Log.Warning("等待数据库写入线程退出超时", ex);
}
_cancellationTokenSource.Dispose();
_batchSemaphore.Dispose();
ReportStatistics();
Log.Information("数据库写入线程管理器已关闭");
}
}
@@ -0,0 +1,94 @@
using Data.Models;
using Data.Services;
namespace Data;
/// <summary>
/// 数据库管理器,提供应用程序数据访问的统一入口
/// </summary>
public static class DatabaseManager
{
private static IDataService? _dataService;
private static readonly object _lock = new object();
/// <summary>
/// 初始化数据库服务
/// </summary>
/// <param name="databasePath">数据库文件路径</param>
public static async Task<IDataService> InitializeAsync(string? databasePath = null)
{
if (_dataService != null)
return _dataService;
lock (_lock)
{
if (_dataService != null)
return _dataService;
// 如果未指定路径,使用默认路径
databasePath ??= GetDefaultDatabasePath();
_dataService = new DataService(databasePath);
}
await _dataService.InitializeAsync();
return _dataService;
}
/// <summary>
/// 获取数据服务实例
/// </summary>
public static IDataService Instance
{
get
{
if (_dataService == null)
throw new InvalidOperationException(
"Database service has not been initialized. Call InitializeAsync first.");
return _dataService;
}
}
/// <summary>
/// 获取用户设置(用于应用启动时读取窗口位置等配置)
/// </summary>
public static async Task<AppSetting> GetUserSettingsAsync()
{
return await Instance.GetOrCreateDefaultSettingAsync();
}
/// <summary>
/// 检查数据库健康状态
/// </summary>
public static async Task<bool> IsHealthyAsync()
{
if (_dataService == null) return false;
return await _dataService.IsHealthyAsync();
}
/// <summary>
/// 获取默认数据库路径
/// </summary>
private static string GetDefaultDatabasePath()
{
var appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
var appFolder = Path.Combine(appDataPath, "NetVizor");
if (!Directory.Exists(appFolder))
Directory.CreateDirectory(appFolder);
return Path.Combine(appFolder, "netvizor.db");
}
/// <summary>
/// 释放资源
/// </summary>
public static void Dispose()
{
if (_dataService is IDisposable disposable)
{
disposable.Dispose();
_dataService = null;
}
}
}
@@ -8,6 +8,11 @@ public class AppInfo
{
public int Id { get; set; }
/// <summary>
/// 软件 ID = 路径 + 文件签名发布者 + 进程名 (文件签名发布者 可无)
/// </summary>
public string AppId { get; set; }
/// <summary>
/// 名字
/// </summary>
+49 -9
View File
@@ -1,14 +1,54 @@
namespace Data.Models;
/// <summary>
/// 应用程序网络活动记录
/// </summary>
public class AppNetwork
{
//时间戳
//本地IP
//本地端口
//远程IP
//远程端口
//协议
//上传字节
//下载字节
//可执行文件路径
public int Id { get; set; }
/// <summary>
/// 时间戳
/// </summary>
public long Timestamp { get; set; }
/// <summary>
/// 本地IP地址
/// </summary>
public string LocalIP { get; set; } = string.Empty;
/// <summary>
/// 本地端口
/// </summary>
public int LocalPort { get; set; }
/// <summary>
/// 远程IP地址
/// </summary>
public string RemoteIP { get; set; } = string.Empty;
/// <summary>
/// 远程端口
/// </summary>
public int RemotePort { get; set; }
/// <summary>
/// 协议类型(TCP/UDP)
/// </summary>
public string Protocol { get; set; } = string.Empty;
/// <summary>
/// 上传字节数
/// </summary>
public long UploadBytes { get; set; }
/// <summary>
/// 下载字节数
/// </summary>
public long DownloadBytes { get; set; }
/// <summary>
/// 软件 ID = 路径 + 文件签名发布者 + 进程名 (文件签名发布者 可无)
/// </summary>
public string AppId { get; set; }
}
@@ -0,0 +1,42 @@
namespace Data.Models;
/// <summary>
/// 应用程序设置
/// </summary>
public class AppSetting
{
public int Id { get; set; }
/// <summary>
/// 窗口X坐标
/// </summary>
public int WindowX { get; set; } = 0;
/// <summary>
/// 窗口Y坐标
/// </summary>
public int WindowY { get; set; } = 0;
/*
*文本颜色
背景颜色
透明度
速度单位
布局方向(横向,纵向)
展示单位(true/false)
双击动作(1,2,3,)
默认以管理员身份启动(true/false)
*
*/
/// <summary>
/// 是否开机自启
/// </summary>
public bool AutoStart { get; set; } = false;
/// <summary>
/// 更新时间
/// </summary>
public long UpdateTime { get; set; }
}
@@ -0,0 +1,276 @@
using System.Data;
using System.Data.SQLite;
using Common.Logger;
using Dapper;
namespace Data;
public class NetVizorDbContext : IDisposable
{
private readonly string _connectionString;
private IDbConnection? _connection;
public NetVizorDbContext(string databasePath)
{
_connectionString = $"Data Source={databasePath};Version=3;";
}
public IDbConnection Connection
{
get
{
if (_connection == null || _connection.State != ConnectionState.Open)
{
_connection = new SQLiteConnection(_connectionString);
_connection.Open();
// 启用WAL模式以支持多读
_connection.Execute("PRAGMA journal_mode=WAL;");
_connection.Execute("PRAGMA synchronous=NORMAL;");
_connection.Execute("PRAGMA cache_size=10000;");
_connection.Execute("PRAGMA temp_store=memory;");
}
return _connection;
}
}
public async Task InitializeDatabaseAsync()
{
await CreateTablesAsync();
await MigrateDatabaseAsync();
}
private async Task CreateTablesAsync()
{
var connection = Connection;
// 创建AppInfo表 - 使用自增Id作为主键,AppId作为普通字段
await connection.ExecuteAsync(@"
CREATE TABLE IF NOT EXISTS AppInfo (
Id INTEGER PRIMARY KEY AUTOINCREMENT,
AppId TEXT NOT NULL,
Name TEXT NOT NULL,
Path TEXT NOT NULL,
Version TEXT,
Company TEXT,
Base64Icon TEXT,
Hash TEXT,
InsertTime INTEGER NOT NULL,
UpdateTime INTEGER NOT NULL,
DeleteTime INTEGER DEFAULT 0
)");
// 创建AppSetting表 - 简化结构
await connection.ExecuteAsync(@"
CREATE TABLE IF NOT EXISTS AppSetting (
Id INTEGER PRIMARY KEY AUTOINCREMENT,
WindowX INTEGER DEFAULT 0,
WindowY INTEGER DEFAULT 0,
AutoStart INTEGER DEFAULT 0,
UpdateTime INTEGER NOT NULL
)");
// 创建AppNetwork表 - 详细的网络记录,移除外键约束
await connection.ExecuteAsync(@"
CREATE TABLE IF NOT EXISTS AppNetwork (
Id INTEGER PRIMARY KEY AUTOINCREMENT,
Timestamp INTEGER NOT NULL,
LocalIP TEXT NOT NULL,
LocalPort INTEGER NOT NULL,
RemoteIP TEXT NOT NULL,
RemotePort INTEGER NOT NULL,
Protocol TEXT NOT NULL,
UploadBytes INTEGER DEFAULT 0,
DownloadBytes INTEGER DEFAULT 0,
AppId TEXT NOT NULL
)");
// 创建GlobalNetwork表
await connection.ExecuteAsync(@"
CREATE TABLE IF NOT EXISTS GlobalNetwork (
Id INTEGER PRIMARY KEY AUTOINCREMENT,
Timestep INTEGER NOT NULL,
Upload INTEGER NOT NULL,
Download INTEGER NOT NULL,
NetworkCardGuid TEXT NOT NULL
)");
// 创建聚合数据表 - 按小时聚合的应用网络数据
await connection.ExecuteAsync(@"
CREATE TABLE IF NOT EXISTS AppNetworkHourly (
Id INTEGER PRIMARY KEY AUTOINCREMENT,
AppId TEXT NOT NULL,
HourTimestamp INTEGER NOT NULL,
RemoteIP TEXT NOT NULL,
RemotePort INTEGER NOT NULL,
Protocol TEXT NOT NULL,
TotalUpload INTEGER DEFAULT 0,
TotalDownload INTEGER DEFAULT 0,
ConnectionCount INTEGER DEFAULT 0,
FirstSeen INTEGER NOT NULL,
LastSeen INTEGER NOT NULL,
UNIQUE(AppId, HourTimestamp, RemoteIP, RemotePort, Protocol)
)");
// 创建聚合数据表 - 按天聚合的应用网络数据
await connection.ExecuteAsync(@"
CREATE TABLE IF NOT EXISTS AppNetworkDaily (
Id INTEGER PRIMARY KEY AUTOINCREMENT,
AppId TEXT NOT NULL,
DayTimestamp INTEGER NOT NULL,
TotalUpload INTEGER DEFAULT 0,
TotalDownload INTEGER DEFAULT 0,
ConnectionCount INTEGER DEFAULT 0,
TopRemoteIP TEXT,
TopRemotePort INTEGER,
FirstSeen INTEGER NOT NULL,
LastSeen INTEGER NOT NULL,
UNIQUE(AppId, DayTimestamp)
)");
// 创建全局网络按小时聚合表
await connection.ExecuteAsync(@"
CREATE TABLE IF NOT EXISTS GlobalNetworkHourly (
Id INTEGER PRIMARY KEY AUTOINCREMENT,
HourTimestamp INTEGER NOT NULL,
NetworkCardGuid TEXT NOT NULL,
TotalUpload INTEGER DEFAULT 0,
TotalDownload INTEGER DEFAULT 0,
MaxUploadSpeed INTEGER DEFAULT 0,
MaxDownloadSpeed INTEGER DEFAULT 0,
UNIQUE(HourTimestamp, NetworkCardGuid)
)");
// 创建全局网络按天聚合表
await connection.ExecuteAsync(@"
CREATE TABLE IF NOT EXISTS GlobalNetworkDaily (
Id INTEGER PRIMARY KEY AUTOINCREMENT,
DayTimestamp INTEGER NOT NULL,
NetworkCardGuid TEXT NOT NULL,
TotalUpload INTEGER DEFAULT 0,
TotalDownload INTEGER DEFAULT 0,
AvgUploadSpeed INTEGER DEFAULT 0,
AvgDownloadSpeed INTEGER DEFAULT 0,
PeakUploadSpeed INTEGER DEFAULT 0,
PeakDownloadSpeed INTEGER DEFAULT 0,
UNIQUE(DayTimestamp, NetworkCardGuid)
)");
// 创建索引以提高查询性能
await connection.ExecuteAsync("CREATE INDEX IF NOT EXISTS idx_appinfo_appid ON AppInfo(AppId)");
await connection.ExecuteAsync("CREATE INDEX IF NOT EXISTS idx_appnetwork_appid ON AppNetwork(AppId)");
await connection.ExecuteAsync("CREATE INDEX IF NOT EXISTS idx_appnetwork_timestamp ON AppNetwork(Timestamp)");
await connection.ExecuteAsync("CREATE INDEX IF NOT EXISTS idx_appnetwork_remoteip ON AppNetwork(RemoteIP)");
await connection.ExecuteAsync("CREATE INDEX IF NOT EXISTS idx_appnetwork_remoteport ON AppNetwork(RemotePort)");
await connection.ExecuteAsync(
"CREATE INDEX IF NOT EXISTS idx_globalnetwork_timestep ON GlobalNetwork(Timestep)");
await connection.ExecuteAsync(
"CREATE INDEX IF NOT EXISTS idx_globalnetwork_guid ON GlobalNetwork(NetworkCardGuid)");
await connection.ExecuteAsync(
"CREATE INDEX IF NOT EXISTS idx_appnetwork_hourly_timestamp ON AppNetworkHourly(HourTimestamp)");
await connection.ExecuteAsync(
"CREATE INDEX IF NOT EXISTS idx_appnetwork_daily_timestamp ON AppNetworkDaily(DayTimestamp)");
await connection.ExecuteAsync(
"CREATE INDEX IF NOT EXISTS idx_globalnetwork_hourly_timestamp ON GlobalNetworkHourly(HourTimestamp)");
await connection.ExecuteAsync(
"CREATE INDEX IF NOT EXISTS idx_globalnetwork_daily_timestamp ON GlobalNetworkDaily(DayTimestamp)");
}
private async Task MigrateDatabaseAsync()
{
var connection = Connection;
try
{
// 检查是否需要迁移AppSetting表
var columns = await GetTableColumnsAsync("AppSetting");
var needsMigration = columns.Contains("WindowWidth") || columns.Contains("WindowHeight") ||
columns.Contains("Theme") || columns.Contains("Language") ||
columns.Contains("MonitoringEnabled");
if (needsMigration)
{
Log.Information("检测到AppSetting表需要迁移,开始迁移...");
// 备份数据
var existingSettings = await connection.QueryAsync(@"
SELECT Id, WindowX, WindowY, AutoStart, UpdateTime FROM AppSetting");
// 删除旧表
await connection.ExecuteAsync("DROP TABLE AppSetting");
// 创建新表
await connection.ExecuteAsync(@"
CREATE TABLE AppSetting (
Id INTEGER PRIMARY KEY AUTOINCREMENT,
WindowX INTEGER DEFAULT 0,
WindowY INTEGER DEFAULT 0,
AutoStart INTEGER DEFAULT 0,
UpdateTime INTEGER NOT NULL
)");
// 恢复数据
foreach (var setting in existingSettings)
{
await connection.ExecuteAsync(@"
INSERT INTO AppSetting (WindowX, WindowY, AutoStart, UpdateTime)
VALUES (@WindowX, @WindowY, @AutoStart, @UpdateTime)",
new
{
WindowX = (int)((dynamic)setting).WindowX,
WindowY = (int)((dynamic)setting).WindowY,
AutoStart = (int)((dynamic)setting).AutoStart,
UpdateTime = (long)((dynamic)setting).UpdateTime
});
}
Log.Information($"AppSetting表迁移完成,恢复了 {existingSettings.Count()} 条记录");
}
}
catch (Exception ex)
{
Log.Warning("数据库迁移过程中发生错误,继续使用现有结构", ex);
}
}
private async Task<List<string>> GetTableColumnsAsync(string tableName)
{
try
{
var connection = Connection;
var result = await connection.QueryAsync<dynamic>($"PRAGMA table_info({tableName})");
var columnNames = new List<string>();
foreach (var row in result)
{
columnNames.Add(row.name.ToString());
}
return columnNames;
}
catch
{
return new List<string>();
}
}
public async Task<bool> IsHealthyAsync()
{
try
{
await Connection.ExecuteScalarAsync("SELECT 1");
return true;
}
catch
{
return false;
}
}
public void Dispose()
{
_connection?.Dispose();
}
}
@@ -0,0 +1,83 @@
using Data.Models;
using Dapper;
namespace Data.Repositories;
public interface IAppInfoRepository
{
Task<IEnumerable<AppInfo>> GetAllAppsAsync();
Task<AppInfo?> GetAppByIdAsync(int id);
Task<AppInfo?> GetAppByAppIdAsync(string appId);
Task<int> SaveAppAsync(AppInfo app);
Task<int> UpdateAppAsync(AppInfo app);
Task<int> DeleteAppAsync(int id);
}
public class AppInfoRepository : IAppInfoRepository
{
private readonly NetVizorDbContext _context;
public AppInfoRepository(NetVizorDbContext context)
{
_context = context;
}
public async Task<IEnumerable<AppInfo>> GetAllAppsAsync()
{
const string sql = "SELECT * FROM AppInfo WHERE DeleteTime <= 0 ORDER BY InsertTime DESC";
return await _context.Connection.QueryAsync<AppInfo>(sql);
}
public async Task<AppInfo?> GetAppByIdAsync(int id)
{
const string sql = "SELECT * FROM AppInfo WHERE Id = @Id AND DeleteTime <= 0";
return await _context.Connection.QueryFirstOrDefaultAsync<AppInfo>(sql, new { Id = id });
}
public async Task<AppInfo?> GetAppByAppIdAsync(string appId)
{
const string sql = "SELECT * FROM AppInfo WHERE AppId = @AppId AND DeleteTime <= 0";
return await _context.Connection.QueryFirstOrDefaultAsync<AppInfo>(sql, new { AppId = appId });
}
public async Task<int> SaveAppAsync(AppInfo app)
{
var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
app.InsertTime = now;
app.UpdateTime = now;
app.DeleteTime = 0;
const string sql = @"
INSERT INTO AppInfo (AppId, Name, Path, Version, Company, Base64Icon, Hash, InsertTime, UpdateTime, DeleteTime)
VALUES (@AppId, @Name, @Path, @Version, @Company, @Base64Icon, @Hash, @InsertTime, @UpdateTime, @DeleteTime)";
var result = await _context.Connection.ExecuteAsync(sql, app);
// 获取插入的ID
const string getIdSql = "SELECT last_insert_rowid()";
var id = await _context.Connection.QuerySingleAsync<int>(getIdSql);
app.Id = id;
return result;
}
public async Task<int> UpdateAppAsync(AppInfo app)
{
app.UpdateTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
const string sql = @"
UPDATE AppInfo
SET AppId = @AppId, Name = @Name, Path = @Path, Version = @Version, Company = @Company,
Base64Icon = @Base64Icon, Hash = @Hash, UpdateTime = @UpdateTime
WHERE Id = @Id";
return await _context.Connection.ExecuteAsync(sql, app);
}
public async Task<int> DeleteAppAsync(int id)
{
const string sql = "UPDATE AppInfo SET DeleteTime = @DeleteTime WHERE Id = @Id";
return await _context.Connection.ExecuteAsync(sql,
new { Id = id, DeleteTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds() });
}
}
@@ -0,0 +1,57 @@
using Data.Models;
using Dapper;
namespace Data.Repositories;
public interface IAppSettingRepository
{
Task<AppSetting?> GetCurrentSettingAsync();
Task<int> SaveSettingAsync(AppSetting setting);
Task<int> UpdateSettingAsync(AppSetting setting);
}
public class AppSettingRepository : IAppSettingRepository
{
private readonly NetVizorDbContext _context;
public AppSettingRepository(NetVizorDbContext context)
{
_context = context;
}
public async Task<AppSetting?> GetCurrentSettingAsync()
{
const string sql = "SELECT * FROM AppSetting ORDER BY UpdateTime DESC LIMIT 1";
return await _context.Connection.QueryFirstOrDefaultAsync<AppSetting>(sql);
}
public async Task<int> SaveSettingAsync(AppSetting setting)
{
setting.UpdateTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
const string sql = @"
INSERT INTO AppSetting (WindowX, WindowY, AutoStart, UpdateTime)
VALUES (@WindowX, @WindowY, @AutoStart, @UpdateTime)";
var result = await _context.Connection.ExecuteAsync(sql, setting);
// 获取插入的ID
const string getIdSql = "SELECT last_insert_rowid()";
var id = await _context.Connection.QuerySingleAsync<int>(getIdSql);
setting.Id = id;
return result;
}
public async Task<int> UpdateSettingAsync(AppSetting setting)
{
setting.UpdateTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
const string sql = @"
UPDATE AppSetting
SET WindowX = @WindowX, WindowY = @WindowY, AutoStart = @AutoStart, UpdateTime = @UpdateTime
WHERE Id = @Id";
return await _context.Connection.ExecuteAsync(sql, setting);
}
}
@@ -0,0 +1,484 @@
using Data.Models;
using Dapper;
namespace Data.Repositories;
public interface INetworkRepository
{
// 基础CRUD操作
Task<int> SaveAppNetworkAsync(AppNetwork network);
Task<int> SaveGlobalNetworkAsync(GlobalNetwork network);
// 应用网络查询
Task<IEnumerable<AppNetwork>> GetAppNetworkHistoryAsync(string appId, int limit = 100);
Task<IEnumerable<AppNetwork>> GetAppNetworkByTimeRangeAsync(string appId, long startTime, long endTime);
// 全局网络查询
Task<IEnumerable<GlobalNetwork>> GetGlobalNetworkHistoryAsync(string networkCardGuid, int hours = 24);
Task<IEnumerable<GlobalNetwork>> GetGlobalNetworkByTimeRangeAsync(string networkCardGuid, long startTime,
long endTime);
// 统计查询
Task<long> GetTotalUploadAsync(string appId = null, long startTime = 0, long endTime = 0);
Task<long> GetTotalDownloadAsync(string appId = null, long startTime = 0, long endTime = 0);
// 按时间单位聚合查询
Task<IEnumerable<AppNetworkSummary>> GetAppNetworkByHourAsync(string appId, int hours = 24);
Task<IEnumerable<AppNetworkSummary>> GetAppNetworkByDayAsync(string appId, int days = 7);
Task<IEnumerable<AppNetworkSummary>> GetAppNetworkByWeekAsync(string appId, int weeks = 4);
Task<IEnumerable<AppNetworkSummary>> GetAppNetworkByMonthAsync(string appId, int months = 12);
// 全局网络按时间单位聚合查询
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);
// 高级查询
Task<IEnumerable<AppNetworkTopInfo>> GetTopAppsByTrafficAsync(int limit = 10, int days = 7);
Task<IEnumerable<PortTrafficInfo>> GetTopPortsByAppAsync(string appId, int limit = 10, int days = 7);
Task<IEnumerable<IpTrafficInfo>> GetTopIpsByAppAsync(string appId, int limit = 10, int days = 7);
// 数据维护
Task<int> DeleteOldAppNetworkDataAsync(long beforeTimestamp);
Task<int> DeleteOldGlobalNetworkDataAsync(long beforeTimestamp);
}
public class NetworkRepository : INetworkRepository
{
private readonly NetVizorDbContext _context;
public NetworkRepository(NetVizorDbContext context)
{
_context = context;
}
#region 基础CRUD操作
public async Task<int> SaveAppNetworkAsync(AppNetwork network)
{
const string sql = @"
INSERT INTO AppNetwork (Timestamp, LocalIP, LocalPort, RemoteIP, RemotePort, Protocol, UploadBytes, DownloadBytes, AppId)
VALUES (@Timestamp, @LocalIP, @LocalPort, @RemoteIP, @RemotePort, @Protocol, @UploadBytes, @DownloadBytes, @AppId)";
return await _context.Connection.ExecuteAsync(sql, network);
}
public async Task<int> SaveGlobalNetworkAsync(GlobalNetwork network)
{
const string sql = @"
INSERT INTO GlobalNetwork (Timestep, Upload, Download, NetworkCardGuid)
VALUES (@Timestep, @Upload, @Download, @NetworkCardGuid)";
return await _context.Connection.ExecuteAsync(sql, network);
}
#endregion
#region 应用网络查询
public async Task<IEnumerable<AppNetwork>> GetAppNetworkHistoryAsync(string appId, int limit = 100)
{
const string sql = @"
SELECT * FROM AppNetwork
WHERE AppId = @AppId
ORDER BY Timestamp DESC
LIMIT @Limit";
return await _context.Connection.QueryAsync<AppNetwork>(sql, new { AppId = appId, Limit = limit });
}
public async Task<IEnumerable<AppNetwork>> GetAppNetworkByTimeRangeAsync(string appId, long startTime, long endTime)
{
const string sql = @"
SELECT * FROM AppNetwork
WHERE AppId = @AppId AND Timestamp >= @StartTime AND Timestamp <= @EndTime
ORDER BY Timestamp DESC";
return await _context.Connection.QueryAsync<AppNetwork>(sql,
new { AppId = appId, StartTime = startTime, EndTime = endTime });
}
#endregion
#region 全局网络查询
public async Task<IEnumerable<GlobalNetwork>> GetGlobalNetworkHistoryAsync(string networkCardGuid, int hours = 24)
{
var startTime = DateTimeOffset.UtcNow.AddHours(-hours).ToUnixTimeSeconds();
const string sql = @"
SELECT * FROM GlobalNetwork
WHERE NetworkCardGuid = @NetworkCardGuid AND Timestep >= @StartTime
ORDER BY Timestep DESC";
return await _context.Connection.QueryAsync<GlobalNetwork>(sql,
new { NetworkCardGuid = networkCardGuid, StartTime = startTime });
}
public async Task<IEnumerable<GlobalNetwork>> GetGlobalNetworkByTimeRangeAsync(string networkCardGuid,
long startTime, long endTime)
{
const string sql = @"
SELECT * FROM GlobalNetwork
WHERE NetworkCardGuid = @NetworkCardGuid AND Timestep >= @StartTime AND Timestep <= @EndTime
ORDER BY Timestep DESC";
return await _context.Connection.QueryAsync<GlobalNetwork>(sql,
new { NetworkCardGuid = networkCardGuid, StartTime = startTime, EndTime = endTime });
}
#endregion
#region 统计查询
public async Task<long> GetTotalUploadAsync(string appId = null, long startTime = 0, long endTime = 0)
{
var sql = "SELECT COALESCE(SUM(UploadBytes), 0) FROM AppNetwork WHERE 1=1";
var parameters = new DynamicParameters();
if (!string.IsNullOrEmpty(appId))
{
sql += " AND AppId = @AppId";
parameters.Add("AppId", appId);
}
if (startTime > 0)
{
sql += " AND Timestamp >= @StartTime";
parameters.Add("StartTime", startTime);
}
if (endTime > 0)
{
sql += " AND Timestamp <= @EndTime";
parameters.Add("EndTime", endTime);
}
return await _context.Connection.QuerySingleAsync<long>(sql, parameters);
}
public async Task<long> GetTotalDownloadAsync(string appId = null, long startTime = 0, long endTime = 0)
{
var sql = "SELECT COALESCE(SUM(DownloadBytes), 0) FROM AppNetwork WHERE 1=1";
var parameters = new DynamicParameters();
if (!string.IsNullOrEmpty(appId))
{
sql += " AND AppId = @AppId";
parameters.Add("AppId", appId);
}
if (startTime > 0)
{
sql += " AND Timestamp >= @StartTime";
parameters.Add("StartTime", startTime);
}
if (endTime > 0)
{
sql += " AND Timestamp <= @EndTime";
parameters.Add("EndTime", endTime);
}
return await _context.Connection.QuerySingleAsync<long>(sql, parameters);
}
#endregion
#region 按时间单位聚合查询
public async Task<IEnumerable<AppNetworkSummary>> GetAppNetworkByHourAsync(string appId, int hours = 24)
{
var startTime = DateTimeOffset.UtcNow.AddHours(-hours).ToUnixTimeSeconds();
const string sql = @"
SELECT
(Timestamp / 3600) * 3600 as HourTimestamp,
SUM(UploadBytes) as TotalUpload,
SUM(DownloadBytes) as TotalDownload,
COUNT(*) as ConnectionCount
FROM AppNetwork
WHERE AppId = @AppId AND Timestamp >= @StartTime
GROUP BY (Timestamp / 3600)
ORDER BY HourTimestamp DESC";
return await _context.Connection.QueryAsync<AppNetworkSummary>(sql,
new { AppId = appId, StartTime = startTime });
}
public async Task<IEnumerable<AppNetworkSummary>> GetAppNetworkByDayAsync(string appId, int days = 7)
{
var startTime = DateTimeOffset.UtcNow.AddDays(-days).ToUnixTimeSeconds();
const string sql = @"
SELECT
(Timestamp / 86400) * 86400 as DayTimestamp,
SUM(UploadBytes) as TotalUpload,
SUM(DownloadBytes) as TotalDownload,
COUNT(*) as ConnectionCount
FROM AppNetwork
WHERE AppId = @AppId AND Timestamp >= @StartTime
GROUP BY (Timestamp / 86400)
ORDER BY DayTimestamp DESC";
return await _context.Connection.QueryAsync<AppNetworkSummary>(sql,
new { AppId = appId, StartTime = startTime });
}
public async Task<IEnumerable<AppNetworkSummary>> GetAppNetworkByWeekAsync(string appId, int weeks = 4)
{
var startTime = DateTimeOffset.UtcNow.AddDays(-weeks * 7).ToUnixTimeSeconds();
const string sql = @"
SELECT
(Timestamp / 604800) * 604800 as WeekTimestamp,
SUM(UploadBytes) as TotalUpload,
SUM(DownloadBytes) as TotalDownload,
COUNT(*) as ConnectionCount
FROM AppNetwork
WHERE AppId = @AppId AND Timestamp >= @StartTime
GROUP BY (Timestamp / 604800)
ORDER BY WeekTimestamp DESC";
return await _context.Connection.QueryAsync<AppNetworkSummary>(sql,
new { AppId = appId, StartTime = startTime });
}
public async Task<IEnumerable<AppNetworkSummary>> GetAppNetworkByMonthAsync(string appId, int months = 12)
{
var startTime = DateTimeOffset.UtcNow.AddMonths(-months).ToUnixTimeSeconds();
const string sql = @"
SELECT
(Timestamp / 2592000) * 2592000 as MonthTimestamp,
SUM(UploadBytes) as TotalUpload,
SUM(DownloadBytes) as TotalDownload,
COUNT(*) as ConnectionCount
FROM AppNetwork
WHERE AppId = @AppId AND Timestamp >= @StartTime
GROUP BY (Timestamp / 2592000)
ORDER BY MonthTimestamp DESC";
return await _context.Connection.QueryAsync<AppNetworkSummary>(sql,
new { AppId = appId, StartTime = startTime });
}
#endregion
#region 全局网络按时间单位聚合查询
public async Task<IEnumerable<GlobalNetworkSummary>> GetGlobalNetworkByHourAsync(string networkCardGuid,
int hours = 24)
{
var startTime = DateTimeOffset.UtcNow.AddHours(-hours).ToUnixTimeSeconds();
const string sql = @"
SELECT
(Timestep / 3600) * 3600 as HourTimestamp,
SUM(Upload) as TotalUpload,
SUM(Download) as TotalDownload,
AVG(Upload) as AvgUpload,
AVG(Download) as AvgDownload,
MAX(Upload) as MaxUpload,
MAX(Download) as MaxDownload
FROM GlobalNetwork
WHERE NetworkCardGuid = @NetworkCardGuid AND Timestep >= @StartTime
GROUP BY (Timestep / 3600)
ORDER BY HourTimestamp DESC";
return await _context.Connection.QueryAsync<GlobalNetworkSummary>(sql,
new { NetworkCardGuid = networkCardGuid, StartTime = startTime });
}
public async Task<IEnumerable<GlobalNetworkSummary>> GetGlobalNetworkByDayAsync(string networkCardGuid,
int days = 7)
{
var startTime = DateTimeOffset.UtcNow.AddDays(-days).ToUnixTimeSeconds();
const string sql = @"
SELECT
(Timestep / 86400) * 86400 as DayTimestamp,
SUM(Upload) as TotalUpload,
SUM(Download) as TotalDownload,
AVG(Upload) as AvgUpload,
AVG(Download) as AvgDownload,
MAX(Upload) as MaxUpload,
MAX(Download) as MaxDownload
FROM GlobalNetwork
WHERE NetworkCardGuid = @NetworkCardGuid AND Timestep >= @StartTime
GROUP BY (Timestep / 86400)
ORDER BY DayTimestamp DESC";
return await _context.Connection.QueryAsync<GlobalNetworkSummary>(sql,
new { NetworkCardGuid = networkCardGuid, StartTime = startTime });
}
public async Task<IEnumerable<GlobalNetworkSummary>> GetGlobalNetworkByMonthAsync(string networkCardGuid,
int months = 12)
{
var startTime = DateTimeOffset.UtcNow.AddMonths(-months).ToUnixTimeSeconds();
const string sql = @"
SELECT
(Timestep / 2592000) * 2592000 as MonthTimestamp,
SUM(Upload) as TotalUpload,
SUM(Download) as TotalDownload,
AVG(Upload) as AvgUpload,
AVG(Download) as AvgDownload,
MAX(Upload) as MaxUpload,
MAX(Download) as MaxDownload
FROM GlobalNetwork
WHERE NetworkCardGuid = @NetworkCardGuid AND Timestep >= @StartTime
GROUP BY (Timestep / 2592000)
ORDER BY MonthTimestamp DESC";
return await _context.Connection.QueryAsync<GlobalNetworkSummary>(sql,
new { NetworkCardGuid = networkCardGuid, StartTime = startTime });
}
#endregion
#region 高级查询
public async Task<IEnumerable<AppNetworkTopInfo>> GetTopAppsByTrafficAsync(int limit = 10, int days = 7)
{
var startTime = DateTimeOffset.UtcNow.AddDays(-days).ToUnixTimeSeconds();
const string sql = @"
SELECT
a.AppId,
ai.Name as AppName,
ai.Path as AppPath,
SUM(a.UploadBytes) as TotalUpload,
SUM(a.DownloadBytes) as TotalDownload,
SUM(a.UploadBytes + a.DownloadBytes) as TotalTraffic,
COUNT(*) as ConnectionCount
FROM AppNetwork a
LEFT JOIN AppInfo ai ON a.AppId = ai.AppId
WHERE a.Timestamp >= @StartTime
GROUP BY a.AppId
ORDER BY TotalTraffic DESC
LIMIT @Limit";
return await _context.Connection.QueryAsync<AppNetworkTopInfo>(sql,
new { StartTime = startTime, Limit = limit });
}
public async Task<IEnumerable<PortTrafficInfo>> GetTopPortsByAppAsync(string appId, int limit = 10, int days = 7)
{
var startTime = DateTimeOffset.UtcNow.AddDays(-days).ToUnixTimeSeconds();
const string sql = @"
SELECT
RemotePort as Port,
SUM(UploadBytes) as TotalUpload,
SUM(DownloadBytes) as TotalDownload,
SUM(UploadBytes + DownloadBytes) as TotalTraffic,
COUNT(*) as ConnectionCount
FROM AppNetwork
WHERE AppId = @AppId AND Timestamp >= @StartTime
GROUP BY RemotePort
ORDER BY TotalTraffic DESC
LIMIT @Limit";
return await _context.Connection.QueryAsync<PortTrafficInfo>(sql,
new { AppId = appId, StartTime = startTime, Limit = limit });
}
public async Task<IEnumerable<IpTrafficInfo>> GetTopIpsByAppAsync(string appId, int limit = 10, int days = 7)
{
var startTime = DateTimeOffset.UtcNow.AddDays(-days).ToUnixTimeSeconds();
const string sql = @"
SELECT
RemoteIP as IpAddress,
SUM(UploadBytes) as TotalUpload,
SUM(DownloadBytes) as TotalDownload,
SUM(UploadBytes + DownloadBytes) as TotalTraffic,
COUNT(*) as ConnectionCount
FROM AppNetwork
WHERE AppId = @AppId AND Timestamp >= @StartTime
GROUP BY RemoteIP
ORDER BY TotalTraffic DESC
LIMIT @Limit";
return await _context.Connection.QueryAsync<IpTrafficInfo>(sql,
new { AppId = appId, StartTime = startTime, Limit = limit });
}
#endregion
#region 数据维护
public async Task<int> DeleteOldAppNetworkDataAsync(long beforeTimestamp)
{
const string sql = "DELETE FROM AppNetwork WHERE Timestamp < @BeforeTimestamp";
return await _context.Connection.ExecuteAsync(sql, new { BeforeTimestamp = beforeTimestamp });
}
public async Task<int> DeleteOldGlobalNetworkDataAsync(long beforeTimestamp)
{
const string sql = "DELETE FROM GlobalNetwork WHERE Timestep < @BeforeTimestamp";
return await _context.Connection.ExecuteAsync(sql, new { BeforeTimestamp = beforeTimestamp });
}
#endregion
}
#region 查询结果模型
public class AppNetworkSummary
{
public long HourTimestamp { get; set; }
public long DayTimestamp { get; set; }
public long WeekTimestamp { get; set; }
public long MonthTimestamp { get; set; }
public long TotalUpload { get; set; }
public long TotalDownload { get; set; }
public int ConnectionCount { get; set; }
}
public class GlobalNetworkSummary
{
public long HourTimestamp { get; set; }
public long DayTimestamp { get; set; }
public long MonthTimestamp { get; set; }
public long TotalUpload { get; set; }
public long TotalDownload { get; set; }
public long AvgUpload { get; set; }
public long AvgDownload { get; set; }
public long MaxUpload { get; set; }
public long MaxDownload { get; set; }
}
public class AppNetworkTopInfo
{
public string AppId { get; set; }
public string AppName { get; set; }
public string AppPath { get; set; }
public long TotalUpload { get; set; }
public long TotalDownload { get; set; }
public long TotalTraffic { get; set; }
public int ConnectionCount { get; set; }
}
public class PortTrafficInfo
{
public int Port { get; set; }
public long TotalUpload { get; set; }
public long TotalDownload { get; set; }
public long TotalTraffic { get; set; }
public int ConnectionCount { get; set; }
}
public class IpTrafficInfo
{
public string IpAddress { get; set; }
public long TotalUpload { get; set; }
public long TotalDownload { get; set; }
public long TotalTraffic { get; set; }
public int ConnectionCount { get; set; }
}
#endregion
@@ -0,0 +1,83 @@
using Data.Core;
using Data.Models;
using Data.Repositories;
namespace Data.Services;
public interface IDataService
{
Task InitializeAsync();
Task<AppSetting> GetOrCreateDefaultSettingAsync();
IAppSettingRepository AppSettings { get; }
IAppInfoRepository AppInfos { get; }
INetworkRepository Networks { get; }
DatabaseWriteManager WriteManager { get; }
NetworkDataCollectionService CollectionService { get; }
Task<bool> IsHealthyAsync();
}
public class DataService : IDataService, IDisposable
{
private readonly NetVizorDbContext _context;
private readonly Lazy<IAppSettingRepository> _appSettingRepository;
private readonly Lazy<IAppInfoRepository> _appInfoRepository;
private readonly Lazy<INetworkRepository> _networkRepository;
private readonly DatabaseWriteManager _writeManager;
private readonly NetworkDataCollectionService _collectionService;
public DataService(string databasePath)
{
_context = new NetVizorDbContext(databasePath);
_appSettingRepository = new Lazy<IAppSettingRepository>(() => new AppSettingRepository(_context));
_appInfoRepository = new Lazy<IAppInfoRepository>(() => new AppInfoRepository(_context));
_networkRepository = new Lazy<INetworkRepository>(() => new NetworkRepository(_context));
// 创建写入管理器和数据收集服务
_writeManager = new DatabaseWriteManager(this);
_collectionService = new NetworkDataCollectionService(_writeManager);
}
public IAppSettingRepository AppSettings => _appSettingRepository.Value;
public IAppInfoRepository AppInfos => _appInfoRepository.Value;
public INetworkRepository Networks => _networkRepository.Value;
public DatabaseWriteManager WriteManager => _writeManager;
public NetworkDataCollectionService CollectionService => _collectionService;
public async Task InitializeAsync()
{
await _context.InitializeDatabaseAsync();
}
public async Task<AppSetting> GetOrCreateDefaultSettingAsync()
{
var setting = await AppSettings.GetCurrentSettingAsync();
if (setting == null)
{
// 创建默认设置
setting = new AppSetting
{
WindowX = 100,
WindowY = 100,
AutoStart = false,
UpdateTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
};
setting.Id = await AppSettings.SaveSettingAsync(setting);
}
return setting;
}
public async Task<bool> IsHealthyAsync()
{
return await _context.IsHealthyAsync();
}
public void Dispose()
{
_collectionService?.Dispose();
_writeManager?.Dispose();
_context?.Dispose();
}
}
@@ -0,0 +1,333 @@
using System.Collections.Concurrent;
using Application.Utils;
using Common.Logger;
using Common.Utils;
using Data.Core;
using Data.Models;
using Infrastructure.utils;
using Utils.ETW.Etw;
namespace Data.Services;
/// <summary>
/// 网络数据收集服务
/// 负责全局网速监控和ETW网络数据收集
/// </summary>
public class NetworkDataCollectionService : IDisposable
{
private readonly DatabaseWriteManager _writeManager;
private readonly Timer _globalNetworkTimer;
private readonly Timer _etwNetworkTimer;
private readonly Timer _networkInterfaceUpdateTimer;
private readonly Timer _batchFlushTimer;
// 网卡信息缓存
private readonly ConcurrentDictionary<string, BasicNetworkMonitor.BasicNetworkInterface> _networkInterfaces = new();
private DateTime _lastNetworkInterfaceUpdate = DateTime.MinValue;
// ETW数据缓存(只保留最活跃的前100条)
private readonly ConcurrentDictionary<string, AppNetworkData> _activeConnections = new();
// AppId生成工具
private readonly ConcurrentDictionary<int, string> _processAppIdCache = new();
public NetworkDataCollectionService(DatabaseWriteManager writeManager)
{
_writeManager = writeManager;
// 每5秒收集一次全局网络数据
_globalNetworkTimer = new Timer(CollectGlobalNetworkData, null, TimeSpan.Zero, TimeSpan.FromSeconds(5));
// 每分钟收集一次ETW网络数据(需要管理员权限)
_etwNetworkTimer = new Timer(CollectEtwNetworkData, null, TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1));
// 每分钟更新一次网卡列表
_networkInterfaceUpdateTimer = new Timer(UpdateNetworkInterfaces, null, TimeSpan.Zero, TimeSpan.FromSeconds(5));
// 每30秒强制刷新一次批处理缓存
_batchFlushTimer = new Timer(async _ => await _writeManager.FlushAllBatchesAsync(), null,
TimeSpan.FromSeconds(30), TimeSpan.FromSeconds(30));
Log.Information("网络数据收集服务已启动");
}
/// <summary>
/// 收集全局网络数据
/// </summary>
private async void CollectGlobalNetworkData(object state)
{
try
{
var connectedInterfaces = _networkInterfaces.Values
.Where(i => i.IsConnected)
.ToList();
if (connectedInterfaces.Count == 0)
{
Log.Debug("没有找到活跃的网络接口");
return;
}
var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
foreach (var networkInterface in connectedInterfaces)
{
try
{
var speed = BasicNetworkMonitor.CalculateSpeedById(networkInterface.Id);
if (speed.DownloadSpeed > 0 || speed.UploadSpeed > 0)
{
var globalNetwork = new GlobalNetwork
{
Timestep = timestamp,
Upload = (long)speed.UploadSpeed,
Download = (long)speed.DownloadSpeed,
NetworkCardGuid = networkInterface.Id
};
await _writeManager.SaveGlobalNetworkAsync(globalNetwork);
Log.Debug(
$"记录网卡 {networkInterface.Name} 速度: 上传={speed.UploadSpeedText}, 下载={speed.DownloadSpeedText}");
}
}
catch (Exception ex)
{
Log.Warning($"收集网卡 {networkInterface.Name} 数据时出错");
}
}
}
catch (Exception ex)
{
Log.Error(ex, "收集全局网络数据时发生错误");
}
}
/// <summary>
/// 收集ETW网络数据(需要管理员权限)
/// </summary>
private async void CollectEtwNetworkData(object state)
{
try
{
// 检查是否有管理员权限
if (!SysHelper.IsAdministrator())
{
Log.Warning("ETW网络监控需要管理员权限,跳过收集");
return;
}
var snapshot = GlobalNetworkMonitor.Instance.GetSnapshot();
var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
Log.Information($"开始收集ETW网络数据,共找到 {snapshot.Applications.Count} 个应用程序");
foreach (var app in snapshot.Applications)
{
try
{
// 生成或获取AppId
var appId = await GetOrGenerateAppIdAsync(app.ProcessId, app.ProgramInfo);
if (string.IsNullOrEmpty(appId))
{
continue;
}
// 保存应用信息到数据库(如果不存在)
if (app.ProgramInfo != null)
{
// 检查应用信息是否已存在
var appExists = await _writeManager.AppExistsAsync(appId);
if (!appExists)
{
var appInfo = new AppInfo
{
AppId = appId,
Name = app.ProgramInfo.ProcessName ?? "Unknown",
Path = app.ProgramInfo.MainModulePath ?? "",
Version = app.ProgramInfo.Version ?? "",
Company = app.ProgramInfo.CompanyName ?? "",
Base64Icon = app.ProgramInfo.IconBase64 ?? "",
Hash = app.ProgramInfo.FileDescription ?? ""
};
await _writeManager.SaveAppInfoAsync(appInfo);
}
}
// 只保留最活跃的前100个连接
var activeConnections = app.Connections
.Where(c => c.IsActive && (c.BytesSent > 0 || c.BytesReceived > 0))
.OrderByDescending(c => c.BytesSent + c.BytesReceived)
.Take(100)
.ToList();
foreach (var connection in activeConnections)
{
var appNetwork = new AppNetwork
{
Timestamp = timestamp,
LocalIP = connection.LocalIp,
LocalPort = connection.LocalPort,
RemoteIP = connection.RemoteIp,
RemotePort = connection.RemotePort,
Protocol = connection.Protocol,
UploadBytes = connection.BytesSent,
DownloadBytes = connection.BytesReceived,
AppId = appId
};
await _writeManager.SaveAppNetworkAsync(appNetwork);
}
if (activeConnections.Count > 0)
{
Log.Debug($"记录应用 {app.ProgramInfo?.ProcessName} 的 {activeConnections.Count} 个网络连接");
}
}
catch (Exception ex)
{
Log.Warning($"处理应用 {app.ProcessId} 的网络数据时出错", ex);
}
}
Log.Information($"ETW网络数据收集完成,处理了 {snapshot.Applications.Count} 个应用程序");
}
catch (Exception ex)
{
Log.Error(ex, "收集ETW网络数据时发生错误");
}
}
/// <summary>
/// 更新网络接口列表
/// </summary>
private void UpdateNetworkInterfaces(object state)
{
try
{
var interfaces = BasicNetworkMonitor.GetConnectedNetworkInterfaces();
_networkInterfaces.Clear();
foreach (var networkInterface in interfaces)
{
_networkInterfaces.TryAdd(networkInterface.Id, networkInterface);
}
_lastNetworkInterfaceUpdate = DateTime.UtcNow;
Log.Information($"更新网络接口列表,共找到 {interfaces.Count} 个活跃接口");
}
catch (Exception ex)
{
Log.Error(ex, "更新网络接口列表时发生错误");
}
}
/// <summary>
/// 获取或生成应用程序ID
/// </summary>
private async Task<string> GetOrGenerateAppIdAsync(int processId, Infrastructure.Models.ProgramInfo programInfo)
{
// 先检查缓存
if (_processAppIdCache.TryGetValue(processId, out var cachedAppId))
{
return cachedAppId;
}
try
{
// 如果没有程序信息,尝试从系统获取
if (programInfo == null)
{
programInfo = SysInfoUtils.InspectProcess(processId);
}
if (programInfo == null)
{
Log.Warning($"无法获取进程 {processId} 的信息");
return null;
}
// 生成AppId: 路径 + 文件签名发布者 + 进程名
var path = programInfo.MainModulePath ?? "";
var publisher = programInfo.CompanyName ?? "";
var processName = programInfo.ProcessName ?? "Unknown";
var appId = $"{path}|{publisher}|{processName}";
// 缓存AppId
_processAppIdCache.TryAdd(processId, appId);
return appId;
}
catch (Exception ex)
{
Log.Warning($"生成进程 {processId} 的AppId时出错", ex);
return null;
}
}
/// <summary>
/// 获取收集统计信息
/// </summary>
public NetworkCollectionStats GetStats()
{
return new NetworkCollectionStats
{
ActiveNetworkInterfaces = _networkInterfaces.Count,
LastNetworkInterfaceUpdate = _lastNetworkInterfaceUpdate,
CachedProcessCount = _processAppIdCache.Count,
ActiveConnectionsCount = _activeConnections.Count
};
}
public void Dispose()
{
Log.Information("正在关闭网络数据收集服务...");
_globalNetworkTimer?.Dispose();
_etwNetworkTimer?.Dispose();
_networkInterfaceUpdateTimer?.Dispose();
_batchFlushTimer?.Dispose();
// 强制刷新所有批处理数据
try
{
_writeManager.FlushAllBatchesAsync().Wait(TimeSpan.FromSeconds(10));
}
catch (Exception ex)
{
Log.Warning("关闭时刷新批处理数据失败", ex);
}
Log.Information("网络数据收集服务已关闭");
}
}
/// <summary>
/// 应用网络数据(用于缓存)
/// </summary>
public class AppNetworkData
{
public string AppId { get; set; }
public long TotalUpload { get; set; }
public long TotalDownload { get; set; }
public DateTime LastUpdate { get; set; }
public int ConnectionCount { get; set; }
}
/// <summary>
/// 网络收集统计信息
/// </summary>
public class NetworkCollectionStats
{
public int ActiveNetworkInterfaces { get; set; }
public DateTime LastNetworkInterfaceUpdate { get; set; }
public int CachedProcessCount { get; set; }
public int ActiveConnectionsCount { get; set; }
}
+35 -9
View File
@@ -10,6 +10,7 @@ using Common.Net.HttpConn;
using Common.Net.Models;
using Common.Net.WebSocketConn;
using Common.Utils;
using Data;
using Shell.Views;
using Utils.ETW.Etw;
using Utils.Firewall;
@@ -23,7 +24,7 @@ namespace Shell;
/// </summary>
public partial class App : System.Windows.Application
{
protected override void OnStartup(StartupEventArgs e)
protected override async void OnStartup(StartupEventArgs e)
{
// 异常处理
ExceptionCatch();
@@ -32,20 +33,45 @@ public partial class App : System.Windows.Application
Log.Initialize(configModelLogging);
base.OnStartup(e);
// 初始化数据库和数据收集服务
try
{
Log.Information("正在初始化数据库和网络监控服务...");
await DatabaseManager.InitializeAsync();
Log.Information("数据库和网络监控服务初始化完成");
}
catch (Exception ex)
{
Log.Error(ex, "数据库初始化失败");
MessageBox.Show($"数据库初始化失败: {ex.Message}", "错误", System.Windows.Forms.MessageBoxButtons.OK,
System.Windows.Forms.MessageBoxIcon.Error);
}
// 启动服务(如 WebSocket、HTTP、端口监听等)
StartMyServer();
// NetView 网络监看
var netView = new NetView();
netView.Show();
// 获取用户设置并应用到NetView窗口
try
{
var userSettings = await DatabaseManager.GetUserSettingsAsync();
Log.Information($"读取用户设置: 位置({userSettings.WindowX}, {userSettings.WindowY})");
// NetView 网络监看 - 应用用户设置的位置
var netView = new NetView();
netView.Left = userSettings.WindowX;
netView.Top = userSettings.WindowY;
netView.Show();
}
catch (Exception ex)
{
Log.Error(ex, "应用用户设置失败,使用默认设置");
// 使用默认设置创建窗口
var netView = new NetView();
netView.Show();
}
// Mouth();
// 网速监视
// var window = new NetView();
// window.Show();
// 设置应用程序在关闭最后一个窗口时不自动退出
this.ShutdownMode = ShutdownMode.OnExplicitShutdown;
}
@@ -19,7 +19,7 @@ public class NetworkMonitorUsageExample
Log.Info("启动网络监控系统...");
// 初始化网络管理器
_networkManager = new EnhancedEtwNetworkManager();
// _networkManager = new EnhancedEtwNetworkManager();
_cancellationTokenSource = new CancellationTokenSource();
try