A:增加详细软件信息接口
This commit is contained in:
1 parent
1732d279d9
commit
1f62e9cf51
7 files changed
+330
-29
No files matched your search
@@ -9,10 +9,15 @@ public class AppInfo
|
|||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 软件 ID = 路径 + 文件签名发布者 + 进程名 (文件签名发布者 可无)
|
/// 软件 ID(Hash版本,用于关联和索引)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string AppId { get; set; }
|
public string AppId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 原始软件 ID = 路径 + 文件签名发布者 + 进程名 (文件签名发布者 可无)
|
||||||
|
/// </summary>
|
||||||
|
public string OriginalAppId { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 名字
|
/// 名字
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ public class NetVizorDbContext : IDisposable
|
|||||||
CREATE TABLE IF NOT EXISTS AppInfo (
|
CREATE TABLE IF NOT EXISTS AppInfo (
|
||||||
Id INTEGER PRIMARY KEY AUTOINCREMENT,
|
Id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
AppId TEXT NOT NULL,
|
AppId TEXT NOT NULL,
|
||||||
|
OriginalAppId TEXT,
|
||||||
Name TEXT NOT NULL,
|
Name TEXT NOT NULL,
|
||||||
Path TEXT NOT NULL,
|
Path TEXT NOT NULL,
|
||||||
Version TEXT,
|
Version TEXT,
|
||||||
@@ -340,6 +341,9 @@ public class NetVizorDbContext : IDisposable
|
|||||||
|
|
||||||
Log.Information($"AppSetting表迁移完成,恢复了 {existingSettings.Count()} 条记录");
|
Log.Information($"AppSetting表迁移完成,恢复了 {existingSettings.Count()} 条记录");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检查并迁移AppInfo表 - 添加OriginalAppId字段
|
||||||
|
await MigrateAppInfoTableAsync();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -347,6 +351,36 @@ public class NetVizorDbContext : IDisposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task MigrateAppInfoTableAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var connection = Connection;
|
||||||
|
var columns = await GetTableColumnsAsync("AppInfo");
|
||||||
|
|
||||||
|
// 检查是否需要添加OriginalAppId字段
|
||||||
|
if (!columns.Contains("OriginalAppId"))
|
||||||
|
{
|
||||||
|
Log.Information("检测到AppInfo表缺少OriginalAppId字段,开始迁移...");
|
||||||
|
|
||||||
|
// 添加OriginalAppId字段
|
||||||
|
await connection.ExecuteAsync("ALTER TABLE AppInfo ADD COLUMN OriginalAppId TEXT");
|
||||||
|
|
||||||
|
// 为现有数据生成OriginalAppId(使用AppId作为默认值)
|
||||||
|
await connection.ExecuteAsync(@"
|
||||||
|
UPDATE AppInfo
|
||||||
|
SET OriginalAppId = COALESCE(Path || '|' || COALESCE(Company, '') || '|' || Name, AppId)
|
||||||
|
WHERE OriginalAppId IS NULL OR OriginalAppId = ''");
|
||||||
|
|
||||||
|
Log.Information("AppInfo表OriginalAppId字段添加完成");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Warning("迁移AppInfo表时出错,将继续使用现有结构", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<List<string>> GetTableColumnsAsync(string tableName)
|
private async Task<List<string>> GetTableColumnsAsync(string tableName)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|||||||
@@ -48,8 +48,8 @@ public class AppInfoRepository : IAppInfoRepository
|
|||||||
app.DeleteTime = 0;
|
app.DeleteTime = 0;
|
||||||
|
|
||||||
const string sql = @"
|
const string sql = @"
|
||||||
INSERT INTO AppInfo (AppId, Name, Path, Version, Company, Base64Icon, Hash, InsertTime, UpdateTime, DeleteTime)
|
INSERT INTO AppInfo (AppId, OriginalAppId, Name, Path, Version, Company, Base64Icon, Hash, InsertTime, UpdateTime, DeleteTime)
|
||||||
VALUES (@AppId, @Name, @Path, @Version, @Company, @Base64Icon, @Hash, @InsertTime, @UpdateTime, @DeleteTime)";
|
VALUES (@AppId, @OriginalAppId, @Name, @Path, @Version, @Company, @Base64Icon, @Hash, @InsertTime, @UpdateTime, @DeleteTime)";
|
||||||
|
|
||||||
var result = await _context.Connection.ExecuteAsync(sql, app);
|
var result = await _context.Connection.ExecuteAsync(sql, app);
|
||||||
|
|
||||||
@@ -67,7 +67,7 @@ public class AppInfoRepository : IAppInfoRepository
|
|||||||
|
|
||||||
const string sql = @"
|
const string sql = @"
|
||||||
UPDATE AppInfo
|
UPDATE AppInfo
|
||||||
SET AppId = @AppId, Name = @Name, Path = @Path, Version = @Version, Company = @Company,
|
SET AppId = @AppId, OriginalAppId = @OriginalAppId, Name = @Name, Path = @Path, Version = @Version, Company = @Company,
|
||||||
Base64Icon = @Base64Icon, Hash = @Hash, UpdateTime = @UpdateTime
|
Base64Icon = @Base64Icon, Hash = @Hash, UpdateTime = @UpdateTime
|
||||||
WHERE Id = @Id";
|
WHERE Id = @Id";
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System.Data;
|
||||||
using Data.Models;
|
using Data.Models;
|
||||||
using Dapper;
|
using Dapper;
|
||||||
|
|
||||||
@@ -122,13 +123,27 @@ public class NetworkRepository : INetworkRepository
|
|||||||
|
|
||||||
public async Task<IEnumerable<AppNetwork>> GetAppNetworkByTimeRangeAsync(string appId, long startTime, long endTime)
|
public async Task<IEnumerable<AppNetwork>> GetAppNetworkByTimeRangeAsync(string appId, long startTime, long endTime)
|
||||||
{
|
{
|
||||||
|
// 清理 appId,去掉多余的空白和引号
|
||||||
|
if (!string.IsNullOrEmpty(appId))
|
||||||
|
{
|
||||||
|
appId = appId.Trim().Trim('"');
|
||||||
|
}
|
||||||
const string sql = @"
|
const string sql = @"
|
||||||
SELECT * FROM AppNetwork
|
SELECT * FROM AppNetwork
|
||||||
WHERE AppId = @AppId AND Timestamp >= @StartTime AND Timestamp <= @EndTime
|
WHERE AppId = @AppId
|
||||||
ORDER BY Timestamp DESC";
|
AND CAST(Timestamp AS INTEGER) >= @StartTime
|
||||||
|
AND CAST(Timestamp AS INTEGER) <= @EndTime
|
||||||
|
ORDER BY CAST(Timestamp AS INTEGER) DESC";
|
||||||
|
|
||||||
return await _context.Connection.QueryAsync<AppNetwork>(sql,
|
var parameters = new DynamicParameters();
|
||||||
new { AppId = appId, StartTime = startTime, EndTime = endTime });
|
parameters.Add("@AppId", appId, DbType.String); // 强制 TEXT 匹配
|
||||||
|
parameters.Add("@StartTime", startTime, DbType.Int64);
|
||||||
|
parameters.Add("@EndTime", endTime, DbType.Int64);
|
||||||
|
|
||||||
|
var appNetworks = await _context.Connection.QueryAsync<AppNetwork>(sql, parameters);
|
||||||
|
|
||||||
|
Console.WriteLine($"[Result] Found {appNetworks.Count()} rows.");
|
||||||
|
return appNetworks;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@@ -141,7 +156,7 @@ public class NetworkRepository : INetworkRepository
|
|||||||
|
|
||||||
string sql;
|
string sql;
|
||||||
object parameters;
|
object parameters;
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(networkCardGuid))
|
if (string.IsNullOrEmpty(networkCardGuid))
|
||||||
{
|
{
|
||||||
// 获取所有网卡数据
|
// 获取所有网卡数据
|
||||||
@@ -169,7 +184,7 @@ public class NetworkRepository : INetworkRepository
|
|||||||
{
|
{
|
||||||
string sql;
|
string sql;
|
||||||
object parameters;
|
object parameters;
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(networkCardGuid))
|
if (string.IsNullOrEmpty(networkCardGuid))
|
||||||
{
|
{
|
||||||
// 获取所有网卡数据
|
// 获取所有网卡数据
|
||||||
@@ -339,7 +354,7 @@ public class NetworkRepository : INetworkRepository
|
|||||||
|
|
||||||
string sql;
|
string sql;
|
||||||
object parameters;
|
object parameters;
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(networkCardGuid))
|
if (string.IsNullOrEmpty(networkCardGuid))
|
||||||
{
|
{
|
||||||
// 获取所有网卡数据
|
// 获取所有网卡数据
|
||||||
@@ -387,7 +402,7 @@ public class NetworkRepository : INetworkRepository
|
|||||||
|
|
||||||
string sql;
|
string sql;
|
||||||
object parameters;
|
object parameters;
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(networkCardGuid))
|
if (string.IsNullOrEmpty(networkCardGuid))
|
||||||
{
|
{
|
||||||
// 获取所有网卡数据
|
// 获取所有网卡数据
|
||||||
@@ -435,7 +450,7 @@ public class NetworkRepository : INetworkRepository
|
|||||||
|
|
||||||
string sql;
|
string sql;
|
||||||
object parameters;
|
object parameters;
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(networkCardGuid))
|
if (string.IsNullOrEmpty(networkCardGuid))
|
||||||
{
|
{
|
||||||
// 获取所有网卡数据
|
// 获取所有网卡数据
|
||||||
@@ -815,4 +830,4 @@ public class IpTrafficInfo
|
|||||||
public int ConnectionCount { get; set; }
|
public int ConnectionCount { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
using Application.Utils;
|
using Application.Utils;
|
||||||
using Common.Logger;
|
using Common.Logger;
|
||||||
using Common.Utils;
|
using Common.Utils;
|
||||||
@@ -29,8 +31,8 @@ public class NetworkDataCollectionService : IDisposable
|
|||||||
// ETW数据缓存(只保留最活跃的前100条)
|
// ETW数据缓存(只保留最活跃的前100条)
|
||||||
private readonly ConcurrentDictionary<string, AppNetworkData> _activeConnections = new();
|
private readonly ConcurrentDictionary<string, AppNetworkData> _activeConnections = new();
|
||||||
|
|
||||||
// AppId生成工具
|
// AppId生成工具 - 缓存原始AppId
|
||||||
private readonly ConcurrentDictionary<int, string> _processAppIdCache = new();
|
private readonly ConcurrentDictionary<int, string> _processOriginalAppIdCache = new();
|
||||||
|
|
||||||
public NetworkDataCollectionService(DatabaseWriteManager writeManager)
|
public NetworkDataCollectionService(DatabaseWriteManager writeManager)
|
||||||
{
|
{
|
||||||
@@ -133,7 +135,7 @@ public class NetworkDataCollectionService : IDisposable
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
// 生成或获取AppId
|
// 生成或获取AppId
|
||||||
var appId = await GetOrGenerateAppIdAsync(app.ProcessId, app.ProgramInfo);
|
var (appId, originalAppId) = await GetOrGenerateAppIdAsync(app.ProcessId, app.ProgramInfo);
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(appId))
|
if (string.IsNullOrEmpty(appId))
|
||||||
{
|
{
|
||||||
@@ -151,6 +153,7 @@ public class NetworkDataCollectionService : IDisposable
|
|||||||
var appInfo = new AppInfo
|
var appInfo = new AppInfo
|
||||||
{
|
{
|
||||||
AppId = appId,
|
AppId = appId,
|
||||||
|
OriginalAppId = originalAppId,
|
||||||
Name = app.ProgramInfo.ProcessName ?? "Unknown",
|
Name = app.ProgramInfo.ProcessName ?? "Unknown",
|
||||||
Path = app.ProgramInfo.MainModulePath ?? "",
|
Path = app.ProgramInfo.MainModulePath ?? "",
|
||||||
Version = app.ProgramInfo.Version ?? "",
|
Version = app.ProgramInfo.Version ?? "",
|
||||||
@@ -236,12 +239,14 @@ public class NetworkDataCollectionService : IDisposable
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取或生成应用程序ID
|
/// 获取或生成应用程序ID
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private async Task<string> GetOrGenerateAppIdAsync(int processId, Infrastructure.Models.ProgramInfo programInfo)
|
/// <returns>返回元组(HashAppId, OriginalAppId)</returns>
|
||||||
|
private async Task<(string appId, string originalAppId)> GetOrGenerateAppIdAsync(int processId, Infrastructure.Models.ProgramInfo programInfo)
|
||||||
{
|
{
|
||||||
// 先检查缓存
|
// 先检查缓存
|
||||||
if (_processAppIdCache.TryGetValue(processId, out var cachedAppId))
|
if (_processOriginalAppIdCache.TryGetValue(processId, out var cachedOriginalAppId))
|
||||||
{
|
{
|
||||||
return cachedAppId;
|
var cachedHashAppId = GenerateHashAppId(cachedOriginalAppId);
|
||||||
|
return (cachedHashAppId, cachedOriginalAppId);
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
@@ -255,25 +260,30 @@ public class NetworkDataCollectionService : IDisposable
|
|||||||
if (programInfo == null)
|
if (programInfo == null)
|
||||||
{
|
{
|
||||||
Log.Warning($"无法获取进程 {processId} 的信息");
|
Log.Warning($"无法获取进程 {processId} 的信息");
|
||||||
return null;
|
return (null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 生成AppId: 路径 + 文件签名发布者 + 进程名
|
// 生成原始AppId: 路径 + 文件签名发布者 + 进程名
|
||||||
var path = programInfo.MainModulePath ?? "";
|
var path = programInfo.MainModulePath ?? "";
|
||||||
var publisher = programInfo.CompanyName ?? "";
|
var publisher = programInfo.CompanyName ?? "";
|
||||||
var processName = programInfo.ProcessName ?? "Unknown";
|
var processName = programInfo.ProcessName ?? "Unknown";
|
||||||
|
|
||||||
var appId = $"{path}|{publisher}|{processName}";
|
var originalAppId = $"{path}|{publisher}|{processName}";
|
||||||
|
|
||||||
|
// 生成Hash版本的AppId(SHA256前16位)
|
||||||
|
var hashAppId = GenerateHashAppId(originalAppId);
|
||||||
|
|
||||||
// 缓存AppId
|
// 缓存原始AppId
|
||||||
_processAppIdCache.TryAdd(processId, appId);
|
_processOriginalAppIdCache.TryAdd(processId, originalAppId);
|
||||||
|
|
||||||
return appId;
|
Log.Debug($"生成AppId: {hashAppId} (原始: {originalAppId.Substring(0, Math.Min(50, originalAppId.Length))}...)");
|
||||||
|
|
||||||
|
return (hashAppId, originalAppId);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Log.Warning($"生成进程 {processId} 的AppId时出错", ex);
|
Log.Warning($"生成进程 {processId} 的AppId时出错", ex);
|
||||||
return null;
|
return (null, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -286,7 +296,7 @@ public class NetworkDataCollectionService : IDisposable
|
|||||||
{
|
{
|
||||||
ActiveNetworkInterfaces = _networkInterfaces.Count,
|
ActiveNetworkInterfaces = _networkInterfaces.Count,
|
||||||
LastNetworkInterfaceUpdate = _lastNetworkInterfaceUpdate,
|
LastNetworkInterfaceUpdate = _lastNetworkInterfaceUpdate,
|
||||||
CachedProcessCount = _processAppIdCache.Count,
|
CachedProcessCount = _processOriginalAppIdCache.Count,
|
||||||
ActiveConnectionsCount = _activeConnections.Count
|
ActiveConnectionsCount = _activeConnections.Count
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -333,6 +343,35 @@ public class NetworkDataCollectionService : IDisposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 生成Hash版本的AppId
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="originalAppId">原始AppId</param>
|
||||||
|
/// <returns>Hash版本的AppId(SHA256前16位)</returns>
|
||||||
|
private static string GenerateHashAppId(string originalAppId)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(originalAppId))
|
||||||
|
{
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var sha256 = SHA256.Create();
|
||||||
|
var hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(originalAppId));
|
||||||
|
|
||||||
|
// 取前8字节(16位十六进制字符)
|
||||||
|
var hashHex = Convert.ToHexString(hashBytes[..8]).ToLowerInvariant();
|
||||||
|
|
||||||
|
return hashHex;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// 如果Hash生成失败,使用原始AppId的哈希码
|
||||||
|
return Math.Abs(originalAppId.GetHashCode()).ToString("x8");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
Log.Information("正在关闭网络数据收集服务...");
|
Log.Information("正在关闭网络数据收集服务...");
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AFuture_002Ecs_002Fl_003AC_0021_003FUsers_003Fyllyo_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F6f90c6c13479b8c2a5be98f3d75dfc3bd885a055652d8a32904ca2448132949e_003FFuture_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AFuture_002Ecs_002Fl_003AC_0021_003FUsers_003Fyllyo_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F6f90c6c13479b8c2a5be98f3d75dfc3bd885a055652d8a32904ca2448132949e_003FFuture_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AHttpListener_002Ecs_002Fl_003AC_0021_003FUsers_003Fyllyo_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F28febf18c0c64165a91fffc7144abdc786908_003F39_003Fa4eb9e55_003FHttpListener_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AHttpListener_002Ecs_002Fl_003AC_0021_003FUsers_003Fyllyo_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F28febf18c0c64165a91fffc7144abdc786908_003F39_003Fa4eb9e55_003FHttpListener_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIcon_002Ecs_002Fl_003AC_0021_003FUsers_003Fcharlatans_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F946b37ae1f1648b7a334a81e4b6ce11c64330_003F21_003F67e31def_003FIcon_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIcon_002Ecs_002Fl_003AC_0021_003FUsers_003Fcharlatans_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F946b37ae1f1648b7a334a81e4b6ce11c64330_003F21_003F67e31def_003FIcon_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIDbConnection_002Ecs_002Fl_003AC_0021_003FUsers_003Fcharlatans_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F190b4d3ba5554d989564edc3fb49234a2ba910_003Fd1_003F982e4f92_003FIDbConnection_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIDisposable_002Ecs_002Fl_003AC_0021_003FUsers_003Fyllyo_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F5aeb6167299d4cfba30c86624130edbcc90938_003F41_003F0f283a8f_003FIDisposable_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIDisposable_002Ecs_002Fl_003AC_0021_003FUsers_003Fyllyo_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F5aeb6167299d4cfba30c86624130edbcc90938_003F41_003F0f283a8f_003FIDisposable_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIGrouping_00602_002Ecs_002Fl_003AC_0021_003FUsers_003Fyllyo_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F9abdf068a8774c97a3780c88b63c27b884928_003Fb6_003Ff1d12dc1_003FIGrouping_00602_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIGrouping_00602_002Ecs_002Fl_003AC_0021_003FUsers_003Fyllyo_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F9abdf068a8774c97a3780c88b63c27b884928_003Fb6_003Ff1d12dc1_003FIGrouping_00602_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AINetFwRule_002Ecs_002Fl_003AC_0021_003FUsers_003Fcharlatans_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fd2ed9a92e29d4a0f91826359e93c440c4c00_003F5c_003F4c2f5fe4_003FINetFwRule_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AINetFwRule_002Ecs_002Fl_003AC_0021_003FUsers_003Fcharlatans_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fd2ed9a92e29d4a0f91826359e93c440c4c00_003F5c_003F4c2f5fe4_003FINetFwRule_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
|||||||
@@ -1572,6 +1572,178 @@ public partial class App : System.Windows.Application
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region 应用详细网络分析API
|
||||||
|
|
||||||
|
// 获取应用详细网络分析数据
|
||||||
|
server.Get("/api/apps/network-analysis", async (context) =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var appId = context.QueryParams["appId"];
|
||||||
|
var timeRange = context.QueryParams["timeRange"] ?? "1day";
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(appId))
|
||||||
|
{
|
||||||
|
context.Response.StatusCode = 400;
|
||||||
|
await context.Response.WriteJsonAsync(new ResponseModel<object>
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
Message = "缺少必需参数 appId"
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算时间范围
|
||||||
|
var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
||||||
|
long startTime = timeRange switch
|
||||||
|
{
|
||||||
|
"1hour" => DateTimeOffset.UtcNow.AddHours(-1).ToUnixTimeSeconds(),
|
||||||
|
"1day" => DateTimeOffset.UtcNow.AddDays(-1).ToUnixTimeSeconds(),
|
||||||
|
"7days" => DateTimeOffset.UtcNow.AddDays(-7).ToUnixTimeSeconds(),
|
||||||
|
"30days" => DateTimeOffset.UtcNow.AddDays(-30).ToUnixTimeSeconds(),
|
||||||
|
_ => DateTimeOffset.UtcNow.AddDays(-1).ToUnixTimeSeconds()
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取应用基本信息
|
||||||
|
var appInfo = await DatabaseManager.Instance.AppInfos.GetAppByAppIdAsync(appId);
|
||||||
|
|
||||||
|
// 获取网络数据
|
||||||
|
var networkData = await DatabaseManager.Instance.Networks.GetAppNetworkByTimeRangeAsync(appId, startTime, now);
|
||||||
|
var networkList = networkData.ToList();
|
||||||
|
|
||||||
|
// 计算基本统计
|
||||||
|
var totalUpload = networkList.Sum(x => x.UploadBytes);
|
||||||
|
var totalDownload = networkList.Sum(x => x.DownloadBytes);
|
||||||
|
var totalConnections = networkList.Count;
|
||||||
|
|
||||||
|
// 获取前30个流量最高的连接
|
||||||
|
var topConnections = networkList
|
||||||
|
.Where(x => x.UploadBytes + x.DownloadBytes > 0) // 只统计有流量的连接
|
||||||
|
.GroupBy(x => new { x.LocalIP, x.LocalPort, x.RemoteIP, x.RemotePort, x.Protocol })
|
||||||
|
.Select(g => new
|
||||||
|
{
|
||||||
|
localIP = g.Key.LocalIP,
|
||||||
|
localPort = g.Key.LocalPort,
|
||||||
|
remoteIP = g.Key.RemoteIP,
|
||||||
|
remotePort = g.Key.RemotePort,
|
||||||
|
protocol = g.Key.Protocol,
|
||||||
|
totalUpload = g.Sum(x => x.UploadBytes),
|
||||||
|
totalDownload = g.Sum(x => x.DownloadBytes),
|
||||||
|
totalTraffic = g.Sum(x => x.UploadBytes + x.DownloadBytes),
|
||||||
|
connectionCount = g.Count(),
|
||||||
|
firstSeen = DateTimeOffset.FromUnixTimeSeconds(g.Min(x => x.Timestamp)).ToString("yyyy-MM-dd HH:mm:ss"),
|
||||||
|
lastSeen = DateTimeOffset.FromUnixTimeSeconds(g.Max(x => x.Timestamp)).ToString("yyyy-MM-dd HH:mm:ss")
|
||||||
|
})
|
||||||
|
.OrderByDescending(x => x.totalTraffic)
|
||||||
|
.Take(30)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
// 计算协议占比
|
||||||
|
var protocolStats = networkList
|
||||||
|
.Where(x => x.UploadBytes + x.DownloadBytes > 0)
|
||||||
|
.GroupBy(x => x.Protocol.ToUpper())
|
||||||
|
.Select(g => new
|
||||||
|
{
|
||||||
|
protocol = g.Key,
|
||||||
|
connectionCount = g.Count(),
|
||||||
|
totalTraffic = g.Sum(x => x.UploadBytes + x.DownloadBytes),
|
||||||
|
percentage = Math.Round((double)g.Count() / networkList.Where(x => x.UploadBytes + x.DownloadBytes > 0).Count() * 100, 2)
|
||||||
|
})
|
||||||
|
.OrderByDescending(x => x.totalTraffic)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
// 获取时间趋势数据(按小时分组)
|
||||||
|
var timeTrends = networkList
|
||||||
|
.GroupBy(x => x.Timestamp / 3600 * 3600) // 按小时分组
|
||||||
|
.Select(g => new
|
||||||
|
{
|
||||||
|
timestamp = g.Key,
|
||||||
|
timeStr = DateTimeOffset.FromUnixTimeSeconds(g.Key).ToString("yyyy-MM-dd HH:mm"),
|
||||||
|
upload = g.Sum(x => x.UploadBytes),
|
||||||
|
download = g.Sum(x => x.DownloadBytes),
|
||||||
|
connections = g.Count()
|
||||||
|
})
|
||||||
|
.OrderBy(x => x.timestamp)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
// 端口分析
|
||||||
|
var portAnalysis = networkList
|
||||||
|
.Where(x => x.UploadBytes + x.DownloadBytes > 0)
|
||||||
|
.GroupBy(x => x.RemotePort)
|
||||||
|
.Select(g => new
|
||||||
|
{
|
||||||
|
port = g.Key,
|
||||||
|
serviceName = GetPortServiceName(g.Key),
|
||||||
|
connectionCount = g.Count(),
|
||||||
|
totalTraffic = g.Sum(x => x.UploadBytes + x.DownloadBytes),
|
||||||
|
protocols = g.Select(x => x.Protocol).Distinct().ToList()
|
||||||
|
})
|
||||||
|
.OrderByDescending(x => x.totalTraffic)
|
||||||
|
.Take(20)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
// 构建返回数据
|
||||||
|
var result = new
|
||||||
|
{
|
||||||
|
// 应用基本信息
|
||||||
|
appInfo = new
|
||||||
|
{
|
||||||
|
appId = appId,
|
||||||
|
name = appInfo?.Name ?? "Unknown Application",
|
||||||
|
company = appInfo?.Company ?? "",
|
||||||
|
version = appInfo?.Version ?? "",
|
||||||
|
path = appInfo?.Path ?? "",
|
||||||
|
icon = appInfo?.Base64Icon ?? "",
|
||||||
|
hash = appInfo?.Hash ?? ""
|
||||||
|
},
|
||||||
|
|
||||||
|
// 统计摘要
|
||||||
|
summary = new
|
||||||
|
{
|
||||||
|
timeRange = timeRange,
|
||||||
|
startTime = DateTimeOffset.FromUnixTimeSeconds(startTime).ToString("yyyy-MM-dd HH:mm:ss"),
|
||||||
|
endTime = DateTimeOffset.FromUnixTimeSeconds(now).ToString("yyyy-MM-dd HH:mm:ss"),
|
||||||
|
totalUpload = totalUpload,
|
||||||
|
totalDownload = totalDownload,
|
||||||
|
totalTraffic = totalUpload + totalDownload,
|
||||||
|
totalConnections = totalConnections,
|
||||||
|
uniqueRemoteIPs = networkList.Select(x => x.RemoteIP).Distinct().Count(),
|
||||||
|
uniqueRemotePorts = networkList.Select(x => x.RemotePort).Distinct().Count()
|
||||||
|
},
|
||||||
|
|
||||||
|
// 前30个流量最高的连接
|
||||||
|
topConnections = topConnections,
|
||||||
|
|
||||||
|
// 协议占比统计
|
||||||
|
protocolStats = protocolStats,
|
||||||
|
|
||||||
|
// 时间趋势
|
||||||
|
timeTrends = timeTrends,
|
||||||
|
|
||||||
|
// 端口分析
|
||||||
|
portAnalysis = portAnalysis
|
||||||
|
};
|
||||||
|
|
||||||
|
await context.Response.WriteJsonAsync(new ResponseModel<object>
|
||||||
|
{
|
||||||
|
Success = true,
|
||||||
|
Data = result,
|
||||||
|
Message = "获取应用网络分析数据成功"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
context.Response.StatusCode = 500;
|
||||||
|
await context.Response.WriteJsonAsync(new ResponseModel<object>
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
Message = $"获取应用网络分析数据失败: {ex.Message}"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
// 启动服务器
|
// 启动服务器
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -1754,6 +1926,41 @@ public partial class App : System.Windows.Application
|
|||||||
|
|
||||||
return points;
|
return points;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 根据端口号获取服务名称
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="port">端口号</param>
|
||||||
|
/// <returns>服务名称</returns>
|
||||||
|
private static string GetPortServiceName(int port)
|
||||||
|
{
|
||||||
|
return port switch
|
||||||
|
{
|
||||||
|
21 => "FTP",
|
||||||
|
22 => "SSH",
|
||||||
|
23 => "Telnet",
|
||||||
|
25 => "SMTP",
|
||||||
|
53 => "DNS",
|
||||||
|
80 => "HTTP",
|
||||||
|
110 => "POP3",
|
||||||
|
123 => "NTP",
|
||||||
|
143 => "IMAP",
|
||||||
|
443 => "HTTPS",
|
||||||
|
993 => "IMAPS",
|
||||||
|
995 => "POP3S",
|
||||||
|
1433 => "SQL Server",
|
||||||
|
3306 => "MySQL",
|
||||||
|
3389 => "RDP",
|
||||||
|
5432 => "PostgreSQL",
|
||||||
|
6379 => "Redis",
|
||||||
|
8080 => "HTTP-Alt",
|
||||||
|
8443 => "HTTPS-Alt",
|
||||||
|
_ when port >= 1024 && port <= 5000 => "User Port",
|
||||||
|
_ when port > 5000 && port < 32768 => "Service Port",
|
||||||
|
_ when port >= 32768 => "Dynamic Port",
|
||||||
|
_ => "Unknown"
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
Reference in new issue
Block a user