A:AppXaml代码分割

This commit is contained in:
jimmy committed 2025-08-13 21:16:35 +08:00
1 parent 707b7dd679
commit 6b3d369b89
13 files changed
+1774 -1816

No files matched your search

+8 -8
View File
@@ -26,19 +26,19 @@ public sealed class AppConfig
/// WebSocket 端口
/// </summary>
public int WebSocketPort { get; set; }
/// <summary>
/// WebSocket 连接地址
/// </summary>
public string? WebSocketPath { get; set; }
/// <summary>
/// Http 端口
/// </summary>
public int HttpPort { get; set; }
/// <summary>
/// Http 连接地址
/// WebSocket 连接地址
/// </summary>
public string? WebSocketPath { get; set; }
/// <summary>
/// Http 服务地址
/// </summary>
public string? HttpPath { get; set; }
@@ -59,4 +59,4 @@ public sealed class AppConfig
public static readonly string ProcessInfoSubscribe = "ProcessInfo";
public static readonly string AppDetailInfoSubscribe = "AppDetailInfo";
}
}
+27 -1808
View File
@@ -1,12 +1,9 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Text.Json;
using System.Windows;
using System.Windows.Threading;
using Application;
using Application.Utils;
using Common;
using Common.Logger;
using Common.Net.HttpConn;
@@ -21,6 +18,7 @@ using Shell.Views;
using Utils.ETW.Etw;
using Utils.Firewall;
using Dapper;
using Shell.Services;
using Shell.Utils;
using MessageBox = System.Windows.Forms.MessageBox;
@@ -32,20 +30,15 @@ namespace Shell;
/// </summary>
public partial class App : System.Windows.Application
{
/// <summary>
/// Http 服务监听
/// </summary>
public static HttpListener? HttpListener { get; set; }
/// <summary>
/// http 服务地址
/// </summary>
public static string WebServerPath { get; set; }
private ServerStartupManager? _serverManager;
private GlobalExceptionHandler? _exceptionHandler;
protected override async void OnStartup(StartupEventArgs e)
{
// 异常处理
ExceptionCatch();
// 初始化异常处理
_exceptionHandler = GlobalExceptionHandler.Instance;
_exceptionHandler.Initialize();
// 初始化日志
var configModelLogging = AppConfig.Instance.ConfigModel.Logging;
Log.Initialize(configModelLogging);
@@ -63,12 +56,23 @@ public partial class App : System.Windows.Application
Log.Error(ex, "数据库初始化失败");
MessageBox.Show($"数据库初始化失败: {ex.Message}", "错误", System.Windows.Forms.MessageBoxButtons.OK,
System.Windows.Forms.MessageBoxIcon.Error);
return;
}
// 启动服务(如 WebSocket、HTTP、端口监听等)
StartMyServer();
// 启动所有服务
try
{
_serverManager = new ServerStartupManager();
await _serverManager.StartAllServicesAsync();
}
catch (Exception ex)
{
Log.Error(ex, "服务启动失败");
MessageBox.Show($"服务启动失败: {ex.Message}", "错误", System.Windows.Forms.MessageBoxButtons.OK,
System.Windows.Forms.MessageBoxIcon.Error);
}
// 创建 NetView 窗口 - 让它自己处理设置加载和应用
// 创建 NetView 窗口
try
{
Log.Information("创建 NetView 窗口...");
@@ -78,11 +82,9 @@ public partial class App : System.Windows.Application
catch (Exception ex)
{
Log.Error(ex, "创建 NetView 窗口失败");
throw; // 重新抛出异常,因为这是关键组件
throw;
}
// Mouth();
// 设置应用程序在关闭最后一个窗口时不自动退出
this.ShutdownMode = ShutdownMode.OnExplicitShutdown;
@@ -104,1714 +106,10 @@ public partial class App : System.Windows.Application
}
}
private async void Mouth()
protected override void OnExit(ExitEventArgs e)
{
var firewallService = new FirewallService();
// 防火墙状态
// var statusAsync = await firewallService.GetStatusAsync();
var rulesAsync = await firewallService.GetRulesAsync();
Console.WriteLine(JsonHelper.ToJson(rulesAsync));
}
/// <summary>
/// 异常捕捉
/// </summary>
private void ExceptionCatch()
{
DispatcherUnhandledException += App_DispatcherUnhandledException;
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
// 注册全局异常处理
TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
}
/// <summary>
/// 处理异常
/// </summary>
/// <param name="exception"></param>
private void HandleException(Exception? exception)
{
Log.Error("未捕获异常", exception);
// 错误记录,错误处理
if (exception != null)
{
// todo:2023年10月29日 恭喜你发现了BUG页面
MessageBox.Show($"恭喜你发现了BUG,请联系管理员解决:{exception.Message}");
}
}
private void App_DispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
{
Log.Error(e.Exception, "UI线程异常");
HandleException(e.Exception);
// 根据异常类型决定是否继续运行
if (CanRecover(e.Exception))
{
e.Handled = true; // 标记为已处理,防止应用崩溃
// ShowUserFriendlyMessage("操作失败,请重试");
}
else
{
// ShowCriticalErrorDialog(e.Exception);
// 让应用正常关闭而不是崩溃
}
}
private void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
{
foreach (var ex in e.Exception.InnerExceptions)
{
Log.Error(e.Exception, "UI线程异常");
}
e.SetObserved(); // 标记异常已被观察,防止应用终止
}
private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
var exception = e.ExceptionObject as Exception;
Log.Error(exception, "非UI线程异常");
if (e.IsTerminating)
{
// 应用即将终止,进行紧急清理
// EmergencyCleanup();
Log.Fatal("应用程序即将因未处理异常而终止");
}
}
private bool CanRecover(Exception exception)
{
return exception switch
{
ArgumentException => true,
InvalidOperationException => true,
HttpRequestException => true,
TimeoutException => true,
OutOfMemoryException => false,
StackOverflowException => false,
AccessViolationException => false,
_ => true
};
}
private void StartMyServer()
{
NewMethod();
int port = SysHelper.GetAvailablePort();
AppConfig.Instance.WebSocketPort = port;
AppConfig.Instance.WebSocketPath = $"ws://127.0.0.1:{port}";
Log.Information4Ctx($"服务启动在端口: {AppConfig.Instance.WebSocketPort}");
Log.Information($"服务完整地址: {AppConfig.Instance.WebSocketPath}");
// 启动WebSocket服务器
WebSocketManager.Instance.Start(AppConfig.Instance.WebSocketPath);
// 订阅 websocket 关闭服务
WebSocketManager.Instance.SubscribeConnectionClosed((args =>
{
if (args.Uuid != null)
{
DispatchEngine.Instance.DeleteApplicationInfo(args.Uuid);
DispatchEngine.Instance.DeleteProcessInfo(args.Uuid);
DispatchEngine.Instance.DeleteAppDetailInfo(args.Uuid);
}
}
));
// 开启 WebSocket 定时发送服务
DispatchEngine.Instance.ApplicationInfoDistribute();
DispatchEngine.Instance.ProcessInfoDistribute();
DispatchEngine.Instance.AppDetailInfoDistribute();
// 启动 http 服务
Task.Run(() => { _ = StartHttpServer(); });
}
private static async Task NewMethod()
{
if (!SysHelper.IsAdministrator())
{
Console.WriteLine("此程序需要管理员权限才能运行ETW监控!");
Console.WriteLine("请以管理员身份重新运行程序。");
Console.ReadKey();
return;
}
var _networkManager = new EnhancedEtwNetworkManager();
_networkManager.StartMonitoring();
}
// 网络接口缓存
private static readonly Dictionary<string, object> _networkInterfaceCache = new Dictionary<string, object>();
private static DateTime _cacheLastUpdateTime = DateTime.MinValue;
private static readonly TimeSpan _cacheExpireTime = TimeSpan.FromMinutes(5); // 缓存5分钟
private async Task StartHttpServer()
{
int port = SysHelper.GetAvailablePort();
AppConfig.Instance.HttpApiPort = port;
AppConfig.Instance.HttpApiPath = $"http://127.0.0.1:{port}";
// 开启 http 服务
var server = new HttpServer(AppConfig.Instance.HttpApiPath);
// 开启html服务
int port1 = SysHelper.GetAvailablePort();
AppConfig.Instance.HttpPort = port1;
AppConfig.Instance.HttpPath = $"http://127.0.0.1:{port1}";
StartHttpServer(AppConfig.Instance.HttpPort); // 启动本地HTTP服务器 // 开启监听
// 启动 Http 服务
// 添加中间件
server.UseMiddleware(Middlewares.RequestLogging);
server.UseMiddleware(Middlewares.Cors);
// 添加路由
server.Get("/api",
async (context) => { await context.Response.WriteJsonAsync(new { message = "Hi!" }); });
#region 软件信息订阅
server.Post("/api/subscribe-application", async (context) =>
{
// 正确的做法:只处理请求体数据
if (string.IsNullOrEmpty(context.RequestBody))
{
context.Response.StatusCode = 400;
await context.Response.WriteJsonAsync(new ResponseModel<object>
{
Success = false,
Message = "请求体不能为空",
});
return;
}
// 解析请求数据
try
{
// 直接使用已经读取的数据
string requestData = context.RequestBody;
// 打印原始数据用于调试
string? uuid = context.Request.Headers["uuid"];
Log.Warning($"接收到的数据: {requestData}, 用户 Id: {uuid}");
if (string.IsNullOrWhiteSpace(uuid))
{
context.Response.StatusCode = 400;
await context.Response.WriteJsonAsync(new ResponseModel<object>
{
Success = false,
Message = $"用户 ID 丢失!",
});
return;
}
var subscriptionInfo = JsonHelper.FromJson<SubscriptionInfo>(requestData);
if (subscriptionInfo != null)
{
// 软件信息订阅
DispatchEngine.Instance.AddApplicationInfo(uuid, new DispatchModel
{
Interval = subscriptionInfo.Interval
});
Log.Warning($"时间: {subscriptionInfo.Interval}");
}
}
catch (JsonException ex)
{
context.Response.StatusCode = 400;
await context.Response.WriteJsonAsync(new ResponseModel<object>
{
Success = false,
Message = $"请求数据格式错误: {ex.Message}",
});
return;
}
// 返回响应 - 只序列化纯数据对象
context.Response.StatusCode = 200;
await context.Response.WriteJsonAsync(new ResponseModel<string>
{
Success = true,
Data = "成功",
Message = "订阅成功",
});
});
#endregion
#region 线程信息订阅
server.Post("/api/subscribe-process", async (context) =>
{
if (string.IsNullOrEmpty(context.RequestBody))
{
context.Response.StatusCode = 400;
await context.Response.WriteJsonAsync(new ResponseModel<object>
{
Success = false,
Message = "请求体不能为空",
});
return;
}
// 解析请求数据
try
{
string requestData = context.RequestBody;
// 打印原始数据用于调试
string? uuid = context.Request.Headers["uuid"];
if (string.IsNullOrWhiteSpace(uuid))
{
context.Response.StatusCode = 400;
await context.Response.WriteJsonAsync(new ResponseModel<object>
{
Success = false,
Message = $"用户 ID 丢失!",
});
return;
}
var subscriptionInfo = JsonHelper.FromJson<SubscriptionProcessInfo>(requestData);
if (subscriptionInfo != null)
{
DispatchEngine.Instance.DeleteProcessInfo(uuid);
DispatchEngine.Instance.AddProcessInfo(uuid, new ProcessDispatchModel
{
Interval = subscriptionInfo.Interval,
ProcessIds = subscriptionInfo.ProcessIds
});
Log.Warning($"时间: {subscriptionInfo.Interval}");
Log.Warning($"监视的 ID : {subscriptionInfo.ProcessIds}");
}
}
catch (JsonException ex)
{
context.Response.StatusCode = 400;
await context.Response.WriteJsonAsync(new ResponseModel<object>
{
Success = false,
Message = $"请求数据格式错误: {ex.Message}",
});
return;
}
// 返回响应 - 只序列化纯数据对象
context.Response.StatusCode = 200;
await context.Response.WriteJsonAsync(new ResponseModel<string>
{
Success = true,
Data = "成功",
Message = "订阅成功",
});
});
#endregion
#region 软件详细信息订阅
server.Post("/api/subscribe-appinfo", async (context) =>
{
if (string.IsNullOrEmpty(context.RequestBody))
{
context.Response.StatusCode = 400;
await context.Response.WriteJsonAsync(
new ResponseModel<object> { Success = false, Message = "请求体不能为空" });
return;
}
try
{
string? uuid = context.Request.Headers["uuid"];
if (string.IsNullOrWhiteSpace(uuid))
{
context.Response.StatusCode = 400;
await context.Response.WriteJsonAsync(new ResponseModel<object>
{ Success = false, Message = "用户 ID 丢失!" });
return;
}
var subscriptionRequest = JsonHelper.FromJson<SubscriptionAppInfo>(context.RequestBody);
if (subscriptionRequest != null && !string.IsNullOrWhiteSpace(subscriptionRequest.ApplicationPath))
{
DispatchEngine.Instance.AddAppDetailInfo(uuid, new AppDetailDispatchModel
{
Interval = subscriptionRequest.Interval,
ApplicationPath = subscriptionRequest.ApplicationPath
});
Log.Information($"客户端 {uuid} 订阅了应用详情: {subscriptionRequest.ApplicationPath}");
}
else
{
context.Response.StatusCode = 400;
await context.Response.WriteJsonAsync(new ResponseModel<object>
{ Success = false, Message = "无效的订阅请求,缺少 'ApplicationPath'" });
return;
}
}
catch (JsonException ex)
{
context.Response.StatusCode = 400;
await context.Response.WriteJsonAsync(new ResponseModel<object>
{ Success = false, Message = $"请求数据格式错误: {ex.Message}" });
return;
}
context.Response.StatusCode = 200;
await context.Response.WriteJsonAsync(new ResponseModel<string>
{ Success = true, Data = "成功", Message = "订阅成功" });
});
#endregion
#region 获取防火墙信息
// 查询防火墙规则 - 支持分页和筛选
server.Get("/api/firewall/rules", async (context) =>
{
try
{
var firewallApi = new WindowsFirewallApi();
// 获取查询参数
var queryParams = context.QueryParams;
var startIndex = !string.IsNullOrEmpty(queryParams["start"]) ? int.Parse(queryParams["start"]) : 0;
var limit = !string.IsNullOrEmpty(queryParams["limit"]) ? int.Parse(queryParams["limit"]) : 50;
// 构建筛选条件
var filter = new RuleFilter();
if (!string.IsNullOrEmpty(queryParams["name"])) filter.NamePattern = queryParams["name"];
if (!string.IsNullOrEmpty(queryParams["direction"]) &&
Enum.TryParse<RuleDirection>(queryParams["direction"], true, out var direction))
filter.Direction = direction;
if (!string.IsNullOrEmpty(queryParams["enabled"]) &&
bool.TryParse(queryParams["enabled"], out var enabled))
filter.Enabled = enabled;
if (!string.IsNullOrEmpty(queryParams["protocol"]) &&
Enum.TryParse<ProtocolType>(queryParams["protocol"], true, out var protocol))
filter.Protocol = protocol;
if (!string.IsNullOrEmpty(queryParams["action"]) &&
Enum.TryParse<RuleAction>(queryParams["action"], true, out var action))
filter.Action = action;
if (!string.IsNullOrEmpty(queryParams["application"]))
filter.ApplicationName = queryParams["application"];
if (!string.IsNullOrEmpty(queryParams["port"])) filter.Port = queryParams["port"];
// 添加关键字搜索支持
if (!string.IsNullOrEmpty(queryParams["search"])) filter.SearchKeyword = queryParams["search"];
// 获取筛选后的规则
var allRules = firewallApi.GetRulesByFilter(filter);
var totalCount = allRules.Count;
// 分页
var pagedRules = allRules.Skip(startIndex).Take(limit).ToList();
await context.Response.WriteJsonAsync(new ResponseModel<object>
{
Success = true,
Data = new
{
rules = pagedRules,
totalCount = totalCount,
startIndex = startIndex,
limit = limit,
hasMore = startIndex + limit < totalCount
},
Message = "查询成功"
});
}
catch (Exception ex)
{
context.Response.StatusCode = 500;
await context.Response.WriteJsonAsync(new ResponseModel<object>
{
Success = false,
Message = $"查询防火墙规则失败: {ex.Message}"
});
}
});
// 新增防火墙规则
server.Post("/api/firewall/rules", async (context) =>
{
try
{
Console.WriteLine(context.RequestBody);
if (string.IsNullOrEmpty(context.RequestBody))
{
context.Response.StatusCode = 400;
await context.Response.WriteJsonAsync(new ResponseModel<object>
{
Success = false,
Message = "请求体不能为空"
});
return;
}
var createRequest = JsonHelper.FromJson<CreateRuleRequest>(context.RequestBody);
if (createRequest == null)
{
context.Response.StatusCode = 400;
await context.Response.WriteJsonAsync(new ResponseModel<object>
{
Success = false,
Message = "请求数据格式错误"
});
return;
}
var firewallApi = new WindowsFirewallApi();
var success = firewallApi.CreateRule(createRequest);
if (success)
{
await context.Response.WriteJsonAsync(new ResponseModel<object>
{
Success = true,
Message = "防火墙规则创建成功"
});
}
else
{
context.Response.StatusCode = 500;
await context.Response.WriteJsonAsync(new ResponseModel<object>
{
Success = false,
Message = "防火墙规则创建失败"
});
}
}
catch (Exception ex)
{
context.Response.StatusCode = 500;
await context.Response.WriteJsonAsync(new ResponseModel<object>
{
Success = false,
Message = $"创建防火墙规则失败: {ex.Message}"
});
}
});
// 编辑防火墙规则
server.Put("/api/firewall/rules", async (context) =>
{
try
{
if (string.IsNullOrEmpty(context.RequestBody))
{
context.Response.StatusCode = 400;
await context.Response.WriteJsonAsync(new ResponseModel<object>
{
Success = false,
Message = "请求体不能为空"
});
return;
}
var updateRequest = JsonHelper.FromJson<UpdateRuleRequest>(context.RequestBody);
if (updateRequest == null || string.IsNullOrEmpty(updateRequest.CurrentName))
{
context.Response.StatusCode = 400;
await context.Response.WriteJsonAsync(new ResponseModel<object>
{
Success = false,
Message = "请求数据格式错误或缺少规则名称"
});
return;
}
var firewallApi = new WindowsFirewallApi();
var success = firewallApi.UpdateRule(updateRequest);
if (success)
{
await context.Response.WriteJsonAsync(new ResponseModel<object>
{
Success = true,
Message = "防火墙规则更新成功"
});
}
else
{
context.Response.StatusCode = 500;
await context.Response.WriteJsonAsync(new ResponseModel<object>
{
Success = false,
Message = "防火墙规则更新失败,可能规则不存在"
});
}
}
catch (Exception ex)
{
context.Response.StatusCode = 500;
await context.Response.WriteJsonAsync(new ResponseModel<object>
{
Success = false,
Message = $"更新防火墙规则失败: {ex.Message}"
});
}
});
// 删除防火墙规则
server.Delete("/api/firewall/rules", async (context) =>
{
try
{
var queryParams = context.QueryParams;
if (string.IsNullOrEmpty(queryParams["name"]))
{
context.Response.StatusCode = 400;
await context.Response.WriteJsonAsync(new ResponseModel<object>
{
Success = false,
Message = "缺少规则名称参数"
});
return;
}
var ruleName = queryParams["name"];
var firewallApi = new WindowsFirewallApi();
var success = firewallApi.DeleteRule(ruleName);
if (success)
{
await context.Response.WriteJsonAsync(new ResponseModel<object>
{
Success = true,
Message = "防火墙规则删除成功"
});
}
else
{
context.Response.StatusCode = 500;
await context.Response.WriteJsonAsync(new ResponseModel<object>
{
Success = false,
Message = "防火墙规则删除失败,可能规则不存在"
});
}
}
catch (Exception ex)
{
context.Response.StatusCode = 500;
await context.Response.WriteJsonAsync(new ResponseModel<object>
{
Success = false,
Message = $"删除防火墙规则失败: {ex.Message}"
});
}
});
// 获取防火墙状态
server.Get("/api/firewall/status", async (context) =>
{
try
{
var firewallApi = new WindowsFirewallApi();
var status = firewallApi.GetFirewallStatus();
await context.Response.WriteJsonAsync(new ResponseModel<FirewallStatus>
{
Success = true,
Data = status,
Message = "获取防火墙状态成功"
});
}
catch (Exception ex)
{
context.Response.StatusCode = 500;
await context.Response.WriteJsonAsync(new ResponseModel<object>
{
Success = false,
Message = $"获取防火墙状态失败: {ex.Message}"
});
}
});
// 获取防火墙统计信息
server.Get("/api/firewall/statistics", async (context) =>
{
try
{
var firewallApi = new WindowsFirewallApi();
var statistics = firewallApi.GetStatistics();
await context.Response.WriteJsonAsync(new ResponseModel<FirewallStatistics>
{
Success = true,
Data = statistics,
Message = "获取防火墙统计信息成功"
});
}
catch (Exception ex)
{
context.Response.StatusCode = 500;
await context.Response.WriteJsonAsync(new ResponseModel<object>
{
Success = false,
Message = $"获取防火墙统计信息失败: {ex.Message}"
});
}
});
#endregion
#region 防火墙开关
// 防火墙开关控制
server.Post("/api/firewall/switch", async (context) =>
{
try
{
var queryParams = context.QueryParams;
// 解析请求参数
var enabledParam = !string.IsNullOrEmpty(queryParams["enabled"]) ? queryParams["enabled"] : "";
var profileParam = !string.IsNullOrEmpty(queryParams["profile"]) ? queryParams["enabled"] : "";
// 解析enabled参数
if (string.IsNullOrEmpty(enabledParam))
{
context.Response.StatusCode = 400;
await context.Response.WriteJsonAsync(new ResponseModel<object>
{
Success = false,
Message = "缺少必需参数 'enabled',请使用 true 或 false",
Data = null
});
return;
}
var trim = enabledParam.Trim();
bool enabled = trim switch
{
"true" => true,
"TRUE" => true,
"True" => true,
"false" => false,
"FALSE" => false,
"False" => false,
"1" => true,
"0" => false,
_ => throw new ArgumentException("enabled参数值无效,请使用 true/false 或 1/0")
};
// 解析profile参数
FirewallProfile profile = FirewallProfile.All;
if (!string.IsNullOrEmpty(profileParam))
{
profile = profileParam switch
{
"domain" => FirewallProfile.Domain,
"private" => FirewallProfile.Private,
"public" => FirewallProfile.Public,
"all" => FirewallProfile.All,
_ => FirewallProfile.All
};
}
var api = new WindowsFirewallApi();
bool result;
string action;
if (enabled)
{
result = api.EnableFirewall(profile);
action = "启用";
}
else
{
result = api.DisableFirewall(profile);
action = "禁用";
}
if (result)
{
await context.Response.WriteJsonAsync(new ResponseModel<object>
{
Success = true,
Message = $"防火墙{action}成功 ({profile})",
Data = new
{
profile = profile.ToString(),
enabled = enabled,
action = action
}
});
}
else
{
await context.Response.WriteJsonAsync(new ResponseModel<object>
{
Success = false,
Message = $"防火墙{action}失败,可能需要管理员权限",
Data = null
});
}
}
catch (ArgumentException ex)
{
context.Response.StatusCode = 400;
await context.Response.WriteJsonAsync(new ResponseModel<object>
{
Success = false,
Message = ex.Message,
Data = null
});
}
catch (Exception ex)
{
await context.Response.WriteJsonAsync(new ResponseModel<object>
{
Success = false,
Message = $"操作防火墙时发生错误: {ex.Message}",
Data = null
});
}
});
#endregion
#region 网络统计API
// 获取网络接口列表
server.Get("/api/statistics/interfaces", async (context) =>
{
try
{
var timeRange = context.QueryParams["timeRange"] ?? "hour";
// 检查缓存是否有效
var cacheKey = $"interfaces_{timeRange}";
var now = DateTime.Now;
if (_networkInterfaceCache.ContainsKey(cacheKey) &&
(now - _cacheLastUpdateTime) < _cacheExpireTime)
{
// 使用缓存数据
await context.Response.WriteJsonAsync(new ResponseModel<object>
{
Success = true,
Data = _networkInterfaceCache[cacheKey],
Message = "获取网络接口列表成功(缓存)"
});
return;
}
// 缓存已过期或不存在,重新获取数据
var networkCardGuids = new HashSet<string>();
// 根据时间范围从不同数据表获取网卡ID
switch (timeRange.ToLower())
{
case "hour":
// 从 GlobalNetwork 表获取最近1小时的数据
var hourData = await DatabaseManager.Instance.Networks.GetGlobalNetworkByTimeRangeAsync("",
DateTimeOffset.UtcNow.AddHours(-1).ToUnixTimeSeconds(),
DateTimeOffset.UtcNow.ToUnixTimeSeconds());
foreach (var item in hourData)
networkCardGuids.Add(item.NetworkCardGuid);
break;
case "day":
// 从 GlobalNetwork 表获取最近1天的数据
var dayData = await DatabaseManager.Instance.Networks.GetGlobalNetworkByTimeRangeAsync("",
DateTimeOffset.UtcNow.AddDays(-1).ToUnixTimeSeconds(),
DateTimeOffset.UtcNow.ToUnixTimeSeconds());
foreach (var item in dayData)
networkCardGuids.Add(item.NetworkCardGuid);
break;
case "week":
// 从 GlobalNetwork 表获取最近7天的数据
var weekData = await DatabaseManager.Instance.Networks.GetGlobalNetworkByTimeRangeAsync("",
DateTimeOffset.UtcNow.AddDays(-7).ToUnixTimeSeconds(),
DateTimeOffset.UtcNow.ToUnixTimeSeconds());
foreach (var item in weekData)
networkCardGuids.Add(item.NetworkCardGuid);
break;
case "month":
// 从 GlobalNetwork 表获取最近30天的数据
var monthData = await DatabaseManager.Instance.Networks.GetGlobalNetworkByTimeRangeAsync("",
DateTimeOffset.UtcNow.AddDays(-30).ToUnixTimeSeconds(),
DateTimeOffset.UtcNow.ToUnixTimeSeconds());
foreach (var item in monthData)
networkCardGuids.Add(item.NetworkCardGuid);
break;
}
// 如果没有找到任何网卡数据,使用当前连接的网卡
if (!networkCardGuids.Any())
{
var connectedInterfaces = BasicNetworkMonitor.GetConnectedNetworkInterfaces();
foreach (var iface in connectedInterfaces)
{
networkCardGuids.Add(iface.Id);
}
}
// 获取每个网卡的详细信息
var interfaces = new List<object>();
foreach (var guid in networkCardGuids)
{
var networkInfo = NetworkAdapterHelper.GetNetworkInfoByGuid(guid);
if (networkInfo != null)
{
interfaces.Add(new
{
id = guid,
name = networkInfo.NetConnectionID ?? networkInfo.Name,
displayName = networkInfo.Description ?? networkInfo.Name,
isActive = true, // 因为在数据库中有流量数据,所以认为是可用的
macAddress = networkInfo.MACAddress
});
}
else
{
// 如果无法获取网卡信息,但数据库中有流量数据,仍然认为是有效的
interfaces.Add(new
{
id = guid,
name = $"Network Interface {guid.Substring(0, 8)}",
displayName = $"Network Adapter {guid.Substring(0, 8)}",
isActive = true, // 有流量数据就认为是可用的
macAddress = ""
});
}
}
// 更新缓存
_networkInterfaceCache[cacheKey] = interfaces;
_cacheLastUpdateTime = now;
await context.Response.WriteJsonAsync(new ResponseModel<object>
{
Success = true,
Data = interfaces,
Message = "获取网络接口列表成功"
});
}
catch (Exception ex)
{
context.Response.StatusCode = 500;
await context.Response.WriteJsonAsync(new ResponseModel<object>
{
Success = false,
Message = $"获取网络接口列表失败: {ex.Message}"
});
}
});
// 获取可用时间范围
server.Get("/api/statistics/available-ranges", async (context) =>
{
try
{
var availableRanges = new List<object>();
var now = DateTimeOffset.UtcNow;
// 检查是否有小时数据 (GlobalNetworkHourly)
var hasHourlyData =
await HasNetworkDataInTimeRangeAsync("hourly", now.AddHours(-1).ToUnixTimeSeconds());
if (hasHourlyData)
{
availableRanges.Add(new
{
type = "hour",
name = "1小时",
available = true,
startTime = now.AddHours(-1).ToString("yyyy-MM-ddTHH:mm:ssZ")
});
}
// 检查是否有天数据 (GlobalNetworkDaily)
var hasDailyData = await HasNetworkDataInTimeRangeAsync("daily", now.AddDays(-1).ToUnixTimeSeconds());
if (hasDailyData)
{
availableRanges.Add(new
{
type = "day",
name = "1天",
available = true,
startTime = now.AddDays(-1).ToString("yyyy-MM-ddTHH:mm:ssZ")
});
}
// 检查是否有周数据
var hasWeeklyData = await HasNetworkDataInTimeRangeAsync("daily", now.AddDays(-7).ToUnixTimeSeconds());
if (hasWeeklyData)
{
availableRanges.Add(new
{
type = "week",
name = "1周",
available = true,
startTime = now.AddDays(-7).ToString("yyyy-MM-ddTHH:mm:ssZ")
});
}
// 检查是否有月数据
var hasMonthlyData =
await HasNetworkDataInTimeRangeAsync("daily", now.AddDays(-30).ToUnixTimeSeconds());
if (hasMonthlyData)
{
availableRanges.Add(new
{
type = "month",
name = "1个月",
available = true,
startTime = now.AddDays(-30).ToString("yyyy-MM-ddTHH:mm:ssZ")
});
}
// 如果没有任何数据,至少返回hour(用户刚安装)
if (!availableRanges.Any())
{
availableRanges.Add(new
{
type = "hour",
name = "1小时",
available = true,
startTime = now.ToString("yyyy-MM-ddTHH:mm:ssZ")
});
}
await context.Response.WriteJsonAsync(new ResponseModel<object>
{
Success = true,
Data = availableRanges.First(),
Message = "获取可用时间范围成功"
});
}
catch (Exception ex)
{
context.Response.StatusCode = 500;
await context.Response.WriteJsonAsync(new ResponseModel<object>
{
Success = false,
Message = $"获取可用时间范围失败: {ex.Message}"
});
}
});
// 清理网络接口缓存
server.Post("/api/statistics/clear-cache", async (context) =>
{
try
{
_networkInterfaceCache.Clear();
_cacheLastUpdateTime = DateTime.MinValue;
await context.Response.WriteJsonAsync(new ResponseModel<string>
{
Success = true,
Data = "缓存已清理",
Message = "网络接口缓存清理成功"
});
}
catch (Exception ex)
{
context.Response.StatusCode = 500;
await context.Response.WriteJsonAsync(new ResponseModel<object>
{
Success = false,
Message = $"清理缓存失败: {ex.Message}"
});
}
});
#endregion
#region 流量数据API
// 获取流量趋势数据
server.Get("/api/traffic/trends", async (context) =>
{
try
{
var timeRange = context.QueryParams["timeRange"] ?? "1hour";
var interfaceId = context.QueryParams["interfaceId"] ?? "all";
var points = new List<object>();
var now = DateTimeOffset.UtcNow;
switch (timeRange)
{
case "1hour":
// 从 GlobalNetwork 表获取最近1小时数据,按5秒间隔
var hourData = await GetTrafficTrendsAsync("hour", interfaceId,
now.AddHours(-1).ToUnixTimeSeconds(), now.ToUnixTimeSeconds());
points = hourData;
break;
case "1day":
// 从 GlobalNetworkHourly 表获取最近1天数据,按小时间隔
var dayData = await GetTrafficTrendsAsync("day", interfaceId,
now.AddDays(-1).ToUnixTimeSeconds(), now.ToUnixTimeSeconds());
points = dayData;
break;
case "7days":
// 从 GlobalNetworkDaily 表获取最近7天数据,按天间隔
var weekData = await GetTrafficTrendsAsync("week", interfaceId,
now.AddDays(-7).ToUnixTimeSeconds(), now.ToUnixTimeSeconds());
points = weekData;
break;
case "30days":
// 从 GlobalNetworkDaily 表获取最近30天数据,按天间隔
var monthData = await GetTrafficTrendsAsync("month", interfaceId,
now.AddDays(-30).ToUnixTimeSeconds(), now.ToUnixTimeSeconds());
points = monthData;
break;
}
await context.Response.WriteJsonAsync(new ResponseModel<object>
{
Success = true,
Data = new
{
@interface = interfaceId,
timeRange = timeRange,
points = points
},
Message = "获取流量趋势数据成功"
});
}
catch (Exception ex)
{
context.Response.StatusCode = 500;
await context.Response.WriteJsonAsync(new ResponseModel<object>
{
Success = false,
Message = $"获取流量趋势数据失败: {ex.Message}"
});
}
});
// 获取Top应用流量数据
server.Get("/api/traffic/top-apps", async (context) =>
{
try
{
var timeRange = context.QueryParams["timeRange"] ?? "1hour";
var limit = int.Parse(context.QueryParams["limit"] ?? "10");
// 根据时间范围计算天数,对于1hour特殊处理
IEnumerable<AppNetworkTopInfo> topApps;
if (timeRange == "1hour")
{
// 对于1小时数据,我们需要特殊处理,因为GetTopAppsByTrafficAsync不支持小时查询
// 使用1天的数据作为替代,实际应用中可能需要创建专门的小时查询方法
topApps = await DatabaseManager.Instance.Networks.GetTopAppsByTrafficAsync(limit, 1);
}
else
{
int days = timeRange switch
{
"1day" => 1,
"7days" => 7,
"30days" => 30,
_ => 1
};
topApps = await DatabaseManager.Instance.Networks.GetTopAppsByTrafficAsync(limit, days);
}
var result = new List<object>();
foreach (var app in topApps)
{
// GetTopAppsByTrafficAsync 已经通过 LEFT JOIN 获取了应用信息
// 只有当JOIN没有获取到信息时才额外查询AppInfo表
string processName = System.IO.Path.GetFileName(app.AppPath ?? "");
string displayName = app.AppName ?? "";
string iconBase64 = "";
if (!string.IsNullOrEmpty(displayName) && !string.IsNullOrEmpty(app.AppId))
{
// 如果JOIN没有获取到应用名称,尝试单独查询
var appInfo = await DatabaseManager.Instance.AppInfos.GetAppByAppIdAsync(app.AppId);
if (appInfo != null)
{
displayName = appInfo.Name ?? "";
iconBase64 = appInfo.Base64Icon ?? "";
}
}
if (string.IsNullOrEmpty(displayName))
{
displayName = processName.Replace(".exe", "") ?? "Unknown";
}
result.Add(new
{
processName = processName,
displayName = displayName,
icon = iconBase64,
totalBytes = app.TotalTraffic
});
}
await context.Response.WriteJsonAsync(new ResponseModel<object>
{
Success = true,
Data = result,
Message = "获取Top应用流量数据成功"
});
}
catch (Exception ex)
{
context.Response.StatusCode = 500;
await context.Response.WriteJsonAsync(new ResponseModel<object>
{
Success = false,
Message = $"获取Top应用流量数据失败: {ex.Message}"
});
}
});
#endregion
#region 软件排行榜API
// 获取软件流量TOP100排行
server.Get("/api/apps/top-traffic", async (context) =>
{
try
{
var timeRange = context.QueryParams["timeRange"] ?? "1hour";
var page = int.Parse(context.QueryParams["page"] ?? "1");
var pageSize = int.Parse(context.QueryParams["pageSize"] ?? "100");
// 根据时间范围计算天数,对于1hour特殊处理
IEnumerable<AppNetworkTopInfo> allApps;
if (timeRange == "1hour")
{
// 对于1小时数据,我们需要特殊处理,因为GetTopAppsByTrafficAsync不支持小时查询
// 使用1天的数据作为替代,实际应用中可能需要创建专门的小时查询方法
allApps = await DatabaseManager.Instance.Networks.GetTopAppsByTrafficAsync(pageSize * page, 1);
}
else
{
int days = timeRange switch
{
"1day" => 1,
"7days" => 7,
"30days" => 30,
_ => 1
};
// 获取总数据(限制较大数量用于分页)
allApps = await DatabaseManager.Instance.Networks.GetTopAppsByTrafficAsync(pageSize * page, days);
}
var totalCount = allApps.Count();
// 分页处理
var pagedApps = allApps.Skip((page - 1) * pageSize).Take(pageSize).ToList();
var items = new List<object>();
int rank = (page - 1) * pageSize + 1;
foreach (var app in pagedApps)
{
// GetTopAppsByTrafficAsync 已经通过 LEFT JOIN 获取了应用信息
// 只有当JOIN没有获取到信息时才额外查询AppInfo表
string processName = System.IO.Path.GetFileName(app.AppPath ?? "");
string displayName = app.AppName ?? "";
string iconBase64 = "";
string version = "";
string company = "";
var b = !string.IsNullOrEmpty(app.AppId);
var isNullOrEmpty = !string.IsNullOrEmpty(displayName);
if (isNullOrEmpty && b)
{
// 如果JOIN没有获取到应用名称,尝试单独查询
var appInfo = await DatabaseManager.Instance.AppInfos.GetAppByAppIdAsync(app.AppId);
if (appInfo != null)
{
displayName = appInfo.Name ?? "";
iconBase64 = appInfo.Base64Icon ?? "";
version = appInfo.Version ?? "";
company = appInfo.Company ?? "";
}
}
if (isNullOrEmpty)
{
displayName = processName.Replace(".exe", "") ?? "Unknown";
}
items.Add(new
{
rank = rank++,
processName = processName,
displayName = displayName,
processPath = app.AppPath ?? "",
appId = app.AppId ?? "",
icon = iconBase64,
version = version,
company = company,
totalBytes = app.TotalTraffic,
uploadBytes = app.TotalUpload,
connectionCount = app.ConnectionCount
});
}
await context.Response.WriteJsonAsync(new ResponseModel<object>
{
Success = true,
Data = new
{
total = totalCount,
page = page,
pageSize = pageSize,
items = items
},
Message = "获取软件流量排行成功"
});
}
catch (Exception ex)
{
context.Response.StatusCode = 500;
await context.Response.WriteJsonAsync(new ResponseModel<object>
{
Success = false,
Message = $"获取软件流量排行失败: {ex.Message}"
});
}
});
#endregion
#region 取消订阅
server.Post("/api/unsubscribe", async (context) =>
{
// 正确的做法:只处理请求体数据
if (string.IsNullOrEmpty(context.RequestBody))
{
context.Response.StatusCode = 400;
await context.Response.WriteJsonAsync(new ResponseModel<object>
{
Success = false,
Message = "请求体不能为空",
});
return;
}
// 解析请求数据
try
{
// 直接使用已经读取的数据
string requestData = context.RequestBody;
// 打印原始数据用于调试
string? uuid = context.Request.Headers["uuid"];
Log.Warning($"接收到的数据: {requestData}, 用户 Id: {uuid}");
var subscriptionInfo = JsonHelper.FromJson<SubscriptionInfo>(requestData);
if (subscriptionInfo != null && !string.IsNullOrWhiteSpace(uuid))
{
// 软件信息订阅
if (AppConfig.ApplicationInfoSubscribe.Equals(subscriptionInfo.SubscriptionType))
{
DispatchEngine.Instance.DeleteApplicationInfo(uuid);
}
// 进程信息订阅
else if (AppConfig.ProcessInfoSubscribe.Equals(subscriptionInfo.SubscriptionType))
{
DispatchEngine.Instance.DeleteProcessInfo(uuid);
}
// 特定应用详情订阅
else if (AppConfig.AppDetailInfoSubscribe.Equals(subscriptionInfo.SubscriptionType))
{
DispatchEngine.Instance.DeleteAppDetailInfo(uuid);
}
Log.Warning($"取消订阅的 名称: {subscriptionInfo.SubscriptionType}");
}
}
catch (JsonException ex)
{
context.Response.StatusCode = 400;
await context.Response.WriteJsonAsync(new ResponseModel<object>
{
Success = false,
Message = $"请求数据格式错误: {ex.Message}",
});
return;
}
// 返回响应 - 只序列化纯数据对象
context.Response.StatusCode = 200;
await context.Response.WriteJsonAsync(new ResponseModel<string>
{
Success = true,
Data = "成功",
Message = "取消订阅成功",
});
});
#endregion
#region 网络数据API接口
// 获取全局网络实时数据
server.Get("/api/network/global/realtime", async (context) =>
{
try
{
var networkCardGuid = context.QueryParams["networkCardGuid"];
// 获取网络接口状态
var interfaces = BasicNetworkMonitor.GetConnectedNetworkInterfaces();
var targetInterfaces = string.IsNullOrEmpty(networkCardGuid)
? interfaces
: interfaces.Where(i => i.Id == networkCardGuid).ToList();
var realtimeData = new List<object>();
foreach (var networkInterface in targetInterfaces)
{
var speed = BasicNetworkMonitor.CalculateSpeedById(networkInterface.Id);
realtimeData.Add(new
{
networkCardGuid = networkInterface.Id,
networkCardName = networkInterface.Name,
uploadSpeed = speed.UploadSpeed,
downloadSpeed = speed.DownloadSpeed,
uploadSpeedText = speed.UploadSpeedText,
downloadSpeedText = speed.DownloadSpeedText,
isConnected = networkInterface.IsConnected,
timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
});
}
await context.Response.WriteJsonAsync(new ResponseModel<object>
{
Success = true,
Data = realtimeData,
Message = "获取实时网络数据成功"
});
}
catch (Exception ex)
{
context.Response.StatusCode = 500;
await context.Response.WriteJsonAsync(new ResponseModel<object>
{
Success = false,
Message = $"获取实时网络数据失败: {ex.Message}"
});
}
});
// 获取实时活跃应用
server.Get("/api/realtime/active-apps", async (context) =>
{
try
{
var limit = int.Parse(context.QueryParams["limit"] ?? "10");
// 获取当前活跃的网络监控快照
var snapshot = GlobalNetworkMonitor.Instance.GetSnapshot();
var activeApps = snapshot.Applications
.Where(app => app.Connections.Any(c => c.IsActive && (c.BytesSent > 0 || c.BytesReceived > 0)))
.OrderByDescending(app => app.Connections.Sum(c => c.BytesSent + c.BytesReceived))
.Take(limit)
.Select(app => new
{
processId = app.ProcessId,
processName = app.ProgramInfo?.ProcessName ?? "Unknown",
path = app.ProgramInfo?.MainModulePath ?? "",
totalUploadBytes = app.Connections.Sum(c => c.BytesSent),
totalDownloadBytes = app.Connections.Sum(c => c.BytesReceived),
activeConnections = app.Connections.Count(c => c.IsActive),
icon = app.ProgramInfo?.IconBase64 ?? ""
})
.ToList();
await context.Response.WriteJsonAsync(new ResponseModel<object>
{
Success = true,
Data = activeApps,
Message = "获取实时活跃应用成功"
});
}
catch (Exception ex)
{
context.Response.StatusCode = 500;
await context.Response.WriteJsonAsync(new ResponseModel<object>
{
Success = false,
Message = $"获取实时活跃应用失败: {ex.Message}"
});
}
});
// 获取系统信息
server.Get("/api/system/info", async (context) =>
{
try
{
var systemInfo = new
{
version = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version?.ToString() ??
"Unknown",
isAdministrator = SysHelper.IsAdministrator(),
etwEnabled = SysHelper.IsAdministrator(), // ETW需要管理员权限
operatingSystem = Environment.OSVersion.ToString(),
machineName = Environment.MachineName,
userDomainName = Environment.UserDomainName,
userName = Environment.UserName,
processorCount = Environment.ProcessorCount,
workingSet = Environment.WorkingSet,
timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
};
await context.Response.WriteJsonAsync(new ResponseModel<object>
{
Success = true,
Data = systemInfo,
Message = "获取系统信息成功"
});
}
catch (Exception ex)
{
context.Response.StatusCode = 500;
await context.Response.WriteJsonAsync(new ResponseModel<object>
{
Success = false,
Message = $"获取系统信息失败: {ex.Message}"
});
}
});
// 获取数据收集统计信息
server.Get("/api/system/collection-stats", async (context) =>
{
try
{
// TODO: 实现获取NetworkDataCollectionService的统计信息
var stats = new
{
isRunning = true,
activeNetworkInterfaces = BasicNetworkMonitor.GetConnectedNetworkInterfaces().Count,
etwStatus = SysHelper.IsAdministrator() ? "运行中" : "需要管理员权限",
timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
};
await context.Response.WriteJsonAsync(new ResponseModel<object>
{
Success = true,
Data = stats,
Message = "获取收集统计信息成功"
});
}
catch (Exception ex)
{
context.Response.StatusCode = 500;
await context.Response.WriteJsonAsync(new ResponseModel<object>
{
Success = false,
Message = $"获取收集统计信息失败: {ex.Message}"
});
}
});
#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
{
await server.StartAsync();
}
catch (Exception ex)
{
Console.WriteLine($"Server error: {ex.Message}");
}
_serverManager?.Stop();
base.OnExit(e);
}
// 辅助方法:获取所有网卡的小时数据
@@ -2025,85 +323,6 @@ public partial class App : System.Windows.Application
};
}
/// <summary>
/// 开启网页服务
/// </summary>
/// <param name="port"></param>
private void StartHttpServer(int port)
{
HttpListener = new HttpListener();
string WebServerPort = $"http://127.0.0.1:{port}/";
WebServerPath = WebServerPort;
HttpListener.Prefixes.Add(WebServerPort);
HttpListener.Start();
ThreadPool.QueueUserWorkItem((_) =>
{
while (HttpListener.IsListening)
{
try
{
var context = HttpListener.GetContext();
string urlPath = context.Request.Url.AbsolutePath.TrimStart('/');
// 如果是根路径,直接返回 index.html
if (string.IsNullOrEmpty(urlPath))
{
urlPath = "index.html";
}
string localPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "wwwroot", urlPath);
// 检查文件是否存在
if (File.Exists(localPath))
{
// 文件存在,直接返回
byte[] buffer = File.ReadAllBytes(localPath);
context.Response.ContentType = GetContentType(localPath);
context.Response.ContentLength64 = buffer.Length;
context.Response.OutputStream.Write(buffer, 0, buffer.Length);
context.Response.OutputStream.Close();
}
else
{
// 文件不存在,判断是否为静态资源
if (IsStaticResource(urlPath))
{
// 静态资源不存在,返回 404
context.Response.StatusCode = 404;
context.Response.Close();
}
else
{
// 非静态资源,SPA fallback 到 index.html
string indexPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "wwwroot",
"index.html");
if (File.Exists(indexPath))
{
byte[] buffer = File.ReadAllBytes(indexPath);
context.Response.ContentType = "text/html";
context.Response.ContentLength64 = buffer.Length;
context.Response.OutputStream.Write(buffer, 0, buffer.Length);
context.Response.OutputStream.Close();
}
else
{
context.Response.StatusCode = 404;
context.Response.Close();
}
}
}
}
catch (Exception ex)
{
// log if needed
Console.WriteLine($"HTTP Server Error: {ex.Message}");
}
}
});
}
// 判断是否为静态资源
private bool IsStaticResource(string path)
{
@@ -2151,4 +370,4 @@ public class SubscriptionAppInfo : SubscriptionInfo
/// 订阅的应用程序路径
/// </summary>
public string ApplicationPath { get; set; }
}
}
@@ -0,0 +1,248 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Application.Utils;
using Common.Net.HttpConn;
using Common.Net.Models;
using Data;
using Data.Models;
using Data.Repositories;
using Shell.Utils;
namespace Shell.Controllers;
public class AppController : BaseController
{
public async Task GetTopTrafficAppsAsync(HttpContext context)
{
try
{
var timeRange = GetQueryParam(context, "timeRange", "1hour");
var page = int.Parse(GetQueryParam(context, "page", "1"));
var pageSize = int.Parse(GetQueryParam(context, "pageSize", "100"));
IEnumerable<AppNetworkTopInfo> allApps;
if (timeRange == "1hour")
{
allApps = await DatabaseManager.Instance.Networks.GetTopAppsByTrafficAsync(pageSize * page, 1);
}
else
{
int days = timeRange switch
{
"1day" => 1,
"7days" => 7,
"30days" => 30,
_ => 1
};
allApps = await DatabaseManager.Instance.Networks.GetTopAppsByTrafficAsync(pageSize * page, days);
}
var totalCount = allApps.Count();
var pagedApps = allApps.Skip((page - 1) * pageSize).Take(pageSize).ToList();
var items = new List<object>();
int rank = (page - 1) * pageSize + 1;
foreach (var app in pagedApps)
{
string processName = System.IO.Path.GetFileName(app.AppPath ?? "");
string displayName = app.AppName ?? "";
string iconBase64 = "";
string version = "";
string company = "";
var hasAppId = !string.IsNullOrEmpty(app.AppId);
var hasDisplayName = !string.IsNullOrEmpty(displayName);
if (hasDisplayName && hasAppId)
{
var appInfo = await DatabaseManager.Instance.AppInfos.GetAppByAppIdAsync(app.AppId);
if (appInfo != null)
{
displayName = appInfo.Name ?? "";
iconBase64 = appInfo.Base64Icon ?? "";
version = appInfo.Version ?? "";
company = appInfo.Company ?? "";
}
}
if (hasDisplayName)
{
displayName = processName.Replace(".exe", "") ?? "Unknown";
}
items.Add(new
{
rank = rank++,
processName = processName,
displayName = displayName,
processPath = app.AppPath ?? "",
appId = app.AppId ?? "",
icon = iconBase64,
version = version,
company = company,
totalBytes = app.TotalTraffic,
uploadBytes = app.TotalUpload,
connectionCount = app.ConnectionCount
});
}
await WriteJsonResponseAsync(context, new ResponseModel<object>
{
Success = true,
Data = new
{
total = totalCount,
page = page,
pageSize = pageSize,
items = items
},
Message = "获取软件流量排行成功"
});
}
catch (Exception ex)
{
await WriteErrorResponseAsync(context, $"获取软件流量排行失败: {ex.Message}", 500);
}
}
public async Task GetNetworkAnalysisAsync(HttpContext context)
{
try
{
var appId = GetQueryParam(context, "appId");
var timeRange = GetQueryParam(context, "timeRange", "1day");
if (string.IsNullOrEmpty(appId))
{
await WriteErrorResponseAsync(context, "缺少必需参数 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;
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 = NetworkApiHelper.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()
},
topConnections = topConnections,
protocolStats = protocolStats,
timeTrends = timeTrends,
portAnalysis = portAnalysis
};
await WriteJsonResponseAsync(context, new ResponseModel<object>
{
Success = true,
Data = result,
Message = "获取应用网络分析数据成功"
});
}
catch (Exception ex)
{
await WriteErrorResponseAsync(context, $"获取应用网络分析数据失败: {ex.Message}", 500);
}
}
}
@@ -0,0 +1,52 @@
using System;
using System.Text.Json;
using System.Threading.Tasks;
using Common.Net.HttpConn;
using Common.Net.Models;
using Common.Utils;
namespace Shell.Controllers;
public abstract class BaseController
{
protected async Task WriteJsonResponseAsync<T>(HttpContext context, ResponseModel<T> response, int statusCode = 200)
{
context.Response.StatusCode = statusCode;
await context.Response.WriteJsonAsync(response);
}
protected async Task WriteErrorResponseAsync(HttpContext context, string message, int statusCode = 400)
{
context.Response.StatusCode = statusCode;
await context.Response.WriteJsonAsync(new ResponseModel<object>
{
Success = false,
Message = message
});
}
protected T? ParseRequestBody<T>(string requestBody) where T : class
{
if (string.IsNullOrEmpty(requestBody))
return null;
try
{
return JsonHelper.FromJson<T>(requestBody);
}
catch (JsonException)
{
return null;
}
}
protected string? GetHeaderValue(HttpContext context, string headerName)
{
return context.Request.Headers[headerName];
}
protected string GetQueryParam(HttpContext context, string paramName, string defaultValue = "")
{
return context.Request.Headers[paramName] ?? defaultValue;
}
}
@@ -0,0 +1,301 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Common.Net.HttpConn;
using Common.Net.Models;
using Utils.Firewall;
namespace Shell.Controllers;
public class FirewallController : BaseController
{
public async Task GetRulesAsync(HttpContext context)
{
try
{
var firewallApi = new WindowsFirewallApi();
var startIndex = int.Parse(GetQueryParam(context, "start", "0"));
var limit = int.Parse(GetQueryParam(context, "limit", "50"));
var filter = new RuleFilter();
if (!string.IsNullOrEmpty(GetQueryParam(context, "name")))
filter.NamePattern = GetQueryParam(context, "name");
if (!string.IsNullOrEmpty(GetQueryParam(context, "direction")) &&
Enum.TryParse<RuleDirection>(GetQueryParam(context, "direction"), true, out var direction))
filter.Direction = direction;
if (!string.IsNullOrEmpty(GetQueryParam(context, "enabled")) &&
bool.TryParse(GetQueryParam(context, "enabled"), out var enabled))
filter.Enabled = enabled;
if (!string.IsNullOrEmpty(GetQueryParam(context, "protocol")) &&
Enum.TryParse<ProtocolType>(GetQueryParam(context, "protocol"), true, out var protocol))
filter.Protocol = protocol;
if (!string.IsNullOrEmpty(GetQueryParam(context, "action")) &&
Enum.TryParse<RuleAction>(GetQueryParam(context, "action"), true, out var action))
filter.Action = action;
if (!string.IsNullOrEmpty(GetQueryParam(context, "application")))
filter.ApplicationName = GetQueryParam(context, "application");
if (!string.IsNullOrEmpty(GetQueryParam(context, "port")))
filter.Port = GetQueryParam(context, "port");
if (!string.IsNullOrEmpty(GetQueryParam(context, "search")))
filter.SearchKeyword = GetQueryParam(context, "search");
var allRules = firewallApi.GetRulesByFilter(filter);
var totalCount = allRules.Count;
var pagedRules = allRules.Skip(startIndex).Take(limit).ToList();
await WriteJsonResponseAsync(context, new ResponseModel<object>
{
Success = true,
Data = new
{
rules = pagedRules,
totalCount = totalCount,
startIndex = startIndex,
limit = limit,
hasMore = startIndex + limit < totalCount
},
Message = "查询成功"
});
}
catch (Exception ex)
{
await WriteErrorResponseAsync(context, $"查询防火墙规则失败: {ex.Message}", 500);
}
}
public async Task CreateRuleAsync(HttpContext context)
{
try
{
if (string.IsNullOrEmpty(context.RequestBody))
{
await WriteErrorResponseAsync(context, "请求体不能为空");
return;
}
var createRequest = ParseRequestBody<CreateRuleRequest>(context.RequestBody);
if (createRequest == null)
{
await WriteErrorResponseAsync(context, "请求数据格式错误");
return;
}
var firewallApi = new WindowsFirewallApi();
var success = firewallApi.CreateRule(createRequest);
if (success)
{
await WriteJsonResponseAsync(context, new ResponseModel<object>
{
Success = true,
Message = "防火墙规则创建成功"
});
}
else
{
await WriteErrorResponseAsync(context, "防火墙规则创建失败", 500);
}
}
catch (Exception ex)
{
await WriteErrorResponseAsync(context, $"创建防火墙规则失败: {ex.Message}", 500);
}
}
public async Task UpdateRuleAsync(HttpContext context)
{
try
{
if (string.IsNullOrEmpty(context.RequestBody))
{
await WriteErrorResponseAsync(context, "请求体不能为空");
return;
}
var updateRequest = ParseRequestBody<UpdateRuleRequest>(context.RequestBody);
if (updateRequest == null || string.IsNullOrEmpty(updateRequest.CurrentName))
{
await WriteErrorResponseAsync(context, "请求数据格式错误或缺少规则名称");
return;
}
var firewallApi = new WindowsFirewallApi();
var success = firewallApi.UpdateRule(updateRequest);
if (success)
{
await WriteJsonResponseAsync(context, new ResponseModel<object>
{
Success = true,
Message = "防火墙规则更新成功"
});
}
else
{
await WriteErrorResponseAsync(context, "防火墙规则更新失败,可能规则不存在", 500);
}
}
catch (Exception ex)
{
await WriteErrorResponseAsync(context, $"更新防火墙规则失败: {ex.Message}", 500);
}
}
public async Task DeleteRuleAsync(HttpContext context)
{
try
{
var ruleName = GetQueryParam(context, "name");
if (string.IsNullOrEmpty(ruleName))
{
await WriteErrorResponseAsync(context, "缺少规则名称参数");
return;
}
var firewallApi = new WindowsFirewallApi();
var success = firewallApi.DeleteRule(ruleName);
if (success)
{
await WriteJsonResponseAsync(context, new ResponseModel<object>
{
Success = true,
Message = "防火墙规则删除成功"
});
}
else
{
await WriteErrorResponseAsync(context, "防火墙规则删除失败,可能规则不存在", 500);
}
}
catch (Exception ex)
{
await WriteErrorResponseAsync(context, $"删除防火墙规则失败: {ex.Message}", 500);
}
}
public async Task GetStatusAsync(HttpContext context)
{
try
{
var firewallApi = new WindowsFirewallApi();
var status = firewallApi.GetFirewallStatus();
await WriteJsonResponseAsync(context, new ResponseModel<FirewallStatus>
{
Success = true,
Data = status,
Message = "获取防火墙状态成功"
});
}
catch (Exception ex)
{
await WriteErrorResponseAsync(context, $"获取防火墙状态失败: {ex.Message}", 500);
}
}
public async Task GetStatisticsAsync(HttpContext context)
{
try
{
var firewallApi = new WindowsFirewallApi();
var statistics = firewallApi.GetStatistics();
await WriteJsonResponseAsync(context, new ResponseModel<FirewallStatistics>
{
Success = true,
Data = statistics,
Message = "获取防火墙统计信息成功"
});
}
catch (Exception ex)
{
await WriteErrorResponseAsync(context, $"获取防火墙统计信息失败: {ex.Message}", 500);
}
}
public async Task SwitchFirewallAsync(HttpContext context)
{
try
{
var enabledParam = GetQueryParam(context, "enabled");
var profileParam = GetQueryParam(context, "profile");
if (string.IsNullOrEmpty(enabledParam))
{
await WriteErrorResponseAsync(context, "缺少必需参数 'enabled',请使用 true 或 false");
return;
}
var trim = enabledParam.Trim();
bool enabled = trim switch
{
"true" or "TRUE" or "True" or "1" => true,
"false" or "FALSE" or "False" or "0" => false,
_ => throw new ArgumentException("enabled参数值无效,请使用 true/false 或 1/0")
};
FirewallProfile profile = string.IsNullOrEmpty(profileParam) ? FirewallProfile.All : profileParam switch
{
"domain" => FirewallProfile.Domain,
"private" => FirewallProfile.Private,
"public" => FirewallProfile.Public,
"all" => FirewallProfile.All,
_ => FirewallProfile.All
};
var api = new WindowsFirewallApi();
bool result;
string action;
if (enabled)
{
result = api.EnableFirewall(profile);
action = "启用";
}
else
{
result = api.DisableFirewall(profile);
action = "禁用";
}
if (result)
{
await WriteJsonResponseAsync(context, new ResponseModel<object>
{
Success = true,
Message = $"防火墙{action}成功 ({profile})",
Data = new
{
profile = profile.ToString(),
enabled = enabled,
action = action
}
});
}
else
{
await WriteJsonResponseAsync(context, new ResponseModel<object>
{
Success = false,
Message = $"防火墙{action}失败,可能需要管理员权限",
Data = null
});
}
}
catch (ArgumentException ex)
{
await WriteErrorResponseAsync(context, ex.Message);
}
catch (Exception ex)
{
await WriteJsonResponseAsync(context, new ResponseModel<object>
{
Success = false,
Message = $"操作防火墙时发生错误: {ex.Message}",
Data = null
});
}
}
}
@@ -0,0 +1,310 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Application.Utils;
using Common.Net.HttpConn;
using Common.Net.Models;
using Common.Utils;
using Data;
using Data.Models;
using Data.Repositories;
using Shell.Utils;
namespace Shell.Controllers;
public class NetworkController : BaseController
{
public async Task GetInterfacesAsync(HttpContext context)
{
try
{
var timeRange = GetQueryParam(context, "timeRange", "hour");
var cacheKey = $"interfaces_{timeRange}";
var now = DateTime.Now;
if (NetworkApiHelper.NetworkInterfaceCache.ContainsKey(cacheKey) &&
(now - NetworkApiHelper.CacheLastUpdateTime) < NetworkApiHelper.CacheExpireTime)
{
await WriteJsonResponseAsync(context, new ResponseModel<object>
{
Success = true,
Data = NetworkApiHelper.NetworkInterfaceCache[cacheKey],
Message = "获取网络接口列表成功(缓存)"
});
return;
}
var networkCardGuids = new HashSet<string>();
var startTime = timeRange.ToLower() switch
{
"hour" => DateTimeOffset.UtcNow.AddHours(-1).ToUnixTimeSeconds(),
"day" => DateTimeOffset.UtcNow.AddDays(-1).ToUnixTimeSeconds(),
"week" => DateTimeOffset.UtcNow.AddDays(-7).ToUnixTimeSeconds(),
"month" => DateTimeOffset.UtcNow.AddDays(-30).ToUnixTimeSeconds(),
_ => DateTimeOffset.UtcNow.AddHours(-1).ToUnixTimeSeconds()
};
var endTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
var data = await DatabaseManager.Instance.Networks.GetGlobalNetworkByTimeRangeAsync("", startTime, endTime);
foreach (var item in data)
networkCardGuids.Add(item.NetworkCardGuid);
if (!networkCardGuids.Any())
{
var connectedInterfaces = BasicNetworkMonitor.GetConnectedNetworkInterfaces();
foreach (var iface in connectedInterfaces)
{
networkCardGuids.Add(iface.Id);
}
}
var interfaces = new List<object>();
foreach (var guid in networkCardGuids)
{
var networkInfo = NetworkAdapterHelper.GetNetworkInfoByGuid(guid);
if (networkInfo != null)
{
interfaces.Add(new
{
id = guid,
name = networkInfo.NetConnectionID ?? networkInfo.Name,
displayName = networkInfo.Description ?? networkInfo.Name,
isActive = true,
macAddress = networkInfo.MACAddress
});
}
else
{
interfaces.Add(new
{
id = guid,
name = $"Network Interface {guid.Substring(0, 8)}",
displayName = $"Network Adapter {guid.Substring(0, 8)}",
isActive = true,
macAddress = ""
});
}
}
NetworkApiHelper.NetworkInterfaceCache[cacheKey] = interfaces;
NetworkApiHelper.UpdateCacheTime();
await WriteJsonResponseAsync(context, new ResponseModel<object>
{
Success = true,
Data = interfaces,
Message = "获取网络接口列表成功"
});
}
catch (Exception ex)
{
await WriteErrorResponseAsync(context, $"获取网络接口列表失败: {ex.Message}", 500);
}
}
public async Task GetAvailableRangesAsync(HttpContext context)
{
try
{
var availableRanges = new List<object>();
var now = DateTimeOffset.UtcNow;
var hasHourlyData = await NetworkApiHelper.HasNetworkDataInTimeRangeAsync("hourly", now.AddHours(-1).ToUnixTimeSeconds());
if (hasHourlyData)
{
availableRanges.Add(new
{
type = "hour",
name = "1小时",
available = true,
startTime = now.AddHours(-1).ToString("yyyy-MM-ddTHH:mm:ssZ")
});
}
var hasDailyData = await NetworkApiHelper.HasNetworkDataInTimeRangeAsync("daily", now.AddDays(-1).ToUnixTimeSeconds());
if (hasDailyData)
{
availableRanges.Add(new
{
type = "day",
name = "1天",
available = true,
startTime = now.AddDays(-1).ToString("yyyy-MM-ddTHH:mm:ssZ")
});
}
var hasWeeklyData = await NetworkApiHelper.HasNetworkDataInTimeRangeAsync("daily", now.AddDays(-7).ToUnixTimeSeconds());
if (hasWeeklyData)
{
availableRanges.Add(new
{
type = "week",
name = "1周",
available = true,
startTime = now.AddDays(-7).ToString("yyyy-MM-ddTHH:mm:ssZ")
});
}
var hasMonthlyData = await NetworkApiHelper.HasNetworkDataInTimeRangeAsync("daily", now.AddDays(-30).ToUnixTimeSeconds());
if (hasMonthlyData)
{
availableRanges.Add(new
{
type = "month",
name = "1个月",
available = true,
startTime = now.AddDays(-30).ToString("yyyy-MM-ddTHH:mm:ssZ")
});
}
if (!availableRanges.Any())
{
availableRanges.Add(new
{
type = "hour",
name = "1小时",
available = true,
startTime = now.ToString("yyyy-MM-ddTHH:mm:ssZ")
});
}
await WriteJsonResponseAsync(context, new ResponseModel<object>
{
Success = true,
Data = availableRanges.First(),
Message = "获取可用时间范围成功"
});
}
catch (Exception ex)
{
await WriteErrorResponseAsync(context, $"获取可用时间范围失败: {ex.Message}", 500);
}
}
public async Task ClearCacheAsync(HttpContext context)
{
try
{
NetworkApiHelper.NetworkInterfaceCache.Clear();
await WriteJsonResponseAsync(context, new ResponseModel<string>
{
Success = true,
Data = "缓存已清理",
Message = "网络接口缓存清理成功"
});
}
catch (Exception ex)
{
await WriteErrorResponseAsync(context, $"清理缓存失败: {ex.Message}", 500);
}
}
public async Task GetTrafficTrendsAsync(HttpContext context)
{
try
{
var timeRange = GetQueryParam(context, "timeRange", "1hour");
var interfaceId = GetQueryParam(context, "interfaceId", "all");
var now = DateTimeOffset.UtcNow;
var (startTime, mappedTimeRange) = timeRange switch
{
"1hour" => (now.AddHours(-1).ToUnixTimeSeconds(), "hour"),
"1day" => (now.AddDays(-1).ToUnixTimeSeconds(), "day"),
"7days" => (now.AddDays(-7).ToUnixTimeSeconds(), "week"),
"30days" => (now.AddDays(-30).ToUnixTimeSeconds(), "month"),
_ => (now.AddHours(-1).ToUnixTimeSeconds(), "hour")
};
var points = await NetworkApiHelper.GetTrafficTrendsAsync(mappedTimeRange, interfaceId, startTime, now.ToUnixTimeSeconds());
await WriteJsonResponseAsync(context, new ResponseModel<object>
{
Success = true,
Data = new
{
@interface = interfaceId,
timeRange = timeRange,
points = points
},
Message = "获取流量趋势数据成功"
});
}
catch (Exception ex)
{
await WriteErrorResponseAsync(context, $"获取流量趋势数据失败: {ex.Message}", 500);
}
}
public async Task GetTopAppsAsync(HttpContext context)
{
try
{
var timeRange = GetQueryParam(context, "timeRange", "1hour");
var limit = int.Parse(GetQueryParam(context, "limit", "10"));
IEnumerable<AppNetworkTopInfo> topApps;
if (timeRange == "1hour")
{
topApps = await DatabaseManager.Instance.Networks.GetTopAppsByTrafficAsync(limit, 1);
}
else
{
int days = timeRange switch
{
"1day" => 1,
"7days" => 7,
"30days" => 30,
_ => 1
};
topApps = await DatabaseManager.Instance.Networks.GetTopAppsByTrafficAsync(limit, days);
}
var result = new List<object>();
foreach (var app in topApps)
{
string processName = System.IO.Path.GetFileName(app.AppPath ?? "");
string displayName = app.AppName ?? "";
string iconBase64 = "";
if (!string.IsNullOrEmpty(displayName) && !string.IsNullOrEmpty(app.AppId))
{
var appInfo = await DatabaseManager.Instance.AppInfos.GetAppByAppIdAsync(app.AppId);
if (appInfo != null)
{
displayName = appInfo.Name ?? "";
iconBase64 = appInfo.Base64Icon ?? "";
}
}
if (string.IsNullOrEmpty(displayName))
{
displayName = processName.Replace(".exe", "") ?? "Unknown";
}
result.Add(new
{
processName = processName,
displayName = displayName,
icon = iconBase64,
totalBytes = app.TotalTraffic
});
}
await WriteJsonResponseAsync(context, new ResponseModel<object>
{
Success = true,
Data = result,
Message = "获取Top应用流量数据成功"
});
}
catch (Exception ex)
{
await WriteErrorResponseAsync(context, $"获取Top应用流量数据失败: {ex.Message}", 500);
}
}
}
@@ -0,0 +1,198 @@
using System;
using System.Text.Json;
using System.Threading.Tasks;
using Application;
using Application.Utils;
using Common;
using Common.Logger;
using Common.Net.HttpConn;
using Common.Net.Models;
using Common.Utils;
using Shell.Models;
namespace Shell.Controllers;
public class SubscriptionController : BaseController
{
public async Task SubscribeApplicationAsync(HttpContext context)
{
if (string.IsNullOrEmpty(context.RequestBody))
{
await WriteErrorResponseAsync(context, "请求体不能为空");
return;
}
try
{
string? uuid = GetHeaderValue(context, "uuid");
Log.Warning($"接收到的数据: {context.RequestBody}, 用户 Id: {uuid}");
if (string.IsNullOrWhiteSpace(uuid))
{
await WriteErrorResponseAsync(context, "用户 ID 丢失!");
return;
}
var subscriptionInfo = ParseRequestBody<SubscriptionInfo>(context.RequestBody);
if (subscriptionInfo != null)
{
DispatchEngine.Instance.AddApplicationInfo(uuid, new DispatchModel
{
Interval = subscriptionInfo.Interval
});
Log.Warning($"时间: {subscriptionInfo.Interval}");
}
}
catch (JsonException ex)
{
await WriteErrorResponseAsync(context, $"请求数据格式错误: {ex.Message}");
return;
}
await WriteJsonResponseAsync(context, new ResponseModel<string>
{
Success = true,
Data = "成功",
Message = "订阅成功"
});
}
public async Task SubscribeProcessAsync(HttpContext context)
{
if (string.IsNullOrEmpty(context.RequestBody))
{
await WriteErrorResponseAsync(context, "请求体不能为空");
return;
}
try
{
string? uuid = GetHeaderValue(context, "uuid");
if (string.IsNullOrWhiteSpace(uuid))
{
await WriteErrorResponseAsync(context, "用户 ID 丢失!");
return;
}
var subscriptionInfo = ParseRequestBody<SubscriptionProcessInfo>(context.RequestBody);
if (subscriptionInfo != null)
{
DispatchEngine.Instance.DeleteProcessInfo(uuid);
DispatchEngine.Instance.AddProcessInfo(uuid, new ProcessDispatchModel
{
Interval = subscriptionInfo.Interval,
ProcessIds = subscriptionInfo.ProcessIds
});
Log.Warning($"时间: {subscriptionInfo.Interval}");
Log.Warning($"监视的 ID : {subscriptionInfo.ProcessIds}");
}
}
catch (JsonException ex)
{
await WriteErrorResponseAsync(context, $"请求数据格式错误: {ex.Message}");
return;
}
await WriteJsonResponseAsync(context, new ResponseModel<string>
{
Success = true,
Data = "成功",
Message = "订阅成功"
});
}
public async Task SubscribeAppInfoAsync(HttpContext context)
{
if (string.IsNullOrEmpty(context.RequestBody))
{
await WriteErrorResponseAsync(context, "请求体不能为空");
return;
}
try
{
string? uuid = GetHeaderValue(context, "uuid");
if (string.IsNullOrWhiteSpace(uuid))
{
await WriteErrorResponseAsync(context, "用户 ID 丢失!");
return;
}
var subscriptionRequest = ParseRequestBody<SubscriptionAppInfo>(context.RequestBody);
if (subscriptionRequest != null && !string.IsNullOrWhiteSpace(subscriptionRequest.ApplicationPath))
{
DispatchEngine.Instance.AddAppDetailInfo(uuid, new AppDetailDispatchModel
{
Interval = subscriptionRequest.Interval,
ApplicationPath = subscriptionRequest.ApplicationPath
});
Log.Information($"客户端 {uuid} 订阅了应用详情: {subscriptionRequest.ApplicationPath}");
}
else
{
await WriteErrorResponseAsync(context, "无效的订阅请求,缺少 'ApplicationPath'");
return;
}
}
catch (JsonException ex)
{
await WriteErrorResponseAsync(context, $"请求数据格式错误: {ex.Message}");
return;
}
await WriteJsonResponseAsync(context, new ResponseModel<string>
{
Success = true,
Data = "成功",
Message = "订阅成功"
});
}
public async Task UnsubscribeAsync(HttpContext context)
{
if (string.IsNullOrEmpty(context.RequestBody))
{
await WriteErrorResponseAsync(context, "请求体不能为空");
return;
}
try
{
string? uuid = GetHeaderValue(context, "uuid");
Log.Warning($"接收到的数据: {context.RequestBody}, 用户 Id: {uuid}");
var subscriptionInfo = ParseRequestBody<SubscriptionInfo>(context.RequestBody);
if (subscriptionInfo != null && !string.IsNullOrWhiteSpace(uuid))
{
if (AppConfig.ApplicationInfoSubscribe.Equals(subscriptionInfo.SubscriptionType))
{
DispatchEngine.Instance.DeleteApplicationInfo(uuid);
}
else if (AppConfig.ProcessInfoSubscribe.Equals(subscriptionInfo.SubscriptionType))
{
DispatchEngine.Instance.DeleteProcessInfo(uuid);
}
else if (AppConfig.AppDetailInfoSubscribe.Equals(subscriptionInfo.SubscriptionType))
{
DispatchEngine.Instance.DeleteAppDetailInfo(uuid);
}
Log.Warning($"取消订阅的 名称: {subscriptionInfo.SubscriptionType}");
}
}
catch (JsonException ex)
{
await WriteErrorResponseAsync(context, $"请求数据格式错误: {ex.Message}");
return;
}
await WriteJsonResponseAsync(context, new ResponseModel<string>
{
Success = true,
Data = "成功",
Message = "取消订阅成功"
});
}
}
@@ -0,0 +1,147 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Application.Utils;
using Common.Net.HttpConn;
using Common.Net.Models;
using Common.Utils;
using Utils.ETW.Etw;
namespace Shell.Controllers;
public class SystemController : BaseController
{
public async Task GetRealtimeNetworkDataAsync(HttpContext context)
{
try
{
var networkCardGuid = GetQueryParam(context, "networkCardGuid");
var interfaces = BasicNetworkMonitor.GetConnectedNetworkInterfaces();
var targetInterfaces = string.IsNullOrEmpty(networkCardGuid)
? interfaces
: interfaces.Where(i => i.Id == networkCardGuid).ToList();
var realtimeData = new List<object>();
foreach (var networkInterface in targetInterfaces)
{
var speed = BasicNetworkMonitor.CalculateSpeedById(networkInterface.Id);
realtimeData.Add(new
{
networkCardGuid = networkInterface.Id,
networkCardName = networkInterface.Name,
uploadSpeed = speed.UploadSpeed,
downloadSpeed = speed.DownloadSpeed,
uploadSpeedText = speed.UploadSpeedText,
downloadSpeedText = speed.DownloadSpeedText,
isConnected = networkInterface.IsConnected,
timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
});
}
await WriteJsonResponseAsync(context, new ResponseModel<object>
{
Success = true,
Data = realtimeData,
Message = "获取实时网络数据成功"
});
}
catch (Exception ex)
{
await WriteErrorResponseAsync(context, $"获取实时网络数据失败: {ex.Message}", 500);
}
}
public async Task GetActiveAppsAsync(HttpContext context)
{
try
{
var limit = int.Parse(GetQueryParam(context, "limit", "10"));
var snapshot = GlobalNetworkMonitor.Instance.GetSnapshot();
var activeApps = snapshot.Applications
.Where(app => app.Connections.Any(c => c.IsActive && (c.BytesSent > 0 || c.BytesReceived > 0)))
.OrderByDescending(app => app.Connections.Sum(c => c.BytesSent + c.BytesReceived))
.Take(limit)
.Select(app => new
{
processId = app.ProcessId,
processName = app.ProgramInfo?.ProcessName ?? "Unknown",
path = app.ProgramInfo?.MainModulePath ?? "",
totalUploadBytes = app.Connections.Sum(c => c.BytesSent),
totalDownloadBytes = app.Connections.Sum(c => c.BytesReceived),
activeConnections = app.Connections.Count(c => c.IsActive),
icon = app.ProgramInfo?.IconBase64 ?? ""
})
.ToList();
await WriteJsonResponseAsync(context, new ResponseModel<object>
{
Success = true,
Data = activeApps,
Message = "获取实时活跃应用成功"
});
}
catch (Exception ex)
{
await WriteErrorResponseAsync(context, $"获取实时活跃应用失败: {ex.Message}", 500);
}
}
public async Task GetSystemInfoAsync(HttpContext context)
{
try
{
var systemInfo = new
{
version = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version?.ToString() ?? "Unknown",
isAdministrator = SysHelper.IsAdministrator(),
etwEnabled = SysHelper.IsAdministrator(),
operatingSystem = Environment.OSVersion.ToString(),
machineName = Environment.MachineName,
userDomainName = Environment.UserDomainName,
userName = Environment.UserName,
processorCount = Environment.ProcessorCount,
workingSet = Environment.WorkingSet,
timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
};
await WriteJsonResponseAsync(context, new ResponseModel<object>
{
Success = true,
Data = systemInfo,
Message = "获取系统信息成功"
});
}
catch (Exception ex)
{
await WriteErrorResponseAsync(context, $"获取系统信息失败: {ex.Message}", 500);
}
}
public async Task GetCollectionStatsAsync(HttpContext context)
{
try
{
var stats = new
{
isRunning = true,
activeNetworkInterfaces = BasicNetworkMonitor.GetConnectedNetworkInterfaces().Count,
etwStatus = SysHelper.IsAdministrator() ? "运行中" : "需要管理员权限",
timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
};
await WriteJsonResponseAsync(context, new ResponseModel<object>
{
Success = true,
Data = stats,
Message = "获取收集统计信息成功"
});
}
catch (Exception ex)
{
await WriteErrorResponseAsync(context, $"获取收集统计信息失败: {ex.Message}", 500);
}
}
}
@@ -0,0 +1,15 @@
using Application;
using Common.Net.Models;
namespace Shell.Models;
/// <summary>
/// 特定应用详情订阅的请求体模型
/// </summary>
public class SubscriptionAppInfo : SubscriptionInfo
{
/// <summary>
/// 订阅的应用程序路径
/// </summary>
public string ApplicationPath { get; set; }
}
@@ -0,0 +1,144 @@
using System;
using System.Threading.Tasks;
using Common.Net.HttpConn;
using Common.Net.Models;
using Shell.Controllers;
namespace Shell.Services;
public class ApiRouteManager : IDisposable
{
private HttpServer? _server;
private readonly SubscriptionController _subscriptionController;
private readonly FirewallController _firewallController;
private readonly NetworkController _networkController;
private readonly AppController _appController;
private readonly SystemController _systemController;
private bool _isDisposed;
public ApiRouteManager()
{
_subscriptionController = new SubscriptionController();
_firewallController = new FirewallController();
_networkController = new NetworkController();
_appController = new AppController();
_systemController = new SystemController();
}
public async Task StartAsync(string serverPath)
{
_server = new HttpServer(serverPath);
// 添加中间件
_server.UseMiddleware(Middlewares.RequestLogging);
_server.UseMiddleware(Middlewares.Cors);
// 基础API
_server.Get("/api", async (context) =>
{
await context.Response.WriteJsonAsync(new { message = "Hi!" });
});
// 订阅相关API
RegisterSubscriptionRoutes();
// 防火墙相关API
RegisterFirewallRoutes();
// 网络统计API
RegisterNetworkRoutes();
// 流量数据API
RegisterTrafficRoutes();
// 应用排行API
RegisterAppRoutes();
// 取消订阅API
RegisterUnsubscribeRoutes();
// 网络数据API
RegisterNetworkDataRoutes();
// 应用详细网络分析API
RegisterAppAnalysisRoutes();
try
{
await _server.StartAsync();
}
catch (Exception ex)
{
Console.WriteLine($"Server error: {ex.Message}");
}
}
private void RegisterSubscriptionRoutes()
{
_server.Post("/api/subscribe-application", _subscriptionController.SubscribeApplicationAsync);
_server.Post("/api/subscribe-process", _subscriptionController.SubscribeProcessAsync);
_server.Post("/api/subscribe-appinfo", _subscriptionController.SubscribeAppInfoAsync);
}
private void RegisterFirewallRoutes()
{
_server.Get("/api/firewall/rules", _firewallController.GetRulesAsync);
_server.Post("/api/firewall/rules", _firewallController.CreateRuleAsync);
_server.Put("/api/firewall/rules", _firewallController.UpdateRuleAsync);
_server.Delete("/api/firewall/rules", _firewallController.DeleteRuleAsync);
_server.Get("/api/firewall/status", _firewallController.GetStatusAsync);
_server.Get("/api/firewall/statistics", _firewallController.GetStatisticsAsync);
_server.Post("/api/firewall/switch", _firewallController.SwitchFirewallAsync);
}
private void RegisterNetworkRoutes()
{
_server.Get("/api/statistics/interfaces", _networkController.GetInterfacesAsync);
_server.Get("/api/statistics/available-ranges", _networkController.GetAvailableRangesAsync);
_server.Post("/api/statistics/clear-cache", _networkController.ClearCacheAsync);
}
private void RegisterTrafficRoutes()
{
_server.Get("/api/traffic/trends", _networkController.GetTrafficTrendsAsync);
_server.Get("/api/traffic/top-apps", _networkController.GetTopAppsAsync);
}
private void RegisterAppRoutes()
{
_server.Get("/api/apps/top-traffic", _appController.GetTopTrafficAppsAsync);
}
private void RegisterUnsubscribeRoutes()
{
_server.Post("/api/unsubscribe", _subscriptionController.UnsubscribeAsync);
}
private void RegisterNetworkDataRoutes()
{
_server.Get("/api/network/global/realtime", _systemController.GetRealtimeNetworkDataAsync);
_server.Get("/api/realtime/active-apps", _systemController.GetActiveAppsAsync);
_server.Get("/api/system/info", _systemController.GetSystemInfoAsync);
_server.Get("/api/system/collection-stats", _systemController.GetCollectionStatsAsync);
}
private void RegisterAppAnalysisRoutes()
{
_server.Get("/api/apps/network-analysis", _appController.GetNetworkAnalysisAsync);
}
public void Stop()
{
_server?.Stop();
}
public void Dispose()
{
if (!_isDisposed)
{
Stop();
_server = null;
_isDisposed = true;
}
}
}
@@ -0,0 +1,77 @@
using System;
using System.Net.Http;
using System.Threading.Tasks;
using System.Windows.Threading;
using Common.Logger;
using MessageBox = System.Windows.Forms.MessageBox;
namespace Shell.Services;
public class GlobalExceptionHandler
{
private static GlobalExceptionHandler? _instance;
public static GlobalExceptionHandler Instance => _instance ??= new GlobalExceptionHandler();
public void Initialize()
{
System.Windows.Application.Current.DispatcherUnhandledException += App_DispatcherUnhandledException;
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
}
private void HandleException(Exception? exception)
{
Log.Error("未捕获异常", exception);
if (exception != null)
{
MessageBox.Show($"恭喜你发现了BUG,请联系管理员解决:{exception.Message}");
}
}
private void App_DispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
{
Log.Error(e.Exception, "UI线程异常");
HandleException(e.Exception);
if (CanRecover(e.Exception))
{
e.Handled = true;
}
}
private void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
{
foreach (var ex in e.Exception.InnerExceptions)
{
Log.Error(e.Exception, "UI线程异常");
}
e.SetObserved();
}
private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
var exception = e.ExceptionObject as Exception;
Log.Error(exception, "非UI线程异常");
if (e.IsTerminating)
{
Log.Fatal("应用程序即将因未处理异常而终止");
}
}
private bool CanRecover(Exception exception)
{
return exception switch
{
ArgumentException => true,
InvalidOperationException => true,
HttpRequestException => true,
TimeoutException => true,
OutOfMemoryException => false,
StackOverflowException => false,
AccessViolationException => false,
_ => true
};
}
}
@@ -0,0 +1,96 @@
using System;
using System.Threading.Tasks;
using Application;
using Application.Utils;
using Common;
using Common.Logger;
using Common.Net.WebSocketConn;
using Common.Utils;
using Utils.ETW.Etw;
namespace Shell.Services;
public class ServerStartupManager
{
private readonly ApiRouteManager _apiRouteManager;
private readonly StaticFileServer _staticFileServer;
public ServerStartupManager()
{
_apiRouteManager = new ApiRouteManager();
_staticFileServer = new StaticFileServer();
}
public async Task StartAllServicesAsync()
{
await InitializeEtwMonitoringAsync();
StartWebSocketServer();
StartApiServer();
StartStaticFileServer();
}
private async Task InitializeEtwMonitoringAsync()
{
if (!SysHelper.IsAdministrator())
{
Console.WriteLine("此程序需要管理员权限才能运行ETW监控!");
Console.WriteLine("请以管理员身份重新运行程序。");
Console.ReadKey();
return;
}
var networkManager = new EnhancedEtwNetworkManager();
networkManager.StartMonitoring();
}
private void StartWebSocketServer()
{
int port = SysHelper.GetAvailablePort();
AppConfig.Instance.WebSocketPort = port;
AppConfig.Instance.WebSocketPath = $"ws://127.0.0.1:{port}";
Log.Information4Ctx($"服务启动在端口: {AppConfig.Instance.WebSocketPort}");
Log.Information($"服务完整地址: {AppConfig.Instance.WebSocketPath}");
WebSocketManager.Instance.Start(AppConfig.Instance.WebSocketPath);
WebSocketManager.Instance.SubscribeConnectionClosed((args =>
{
if (args.Uuid != null)
{
DispatchEngine.Instance.DeleteApplicationInfo(args.Uuid);
DispatchEngine.Instance.DeleteProcessInfo(args.Uuid);
DispatchEngine.Instance.DeleteAppDetailInfo(args.Uuid);
}
}));
DispatchEngine.Instance.ApplicationInfoDistribute();
DispatchEngine.Instance.ProcessInfoDistribute();
DispatchEngine.Instance.AppDetailInfoDistribute();
}
private void StartApiServer()
{
int port = SysHelper.GetAvailablePort();
AppConfig.Instance.HttpApiPort = port;
AppConfig.Instance.HttpApiPath = $"http://127.0.0.1:{port}";
Task.Run(() => _apiRouteManager.StartAsync(AppConfig.Instance.HttpApiPath));
}
private void StartStaticFileServer()
{
// int port = SysHelper.GetAvailablePort();
int port = 3000;
AppConfig.Instance.HttpPort = port;
AppConfig.Instance.HttpPath = $"http://127.0.0.1:{port}";
// _staticFileServer.Start(AppConfig.Instance.HttpPort);
}
public void Stop()
{
_staticFileServer?.Stop();
_apiRouteManager?.Stop();
}
}
@@ -0,0 +1,151 @@
using System;
using System.IO;
using System.Net;
using System.Threading;
using Common;
namespace Shell.Services;
public class StaticFileServer : IDisposable
{
private HttpListener? _httpListener;
private string? _webServerPath;
private bool _isDisposed;
public string? WebServerPath => _webServerPath;
public void Start(int port)
{
_httpListener = new HttpListener();
_webServerPath = $"http://127.0.0.1:{port}/";
_httpListener.Prefixes.Add(_webServerPath);
_httpListener.Start();
ThreadPool.QueueUserWorkItem((_) =>
{
while (_httpListener.IsListening && !_isDisposed)
{
try
{
var context = _httpListener.GetContext();
ProcessRequest(context);
}
catch (Exception ex)
{
Console.WriteLine($"HTTP Server Error: {ex.Message}");
}
}
});
}
private void ProcessRequest(HttpListenerContext context)
{
string urlPath = context.Request.Url.AbsolutePath.TrimStart('/');
if (string.IsNullOrEmpty(urlPath))
{
urlPath = "index.html";
}
string localPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "wwwroot", urlPath);
if (File.Exists(localPath))
{
ServeFile(context, localPath);
}
else
{
HandleFileNotFound(context, urlPath);
}
}
private void ServeFile(HttpListenerContext context, string localPath)
{
byte[] buffer = File.ReadAllBytes(localPath);
context.Response.ContentType = GetContentType(localPath);
context.Response.ContentLength64 = buffer.Length;
context.Response.OutputStream.Write(buffer, 0, buffer.Length);
context.Response.OutputStream.Close();
}
private void HandleFileNotFound(HttpListenerContext context, string urlPath)
{
if (IsStaticResource(urlPath))
{
context.Response.StatusCode = 404;
context.Response.Close();
}
else
{
ServeSpaFallback(context);
}
}
private void ServeSpaFallback(HttpListenerContext context)
{
string indexPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "wwwroot", "index.html");
if (File.Exists(indexPath))
{
byte[] buffer = File.ReadAllBytes(indexPath);
context.Response.ContentType = "text/html";
context.Response.ContentLength64 = buffer.Length;
context.Response.OutputStream.Write(buffer, 0, buffer.Length);
context.Response.OutputStream.Close();
}
else
{
context.Response.StatusCode = 404;
context.Response.Close();
}
}
private bool IsStaticResource(string path)
{
string[] staticExtensions =
{
".js", ".css", ".png", ".jpg", ".jpeg", ".gif", ".ico", ".svg", ".woff", ".woff2", ".ttf", ".eot", ".map"
};
string extension = Path.GetExtension(path).ToLower();
return staticExtensions.Contains(extension);
}
private string GetContentType(string filePath)
{
string extension = Path.GetExtension(filePath).ToLower();
return extension switch
{
".html" or ".htm" => "text/html",
".css" => "text/css",
".js" => "application/javascript",
".mjs" => "application/javascript",
".json" => "application/json",
".png" => "image/png",
".jpg" or ".jpeg" => "image/jpeg",
".gif" => "image/gif",
".svg" => "image/svg+xml",
".ico" => "image/x-icon",
".woff" => "font/woff",
".woff2" => "font/woff2",
".ttf" => "font/ttf",
".eot" => "application/vnd.ms-fontobject",
".map" => "application/json",
_ => "application/octet-stream"
};
}
public void Stop()
{
_httpListener?.Stop();
_httpListener?.Close();
}
public void Dispose()
{
if (!_isDisposed)
{
Stop();
_httpListener?.Close();
_isDisposed = true;
}
}
}