A:添加软件信息获取接口
This commit is contained in:
1 parent
1587eb62ee
commit
54c44fc7cd
4 files changed
+272
-20
No files matched your search
@@ -1,4 +1,5 @@
|
|||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
using System.Text;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
using Common;
|
using Common;
|
||||||
@@ -270,7 +271,7 @@ public class DispatchEngine
|
|||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取用户的订阅信息
|
// 获取用户的订阅信息
|
||||||
var dispatchModel = processDispatchModel.Value;
|
var dispatchModel = processDispatchModel.Value;
|
||||||
|
|
||||||
@@ -278,7 +279,7 @@ public class DispatchEngine
|
|||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<ProcessType> pts = new List<ProcessType>();
|
List<ProcessType> pts = new List<ProcessType>();
|
||||||
foreach (var processId in dispatchModel.ProcessIds)
|
foreach (var processId in dispatchModel.ProcessIds)
|
||||||
{
|
{
|
||||||
@@ -289,7 +290,7 @@ public class DispatchEngine
|
|||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
ProcessType pt = new ProcessType();
|
ProcessType pt = new ProcessType();
|
||||||
// 上行总数据
|
// 上行总数据
|
||||||
long allBytesSent = 0;
|
long allBytesSent = 0;
|
||||||
@@ -328,10 +329,10 @@ public class DispatchEngine
|
|||||||
pt.UploadSpeed = allSendSpeed;
|
pt.UploadSpeed = allSendSpeed;
|
||||||
pt.DownloadSpeed = allReceiveSpeed;
|
pt.DownloadSpeed = allReceiveSpeed;
|
||||||
pt.Connections = connection;
|
pt.Connections = connection;
|
||||||
|
|
||||||
pts.Add(pt);
|
pts.Add(pt);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 数据整理完毕,开始分发内容
|
// 数据整理完毕,开始分发内容
|
||||||
var json = JsonHelper.ToJson(pts);
|
var json = JsonHelper.ToJson(pts);
|
||||||
AppInfoLastSendTime[processDispatchModel.Key] = DateTime.Now;
|
AppInfoLastSendTime[processDispatchModel.Key] = DateTime.Now;
|
||||||
@@ -344,12 +345,185 @@ public class DispatchEngine
|
|||||||
Type = AppConfig.ProcessInfoSubscribe
|
Type = AppConfig.ProcessInfoSubscribe
|
||||||
};
|
};
|
||||||
WebSocketManager.Instance.SendToClient(processDispatchModel.Key, rm);
|
WebSocketManager.Instance.SendToClient(processDispatchModel.Key, rm);
|
||||||
|
|
||||||
}
|
}
|
||||||
}, null, TimeSpan.Zero, TimeSpan.FromSeconds(1));
|
}, null, TimeSpan.Zero, TimeSpan.FromSeconds(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region 软件详情信息订阅
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 特定软件详情的订阅
|
||||||
|
/// </summary>
|
||||||
|
private ConcurrentDictionary<string, AppDetailDispatchModel> AppDetailInfoDispatch = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 特定软件详情上次发送时间
|
||||||
|
/// </summary>
|
||||||
|
private ConcurrentDictionary<string, DateTime> AppDetailInfoLastSendTime = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 添加订阅
|
||||||
|
/// </summary>
|
||||||
|
public void AddAppDetailInfo(string clientId, AppDetailDispatchModel model)
|
||||||
|
{
|
||||||
|
// 一个客户端只能订阅一个应用的详情,新的订阅会覆盖旧的
|
||||||
|
AppDetailInfoDispatch[clientId] = model;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 删除订阅
|
||||||
|
/// </summary>
|
||||||
|
public void DeleteAppDetailInfo(string clientId)
|
||||||
|
{
|
||||||
|
AppDetailInfoDispatch.TryRemove(clientId, out _);
|
||||||
|
AppDetailInfoLastSendTime.TryRemove(clientId, out _);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Timer _appDetailInfoTimer;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 特定软件详情分发
|
||||||
|
/// </summary>
|
||||||
|
public void AppDetailInfoDistribute()
|
||||||
|
{
|
||||||
|
_appDetailInfoTimer = new Timer(state =>
|
||||||
|
{
|
||||||
|
if (AppDetailInfoDispatch.IsEmpty)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var keyValuePair in AppDetailInfoDispatch)
|
||||||
|
{
|
||||||
|
var clientId = keyValuePair.Key;
|
||||||
|
var dispatchModel = keyValuePair.Value;
|
||||||
|
|
||||||
|
// 检查是否到了发送时间
|
||||||
|
var isShouldSend = false;
|
||||||
|
if (AppDetailInfoLastSendTime.TryGetValue(clientId, out var lastSendTime))
|
||||||
|
{
|
||||||
|
if ((DateTime.Now - lastSendTime).TotalMilliseconds >= dispatchModel.Interval)
|
||||||
|
{
|
||||||
|
isShouldSend = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
isShouldSend = true; // 第一次发送
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isShouldSend)
|
||||||
|
{
|
||||||
|
// 获取数据
|
||||||
|
var appDetails = GetApplicationDetails(dispatchModel.ApplicationPath);
|
||||||
|
if (appDetails != null)
|
||||||
|
{
|
||||||
|
// 发送数据
|
||||||
|
var rm = new ResponseMessage
|
||||||
|
{
|
||||||
|
Success = true,
|
||||||
|
Data = JsonHelper.ToJson(appDetails),
|
||||||
|
Message = "特定应用信息发送",
|
||||||
|
Timestamp = DateTime.Now,
|
||||||
|
Type = AppConfig.AppDetailInfoSubscribe
|
||||||
|
};
|
||||||
|
WebSocketManager.Instance.SendToClient(clientId, rm);
|
||||||
|
|
||||||
|
// 更新发送时间
|
||||||
|
AppDetailInfoLastSendTime[clientId] = DateTime.Now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, null, TimeSpan.Zero, TimeSpan.FromSeconds(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取指定路径应用程序的详细信息
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="applicationPath">应用程序的主模块路径</param>
|
||||||
|
/// <returns>聚合后的应用程序信息,如果未找到则返回 null</returns>
|
||||||
|
private ApplicationInfoModel? GetApplicationDetails(string applicationPath)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(applicationPath))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 获取所有正在运行的程序信息
|
||||||
|
var allPrograms = GlobalNetworkMonitor.Instance.GetAllPrograms();
|
||||||
|
|
||||||
|
// 2. 根据路径筛选出相关的所有进程
|
||||||
|
var matchingPrograms = allPrograms
|
||||||
|
.Where(p => applicationPath.Equals(p.MainModulePath, StringComparison.OrdinalIgnoreCase))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (matchingPrograms.Count == 0)
|
||||||
|
{
|
||||||
|
return null; // 没有找到匹配的程序
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 聚合信息(与 ApplicationInfoDistribute 中的逻辑类似)
|
||||||
|
var pi = new ApplicationInfoModel
|
||||||
|
{
|
||||||
|
Id = "null" // 稍后生成
|
||||||
|
};
|
||||||
|
var pids = new List<int>();
|
||||||
|
var startTime = DateTime.MaxValue;
|
||||||
|
var isExit = true;
|
||||||
|
var useMemory = 0L;
|
||||||
|
var threadCount = 0;
|
||||||
|
var idBuilder = new StringBuilder();
|
||||||
|
|
||||||
|
foreach (var programInfo in matchingPrograms)
|
||||||
|
{
|
||||||
|
pi.ProcessName ??= programInfo.ProcessName;
|
||||||
|
pi.ExitTime ??= programInfo.ExitTime;
|
||||||
|
pi.ExitCode ??= programInfo.ExitCode;
|
||||||
|
pi.MainModulePath ??= programInfo.MainModulePath;
|
||||||
|
pi.MainModuleName ??= programInfo.MainModuleName;
|
||||||
|
pi.FileDescription ??= programInfo.FileDescription;
|
||||||
|
pi.ProductName ??= programInfo.ProductName;
|
||||||
|
pi.CompanyName ??= programInfo.CompanyName;
|
||||||
|
pi.Version ??= programInfo.Version;
|
||||||
|
pi.LegalCopyright ??= programInfo.LegalCopyright;
|
||||||
|
// 使用最新的图标
|
||||||
|
if (!string.IsNullOrEmpty(programInfo.IconBase64))
|
||||||
|
{
|
||||||
|
pi.IconBase64 = programInfo.IconBase64;
|
||||||
|
}
|
||||||
|
|
||||||
|
idBuilder.Append(programInfo.ProcessId);
|
||||||
|
pids.Add(programInfo.ProcessId);
|
||||||
|
|
||||||
|
// 记录最早的启动时间
|
||||||
|
if (programInfo.StartTime < startTime)
|
||||||
|
{
|
||||||
|
startTime = programInfo.StartTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 只要有一个进程在运行,应用就未退出
|
||||||
|
if (!programInfo.HasExited)
|
||||||
|
{
|
||||||
|
isExit = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
useMemory += programInfo.UseMemory;
|
||||||
|
threadCount += programInfo.ThreadCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
pi.Id = idBuilder.ToString();
|
||||||
|
pi.ProcessIds = pids;
|
||||||
|
pi.StartTime = startTime;
|
||||||
|
pi.HasExited = isExit;
|
||||||
|
pi.UseMemory = useMemory;
|
||||||
|
pi.ThreadCount = threadCount;
|
||||||
|
|
||||||
|
return pi;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -372,6 +546,17 @@ public class ProcessDispatchModel : DispatchModel
|
|||||||
public List<int>? ProcessIds { get; set; }
|
public List<int>? ProcessIds { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 特定应用详情订阅模型
|
||||||
|
/// </summary>
|
||||||
|
public class AppDetailDispatchModel : DispatchModel
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 订阅的应用程序路径
|
||||||
|
/// </summary>
|
||||||
|
public string ApplicationPath { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 订阅信息内容
|
/// 订阅信息内容
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ public sealed class AppConfig
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 订阅字符串
|
/// 订阅字符串
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly string ApplicationInfoSubscribe= "ApplicationInfo";
|
public static readonly string ApplicationInfoSubscribe = "ApplicationInfo";
|
||||||
public static readonly string ProcessInfoSubscribe= "ProcessInfo";
|
|
||||||
}
|
public static readonly string ProcessInfoSubscribe = "ProcessInfo";
|
||||||
|
public static readonly string AppDetailInfoSubscribe = "AppDetailInfo";
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
using System.Configuration;
|
using System.Configuration;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
@@ -15,6 +15,7 @@ using Common.Net.HttpConn;
|
|||||||
using Common.Net.Models;
|
using Common.Net.Models;
|
||||||
using Common.Net.WebSocketConn;
|
using Common.Net.WebSocketConn;
|
||||||
using Common.Utils;
|
using Common.Utils;
|
||||||
|
using Infrastructure.Models;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Serilog.Events;
|
using Serilog.Events;
|
||||||
using Utils.ETW.Etw;
|
using Utils.ETW.Etw;
|
||||||
@@ -140,12 +141,14 @@ public partial class App : System.Windows.Application
|
|||||||
{
|
{
|
||||||
DispatchEngine.Instance.DeleteApplicationInfo(args.Uuid);
|
DispatchEngine.Instance.DeleteApplicationInfo(args.Uuid);
|
||||||
DispatchEngine.Instance.DeleteProcessInfo(args.Uuid);
|
DispatchEngine.Instance.DeleteProcessInfo(args.Uuid);
|
||||||
|
DispatchEngine.Instance.DeleteAppDetailInfo(args.Uuid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
// 开启 WebSocket 定时发送服务
|
// 开启 WebSocket 定时发送服务
|
||||||
DispatchEngine.Instance.ApplicationInfoDistribute();
|
DispatchEngine.Instance.ApplicationInfoDistribute();
|
||||||
DispatchEngine.Instance.ProcessInfoDistribute();
|
DispatchEngine.Instance.ProcessInfoDistribute();
|
||||||
|
DispatchEngine.Instance.AppDetailInfoDistribute();
|
||||||
|
|
||||||
// 启动 http 服务
|
// 启动 http 服务
|
||||||
Task.Run(() => { _ = StartHttpServer(); });
|
Task.Run(() => { _ = StartHttpServer(); });
|
||||||
@@ -323,13 +326,59 @@ public partial class App : System.Windows.Application
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region 软件信息获取
|
#region 软件详细信息订阅
|
||||||
|
|
||||||
server.Get("/api/appinfo",
|
server.Post("/api/subscribe-appinfo", async (context) =>
|
||||||
async (context) =>
|
{
|
||||||
|
if (string.IsNullOrEmpty(context.RequestBody))
|
||||||
{
|
{
|
||||||
await context.Response.WriteJsonAsync(new { message = "Hi!" });
|
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
|
#endregion
|
||||||
|
|
||||||
@@ -372,6 +421,11 @@ public partial class App : System.Windows.Application
|
|||||||
{
|
{
|
||||||
DispatchEngine.Instance.DeleteProcessInfo(uuid);
|
DispatchEngine.Instance.DeleteProcessInfo(uuid);
|
||||||
}
|
}
|
||||||
|
// 特定应用详情订阅
|
||||||
|
else if (AppConfig.AppDetailInfoSubscribe.Equals(subscriptionInfo.SubscriptionType))
|
||||||
|
{
|
||||||
|
DispatchEngine.Instance.DeleteAppDetailInfo(uuid);
|
||||||
|
}
|
||||||
|
|
||||||
Log.Warning($"取消订阅的 名称: {subscriptionInfo.SubscriptionType}");
|
Log.Warning($"取消订阅的 名称: {subscriptionInfo.SubscriptionType}");
|
||||||
}
|
}
|
||||||
@@ -410,3 +464,14 @@ public partial class App : System.Windows.Application
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 特定应用详情订阅的请求体模型
|
||||||
|
/// </summary>
|
||||||
|
public class SubscriptionAppInfo : SubscriptionInfo
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 订阅的应用程序路径
|
||||||
|
/// </summary>
|
||||||
|
public string ApplicationPath { get; set; }
|
||||||
|
}
|
||||||
@@ -40,12 +40,12 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- 时间轴分割线 -->
|
<!-- 时间轴分割线 -->
|
||||||
<div class="resize-handle-horizontal" @mousedown="startTimelineResize">
|
<!-- <div class="resize-handle-horizontal" @mousedown="startTimelineResize">-->
|
||||||
<div class="resize-handle-hover-h"></div>
|
<!-- <div class="resize-handle-hover-h"></div>-->
|
||||||
</div>
|
<!-- </div>-->
|
||||||
|
|
||||||
<!-- 时间轴事件流 - 现在更简洁了 -->
|
<!-- 时间轴事件流 -->
|
||||||
<EventTimeline :height="timelineHeight" />
|
<!-- <EventTimeline :height="timelineHeight" />-->
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user