A:添加软件信息获取接口

This commit is contained in:
jimmy committed 2025-07-10 16:46:23 +08:00
1 parent 1587eb62ee
commit 54c44fc7cd
4 files changed
+266 -14

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;
@@ -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>
+4 -2
View File
@@ -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";
} }
+71 -6
View File
@@ -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>