A:增加消息记录功能
This commit is contained in:
1 parent
2f6e912c58
commit
2f54037836
14 files changed
+1975
-121
No files matched your search
@@ -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>
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user