diff --git a/aspnet-core/modules/system-info/LINGYUN.Abp.SystemInfo.HttpApi/FodyWeavers.xml b/aspnet-core/modules/system-info/LINGYUN.Abp.SystemInfo.HttpApi/FodyWeavers.xml
new file mode 100644
index 000000000..5d6962159
--- /dev/null
+++ b/aspnet-core/modules/system-info/LINGYUN.Abp.SystemInfo.HttpApi/FodyWeavers.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/aspnet-core/modules/system-info/LINGYUN.Abp.SystemInfo.HttpApi/FodyWeavers.xsd b/aspnet-core/modules/system-info/LINGYUN.Abp.SystemInfo.HttpApi/FodyWeavers.xsd
new file mode 100644
index 000000000..3f3946e28
--- /dev/null
+++ b/aspnet-core/modules/system-info/LINGYUN.Abp.SystemInfo.HttpApi/FodyWeavers.xsd
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.
+
+
+
+
+ A comma-separated list of error codes that can be safely ignored in assembly verification.
+
+
+
+
+ 'false' to turn off automatic generation of the XML Schema file.
+
+
+
+
+
\ No newline at end of file
diff --git a/aspnet-core/modules/system-info/LINGYUN.Abp.SystemInfo.HttpApi/LINGYUN.Abp.SystemInfo.HttpApi.csproj b/aspnet-core/modules/system-info/LINGYUN.Abp.SystemInfo.HttpApi/LINGYUN.Abp.SystemInfo.HttpApi.csproj
new file mode 100644
index 000000000..e43e91925
--- /dev/null
+++ b/aspnet-core/modules/system-info/LINGYUN.Abp.SystemInfo.HttpApi/LINGYUN.Abp.SystemInfo.HttpApi.csproj
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+ net9.0
+ LINGYUN.Abp.SystemInfo.HttpApi
+ LINGYUN.Abp.SystemInfo.HttpApi
+ false
+ false
+ false
+
+
+
+
+
+
+
+
+
+
diff --git a/aspnet-core/modules/system-info/LINGYUN.Abp.SystemInfo.HttpApi/LINGYUN/Abp/SystemInfo/AbpSystemInfoHttpApiModule.cs b/aspnet-core/modules/system-info/LINGYUN.Abp.SystemInfo.HttpApi/LINGYUN/Abp/SystemInfo/AbpSystemInfoHttpApiModule.cs
new file mode 100644
index 000000000..a36834de0
--- /dev/null
+++ b/aspnet-core/modules/system-info/LINGYUN.Abp.SystemInfo.HttpApi/LINGYUN/Abp/SystemInfo/AbpSystemInfoHttpApiModule.cs
@@ -0,0 +1,17 @@
+using Microsoft.Extensions.DependencyInjection;
+using Volo.Abp.AspNetCore.Mvc;
+using Volo.Abp.Modularity;
+
+namespace LINGYUN.Abp.SystemInfo;
+
+[DependsOn(typeof(AbpAspNetCoreMvcModule))]
+public class AbpSystemInfoHttpApiModule : AbpModule
+{
+ public override void PreConfigureServices(ServiceConfigurationContext context)
+ {
+ PreConfigure(mvcBuilder =>
+ {
+ mvcBuilder.AddApplicationPartIfNotExists(typeof(AbpSystemInfoHttpApiModule).Assembly);
+ });
+ }
+}
diff --git a/aspnet-core/modules/system-info/LINGYUN.Abp.SystemInfo.HttpApi/LINGYUN/Abp/SystemInfo/Models/ComponentInfoModel.cs b/aspnet-core/modules/system-info/LINGYUN.Abp.SystemInfo.HttpApi/LINGYUN/Abp/SystemInfo/Models/ComponentInfoModel.cs
new file mode 100644
index 000000000..216f36741
--- /dev/null
+++ b/aspnet-core/modules/system-info/LINGYUN.Abp.SystemInfo.HttpApi/LINGYUN/Abp/SystemInfo/Models/ComponentInfoModel.cs
@@ -0,0 +1,27 @@
+using System.Collections.Generic;
+
+namespace LINGYUN.Abp.SystemInfo.Models;
+///
+/// 组件
+///
+public class ComponentInfoModel
+{
+ ///
+ /// 组件名称
+ ///
+ public string Name { get; set; }
+ ///
+ /// 组件名称集合
+ ///
+ public ComponentKeyModel[] Keys { get; set; }
+ ///
+ /// 组件状态集合
+ ///
+ public Dictionary Details { get; set; }
+ public ComponentInfoModel(string name, ComponentKeyModel[] keys, Dictionary details)
+ {
+ Name = name;
+ Keys = keys;
+ Details = details;
+ }
+}
diff --git a/aspnet-core/modules/system-info/LINGYUN.Abp.SystemInfo.HttpApi/LINGYUN/Abp/SystemInfo/Models/ComponentKeyModel.cs b/aspnet-core/modules/system-info/LINGYUN.Abp.SystemInfo.HttpApi/LINGYUN/Abp/SystemInfo/Models/ComponentKeyModel.cs
new file mode 100644
index 000000000..6299bb203
--- /dev/null
+++ b/aspnet-core/modules/system-info/LINGYUN.Abp.SystemInfo.HttpApi/LINGYUN/Abp/SystemInfo/Models/ComponentKeyModel.cs
@@ -0,0 +1,20 @@
+namespace LINGYUN.Abp.SystemInfo.Models;
+///
+/// 组件名称
+///
+public class ComponentKeyModel
+{
+ ///
+ /// 组件名称
+ ///
+ public string Name { get; set; }
+ ///
+ /// 组件显示名称
+ ///
+ public string DisplayName { get; set; }
+ public ComponentKeyModel(string name, string displayName)
+ {
+ Name = name;
+ DisplayName = displayName;
+ }
+}
diff --git a/aspnet-core/modules/system-info/LINGYUN.Abp.SystemInfo.HttpApi/LINGYUN/Abp/SystemInfo/Models/SystemInfoModel.cs b/aspnet-core/modules/system-info/LINGYUN.Abp.SystemInfo.HttpApi/LINGYUN/Abp/SystemInfo/Models/SystemInfoModel.cs
new file mode 100644
index 000000000..e12a794af
--- /dev/null
+++ b/aspnet-core/modules/system-info/LINGYUN.Abp.SystemInfo.HttpApi/LINGYUN/Abp/SystemInfo/Models/SystemInfoModel.cs
@@ -0,0 +1,9 @@
+namespace LINGYUN.Abp.SystemInfo.Models;
+
+public class SystemInfoModel
+{
+ ///
+ /// 组件状态集合
+ ///
+ public ComponentInfoModel[] Components { get; set; }
+}
diff --git a/aspnet-core/modules/system-info/LINGYUN.Abp.SystemInfo.HttpApi/LINGYUN/Abp/SystemInfo/Permissions/SystemInfoPermissionDefinitionProvider.cs b/aspnet-core/modules/system-info/LINGYUN.Abp.SystemInfo.HttpApi/LINGYUN/Abp/SystemInfo/Permissions/SystemInfoPermissionDefinitionProvider.cs
new file mode 100644
index 000000000..479c2b8a2
--- /dev/null
+++ b/aspnet-core/modules/system-info/LINGYUN.Abp.SystemInfo.HttpApi/LINGYUN/Abp/SystemInfo/Permissions/SystemInfoPermissionDefinitionProvider.cs
@@ -0,0 +1,20 @@
+using Volo.Abp.Authorization.Permissions;
+using Volo.Abp.Localization;
+
+namespace LINGYUN.Abp.SystemInfo.Permissions;
+
+public class SystemInfoPermissionDefinitionProvider : PermissionDefinitionProvider
+{
+ public override void Define(IPermissionDefinitionContext context)
+ {
+ var systemInfoGroup = context.AddGroup(
+ SystemInfoPermissions.GroupName,
+ new FixedLocalizableString("系统详情"));
+
+ systemInfoGroup.AddPermission(
+ SystemInfoPermissions.Default,
+ new FixedLocalizableString("获取系统详情"),
+ Volo.Abp.MultiTenancy.MultiTenancySides.Host)
+ .WithProviders(ClientPermissionValueProvider.ProviderName); // 使用客户端授权
+ }
+}
diff --git a/aspnet-core/modules/system-info/LINGYUN.Abp.SystemInfo.HttpApi/LINGYUN/Abp/SystemInfo/Permissions/SystemInfoPermissions.cs b/aspnet-core/modules/system-info/LINGYUN.Abp.SystemInfo.HttpApi/LINGYUN/Abp/SystemInfo/Permissions/SystemInfoPermissions.cs
new file mode 100644
index 000000000..702cf70d6
--- /dev/null
+++ b/aspnet-core/modules/system-info/LINGYUN.Abp.SystemInfo.HttpApi/LINGYUN/Abp/SystemInfo/Permissions/SystemInfoPermissions.cs
@@ -0,0 +1,8 @@
+namespace LINGYUN.Abp.SystemInfo.Permissions;
+
+public static class SystemInfoPermissions
+{
+ public const string GroupName = "SystemInfo";
+
+ public const string Default = GroupName + ".Get";
+}
diff --git a/aspnet-core/modules/system-info/LINGYUN.Abp.SystemInfo.HttpApi/LINGYUN/Abp/SystemInfo/SystemInfoController.cs b/aspnet-core/modules/system-info/LINGYUN.Abp.SystemInfo.HttpApi/LINGYUN/Abp/SystemInfo/SystemInfoController.cs
new file mode 100644
index 000000000..76e091c4c
--- /dev/null
+++ b/aspnet-core/modules/system-info/LINGYUN.Abp.SystemInfo.HttpApi/LINGYUN/Abp/SystemInfo/SystemInfoController.cs
@@ -0,0 +1,361 @@
+using DotNetCore.CAP;
+using LINGYUN.Abp.SystemInfo.Models;
+using LINGYUN.Abp.SystemInfo.Permissions;
+using LINGYUN.Abp.SystemInfo.Utils;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+using StackExchange.Redis;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Runtime.InteropServices;
+using System.Threading.Tasks;
+using Volo.Abp.AspNetCore.Mvc;
+
+namespace LINGYUN.Abp.SystemInfo;
+
+[Controller]
+[Route("api/system-info")]
+[Authorize(SystemInfoPermissions.Default)]
+public class SystemInfoController : AbpControllerBase
+{
+ [HttpGet]
+ public async virtual Task GetSystemInfoAsync()
+ {
+ return new SystemInfoModel
+ {
+ Components = new ComponentInfoModel[]
+ {
+ GetSystemInfo(),
+ GetPerformanceInfo(),
+ GetRedisInfo(),
+ GetCapInfo(),
+ },
+ };
+ }
+
+ private ComponentInfoModel GetSystemInfo()
+ {
+ var env = HttpContext.RequestServices.GetService();
+ using var process = Process.GetCurrentProcess();
+
+ var systemKeys = new List
+ {
+ new ComponentKeyModel("sys_machine_name", "机器名称"),
+ new ComponentKeyModel("sys_environment", "运行环境"),
+ new ComponentKeyModel("sys_framework_version", ".NET版本"),
+ new ComponentKeyModel("sys_os_version", "操作系统版本"),
+ new ComponentKeyModel("sys_app_db_provider", "数据库"),
+ new ComponentKeyModel("sys_app_version", "应用版本"),
+ new ComponentKeyModel("sys_app_build_time", "编译时间"),
+ new ComponentKeyModel("sys_app_start_time", "启动时间"),
+ };
+
+ var systemDetails = new Dictionary
+ {
+ { "sys_machine_name", Environment.MachineName },
+ { "sys_environment", env?.EnvironmentName ?? Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") },
+ { "sys_framework_version", RuntimeInformation.FrameworkDescription },
+ { "sys_os_version", RuntimeInformation.OSDescription },
+ { "sys_app_db_provider", Environment.GetEnvironmentVariable("APPLICATION_DATABASE_PROVIDER") },
+ { "sys_app_start_time", process.StartTime.ToString("yyyy-MM-dd HH:mm:ss") },
+ };
+
+ try
+ {
+ var assembly = Assembly.GetEntryAssembly() ?? Assembly.GetExecutingAssembly();
+ var fileVersionInfo = FileVersionInfo.GetVersionInfo(assembly.Location);
+ var assemblyName = assembly.GetName();
+ var buildTime = GetBuildTime(assembly);
+
+ systemDetails.Add("sys_app_version", fileVersionInfo.FileVersion ?? assemblyName.Version?.ToString() ?? "N/A");
+ systemDetails.Add("sys_app_build_time", buildTime.HasValue ? buildTime.Value.ToString("yyyy-MM-dd HH:mm:ss") : "N/A");
+ }
+ catch
+ {
+ systemDetails.Add("sys_app_version", "N/A");
+ systemDetails.Add("sys_app_build_time", "N/A");
+ }
+
+ return new ComponentInfoModel("系统", systemKeys.ToArray(), systemDetails);
+ }
+
+ private ComponentInfoModel GetPerformanceInfo()
+ {
+ using var process = Process.GetCurrentProcess();
+
+ var memoryMetrics = new MemoryMonitor().GetMemoryMetrics();
+ var threadMetrics = new ThreadMonitor().GetThreadMetrics();
+ var gcMetrics = new GCMonitor().GetGCMetrics();
+
+ return new ComponentInfoModel(
+ "性能",
+ new ComponentKeyModel[]
+ {
+ new ComponentKeyModel("perf_total_memory", "总内存(MB)"),
+ new ComponentKeyModel("perf_working_set", "工作集内存(MB)"),
+ new ComponentKeyModel("perf_private_memory", "私有内存(MB)"),
+ new ComponentKeyModel("perf_gc_heap_size", "GC堆大小(MB)"),
+ new ComponentKeyModel("perf_gc0", "Gen0回收次数"),
+ new ComponentKeyModel("perf_gc1", "Gen1回收次数"),
+ new ComponentKeyModel("perf_gc2", "Gen2回收次数"),
+ new ComponentKeyModel("perf_active_total_memory", "总可用内存"),
+ new ComponentKeyModel("perf_memory_load", "内存负载"),
+ new ComponentKeyModel("perf_fragmented_bytes", "内存碎片大小"),
+ new ComponentKeyModel("perf_available_worker_threads", "可用工作线程数"),
+ new ComponentKeyModel("perf_available_completion_port_threads", "可用I/O线程数"),
+ new ComponentKeyModel("perf_max_worker_threads", "最大工作线程数"),
+ new ComponentKeyModel("perf_max_completion_port_threads", "最大I/O线程数"),
+ new ComponentKeyModel("perf_active_threads", "进程总线程数"),
+ new ComponentKeyModel("perf_thread_pool_thread_count", "线程池活动线程数"),
+ },
+ new Dictionary
+ {
+ { "perf_total_memory", memoryMetrics.TotalMemory },
+ { "perf_working_set", memoryMetrics.WorkingSet },
+ { "perf_private_memory", memoryMetrics.PrivateMemory },
+ { "perf_gc_heap_size", memoryMetrics.GCHeapSize },
+ { "perf_gc0", gcMetrics.Gen0Collections },
+ { "perf_gc1", gcMetrics.Gen1Collections },
+ { "perf_gc2", gcMetrics.Gen2Collections },
+ { "perf_active_total_memory", gcMetrics.TotalMemory },
+ { "perf_memory_load", gcMetrics.MemoryLoad },
+ { "perf_fragmented_bytes", gcMetrics.FragmentedBytes },
+ { "perf_available_worker_threads", threadMetrics.AvailableWorkerThreads },
+ { "perf_available_completion_port_threads", threadMetrics.AvailableCompletionPortThreads },
+ { "perf_max_worker_threads", threadMetrics.MaxWorkerThreads },
+ { "perf_max_completion_port_threads", threadMetrics.MaxCompletionPortThreads },
+ { "perf_active_threads", threadMetrics.ActiveThreads },
+ { "perf_thread_pool_thread_count", threadMetrics.ThreadPoolThreadCount },
+ });
+ }
+
+ private ComponentInfoModel GetCapInfo()
+ {
+ var cap = HttpContext.RequestServices.GetService();
+ var storage = HttpContext.RequestServices.GetService();
+ var broker = HttpContext.RequestServices.GetService();
+
+ var capKeys = new List
+ {
+ new ComponentKeyModel("cap_status", "状态"),
+ new ComponentKeyModel("cap_version", "版本"),
+ new ComponentKeyModel("cap_storage", "持久化"),
+ new ComponentKeyModel("cap_transport", "传输"),
+ };
+ var capDetails = new Dictionary
+ {
+ { "cap_status", cap != null ? "正常" : "未启用" },
+ { "cap_version", cap != null ? cap.Version.Substring(0, 5) : "N/A" },
+ { "cap_storage", storage != null ? storage.Name : "N/A" },
+ { "cap_transport", broker != null ? broker.Name : "N/A" }
+ };
+
+ return new ComponentInfoModel("CAP组件", capKeys.ToArray(), capDetails);
+ }
+
+ private ComponentInfoModel GetRedisInfo()
+ {
+ var redis = HttpContext.RequestServices.GetService();
+ if (redis == null)
+ {
+ return new ComponentInfoModel(
+ "Redis组件",
+ new ComponentKeyModel[]
+ {
+ new ComponentKeyModel("redis_status", "状态")
+ },
+ new Dictionary
+ {
+ { "redis_status", "未注册" },
+ });
+ }
+ var redisInfo = new Dictionary();
+
+ try
+ {
+ var server = redis.GetServer(redis.GetEndPoints().First());
+ var serverInfo = server.Info();
+ foreach (var section in serverInfo)
+ {
+ foreach (var item in section)
+ {
+ redisInfo[item.Key] = item.Value;
+ }
+ }
+ }
+ catch(Exception ex)
+ {
+ Logger.LogWarning("There is an exception in connecting to the redis server: {message}", ex.Message);
+
+ return new ComponentInfoModel(
+ "Redis组件",
+ new ComponentKeyModel[]
+ {
+ new ComponentKeyModel("redis_status", "状态")
+ },
+ new Dictionary
+ {
+ { "redis_status", "连接异常" },
+ });
+ }
+
+ var uptimeSeconds = redisInfo.ContainsKey("uptime_in_seconds") ? long.Parse(redisInfo["uptime_in_seconds"]) : 0;
+ var days = Math.Floor((double)uptimeSeconds / 86400);
+ var hours = Math.Floor((double)uptimeSeconds % 86400 / 3600);
+ var minutes = Math.Floor((double)uptimeSeconds % 3600 / 60);
+
+ var usedMemory = redisInfo.ContainsKey("used_memory") ? long.Parse(redisInfo["used_memory"]) : 0;
+ var maxMemory = redisInfo.ContainsKey("maxmemory") ? long.Parse(redisInfo["maxmemory"]) : 0;
+ var totalKeys = CalculateTotalKeys(redisInfo);
+ var expiresKeys = CalculateExpires(redisInfo);
+ var avgTtl = CalculateAvgTtl(redisInfo);
+
+ var redisKeys = new List()
+ {
+ new ComponentKeyModel("redis_version", "版本"),
+ new ComponentKeyModel("redis_mode", "运行模式"),
+ new ComponentKeyModel("redis_os", "操作系统"),
+ new ComponentKeyModel("redis_uptime", "运行时间"),
+ new ComponentKeyModel("redis_used_cpu_sys", "系统CPU使用时间"),
+ new ComponentKeyModel("redis_used_cpu_user", "用户CPU使用时间"),
+ new ComponentKeyModel("redis_used_memory_mb", "使用内存(MB)"),
+ new ComponentKeyModel("redis_used_memory_rss_mb", "物理内存(MB)"),
+ new ComponentKeyModel("redis_used_memory_peak_mb", "内存使用峰值(MB)"),
+ new ComponentKeyModel("redis_maxmemory_mb", "最大内存限制(MB)"),
+ new ComponentKeyModel("redis_memory_usage_percent", "内存使用率(%)"),
+ new ComponentKeyModel("redis_mem_fragmentation_ratio", "内存碎片率"),
+ new ComponentKeyModel("redis_connected_clients", "已连接客户端数"),
+ new ComponentKeyModel("redis_blocked_clients", "阻塞客户端数"),
+ new ComponentKeyModel("redis_client_longest_output_list", "客户端最长输出列表"),
+ new ComponentKeyModel("redis_client_biggest_input_buf", "客户端最大输入缓冲区"),
+ new ComponentKeyModel("redis_maxclients", "最大客户端数"),
+ new ComponentKeyModel("redis_total_keys", "总键数量"),
+ new ComponentKeyModel("redis_expires_keys", "设置过期键数量"),
+ new ComponentKeyModel("redis_expired_keys", "已过期键数量"),
+ new ComponentKeyModel("redis_evicted_keys", "被驱逐键数量"),
+ new ComponentKeyModel("redis_avg_ttl_seconds", "平均TTL(秒)"),
+ };
+ var redisDetails = new Dictionary()
+ {
+ { "redis_version", redisInfo.GetValueOrDefault("redis_version", "unknown") },
+ { "redis_mode", redisInfo.GetValueOrDefault("redis_mode", "unknown") },
+ { "redis_os", redisInfo.GetValueOrDefault("os", "unknown") },
+ { "redis_uptime", $"{days}天{hours}时{minutes}分" },
+ { "redis_used_cpu_sys", redisInfo.GetValueOrDefault("used_cpu_sys", "0") },
+ { "redis_used_cpu_user", redisInfo.GetValueOrDefault("used_cpu_user", "0") },
+ { "redis_used_memory_mb", Math.Round((double)usedMemory / 1024 / 1024, 2) },
+ { "redis_used_memory_rss_mb", redisInfo.ContainsKey("used_memory_rss") ?
+ Math.Round(double.Parse(redisInfo["used_memory_rss"]) / 1024 / 1024, 2) : 0 },
+ { "redis_used_memory_peak_mb", redisInfo.ContainsKey("used_memory_peak") ?
+ Math.Round(double.Parse(redisInfo["used_memory_peak"]) / 1024 / 1024, 2) : 0 },
+ { "redis_maxmemory_mb", maxMemory > 0 ? Math.Round((double)maxMemory / 1024 / 1024, 2) : 0 },
+ { "redis_memory_usage_percent", maxMemory > 0 ? Math.Round((double)usedMemory / maxMemory * 100, 2) : 0 },
+ { "redis_mem_fragmentation_ratio", redisInfo.GetValueOrDefault("mem_fragmentation_ratio", "0") },
+ { "redis_connected_clients", redisInfo.GetValueOrDefault("connected_clients", "0") },
+ { "redis_blocked_clients", redisInfo.GetValueOrDefault("blocked_clients", "0") },
+ { "redis_client_longest_output_list", redisInfo.GetValueOrDefault("client_longest_output_list", "0") },
+ { "redis_client_biggest_input_buf", redisInfo.GetValueOrDefault("client_biggest_input_buf", "0") },
+ { "redis_maxclients", redisInfo.GetValueOrDefault("maxclients", "unlimited") },
+ { "redis_total_keys", totalKeys },
+ { "redis_expires_keys", expiresKeys },
+ { "redis_expired_keys", redisInfo.GetValueOrDefault("expired_keys", "0") },
+ { "redis_evicted_keys", redisInfo.GetValueOrDefault("evicted_keys", "0") },
+ { "redis_avg_ttl_seconds", avgTtl },
+ };
+
+ return new ComponentInfoModel("Redis组件", redisKeys.ToArray(), redisDetails);
+ }
+
+ private long CalculateTotalKeys(Dictionary redisInfo)
+ {
+ long total = 0;
+
+ foreach (var item in redisInfo)
+ {
+ if (item.Key.StartsWith("db"))
+ {
+ var keysPart = item.Value.Split(',').FirstOrDefault(x => x.StartsWith("keys="));
+ if (keysPart != null && long.TryParse(keysPart.Split('=')[1], out var count))
+ {
+ total += count;
+ }
+ }
+ }
+
+ return total;
+ }
+
+ private long CalculateExpires(Dictionary redisInfo)
+ {
+ long expires = 0;
+
+ foreach (var item in redisInfo)
+ {
+ if (item.Key.StartsWith("db"))
+ {
+ var expiresPart = item.Value.Split(',').FirstOrDefault(x => x.StartsWith("expires="));
+ if (expiresPart != null && long.TryParse(expiresPart.Split('=')[1], out var count))
+ {
+ expires += count;
+ }
+ }
+ }
+
+ return expires;
+ }
+
+ private long CalculateAvgTtl(Dictionary redisInfo)
+ {
+ long totalTtl = 0;
+ long dbCount = 0;
+
+ foreach (var item in redisInfo)
+ {
+ if (item.Key.StartsWith("db"))
+ {
+ var ttlPart = item.Value.Split(',').FirstOrDefault(x => x.StartsWith("avg_ttl="));
+ if (ttlPart != null && long.TryParse(ttlPart.Split('=')[1], out var ttl) && ttl > 0)
+ {
+ totalTtl += ttl;
+ dbCount++;
+ }
+ }
+ }
+
+ return dbCount > 0 ? totalTtl / dbCount : -1;
+ }
+
+ private DateTime? GetBuildTime(Assembly assembly)
+ {
+ try
+ {
+ var attribute = assembly.GetCustomAttribute();
+ if (attribute != null && attribute.Key == "BuildTimestamp")
+ {
+ if (DateTime.TryParse(attribute.Value, out var buildTime))
+ {
+ return buildTime;
+ }
+ }
+
+ var fileInfo = new FileInfo(assembly.Location);
+ if (fileInfo.Exists)
+ {
+ return fileInfo.LastWriteTime;
+ }
+
+ return null;
+ }
+ catch
+ {
+ return null;
+ }
+ }
+}
diff --git a/aspnet-core/modules/system-info/LINGYUN.Abp.SystemInfo.HttpApi/LINGYUN/Abp/SystemInfo/Utils/GCMonitor.cs b/aspnet-core/modules/system-info/LINGYUN.Abp.SystemInfo.HttpApi/LINGYUN/Abp/SystemInfo/Utils/GCMonitor.cs
new file mode 100644
index 000000000..b3f8cfa02
--- /dev/null
+++ b/aspnet-core/modules/system-info/LINGYUN.Abp.SystemInfo.HttpApi/LINGYUN/Abp/SystemInfo/Utils/GCMonitor.cs
@@ -0,0 +1,63 @@
+using System;
+
+namespace LINGYUN.Abp.SystemInfo.Utils;
+
+public class GCMetrics
+{
+ ///
+ /// Gen0回收次数
+ ///
+ public int Gen0Collections { get; set; }
+ ///
+ /// Gen1回收次数
+ ///
+ public int Gen1Collections { get; set; }
+ ///
+ /// Gen2回收次数
+ ///
+ public int Gen2Collections { get; set; }
+ ///
+ /// 总可用内存
+ ///
+ public long TotalMemory { get; set; }
+ ///
+ /// 内存负载
+ ///
+ public long MemoryLoad { get; set; }
+ ///
+ /// 内存碎片大小
+ ///
+ public long FragmentedBytes { get; set; }
+}
+
+public class GCMonitor
+{
+ private int _lastGen0;
+ private int _lastGen1;
+ private int _lastGen2;
+
+ public GCMetrics GetGCMetrics()
+ {
+ var currentGen0 = GC.CollectionCount(0);
+ var currentGen1 = GC.CollectionCount(1);
+ var currentGen2 = GC.CollectionCount(2);
+
+ var metrics = new GCMetrics
+ {
+ Gen0Collections = currentGen0 - _lastGen0,
+ Gen1Collections = currentGen1 - _lastGen1,
+ Gen2Collections = currentGen2 - _lastGen2
+ };
+
+ var gcInfo = GC.GetGCMemoryInfo();
+ metrics.TotalMemory = gcInfo.TotalAvailableMemoryBytes;
+ metrics.MemoryLoad = gcInfo.MemoryLoadBytes;
+ metrics.FragmentedBytes = gcInfo.FragmentedBytes;
+
+ _lastGen0 = currentGen0;
+ _lastGen1 = currentGen1;
+ _lastGen2 = currentGen2;
+
+ return metrics;
+ }
+}
diff --git a/aspnet-core/modules/system-info/LINGYUN.Abp.SystemInfo.HttpApi/LINGYUN/Abp/SystemInfo/Utils/MemoryMonitor.cs b/aspnet-core/modules/system-info/LINGYUN.Abp.SystemInfo.HttpApi/LINGYUN/Abp/SystemInfo/Utils/MemoryMonitor.cs
new file mode 100644
index 000000000..892842cb7
--- /dev/null
+++ b/aspnet-core/modules/system-info/LINGYUN.Abp.SystemInfo.HttpApi/LINGYUN/Abp/SystemInfo/Utils/MemoryMonitor.cs
@@ -0,0 +1,54 @@
+using System;
+using System.Diagnostics;
+
+namespace LINGYUN.Abp.SystemInfo.Utils;
+
+public class MemoryMetrics
+{
+ ///
+ /// 总物理内存
+ ///
+ public long TotalMemory { get; set; }
+ ///
+ /// 工作集内存
+ ///
+ public long WorkingSet { get; set; }
+ ///
+ /// 私有内存
+ ///
+ public long PrivateMemory { get; set; }
+ ///
+ /// 虚拟内存
+ ///
+ public long VirtualMemory { get; set; }
+ ///
+ /// GC堆大小
+ ///
+ public long GCHeapSize { get; set; }
+}
+
+public class MemoryMonitor
+{
+ // 转换为MB
+ const long MB = 1024 * 1024;
+
+ public MemoryMetrics GetMemoryMetrics()
+ {
+ var process = Process.GetCurrentProcess();
+
+ return new MemoryMetrics
+ {
+ TotalMemory = GC.GetTotalMemory(false) / MB,
+ WorkingSet = process.WorkingSet64 / MB,
+ PrivateMemory = process.PrivateMemorySize64 / MB,
+ VirtualMemory = process.VirtualMemorySize64 / MB,
+ GCHeapSize = GetGCHeapSize() / MB
+ };
+ }
+
+ private long GetGCHeapSize()
+ {
+ var gcInfo = GC.GetGCMemoryInfo();
+ return (long)gcInfo.HeapSizeBytes;
+ }
+}
diff --git a/aspnet-core/modules/system-info/LINGYUN.Abp.SystemInfo.HttpApi/LINGYUN/Abp/SystemInfo/Utils/ThreadMonitor.cs b/aspnet-core/modules/system-info/LINGYUN.Abp.SystemInfo.HttpApi/LINGYUN/Abp/SystemInfo/Utils/ThreadMonitor.cs
new file mode 100644
index 000000000..b1367d0ff
--- /dev/null
+++ b/aspnet-core/modules/system-info/LINGYUN.Abp.SystemInfo.HttpApi/LINGYUN/Abp/SystemInfo/Utils/ThreadMonitor.cs
@@ -0,0 +1,59 @@
+using System.Diagnostics;
+using System.Threading;
+
+namespace LINGYUN.Abp.SystemInfo.Utils;
+
+public class ThreadMetrics
+{
+ ///
+ /// 可用工作线程数
+ ///
+ public int AvailableWorkerThreads { get; set; }
+ ///
+ /// 可用I/O线程数
+ ///
+ public int AvailableCompletionPortThreads { get; set; }
+ ///
+ /// 最大工作线程数
+ ///
+ public int MaxWorkerThreads { get; set; }
+ ///
+ /// 最大I/O线程数
+ ///
+ public int MaxCompletionPortThreads { get; set; }
+ ///
+ /// 进程总线程数
+ ///
+ public int ActiveThreads { get; set; }
+ ///
+ /// 线程池活动线程数
+ ///
+ public int ThreadPoolThreadCount { get; set; }
+}
+
+public class ThreadMonitor
+{
+ public ThreadMetrics GetThreadMetrics()
+ {
+ ThreadPool.GetAvailableThreads(
+ out var availableWorkerThreads,
+ out var availableCompletionPortThreads);
+ ThreadPool.GetMaxThreads(
+ out var maxWorkerThreads,
+ out var maxCompletionPortThreads);
+
+ // 获取进程线程数(近似活动线程数)
+ var process = Process.GetCurrentProcess();
+ var threads = process.Threads;
+
+ return new ThreadMetrics
+ {
+ AvailableWorkerThreads = availableWorkerThreads,
+ AvailableCompletionPortThreads = availableCompletionPortThreads,
+ MaxWorkerThreads = maxWorkerThreads,
+ MaxCompletionPortThreads = maxCompletionPortThreads,
+ ActiveThreads = threads.Count,
+ ThreadPoolThreadCount = ThreadPool.ThreadCount
+ };
+ }
+}
diff --git a/aspnet-core/services/LY.MicroService.Applications.Single/LY.MicroService.Applications.Single.csproj b/aspnet-core/services/LY.MicroService.Applications.Single/LY.MicroService.Applications.Single.csproj
index 54f1418d6..21e51f851 100644
--- a/aspnet-core/services/LY.MicroService.Applications.Single/LY.MicroService.Applications.Single.csproj
+++ b/aspnet-core/services/LY.MicroService.Applications.Single/LY.MicroService.Applications.Single.csproj
@@ -7,6 +7,7 @@
enable
LY.MicroService.Applications.Single
enable
+ 9.3.6.2
@@ -22,6 +23,7 @@
+
@@ -247,6 +249,7 @@
+
diff --git a/aspnet-core/services/LY.MicroService.Applications.Single/MicroServiceApplicationsSingleModule.Configure.cs b/aspnet-core/services/LY.MicroService.Applications.Single/MicroServiceApplicationsSingleModule.Configure.cs
index b4c33806c..b77959e79 100644
--- a/aspnet-core/services/LY.MicroService.Applications.Single/MicroServiceApplicationsSingleModule.Configure.cs
+++ b/aspnet-core/services/LY.MicroService.Applications.Single/MicroServiceApplicationsSingleModule.Configure.cs
@@ -1,3 +1,4 @@
+using Microsoft.AspNetCore.SignalR;
using VoloAbpExceptionHandlingOptions = Volo.Abp.AspNetCore.ExceptionHandling.AbpExceptionHandlingOptions;
namespace LY.MicroService.Applications.Single;
@@ -135,6 +136,28 @@ public partial class MicroServiceApplicationsSingleModule
}
}
+ private void PreConfigureSignalR(IConfiguration configuration)
+ {
+ PreConfigure(builder =>
+ {
+ var redisEnabled = configuration["SignalR:Redis:IsEnabled"];
+ if (redisEnabled.IsNullOrEmpty() || bool.Parse(redisEnabled))
+ {
+ builder.AddStackExchangeRedis(redis =>
+ {
+ var redisConfiguration = configuration["SignalR:Redis:Configuration"];
+ if (!redisConfiguration.IsNullOrEmpty())
+ {
+ redis.ConnectionFactory = async (writer) =>
+ {
+ return await ConnectionMultiplexer.ConnectAsync(redisConfiguration);
+ };
+ }
+ });
+ }
+ });
+ }
+
private void PreConfigureQuartz(IConfiguration configuration)
{
PreConfigure(options =>
@@ -522,7 +545,7 @@ public partial class MicroServiceApplicationsSingleModule
});
}
- private void ConfigureCaching(IConfiguration configuration)
+ private void ConfigureCaching(IServiceCollection services, IConfiguration configuration)
{
Configure(options =>
{
@@ -535,6 +558,8 @@ public partial class MicroServiceApplicationsSingleModule
options.ConfigurationOptions = redisConfig;
options.InstanceName = configuration["Redis:InstanceName"];
});
+
+ services.AddSingleton(sp => ConnectionMultiplexer.Connect(configuration["Redis:Configuration"]));
}
private void ConfigureMultiTenancy(IConfiguration configuration)
diff --git a/aspnet-core/services/LY.MicroService.Applications.Single/MicroServiceApplicationsSingleModule.cs b/aspnet-core/services/LY.MicroService.Applications.Single/MicroServiceApplicationsSingleModule.cs
index ef5ac78d9..b41c08a84 100644
--- a/aspnet-core/services/LY.MicroService.Applications.Single/MicroServiceApplicationsSingleModule.cs
+++ b/aspnet-core/services/LY.MicroService.Applications.Single/MicroServiceApplicationsSingleModule.cs
@@ -1,3 +1,5 @@
+using LINGYUN.Abp.SystemInfo;
+
namespace LY.MicroService.Applications.Single;
[DependsOn(
@@ -382,6 +384,9 @@ namespace LY.MicroService.Applications.Single;
typeof(AbpMailKitModule),
typeof(AbpAutofacModule),
+ // 单体应用系统状态接口
+ typeof(AbpSystemInfoHttpApiModule),
+
// MySql
typeof(SingleMigrationsEntityFrameworkCoreMySqlModule),
// SqlServer
@@ -402,6 +407,7 @@ public partial class MicroServiceApplicationsSingleModule : AbpModule
PreConfigureApp(configuration);
PreConfigureCAP(configuration);
PreConfigureQuartz(configuration);
+ PreConfigureSignalR(configuration);
PreConfigureAuthServer(configuration);
PreConfigureElsa(context.Services, configuration);
PreConfigureCertificate(configuration, hostingEnvironment);
@@ -424,7 +430,6 @@ public partial class MicroServiceApplicationsSingleModule : AbpModule
ConfigureVirtualFileSystem();
ConfigureEntityDataProtected();
ConfigureUrls(configuration);
- ConfigureCaching(configuration);
ConfigureAuditing(configuration);
ConfigureIdentity(configuration);
ConfigureDbContext(configuration);
@@ -440,6 +445,7 @@ public partial class MicroServiceApplicationsSingleModule : AbpModule
ConfigureNotificationManagement(configuration);
ConfigureCors(context.Services, configuration);
ConfigureSwagger(context.Services, configuration);
+ ConfigureCaching(context.Services, configuration);
ConfigureDistributedLock(context.Services, configuration);
ConfigureKestrelServer(configuration, hostingEnvironment);
ConfigureSecurity(context.Services, configuration, hostingEnvironment.IsDevelopment());
diff --git a/aspnet-core/services/LY.MicroService.Applications.Single/appsettings.Development.json b/aspnet-core/services/LY.MicroService.Applications.Single/appsettings.Development.json
index 583184e92..d8e058b4a 100644
--- a/aspnet-core/services/LY.MicroService.Applications.Single/appsettings.Development.json
+++ b/aspnet-core/services/LY.MicroService.Applications.Single/appsettings.Development.json
@@ -93,7 +93,7 @@
},
"Redis": {
"IsEnabled": true,
- "Configuration": "127.0.0.1,defaultDatabase=15",
+ "Configuration": "127.0.0.1,defaultDatabase=15,allowAdmin=true",
"InstanceName": "LINGYUN.Abp.Application"
},
"Features": {