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; }
|
||||
|
||||
/// <summary>
|
||||
/// 软件 ID = 路径 + 文件签名发布者 + 进程名 (文件签名发布者 可无)
|
||||
/// 软件 ID(Hash版本,用于关联和索引)
|
||||
/// </summary>
|
||||
public string AppId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 原始软件 ID = 路径 + 文件签名发布者 + 进程名 (文件签名发布者 可无)
|
||||
/// </summary>
|
||||
public string OriginalAppId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 名字
|
||||
/// </summary>
|
||||
|
||||
@@ -51,6 +51,7 @@ public class NetVizorDbContext : IDisposable
|
||||
CREATE TABLE IF NOT EXISTS AppInfo (
|
||||
Id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
AppId TEXT NOT NULL,
|
||||
OriginalAppId TEXT,
|
||||
Name TEXT NOT NULL,
|
||||
Path TEXT NOT NULL,
|
||||
Version TEXT,
|
||||
@@ -340,6 +341,9 @@ public class NetVizorDbContext : IDisposable
|
||||
|
||||
Log.Information($"AppSetting表迁移完成,恢复了 {existingSettings.Count()} 条记录");
|
||||
}
|
||||
|
||||
// 检查并迁移AppInfo表 - 添加OriginalAppId字段
|
||||
await MigrateAppInfoTableAsync();
|
||||
}
|
||||
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)
|
||||
{
|
||||
try
|
||||
|
||||
@@ -48,8 +48,8 @@ public class AppInfoRepository : IAppInfoRepository
|
||||
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)";
|
||||
INSERT INTO AppInfo (AppId, OriginalAppId, 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);
|
||||
|
||||
@@ -67,7 +67,7 @@ public class AppInfoRepository : IAppInfoRepository
|
||||
|
||||
const string sql = @"
|
||||
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
|
||||
WHERE Id = @Id";
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Data;
|
||||
using Data.Models;
|
||||
using Dapper;
|
||||
|
||||
@@ -122,13 +123,27 @@ public class NetworkRepository : INetworkRepository
|
||||
|
||||
public async Task<IEnumerable<AppNetwork>> GetAppNetworkByTimeRangeAsync(string appId, long startTime, long endTime)
|
||||
{
|
||||
// 清理 appId,去掉多余的空白和引号
|
||||
if (!string.IsNullOrEmpty(appId))
|
||||
{
|
||||
appId = appId.Trim().Trim('"');
|
||||
}
|
||||
const string sql = @"
|
||||
SELECT * FROM AppNetwork
|
||||
WHERE AppId = @AppId AND Timestamp >= @StartTime AND Timestamp <= @EndTime
|
||||
ORDER BY Timestamp DESC";
|
||||
SELECT * FROM AppNetwork
|
||||
WHERE AppId = @AppId
|
||||
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,
|
||||
new { AppId = appId, StartTime = startTime, EndTime = endTime });
|
||||
var parameters = new DynamicParameters();
|
||||
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
|
||||
@@ -141,7 +156,7 @@ public class NetworkRepository : INetworkRepository
|
||||
|
||||
string sql;
|
||||
object parameters;
|
||||
|
||||
|
||||
if (string.IsNullOrEmpty(networkCardGuid))
|
||||
{
|
||||
// 获取所有网卡数据
|
||||
@@ -169,7 +184,7 @@ public class NetworkRepository : INetworkRepository
|
||||
{
|
||||
string sql;
|
||||
object parameters;
|
||||
|
||||
|
||||
if (string.IsNullOrEmpty(networkCardGuid))
|
||||
{
|
||||
// 获取所有网卡数据
|
||||
@@ -339,7 +354,7 @@ public class NetworkRepository : INetworkRepository
|
||||
|
||||
string sql;
|
||||
object parameters;
|
||||
|
||||
|
||||
if (string.IsNullOrEmpty(networkCardGuid))
|
||||
{
|
||||
// 获取所有网卡数据
|
||||
@@ -387,7 +402,7 @@ public class NetworkRepository : INetworkRepository
|
||||
|
||||
string sql;
|
||||
object parameters;
|
||||
|
||||
|
||||
if (string.IsNullOrEmpty(networkCardGuid))
|
||||
{
|
||||
// 获取所有网卡数据
|
||||
@@ -435,7 +450,7 @@ public class NetworkRepository : INetworkRepository
|
||||
|
||||
string sql;
|
||||
object parameters;
|
||||
|
||||
|
||||
if (string.IsNullOrEmpty(networkCardGuid))
|
||||
{
|
||||
// 获取所有网卡数据
|
||||
@@ -815,4 +830,4 @@ public class IpTrafficInfo
|
||||
public int ConnectionCount { get; set; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
#endregion
|
||||
@@ -1,4 +1,6 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Application.Utils;
|
||||
using Common.Logger;
|
||||
using Common.Utils;
|
||||
@@ -29,8 +31,8 @@ public class NetworkDataCollectionService : IDisposable
|
||||
// ETW数据缓存(只保留最活跃的前100条)
|
||||
private readonly ConcurrentDictionary<string, AppNetworkData> _activeConnections = new();
|
||||
|
||||
// AppId生成工具
|
||||
private readonly ConcurrentDictionary<int, string> _processAppIdCache = new();
|
||||
// AppId生成工具 - 缓存原始AppId
|
||||
private readonly ConcurrentDictionary<int, string> _processOriginalAppIdCache = new();
|
||||
|
||||
public NetworkDataCollectionService(DatabaseWriteManager writeManager)
|
||||
{
|
||||
@@ -133,7 +135,7 @@ public class NetworkDataCollectionService : IDisposable
|
||||
try
|
||||
{
|
||||
// 生成或获取AppId
|
||||
var appId = await GetOrGenerateAppIdAsync(app.ProcessId, app.ProgramInfo);
|
||||
var (appId, originalAppId) = await GetOrGenerateAppIdAsync(app.ProcessId, app.ProgramInfo);
|
||||
|
||||
if (string.IsNullOrEmpty(appId))
|
||||
{
|
||||
@@ -151,6 +153,7 @@ public class NetworkDataCollectionService : IDisposable
|
||||
var appInfo = new AppInfo
|
||||
{
|
||||
AppId = appId,
|
||||
OriginalAppId = originalAppId,
|
||||
Name = app.ProgramInfo.ProcessName ?? "Unknown",
|
||||
Path = app.ProgramInfo.MainModulePath ?? "",
|
||||
Version = app.ProgramInfo.Version ?? "",
|
||||
@@ -236,12 +239,14 @@ public class NetworkDataCollectionService : IDisposable
|
||||
/// <summary>
|
||||
/// 获取或生成应用程序ID
|
||||
/// </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
|
||||
@@ -255,25 +260,30 @@ public class NetworkDataCollectionService : IDisposable
|
||||
if (programInfo == null)
|
||||
{
|
||||
Log.Warning($"无法获取进程 {processId} 的信息");
|
||||
return null;
|
||||
return (null, null);
|
||||
}
|
||||
|
||||
// 生成AppId: 路径 + 文件签名发布者 + 进程名
|
||||
// 生成原始AppId: 路径 + 文件签名发布者 + 进程名
|
||||
var path = programInfo.MainModulePath ?? "";
|
||||
var publisher = programInfo.CompanyName ?? "";
|
||||
var processName = programInfo.ProcessName ?? "Unknown";
|
||||
|
||||
var appId = $"{path}|{publisher}|{processName}";
|
||||
var originalAppId = $"{path}|{publisher}|{processName}";
|
||||
|
||||
// 生成Hash版本的AppId(SHA256前16位)
|
||||
var hashAppId = GenerateHashAppId(originalAppId);
|
||||
|
||||
// 缓存AppId
|
||||
_processAppIdCache.TryAdd(processId, appId);
|
||||
// 缓存原始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)
|
||||
{
|
||||
Log.Warning($"生成进程 {processId} 的AppId时出错", ex);
|
||||
return null;
|
||||
return (null, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -286,7 +296,7 @@ public class NetworkDataCollectionService : IDisposable
|
||||
{
|
||||
ActiveNetworkInterfaces = _networkInterfaces.Count,
|
||||
LastNetworkInterfaceUpdate = _lastNetworkInterfaceUpdate,
|
||||
CachedProcessCount = _processAppIdCache.Count,
|
||||
CachedProcessCount = _processOriginalAppIdCache.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()
|
||||
{
|
||||
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_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_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_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>
|
||||
|
||||
@@ -1572,6 +1572,178 @@ public partial class App : System.Windows.Application
|
||||
|
||||
#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
|
||||
{
|
||||
@@ -1754,6 +1926,41 @@ public partial class App : System.Windows.Application
|
||||
|
||||
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>
|
||||
|
||||
Reference in new issue
Block a user