mirror of
				https://gitee.com/ThingsGateway/ThingsGateway.git
				synced 2025-10-31 15:43:59 +08:00 
			
		
		
		
	Compare commits
	
		
			19 Commits
		
	
	
		
			10.12.1.0
			...
			10.12.12.0
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | b42f8afa35 | ||
|   | facf8bd401 | ||
|   | feeb17eca3 | ||
|   | b3405cd674 | ||
|   | c35f9cef93 | ||
|   | 3f382202db | ||
|   | 2a3493cc82 | ||
|   | aaa459ebe0 | ||
|   | 51a8acbc3e | ||
|   | dc132a1999 | ||
|   | 0c31cfcbc5 | ||
|   | 0e44bc67cd | ||
|   | 12dfbba42c | ||
|   | 96b4287f3a | ||
|   | 7d406de29f | ||
|   | 81f0ef466a | ||
|   | 3f2d6b133c | ||
|   | e776dc67eb | ||
| ![devin-ai-integration[bot]](/assets/img/avatar_default.png)  | bc5827d140 | 
							
								
								
									
										14
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								README.md
									
									
									
									
									
								
							| @@ -1,11 +1,18 @@ | ||||
| # ThingsGateway | ||||
|  | ||||
|  | ||||
| [](https://gitee.com/ThingsGateway/ThingsGateway/stargazers)  | ||||
| [](https://github.com/ThingsGateway/ThingsGateway) | ||||
| [](https://deepwiki.com/ThingsGateway/ThingsGateway) | ||||
| [](https://www.nuget.org/packages/ThingsGateway.Foundation/) | ||||
| [](https://www.nuget.org/packages/ThingsGateway.Foundation/) | ||||
| [](https://thingsgateway.cn/docs/1) | ||||
| <a href="http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=NnBjPO-8kcNFzo_RzSbdICflb97u2O1i&authKey=V1MI3iJtpDMHc08myszP262kDykbx2Yev6ebE4Me0elTe0P0IFAmtU5l7Sy5w0jx&noverify=0&group_code=605534569"> | ||||
| <img src="https://img.shields.io/badge/QQ群-605534569-red" alt="QQ"> | ||||
| </a> | ||||
|  | ||||
| ## Introduction | ||||
|  | ||||
|  | ||||
| A cross-platform, high-performance edge data collection gateway based on net9. | ||||
| A cross-platform, high-performance edge data collection gateway based on net8/10. | ||||
|  | ||||
|  | ||||
| ## Documentation | ||||
| @@ -29,7 +36,6 @@ Account: **SuperAdmin** | ||||
| Password: **111111** | ||||
|  | ||||
|  | ||||
| **In the upper-right corner, switch to the IoT Gateway module in the personal popup box** | ||||
|  | ||||
| ## Docker | ||||
|  | ||||
|   | ||||
| @@ -1,8 +1,18 @@ | ||||
| # ThingsGateway | ||||
|  | ||||
| [](https://gitee.com/ThingsGateway/ThingsGateway/stargazers)  | ||||
| [](https://github.com/ThingsGateway/ThingsGateway) | ||||
| [](https://deepwiki.com/ThingsGateway/ThingsGateway) | ||||
| [](https://www.nuget.org/packages/ThingsGateway.Foundation/) | ||||
| [](https://www.nuget.org/packages/ThingsGateway.Foundation/) | ||||
| [](https://thingsgateway.cn/docs/1) | ||||
| <a href="http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=NnBjPO-8kcNFzo_RzSbdICflb97u2O1i&authKey=V1MI3iJtpDMHc08myszP262kDykbx2Yev6ebE4Me0elTe0P0IFAmtU5l7Sy5w0jx&noverify=0&group_code=605534569"> | ||||
| <img src="https://img.shields.io/badge/QQ群-605534569-red" alt="QQ"> | ||||
| </a> | ||||
|  | ||||
| ## 介绍 | ||||
|  | ||||
| 基于net9的跨平台高性能边缘采集网关 | ||||
| 基于net8/10的跨平台高性能边缘采集网关 | ||||
|  | ||||
| ## 文档 | ||||
|  | ||||
| @@ -19,7 +29,6 @@ | ||||
|  | ||||
| 密码 : **111111** | ||||
|  | ||||
| **右上角个人弹出框中,切换到物联网关模块** | ||||
|  | ||||
| ## Docker | ||||
|  | ||||
|   | ||||
| @@ -8,6 +8,9 @@ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| using System.ComponentModel; | ||||
| using System.Runtime; | ||||
|  | ||||
| using ThingsGateway.NewLife; | ||||
|  | ||||
| namespace ThingsGateway.Admin.Application; | ||||
| @@ -19,11 +22,7 @@ public class HardwareInfo | ||||
|     /// 当前磁盘信息 | ||||
|     /// </summary> | ||||
|     public DriveInfo DriveInfo { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 硬件信息获取 | ||||
|     /// </summary> | ||||
|     public MachineInfo? MachineInfo { get; set; } | ||||
|   | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 主机环境 | ||||
| @@ -40,19 +39,118 @@ public class HardwareInfo | ||||
|     /// </summary> | ||||
|     public string OsArchitecture { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 唯一编码 | ||||
|     /// </summary> | ||||
|     public string UUID { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 进程占用内存 | ||||
|     /// </summary> | ||||
|     [AutoGenerateColumn(Ignore = true)] | ||||
|     public int WorkingSet { get; set; } | ||||
|     /// <summary>系统名称</summary> | ||||
|     public String OSName { get; set; } | ||||
|  | ||||
|     /// <summary>系统版本</summary> | ||||
|     public String OSVersion { get; set; } | ||||
|     public String UUID { get; set; } | ||||
|  | ||||
|     /// <summary>内存总量。单位MB</summary> | ||||
|     public UInt64 Memory { get; set; } | ||||
|  | ||||
|     /// <summary>可用内存。单位MB</summary> | ||||
|     public UInt64 AvailableMemory { get; set; } | ||||
|  | ||||
|     /// <summary>CPU占用率</summary> | ||||
|     public Double CpuRate { get; set; } | ||||
|     public Double Battery { get; set; } | ||||
|     public Double Temperature { get; set; } | ||||
|  | ||||
|  | ||||
|     /// <summary>处理器型号</summary> | ||||
|     public String? Processor { get; set; } | ||||
|     #region GC与进程内存信息 | ||||
|  | ||||
|     /// <summary>GC 认为“内存吃紧”的阈值。单位:MB</summary> | ||||
|     [DisplayName("GC高内存阈值")] | ||||
|     public UInt64 HighMemoryLoadThreshold { get; set; } | ||||
|  | ||||
|     /// <summary>GC 可用内存上限。单位:MB</summary> | ||||
|     [DisplayName("GC可用内存上限")] | ||||
|     public UInt64 TotalAvailableMemory { get; set; } | ||||
|  | ||||
|     /// <summary>当前托管堆容量。单位:MB</summary> | ||||
|     [DisplayName("托管堆容量")] | ||||
|     public UInt64 HeapSize { get; set; } | ||||
|  | ||||
|     /// <summary>托管堆已用内存。单位:MB</summary> | ||||
|     [DisplayName("托管堆已用")] | ||||
|     public UInt64 TotalMemory { get; set; } | ||||
|  | ||||
|     /// <summary>托管堆碎片大小。单位:MB</summary> | ||||
|     [DisplayName("托管堆碎片")] | ||||
|     public UInt64 FragmentedBytes { get; set; } | ||||
|  | ||||
|     /// <summary>GC识别可用内存。单位:MB</summary> | ||||
|     [DisplayName("GC识别可用内存")] | ||||
|     public UInt64 GCAvailableMemory { get; set; } | ||||
|  | ||||
|     /// <summary>GC 已提交的内存。单位:MB</summary> | ||||
|     [DisplayName("GC已提交内存")] | ||||
|     public UInt64 CommittedBytes { get; set; } | ||||
|  | ||||
|     /// <summary>GC 累计分配的托管内存。单位:MB</summary> | ||||
|     [DisplayName("GC累计分配")] | ||||
|     public UInt64 TotalAllocatedBytes { get; set; } | ||||
|     /// <summary>GC 暂停累计时间。单位:毫秒</summary> | ||||
|     [DisplayName("GC累计暂停时间")] | ||||
|     public UInt64 TotalPauseDurationMs { get; set; } | ||||
|     /// <summary>GC 代0收集次数</summary> | ||||
|     [DisplayName("GC Gen0 次数")] | ||||
|     public Int32 GcGen0Count { get; set; } | ||||
|  | ||||
|     /// <summary>GC 代1收集次数</summary> | ||||
|     [DisplayName("GC Gen1 次数")] | ||||
|     public Int32 GcGen1Count { get; set; } | ||||
|  | ||||
|     /// <summary>GC 代2收集次数</summary> | ||||
|     [DisplayName("GC Gen2 次数")] | ||||
|     public Int32 GcGen2Count { get; set; } | ||||
|  | ||||
|     /// <summary>Server GC 是否启用</summary> | ||||
|     [DisplayName("是否使用Server GC")] | ||||
|     public Boolean IsServerGC { get; set; } | ||||
|  | ||||
|     /// <summary>GC 延迟模式</summary> | ||||
|     [DisplayName("GC延迟模式")] | ||||
|     public GCLatencyMode? GCLatencyMode { get; set; } | ||||
|  | ||||
|     /// <summary>GC 固定对象数</summary> | ||||
|     [DisplayName("固定对象数")] | ||||
|     public Int64 PinnedObjectsCount { get; set; } | ||||
|  | ||||
|     /// <summary>终结队列挂起对象数</summary> | ||||
|     [DisplayName("终结挂起数")] | ||||
|     public Int64 FinalizationPendingCount { get; set; } | ||||
|  | ||||
|     #endregion | ||||
|  | ||||
|     #region 进程内存信息 | ||||
|  | ||||
|     /// <summary>进程虚拟内存使用量。单位:MB</summary> | ||||
|     [DisplayName("虚拟内存")] | ||||
|     public UInt64 VirtualMemory { get; set; } | ||||
|  | ||||
|     /// <summary>进程私有内存使用量。单位:MB</summary> | ||||
|     [DisplayName("私有内存")] | ||||
|     public UInt64 PrivateMemory { get; set; } | ||||
|  | ||||
|     /// <summary>进程峰值工作集。单位:MB</summary> | ||||
|     [DisplayName("峰值工作集")] | ||||
|     public UInt64 PeakWorkingSet { get; set; } | ||||
|  | ||||
|     /// <summary>进程当前工作集。单位:MB</summary> | ||||
|     [DisplayName("当前工作集")] | ||||
|     public UInt64 WorkingSet { get; set; } | ||||
|  | ||||
|     #endregion | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 更新时间 | ||||
|     /// </summary> | ||||
|     public string UpdateTime { get; set; } | ||||
|     public DateTime UpdateTime { get; set; } | ||||
|     public ulong AppRunTotalMinute { get;  set; } | ||||
|     public ulong SystemRunTotalMinute { get;  set; } | ||||
| } | ||||
|   | ||||
| @@ -8,12 +8,10 @@ | ||||
| // QQ群:605534569 | ||||
| // ------------------------------------------------------------------------------ | ||||
|  | ||||
| using Microsoft.Extensions.Hosting; | ||||
| using Microsoft.Extensions.Logging; | ||||
|  | ||||
| using System.Runtime.InteropServices; | ||||
|  | ||||
| using ThingsGateway.Extension; | ||||
| using ThingsGateway.NewLife; | ||||
| using ThingsGateway.NewLife.Caching; | ||||
| using ThingsGateway.NewLife.Threading; | ||||
| @@ -43,7 +41,7 @@ public class HardwareJob : IJob, IHardwareJob | ||||
|     /// <summary> | ||||
|     /// 运行信息获取 | ||||
|     /// </summary> | ||||
|     public HardwareInfo HardwareInfo { get; } = new(); | ||||
|     public HardwareInfo HardwareInfo { get; private set; } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public HardwareInfoOptions HardwareInfoOptions { get; private set; } | ||||
| @@ -76,9 +74,10 @@ public class HardwareJob : IJob, IHardwareJob | ||||
|         { | ||||
|             try | ||||
|             { | ||||
|                 if (HardwareInfo.MachineInfo == null) | ||||
|                     var machine = MachineInfo.GetCurrent(); | ||||
|                 if (HardwareInfo == null) | ||||
|                 { | ||||
|                     HardwareInfo.MachineInfo = MachineInfo.GetCurrent(); | ||||
|                     HardwareInfo=machine.AdaptHardwareInfo(); | ||||
|  | ||||
|                     string currentPath = Directory.GetCurrentDirectory(); | ||||
|                     DriveInfo drive = new(Path.GetPathRoot(currentPath)); | ||||
| @@ -88,10 +87,9 @@ public class HardwareJob : IJob, IHardwareJob | ||||
|                     HardwareInfo.DriveInfo = drive; | ||||
|                     HardwareInfo.OsArchitecture = Environment.OSVersion.Platform.ToString() + " " + RuntimeInformation.OSArchitecture.ToString(); // 系统架构 | ||||
|                     HardwareInfo.FrameworkDescription = RuntimeInformation.FrameworkDescription; // NET框架 | ||||
|                     HardwareInfo.Environment = App.HostEnvironment.IsDevelopment() ? "Development" : "Production"; | ||||
|                     HardwareInfo.UUID = HardwareInfo.MachineInfo.UUID; | ||||
|                     HardwareInfo.Environment = App.HostEnvironment.EnvironmentName; | ||||
|  | ||||
|                     HardwareInfo.UpdateTime = TimerX.Now.ToDefaultDateTimeFormat(); | ||||
|                     HardwareInfo.UpdateTime = TimerX.Now; | ||||
|                 } | ||||
|             } | ||||
|             catch | ||||
| @@ -99,9 +97,12 @@ public class HardwareJob : IJob, IHardwareJob | ||||
|             } | ||||
|             try | ||||
|             { | ||||
|                 HardwareInfo.MachineInfo.Refresh(); | ||||
|                 HardwareInfo.UpdateTime = TimerX.Now.ToDefaultDateTimeFormat(); | ||||
|                 HardwareInfo.WorkingSet = (Environment.WorkingSet / 1024.0 / 1024.0).ToInt(); | ||||
|                 var machine = MachineInfo.GetCurrent(); | ||||
|                 machine.Refresh(); | ||||
|                 machine.AdaptHardwareInfo(HardwareInfo); | ||||
|                 HardwareInfo.AppRunTotalMinute = (ulong)Runtime.AppTickCount64 / 1000 /60; | ||||
|                 HardwareInfo.SystemRunTotalMinute = (ulong)Runtime.TickCount64 / 1000 /60; | ||||
|                 HardwareInfo.UpdateTime = TimerX.Now; | ||||
|                 error = false; | ||||
|             } | ||||
|             catch (Exception ex) | ||||
| @@ -123,10 +124,10 @@ public class HardwareJob : IJob, IHardwareJob | ||||
|                             { | ||||
|                                 Date = TimerX.Now, | ||||
|                                 DriveUsage = (100 - (HardwareInfo.DriveInfo.TotalFreeSpace * 100.00 / HardwareInfo.DriveInfo.TotalSize)).ToInt(), | ||||
|                                 Battery = (HardwareInfo.MachineInfo.Battery * 100).ToInt(), | ||||
|                                 Battery = (HardwareInfo.Battery * 100).ToInt(), | ||||
|                                 MemoryUsage = (HardwareInfo.WorkingSet), | ||||
|                                 CpuUsage = (HardwareInfo.MachineInfo.CpuRate * 100).ToInt(), | ||||
|                                 Temperature = (HardwareInfo.MachineInfo.Temperature).ToInt(), | ||||
|                                 CpuUsage = (HardwareInfo.CpuRate * 100).ToInt(), | ||||
|                                 Temperature = (HardwareInfo.Temperature).ToInt(), | ||||
|                             }; | ||||
|                             await _db.InsertableT(his).ExecuteCommandAsync(stoppingToken).ConfigureAwait(false); | ||||
|                             MemoryCache.Remove(CacheKey); | ||||
|   | ||||
| @@ -21,7 +21,7 @@ public class HistoryHardwareInfo | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     [SugarColumn(ColumnDescription = "内存")] | ||||
|     public int MemoryUsage { get; set; } | ||||
|     public UInt64 MemoryUsage { get; set; } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     [SugarColumn(ColumnDescription = "CPU使用率")] | ||||
|   | ||||
| @@ -21,7 +21,7 @@ | ||||
|     "UserNoModule": "This account has not been assigned a module. Please contact the administrator", | ||||
|     "UserNull": "User {0} does not exist" | ||||
|   }, | ||||
|    | ||||
|  | ||||
|   "ThingsGateway.Admin.Application.BlazorAuthenticationHandler": { | ||||
|     "UserExpire": "User expired, please login again" | ||||
|   }, | ||||
| @@ -46,12 +46,44 @@ | ||||
|     "FileTypeError": "Not supported format {0}" | ||||
|   }, | ||||
|   "ThingsGateway.Admin.Application.HardwareInfo": { | ||||
|     "Environment": "HostEnvironment", | ||||
|     "FrameworkDescription": ".NETFramework", | ||||
|     "OsArchitecture": "System Architecture", | ||||
|     "UpdateTime": "UpdateTime", | ||||
|     "UUID": "UUID" | ||||
|     "DriveInfo": "Current Disk Info", | ||||
|     "AppRunTotalMinute": "AppRunTotalMinute(min)", | ||||
|     "SystemRunTotalMinute": "SystemRunTotalMinute(min)", | ||||
|     "Environment": "Host Environment", | ||||
|     "FrameworkDescription": ".NET Framework", | ||||
|     "OsArchitecture": "OS Architecture", | ||||
|     "OSName": "OS Name", | ||||
|     "OSVersion": "OS Version", | ||||
|     "UUID": "UUID", | ||||
|     "Memory": "Total Memory", | ||||
|     "AvailableMemory": "Available Memory", | ||||
|     "CpuRate": "CPU Usage", | ||||
|     "Battery": "Battery Level", | ||||
|     "Temperature": "Temperature", | ||||
|     "Processor": "Processor Model", | ||||
|     "HighMemoryLoadThreshold": "GC High Memory Threshold", | ||||
|     "TotalAvailableMemory": "GC Total Available Memory", | ||||
|     "HeapSize": "Managed Heap Size", | ||||
|     "TotalMemory": "Managed Heap Used", | ||||
|     "FragmentedBytes": "Managed Heap Fragmented", | ||||
|     "GCAvailableMemory": "GC Available Memory", | ||||
|     "CommittedBytes": "GC Committed Bytes", | ||||
|     "TotalAllocatedBytes": "GC Total Allocated (MB)", | ||||
|     "TotalPauseDurationMs": "GC Total Pause Duration (ms)", | ||||
|     "GcGen0Count": "GC Gen0 Count", | ||||
|     "GcGen1Count": "GC Gen1 Count", | ||||
|     "GcGen2Count": "GC Gen2 Count", | ||||
|     "IsServerGC": "Server GC", | ||||
|     "GCLatencyMode": "GC Latency Mode", | ||||
|     "PinnedObjectsCount": "Pinned Objects Count", | ||||
|     "FinalizationPendingCount": "Finalization Pending Count", | ||||
|     "VirtualMemory": "Virtual Memory", | ||||
|     "PrivateMemory": "Private Memory", | ||||
|     "PeakWorkingSet": "Peak Working Set", | ||||
|     "WorkingSet": "Current Working Set", | ||||
|     "UpdateTime": "Update Time" | ||||
|   }, | ||||
|  | ||||
|   "ThingsGateway.Admin.Application.HardwareJob": { | ||||
|     "GetHardwareInfoFail": "Get Hardwareinfo Fail" | ||||
|   }, | ||||
|   | ||||
| @@ -46,11 +46,42 @@ | ||||
|     "FileTypeError": "不支持 {0} 格式" | ||||
|   }, | ||||
|   "ThingsGateway.Admin.Application.HardwareInfo": { | ||||
|     "DriveInfo": "当前磁盘信息", | ||||
|     "AppRunTotalMinute": "软件运行时长(min)", | ||||
|     "SystemRunTotalMinute": "系统运行时长(min)", | ||||
|     "Environment": "主机环境", | ||||
|     "FrameworkDescription": "NET框架", | ||||
|     "FrameworkDescription": ".NET 框架", | ||||
|     "OsArchitecture": "系统架构", | ||||
|     "UpdateTime": "更新时间", | ||||
|     "UUID": "唯一编码" | ||||
|     "OSName": "系统名称", | ||||
|     "OSVersion": "系统版本", | ||||
|     "UUID": "UUID", | ||||
|     "Memory": "系统总内存", | ||||
|     "AvailableMemory": "系统可用内存", | ||||
|     "CpuRate": "CPU 占用率", | ||||
|     "Battery": "电池电量", | ||||
|     "Temperature": "温度", | ||||
|     "Processor": "处理器型号", | ||||
|     "HighMemoryLoadThreshold": "GC 高内存阈值", | ||||
|     "TotalAvailableMemory": "GC 总内存阈值", | ||||
|     "HeapSize": "托管堆容量", | ||||
|     "TotalMemory": "托管对象占用", | ||||
|     "FragmentedBytes": "托管堆碎片", | ||||
|     "GCAvailableMemory": "GC 可用内存", | ||||
|     "CommittedBytes": "GC 提交内存总量", | ||||
|     "TotalAllocatedBytes": "GC 累计分配(MB)", | ||||
|     "TotalPauseDurationMs": "GC 累计暂停时间(ms)", | ||||
|     "GcGen0Count": "GC Gen0 次数", | ||||
|     "GcGen1Count": "GC Gen1 次数", | ||||
|     "GcGen2Count": "GC Gen2 次数", | ||||
|     "IsServerGC": "Server GC", | ||||
|     "GCLatencyMode": "GC 延迟模式", | ||||
|     "PinnedObjectsCount": "固定对象数", | ||||
|     "FinalizationPendingCount": "终结挂起数", | ||||
|     "VirtualMemory": "虚拟内存", | ||||
|     "PrivateMemory": "私有内存", | ||||
|     "PeakWorkingSet": "峰值工作集", | ||||
|     "WorkingSet": "当前工作集", | ||||
|     "UpdateTime": "更新时间" | ||||
|   }, | ||||
|   "ThingsGateway.Admin.Application.HardwareJob": { | ||||
|     "GetHardwareInfoFail": "获取硬件信息出错" | ||||
|   | ||||
| @@ -8,13 +8,20 @@ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| using BootstrapBlazor.Components; | ||||
|  | ||||
| using Riok.Mapperly.Abstractions; | ||||
|  | ||||
| using ThingsGateway.NewLife; | ||||
|  | ||||
| namespace ThingsGateway.Admin.Application; | ||||
|  | ||||
| [Mapper(UseDeepCloning = true, EnumMappingStrategy = EnumMappingStrategy.ByName, RequiredMappingStrategy = RequiredMappingStrategy.None)] | ||||
| public static partial class AdminMapper | ||||
| { | ||||
|     public static partial HardwareInfo AdaptHardwareInfo(this MachineInfo src); | ||||
|         public static partial void AdaptHardwareInfo(this MachineInfo src, HardwareInfo dto); | ||||
|  | ||||
|     public static partial LoginInput AdaptLoginInput(this OpenApiLoginInput src); | ||||
|     public static partial OpenApiLoginOutput AdaptOpenApiLoginOutput(this LoginOutput src); | ||||
|     public static partial SessionOutput AdaptSessionOutput(this SysUser src); | ||||
|   | ||||
| @@ -19,12 +19,14 @@ | ||||
| 	</ItemGroup> | ||||
| 	 | ||||
| 	<ItemGroup> | ||||
| 		<PackageReference Include="Riok.Mapperly" Version="4.2.1" ExcludeAssets="runtime" PrivateAssets="all" /> | ||||
| 		<PackageReference Include="Riok.Mapperly" Version="4.3.0" ExcludeAssets="runtime" PrivateAssets="all"> | ||||
| 		  <IncludeAssets>compile; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||||
| 		</PackageReference> | ||||
| 		<PackageReference Include="Rougamo.Fody" Version="5.0.2" /> | ||||
| 	</ItemGroup> | ||||
| 	<ItemGroup Condition=" '$(TargetFramework)' == 'net8.0' "> | ||||
| 		<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.1" /> | ||||
| 		<PackageReference Include="System.Formats.Asn1" Version="8.0.2" /> | ||||
| 		<PackageReference Include="System.Formats.Asn1" Version="9.0.10" /> | ||||
| 		<PackageReference Include="System.Threading.RateLimiting" Version="8.0.0" /> | ||||
|  | ||||
| 	</ItemGroup> | ||||
|   | ||||
| @@ -22,6 +22,9 @@ | ||||
|     "SetDefaultModule": "Set as default module" | ||||
|   }, | ||||
|   "ThingsGateway.Admin.Razor.HardwareInfoPage": { | ||||
|     "GCMemoryInfoConfig": "GCMemoryInfoConfig(MB)", | ||||
|     "GCMemoryInfo": "GCMemoryInfo(MB)", | ||||
|     "GCAnalyzeInfo": "GCAnalyzeInfo", | ||||
|     "AvailableDisk": "Available Disk", | ||||
|     "AvailableMemory": "Available Memory", | ||||
|     "CpuUsage": "CPU Usage", | ||||
|   | ||||
| @@ -22,6 +22,10 @@ | ||||
|     "SetDefaultModule": "设置为默认模块" | ||||
|   }, | ||||
|   "ThingsGateway.Admin.Razor.HardwareInfoPage": { | ||||
|     "GCMemoryInfoConfig": "GC配置信息(MB)", | ||||
|     "GCMemoryInfo": "GC内存信息(MB)", | ||||
|     "GCAnalyzeInfo": "GC统计", | ||||
|  | ||||
|     "AvailableDisk": "可用磁盘", | ||||
|     "AvailableMemory": "可用内存", | ||||
|     "CpuUsage": "CPU使用率", | ||||
|   | ||||
| @@ -5,131 +5,189 @@ | ||||
| @using ThingsGateway.Admin.Application | ||||
| @namespace ThingsGateway.Admin.Razor | ||||
| <div class="row g-2 mx-1 form-inline"> | ||||
|  | ||||
|     <div class="col-12 col-md-4"> | ||||
|     <div class="col-12 col-md-12"> | ||||
|  | ||||
|         <Card IsShadow=true class="m-2 flex-fill" Color="Color.Primary"> | ||||
|             <HeaderTemplate> | ||||
|                 @Localizer["SystemInfo"] | ||||
|             </HeaderTemplate> | ||||
|  | ||||
|             <BodyTemplate> | ||||
|                 <EditorFormObject IsDisplay Model="HardwareJob.HardwareInfo" ItemsPerRow="1" RowType=RowType.Inline LabelWidth="160"> | ||||
|                     <FieldItems> | ||||
|                         <EditorItem @bind-Field="@context.MachineInfo"> | ||||
|                             <EditTemplate Context="value"> | ||||
|                                 <div class="col-12  col-md-12"> | ||||
|                                     <Display @bind-Value="@value.MachineInfo.OSName" DisplayText="@Localizer[nameof(value.MachineInfo.OSName)]" ShowTooltip ShowLabel=true> | ||||
|                                     </Display> | ||||
|                                 </div> | ||||
|                                 <div class="col-12  col-md-12"> | ||||
|                                     <Display @bind-Value="@value.MachineInfo.OSVersion" DisplayText="@Localizer[nameof(value.MachineInfo.OSVersion)]" ShowTooltip ShowLabel=true> | ||||
|                                     </Display> | ||||
|                                 </div> | ||||
|                             </EditTemplate> | ||||
|                         </EditorItem> | ||||
|  | ||||
|                         <EditorItem @bind-Field="@context.UUID"> | ||||
|                             <EditTemplate Context="value"> | ||||
|                                 <div class="col-12  col-md-12"> | ||||
|                                     <Display @bind-Value="@value.UUID" ShowTooltip ShowLabel=true> | ||||
|                                     </Display> | ||||
|                                 </div> | ||||
|  | ||||
|                             </EditTemplate> | ||||
|                         </EditorItem> | ||||
|                         <EditorItem @bind-Field="@context.DriveInfo" Ignore=true> | ||||
|                         </EditorItem> | ||||
|                     </FieldItems> | ||||
|                 </EditorFormObject> | ||||
|  | ||||
|             </BodyTemplate> | ||||
|         </Card> | ||||
|     </div> | ||||
|     <div class="col-12 col-md-8"> | ||||
|         <Card IsShadow=true class="m-2 flex-fill" Color="Color.Primary"> | ||||
|             <HeaderTemplate> | ||||
|                 @Localizer["HardwareInfo"] | ||||
|             </HeaderTemplate> | ||||
|  | ||||
|             <BodyTemplate> | ||||
|                 <div class="d-flex align-items-center justify-content-center w-100 h-100" > | ||||
|  | ||||
|                     <div class="row g-2 mx-1 form-inline d-flex justify-content-center w-100"> | ||||
|                         <div class="col-12 col-md-4  justify-content-center h-100" > | ||||
|                             @{ | ||||
|                                 var item = HardwareJob.HardwareInfo.MachineInfo.CpuRate; | ||||
|                             } | ||||
|                             <div class="d-flex flex-column align-items-center"> | ||||
|                                 <Circle Width="200" class="m-3" | ||||
|                                         Value=@((int)(item*100<=100?item*100:100)) | ||||
|                                         Color=@((item*100>70?Color.Warning:Color.Success)) | ||||
|                                         StrokeWidth="4" ShowProgress=false> | ||||
|                                     <div class="circle-hardware"> | ||||
|                                         <span> | ||||
|                                             @Localizer["CpuUsage"] <i> @((item * 100).ToString("F0") + " %")</i> | ||||
|                                         </span> | ||||
|                                     </div> | ||||
|                                 </Circle> | ||||
|                                 <div class="mt-1"> | ||||
|                                     <span>@(HardwareJob.HardwareInfo.MachineInfo.Processor)</span> | ||||
|                                 </div> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                         <div class="col-12 col-md-4 justify-content-center h-100"> | ||||
|                             @{ | ||||
|                                 var availableMemory = HardwareJob.HardwareInfo.MachineInfo.AvailableMemory; | ||||
|                                 var memory = HardwareJob.HardwareInfo.MachineInfo.Memory; | ||||
|                             } | ||||
|                             <div class="d-flex flex-column align-items-center "> | ||||
|                                 <Circle Width="200" class="m-3" | ||||
|                                         Value=@((int)(memory>availableMemory?100 - (availableMemory * 100.00 / memory):100)) | ||||
|                                         Color=@((availableMemory * 100.00 / memory<20?Color.Warning:Color.Success)) | ||||
|                                         StrokeWidth="4"> | ||||
|                                     <div class="circle-hardware"> | ||||
|                                         <h6>   @((100 - (availableMemory * 100.00 / memory)).ToString("F2") + " %")  </h6> | ||||
|  | ||||
|                                         <span>   @Localizer["WorkingSet"] <i> @(HardwareJob.HardwareInfo.WorkingSet + " MB")</i></span> | ||||
|                                         <span>   @Localizer["AvailableMemory"] <i> @((availableMemory / 1024.00 / 1024).ToString("F2") + " GB")</i></span> | ||||
|                                         <span>   @Localizer["TotalMemory"] <i> @((memory / 1024.00 / 1024).ToString("F2") + " GB")</i></span> | ||||
|  | ||||
|                                     </div> | ||||
|                                 </Circle> | ||||
|                                 <div class="mt-1"> | ||||
|                                     <span>  @Localizer["MemoryUsage"] </span> | ||||
|                                 </div> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                         <div class="col-12 col-md-4 justify-content-center h-100"> | ||||
|                             @{ | ||||
|                                 var totalFreeSpace = HardwareJob.HardwareInfo.DriveInfo.TotalFreeSpace; | ||||
|                                 var totalSize = HardwareJob.HardwareInfo.DriveInfo.TotalSize; | ||||
|                             } | ||||
|                             <div class="d-flex flex-column align-items-center "> | ||||
|                                 <Circle Width="200" class="m-3" | ||||
|                                         Value=@((int)(totalSize>totalFreeSpace?100 - (totalFreeSpace * 100.00 / totalSize):100)) | ||||
|                                         Color=@((totalFreeSpace * 100.00 / totalSize<20?Color.Warning:Color.Success)) | ||||
|                                         StrokeWidth="4"> | ||||
|                                     <div class="circle-hardware"> | ||||
|                                         <h6>   @((100 - (totalFreeSpace * 100.00 / totalSize)).ToString("F2") + " %")</h6> | ||||
|                                         <span>   @(HardwareJob.HardwareInfo.DriveInfo.VolumeLabel) </span> | ||||
|                                         <span>   @Localizer["AvailableDisk"] <i> @((totalFreeSpace / 1024.00 / 1024 / 1024).ToString("F2") + " GB")</i></span> | ||||
|                                         <span>   @Localizer["TotalDisk"] <i> @((totalSize / 1024.00 / 1024 / 1024).ToString("F2") + " GB")</i></span> | ||||
|                                     </div> | ||||
|                                 </Circle> | ||||
|                                 <div class="mt-1"> | ||||
|                                     <span>  @Localizer["DiskUsage"] </span> | ||||
|                                 </div> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                     </div> | ||||
|                 <div class="d-flex justify-content-between align-items-center w-100"> | ||||
|                     <span>@Localizer["SystemInfo"]</span> | ||||
|                     <small class="text-muted"> | ||||
|                         @HardwareJob.HardwareInfo.UpdateTime.ToString("yyyy-MM-dd HH:mm:ss") | ||||
|                     </small> | ||||
|                 </div> | ||||
|             </HeaderTemplate> | ||||
|  | ||||
|             <BodyTemplate> | ||||
|                 <EditorForm class="mt-3" AutoGenerateAllItem="false" IsDisplay Model="HardwareJob.HardwareInfo" ItemsPerRow=3 RowType=RowType.Inline LabelWidth=300> | ||||
|                     <FieldItems> | ||||
|  | ||||
|                         <EditorItem @bind-Field="@context.OSName" GroupName="@Localizer["SystemInfo"]" /> | ||||
|                         <EditorItem @bind-Field="@context.OsArchitecture" GroupName="@Localizer["SystemInfo"]" /> | ||||
|                         <EditorItem @bind-Field="@context.OSVersion" GroupName="@Localizer["SystemInfo"]" /> | ||||
|                         <EditorItem @bind-Field="@context.Environment" GroupName="@Localizer["SystemInfo"]" /> | ||||
|                         <EditorItem @bind-Field="@context.FrameworkDescription" GroupName="@Localizer["SystemInfo"]" /> | ||||
|                         <EditorItem @bind-Field="@context.UUID" GroupName="@Localizer["SystemInfo"]" /> | ||||
|                         <EditorItem @bind-Field="@context.SystemRunTotalMinute" GroupName="@Localizer["SystemInfo"]" /> | ||||
|                         <EditorItem @bind-Field="@context.AppRunTotalMinute" GroupName="@Localizer["SystemInfo"]" /> | ||||
|  | ||||
|  | ||||
|                         <EditorItem @bind-Field="@context.Memory" GroupName="@Localizer["GCMemoryInfoConfig"]" GroupOrder=2 /> | ||||
|                         <EditorItem @bind-Field="@context.TotalAvailableMemory" GroupName="@Localizer["GCMemoryInfoConfig"]" GroupOrder=2 /> | ||||
|                         <EditorItem @bind-Field="@context.HighMemoryLoadThreshold" GroupName="@Localizer["GCMemoryConfig"]" GroupOrder=2 /> | ||||
|  | ||||
|                         <EditorItem TValue="bool" TModel="HardwareInfo" @bind-Field="@context.IsServerGC" GroupName="@Localizer["GCMemoryConfig"]" GroupOrder=2> | ||||
|                             <EditTemplate Context="value"> | ||||
|                                 <div class="col-12"> | ||||
|                                     <h6></h6> | ||||
|                                 </div> | ||||
|                             </EditTemplate> | ||||
|                         </EditorItem> | ||||
|  | ||||
|                         <EditorItem @bind-Field="@context.IsServerGC" GroupName="@Localizer["GCMemoryInfoConfig"]" GroupOrder=2 /> | ||||
|                         <EditorItem @bind-Field="@context.GCLatencyMode" GroupName="@Localizer["GCMemoryInfoConfig"]" GroupOrder=2 /> | ||||
|  | ||||
|  | ||||
|                         <EditorItem @bind-Field="@context.WorkingSet" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3 /> | ||||
|                         <EditorItem @bind-Field="@context.PrivateMemory" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3 /> | ||||
|                         <EditorItem @bind-Field="@context.PeakWorkingSet" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3 /> | ||||
|                         <EditorItem TValue="ulong" TModel="HardwareInfo" @bind-Field="@context.HeapSize" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3> | ||||
|                             <EditTemplate Context="value"> | ||||
|                                 <div class="col-12"> | ||||
|                                     <h6></h6> | ||||
|                                 </div> | ||||
|                             </EditTemplate> | ||||
|                         </EditorItem> | ||||
|                         <EditorItem @bind-Field="@context.HeapSize" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3 /> | ||||
|                         <EditorItem @bind-Field="@context.TotalMemory" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3 /> | ||||
|                         <EditorItem @bind-Field="@context.FragmentedBytes" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3 /> | ||||
|                         <EditorItem @bind-Field="@context.CommittedBytes" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3 /> | ||||
|  | ||||
|                         <EditorItem TValue="ulong" TModel="HardwareInfo" @bind-Field="@context.AvailableMemory" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3> | ||||
|                             <EditTemplate Context="value"> | ||||
|                                 <div class="col-12"> | ||||
|                                     <h6></h6> | ||||
|                                 </div> | ||||
|                             </EditTemplate> | ||||
|                         </EditorItem> | ||||
|  | ||||
|                         <EditorItem @bind-Field="@context.AvailableMemory" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3 /> | ||||
|                         <EditorItem @bind-Field="@context.GCAvailableMemory" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3 /> | ||||
|                         <EditorItem @bind-Field="@context.VirtualMemory" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3 /> | ||||
|  | ||||
|  | ||||
|                         <EditorItem @bind-Field="@context.GcGen0Count" GroupName="@Localizer["GCAnalyzeInfo"]" GroupOrder=4 /> | ||||
|                         <EditorItem @bind-Field="@context.GcGen1Count" GroupName="@Localizer["GCAnalyzeInfo"]" GroupOrder=4 /> | ||||
|                         <EditorItem @bind-Field="@context.GcGen2Count" GroupName="@Localizer["GCAnalyzeInfo"]" GroupOrder=4 /> | ||||
|                         <EditorItem TValue="ulong" TModel="HardwareInfo" @bind-Field="@context.TotalAllocatedBytes" GroupName="@Localizer["GCAnalyzeInfo"]" GroupOrder=4> | ||||
|                             <EditTemplate Context="value"> | ||||
|                                 <div class="col-12"> | ||||
|                                     <h6></h6> | ||||
|                                 </div> | ||||
|                             </EditTemplate> | ||||
|                         </EditorItem> | ||||
|  | ||||
|                         <EditorItem @bind-Field="@context.TotalAllocatedBytes" GroupName="@Localizer["GCAnalyzeInfo"]" GroupOrder=4 /> | ||||
|                         <EditorItem @bind-Field="@context.TotalPauseDurationMs" GroupName="@Localizer["GCAnalyzeInfo"]" GroupOrder=4 /> | ||||
|  | ||||
|                     </FieldItems> | ||||
|                 </EditorForm> | ||||
|  | ||||
|  | ||||
|  | ||||
|             </BodyTemplate> | ||||
|         </Card> | ||||
|  | ||||
|     </div> | ||||
| </div> | ||||
|  | ||||
| <div class="row g-2 mx-1 form-inline mt-2"> | ||||
|  | ||||
|     <div class="col-12 col-md-12"> | ||||
|         <Card IsShadow=true class="m-2 flex-fill" Color="Color.Primary"> | ||||
|             <HeaderTemplate> | ||||
|                 @Localizer["HardwareInfo"] | ||||
|             </HeaderTemplate> | ||||
|  | ||||
|             <BodyTemplate> | ||||
|                 <div class="d-flex align-items-center justify-content-center w-100 h-100"> | ||||
|                     <div class="row g-2 mx-1 form-inline d-flex justify-content-center w-100"> | ||||
|                         <!-- ✅ CPU 使用率 --> | ||||
|                         <div class="col-12 col-md-4 justify-content-center h-100"> | ||||
|                             @{ | ||||
|                                 var cpuUsage = HardwareJob.HardwareInfo.CpuRate; | ||||
|                             } | ||||
|                             <div class="d-flex flex-column align-items-center"> | ||||
|                                 <Circle Width="200" class="m-3" | ||||
|                                         Value=@((int)(cpuUsage*100<=100?cpuUsage*100:100)) | ||||
|                                         Color=@((cpuUsage*100>70?Color.Warning:Color.Success)) | ||||
|                                         StrokeWidth="4" ShowProgress=false> | ||||
|                                     <div class="circle-hardware"> | ||||
|                                         <span> | ||||
|                                             @Localizer["CpuUsage"] <i>@((cpuUsage * 100).ToString("F0") + " %")</i> | ||||
|                                         </span> | ||||
|                                     </div> | ||||
|                                 </Circle> | ||||
|                                 <div class="mt-1"> | ||||
|                                     <span>@(HardwareJob.HardwareInfo.Processor)</span> | ||||
|                                 </div> | ||||
|                             </div> | ||||
|                         </div> | ||||
|  | ||||
|                         <!-- ✅ 内存使用率 --> | ||||
|                         <div class="col-12 col-md-4 justify-content-center h-100"> | ||||
|                             @{ | ||||
|                                 var availableMemory = HardwareJob.HardwareInfo.AvailableMemory; | ||||
|                                 var memory = HardwareJob.HardwareInfo.Memory; | ||||
|                             } | ||||
|                             <div class="d-flex flex-column align-items-center"> | ||||
|                                 <Circle Width="200" class="m-3" | ||||
|                                         Value=@((int)(memory>availableMemory?100 - (availableMemory * 100.00 / memory):100)) | ||||
|                                         Color=@((availableMemory * 100.00 / memory<20?Color.Warning:Color.Success)) | ||||
|                                         StrokeWidth="4"> | ||||
|                                     <div class="circle-hardware"> | ||||
|                                         <h6>@((100 - (availableMemory * 100.00 / memory)).ToString("F2") + " %")</h6> | ||||
|                                         <span>@Localizer["WorkingSet"] <i>@(HardwareJob.HardwareInfo.WorkingSet + " MB")</i></span> | ||||
|                                         <span>@Localizer["AvailableMemory"] <i>@((availableMemory / 1024.00).ToString("F2") + " GB")</i></span> | ||||
|                                         <span>@Localizer["TotalMemory"] <i>@((memory / 1024.00).ToString("F2") + " GB")</i></span> | ||||
|                                     </div> | ||||
|                                 </Circle> | ||||
|                                 <div class="mt-1"> | ||||
|                                     <span>@Localizer["MemoryUsage"]</span> | ||||
|                                 </div> | ||||
|                             </div> | ||||
|                         </div> | ||||
|  | ||||
|                         <!-- ✅ 磁盘使用率 --> | ||||
|                         <div class="col-12 col-md-4 justify-content-center h-100"> | ||||
|                             @{ | ||||
|                                 var totalFreeSpace = HardwareJob.HardwareInfo.DriveInfo.TotalFreeSpace; | ||||
|                                 var totalSize = HardwareJob.HardwareInfo.DriveInfo.TotalSize; | ||||
|                             } | ||||
|                             <div class="d-flex flex-column align-items-center"> | ||||
|                                 <Circle Width="200" class="m-3" | ||||
|                                         Value=@((int)(totalSize>totalFreeSpace?100 - (totalFreeSpace * 100.00 / totalSize):100)) | ||||
|                                         Color=@((totalFreeSpace * 100.00 / totalSize<20?Color.Warning:Color.Success)) | ||||
|                                         StrokeWidth="4"> | ||||
|                                     <div class="circle-hardware"> | ||||
|                                         <h6>@((100 - (totalFreeSpace * 100.00 / totalSize)).ToString("F2") + " %")</h6> | ||||
|                                         <span>@(HardwareJob.HardwareInfo.DriveInfo.VolumeLabel)</span> | ||||
|                                         <span>@Localizer["AvailableDisk"] <i>@((totalFreeSpace / 1024.00 / 1024 / 1024).ToString("F2") + " GB")</i></span> | ||||
|                                         <span>@Localizer["TotalDisk"] <i>@((totalSize / 1024.00 / 1024 / 1024).ToString("F2") + " GB")</i></span> | ||||
|                                     </div> | ||||
|                                 </Circle> | ||||
|                                 <div class="mt-1"> | ||||
|                                     <span>@Localizer["DiskUsage"]</span> | ||||
|                                 </div> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                     </div> | ||||
|                 </div> | ||||
|             </BodyTemplate> | ||||
|         </Card> | ||||
|     </div> | ||||
| </div> | ||||
|  | ||||
|  | ||||
| <div class="row g-2 mx-1 form-inline"> | ||||
|  | ||||
|     <div class="col-12 col-md-12"> | ||||
| @@ -140,7 +198,7 @@ | ||||
|             </HeaderTemplate> | ||||
|  | ||||
|             <BodyTemplate> | ||||
|                 <Chart @ref=LineChart OnInitAsync="OnInit" Height="var(--line-chart-height)" Width="100%" OnAfterInitAsync="()=>{chartInit=true;return Task.CompletedTask;}" /> | ||||
|                 <Chart @ref=LineChart OnInitAsync="OnInit" Height="var(--line-chart-height)" Width="100%" OnAfterInitAsync="() => { chartInit = true; return Task.CompletedTask; }" /> | ||||
|             </BodyTemplate> | ||||
|         </Card> | ||||
|     </div> | ||||
|   | ||||
| @@ -87,42 +87,45 @@ public class Startup : AppStartup | ||||
| #if NET8_0_OR_GREATER | ||||
|         services | ||||
|          .AddRazorComponents(options => options.TemporaryRedirectionUrlValidityDuration = TimeSpan.FromMinutes(10)) | ||||
|          .AddInteractiveServerComponents(options => | ||||
|          { | ||||
|              options.RootComponents.MaxJSRootComponents = 500; | ||||
|              options.JSInteropDefaultCallTimeout = TimeSpan.FromMinutes(2); | ||||
|              options.MaxBufferedUnacknowledgedRenderBatches = 20; | ||||
|              options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromMinutes(10); | ||||
|          }) | ||||
|          .AddHubOptions(options => | ||||
|          { | ||||
|              //单个传入集线器消息的最大大小。默认 32 KB | ||||
|              options.MaximumReceiveMessageSize = 32 * 1024 * 1024; | ||||
|              //可为客户端上载流缓冲的最大项数。 如果达到此限制,则会阻止处理调用,直到服务器处理流项。 | ||||
|              options.StreamBufferCapacity = 30; | ||||
|              options.ClientTimeoutInterval = TimeSpan.FromMinutes(2); | ||||
|              options.KeepAliveInterval = TimeSpan.FromSeconds(15); | ||||
|              options.HandshakeTimeout = TimeSpan.FromSeconds(30); | ||||
|          }); | ||||
|             .AddInteractiveServerComponents(options => | ||||
|             { | ||||
|                 options.RootComponents.MaxJSRootComponents = 500; | ||||
|                 options.JSInteropDefaultCallTimeout = TimeSpan.FromSeconds(30); | ||||
|                 options.MaxBufferedUnacknowledgedRenderBatches = 5; | ||||
|                 options.DisconnectedCircuitMaxRetained = 1; | ||||
|                 options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromSeconds(10); | ||||
|             }) | ||||
|             .AddHubOptions(options => | ||||
|             { | ||||
|                 //单个传入集线器消息的最大大小。默认 32 KB | ||||
|                 options.MaximumReceiveMessageSize = 32 * 1024 * 1024; | ||||
|                 //可为客户端上载流缓冲的最大项数。 如果达到此限制,则会阻止处理调用,直到服务器处理流项。 | ||||
|                 options.StreamBufferCapacity = 30; | ||||
|                 options.ClientTimeoutInterval = TimeSpan.FromSeconds(30); | ||||
|                 options.KeepAliveInterval = TimeSpan.FromSeconds(15); | ||||
|                 options.HandshakeTimeout = TimeSpan.FromSeconds(15); | ||||
|             }); | ||||
|  | ||||
| #else | ||||
|  | ||||
|         services.AddServerSideBlazor(options => | ||||
|         { | ||||
|             options.RootComponents.MaxJSRootComponents = 500; | ||||
|             options.JSInteropDefaultCallTimeout = TimeSpan.FromMinutes(2); | ||||
|             options.MaxBufferedUnacknowledgedRenderBatches = 20; | ||||
|             options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromMinutes(10); | ||||
|         }).AddHubOptions(options => | ||||
|         { | ||||
|             //单个传入集线器消息的最大大小。默认 32 KB | ||||
|             options.MaximumReceiveMessageSize = 32 * 1024 * 1024; | ||||
|             //可为客户端上载流缓冲的最大项数。 如果达到此限制,则会阻止处理调用,直到服务器处理流项。 | ||||
|             options.StreamBufferCapacity = 30; | ||||
|             options.ClientTimeoutInterval = TimeSpan.FromMinutes(2); | ||||
|             options.KeepAliveInterval = TimeSpan.FromSeconds(15); | ||||
|             options.HandshakeTimeout = TimeSpan.FromSeconds(30); | ||||
|         }); | ||||
|              { | ||||
|                  options.RootComponents.MaxJSRootComponents = 500; | ||||
|                  options.JSInteropDefaultCallTimeout = TimeSpan.FromSeconds(30); | ||||
|                  options.MaxBufferedUnacknowledgedRenderBatches = 5; | ||||
|                  options.DisconnectedCircuitMaxRetained = 1; | ||||
|                  options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromSeconds(10); | ||||
|              }) | ||||
|              .AddHubOptions(options => | ||||
|              { | ||||
|                  //单个传入集线器消息的最大大小。默认 32 KB | ||||
|                  options.MaximumReceiveMessageSize = 32 * 1024 * 1024; | ||||
|                  //可为客户端上载流缓冲的最大项数。 如果达到此限制,则会阻止处理调用,直到服务器处理流项。 | ||||
|                  options.StreamBufferCapacity = 30; | ||||
|                  options.ClientTimeoutInterval = TimeSpan.FromSeconds(30); | ||||
|                  options.KeepAliveInterval = TimeSpan.FromSeconds(15); | ||||
|                  options.HandshakeTimeout = TimeSpan.FromSeconds(15); | ||||
|              }); | ||||
|  | ||||
| #endif | ||||
|  | ||||
|   | ||||
| @@ -15,15 +15,11 @@ | ||||
| 		<PublishReadyToRunComposite>true</PublishReadyToRunComposite> | ||||
| 		<ApplicationIcon>wwwroot\favicon.ico</ApplicationIcon> | ||||
|  | ||||
| 		<CETCompat>false</CETCompat> | ||||
| 		<ServerGarbageCollection>true</ServerGarbageCollection> | ||||
| 		<!--动态适用GC--> | ||||
| 		<GarbageCollectionAdaptationMode>1</GarbageCollectionAdaptationMode> | ||||
| 		<CETCompat>false</CETCompat> | ||||
| 		<!--使用自托管线程池--> | ||||
| 		<!--<UseWindowsThreadPool>false</UseWindowsThreadPool> --> | ||||
|  | ||||
| 		<!--使用工作站GC--> | ||||
| 		<!--<ServerGarbageCollection>true</ServerGarbageCollection>--> | ||||
|  | ||||
| 		 | ||||
| 		<!--<PlatformTarget>x86</PlatformTarget>--> | ||||
| 	</PropertyGroup> | ||||
|  | ||||
|   | ||||
| @@ -14,6 +14,9 @@ using System.ComponentModel.DataAnnotations; | ||||
|  | ||||
| using ThingsGateway.Common.Extension; | ||||
|  | ||||
| using Swashbuckle.AspNetCore.SwaggerGen; | ||||
|  | ||||
|  | ||||
| #if NET8_0_OR_GREATER | ||||
| using System.Collections.Frozen; | ||||
| #endif | ||||
| @@ -104,37 +107,6 @@ internal class CacheManager | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 设置 App 开始时间 | ||||
|     /// </summary> | ||||
|     public void SetStartTime() => SetStartTime(DateTimeOffset.Now); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 设置 App 开始时间 | ||||
|     /// </summary> | ||||
|     private void SetStartTime(DateTimeOffset startDateTimeOffset) | ||||
|     { | ||||
|         GetOrCreate("BootstrapBlazor_StartTime", entry => | ||||
|         { | ||||
|             entry.Priority = CacheItemPriority.NeverRemove; | ||||
|             return startDateTimeOffset; | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获取 App 开始时间 | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     public DateTimeOffset GetStartTime() | ||||
|     { | ||||
|         var ret = DateTimeOffset.MinValue; | ||||
|         if (Cache.TryGetValue("BootstrapBlazor_StartTime", out var v) && v is DateTimeOffset d) | ||||
|         { | ||||
|             ret = d; | ||||
|         } | ||||
|         return ret; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获得 缓存数量 | ||||
|     /// </summary> | ||||
| @@ -286,8 +258,15 @@ internal class CacheManager | ||||
|     /// </summary> | ||||
|     /// <param name="assembly">Assembly 程序集实例</param> | ||||
|     /// <param name="typeName">类型名称</param> | ||||
|     public static IEnumerable<LocalizedString>? GetAllStringsByTypeName(Assembly assembly, string typeName) | ||||
|     public static FrozenSet<LocalizedString>? GetAllStringsByTypeName(Assembly assembly, string typeName) | ||||
|         => GetJsonStringByTypeName(GetJsonLocalizationOption(), assembly, typeName, CultureInfo.CurrentUICulture.Name); | ||||
|     /// <summary> | ||||
|     /// 获取指定文化本地化资源集合 | ||||
|     /// </summary> | ||||
|     /// <param name="assembly">Assembly 程序集实例</param> | ||||
|     /// <param name="typeName">类型名称</param> | ||||
|     public static FrozenDictionary<string, string>? GetAllHasValueStringsByTypeName(Assembly assembly, string typeName) | ||||
|         => GetHasValueJsonStringByTypeName(GetJsonLocalizationOption(), assembly, typeName, CultureInfo.CurrentUICulture.Name); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 通过指定程序集获取所有本地化信息键值集合 | ||||
| @@ -298,7 +277,7 @@ internal class CacheManager | ||||
|     /// <param name="cultureName">cultureName 未空时使用 CultureInfo.CurrentUICulture.Name</param> | ||||
|     /// <param name="forceLoad">默认 false 使用缓存值 设置 true 时内部强制重新加载</param> | ||||
|     /// <returns></returns> | ||||
|     public static IEnumerable<LocalizedString>? GetJsonStringByTypeName(JsonLocalizationOptions option, Assembly assembly, string typeName, string? cultureName = null, bool forceLoad = false) | ||||
|     public static FrozenSet<LocalizedString>? GetJsonStringByTypeName(JsonLocalizationOptions option, Assembly assembly, string typeName, string? cultureName = null, bool forceLoad = false) | ||||
|     { | ||||
|         if (assembly.IsDynamic) | ||||
|         { | ||||
| @@ -308,13 +287,15 @@ internal class CacheManager | ||||
|         cultureName ??= CultureInfo.CurrentUICulture.Name; | ||||
|         if (string.IsNullOrEmpty(cultureName)) | ||||
|         { | ||||
|             return []; | ||||
|             return null; | ||||
|         } | ||||
|  | ||||
|         var key = $"{CacheKeyPrefix}-{nameof(GetJsonStringByTypeName)}-{assembly.GetUniqueName()}-{cultureName}"; | ||||
|         var typeKey = $"{CacheKeyPrefix}-{nameof(GetJsonStringByTypeName)}-{assembly.GetUniqueName()}-{typeName}-{cultureName}"; | ||||
|         if (forceLoad) | ||||
|         { | ||||
|             Instance.Cache.Remove(key); | ||||
|             Instance.Cache.Remove(typeKey); | ||||
|         } | ||||
|  | ||||
|         var localizedItems = Instance.GetOrCreate(key, entry => | ||||
| @@ -335,16 +316,77 @@ internal class CacheManager | ||||
|             return items.ToHashSet(); | ||||
| #endif | ||||
|         }); | ||||
|         return localizedItems.Where(item => item.SearchedLocation!.Equals(typeName, StringComparison.OrdinalIgnoreCase)); | ||||
|     } | ||||
|  | ||||
|         var typeLocalizedItems = Instance.GetOrCreate(typeKey, entry => | ||||
|         { | ||||
|             return localizedItems.Where(item => item.SearchedLocation!.Equals(typeName, StringComparison.OrdinalIgnoreCase)).ToFrozenSet(); | ||||
|         }); | ||||
|         return typeLocalizedItems; | ||||
|     } | ||||
|     /// <summary> | ||||
|     /// 通过 ILocalizationResolve 接口实现类获得本地化键值集合 | ||||
|     /// 通过指定程序集获取所有本地化信息键值集合 | ||||
|     /// </summary> | ||||
|     /// <param name="typeName"></param> | ||||
|     /// <param name="includeParentCultures"></param> | ||||
|     /// <param name="option">JsonLocalizationOptions 实例</param> | ||||
|     /// <param name="assembly">Assembly 程序集实例</param> | ||||
|     /// <param name="typeName">类型名称</param> | ||||
|     /// <param name="cultureName">cultureName 未空时使用 CultureInfo.CurrentUICulture.Name</param> | ||||
|     /// <param name="forceLoad">默认 false 使用缓存值 设置 true 时内部强制重新加载</param> | ||||
|     /// <returns></returns> | ||||
|     public static IEnumerable<LocalizedString> GetTypeStringsFromResolve(string typeName, bool includeParentCultures = true) => Provider.GetRequiredService<ILocalizationResolve>().GetAllStringsByType(typeName, includeParentCultures); | ||||
|     public static FrozenDictionary<string, string>? GetHasValueJsonStringByTypeName(JsonLocalizationOptions option, Assembly assembly, string typeName, string? cultureName = null, bool forceLoad = false) | ||||
|     { | ||||
|         if (assembly.IsDynamic) | ||||
|         { | ||||
|             return null; | ||||
|         } | ||||
|  | ||||
|         cultureName ??= CultureInfo.CurrentUICulture.Name; | ||||
|         if (string.IsNullOrEmpty(cultureName)) | ||||
|         { | ||||
|             return null; | ||||
|         } | ||||
|  | ||||
|         var key = $"{CacheKeyPrefix}-{nameof(GetJsonStringByTypeName)}-{assembly.GetUniqueName()}-{cultureName}"; | ||||
|  | ||||
|  | ||||
|         var typeKey = $"{CacheKeyPrefix}-{nameof(GetHasValueJsonStringByTypeName)}-{assembly.GetUniqueName()}-{typeName}-{cultureName}"; | ||||
|         if (forceLoad) | ||||
|         { | ||||
|             Instance.Cache.Remove(key); | ||||
|             Instance.Cache.Remove(typeKey); | ||||
|         } | ||||
|  | ||||
|         var localizedItems = Instance.GetOrCreate(key, entry => | ||||
|         { | ||||
|             var sections = option.GetJsonStringFromAssembly(assembly, cultureName); | ||||
|             var items = sections.SelectMany(section => section.GetChildren().Select(kv => | ||||
|             { | ||||
|                 var value = kv.Value; | ||||
|                 if (value == null && option.UseKeyWhenValueIsNull == true) | ||||
|                 { | ||||
|                     value = kv.Key; | ||||
|                 } | ||||
|                 return new LocalizedString(kv.Key, value ?? "", false, section.Key); | ||||
|             })); | ||||
| #if NET8_0_OR_GREATER | ||||
|             return items.ToFrozenSet(); | ||||
| #else | ||||
|             return items.ToHashSet(); | ||||
| #endif | ||||
|         }); | ||||
|  | ||||
|         var typeLocalizedItems = Instance.GetOrCreate(typeKey, entry => | ||||
|         { | ||||
|             return localizedItems.Where(item => item.SearchedLocation!.Equals(typeName, StringComparison.OrdinalIgnoreCase) && !item.ResourceNotFound).ToFrozenDictionary(a => a.Name, a => a.Value); | ||||
|         }); | ||||
|         return typeLocalizedItems; | ||||
|     } | ||||
|     ///// <summary> | ||||
|     ///// 通过 ILocalizationResolve 接口实现类获得本地化键值集合 | ||||
|     ///// </summary> | ||||
|     ///// <param name="typeName"></param> | ||||
|     ///// <param name="includeParentCultures"></param> | ||||
|     ///// <returns></returns> | ||||
|     //public static IEnumerable<LocalizedString> GetTypeStringsFromResolve(string typeName, bool includeParentCultures = true) => Provider.GetRequiredService<ILocalizationResolve>().GetAllStringsByType(typeName, includeParentCultures); | ||||
|     #endregion | ||||
|  | ||||
|     #region DisplayName | ||||
|   | ||||
| @@ -81,50 +81,16 @@ internal class JsonStringLocalizer(Assembly assembly, string typeName, string ba | ||||
|     /// <returns></returns> | ||||
|     private string? GetStringSafely(string name) => GetStringFromJson(name); | ||||
|  | ||||
|     private string? GetStringFromService(string name) | ||||
|     { | ||||
|         // get string from inject service | ||||
|         string? ret = null; | ||||
|         if (jsonLocalizationOptions.DisableGetLocalizerFromService == false) | ||||
|         { | ||||
|             var localizer = Utility.GetStringLocalizerFromService(Assembly, typeName); | ||||
|             if (localizer != null && localizer is not JsonStringLocalizer) | ||||
|             { | ||||
|                 var l = localizer[name]; | ||||
|                 if (!l.ResourceNotFound) | ||||
|                 { | ||||
|                     ret = l.Value; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return ret; | ||||
|     } | ||||
|  | ||||
|     private string? GetStringFromResourceManager(string name) | ||||
|     { | ||||
|         string? ret = null; | ||||
|         if (jsonLocalizationOptions.DisableGetLocalizerFromResourceManager == false) | ||||
|         { | ||||
|             ret = GetStringSafely(name, CultureInfo.CurrentUICulture); | ||||
|         } | ||||
|         return ret; | ||||
|     } | ||||
|  | ||||
|     private readonly ConcurrentHashSet<string> _missingManifestCache = []; | ||||
|     private string? GetStringFromJson(string name) | ||||
|     { | ||||
|         // get string from json localization file | ||||
|         var localizerStrings = MegerResolveLocalizers(CacheManager.GetAllStringsByTypeName(Assembly, typeName)); | ||||
|         var cacheKey = $"name={name}&culture={CultureInfo.CurrentUICulture.Name}"; | ||||
|         string? ret = null; | ||||
|         if (!_missingManifestCache.Contain(cacheKey)) | ||||
|         { | ||||
|             var l = localizerStrings.Find(i => i.Name == name); | ||||
|             if (l is { ResourceNotFound: false }) | ||||
|             { | ||||
|                 ret = l.Value; | ||||
|             } | ||||
|             else | ||||
|             var localizerStrings = CacheManager.GetAllHasValueStringsByTypeName(Assembly, typeName); | ||||
|             if (localizerStrings?.TryGetValue(name, out ret) != true) | ||||
|             { | ||||
|                 // 如果没有找到资源信息则尝试从父类中查找 | ||||
|                 ret ??= GetStringFromBaseType(name); | ||||
| @@ -150,28 +116,13 @@ internal class JsonStringLocalizer(Assembly assembly, string typeName, string ba | ||||
|             if (baseType != type) | ||||
|             { | ||||
|                 var baseAssembly = baseType.Assembly; | ||||
|                 var localizerStrings = MegerResolveLocalizers(CacheManager.GetAllStringsByTypeName(baseAssembly, baseType.FullName!)); | ||||
|                 var l = localizerStrings.Find(i => i.Name == name); | ||||
|                 if (l is { ResourceNotFound: false }) | ||||
|                 { | ||||
|                     ret = l.Value; | ||||
|                 } | ||||
|                 var localizerStrings = CacheManager.GetAllHasValueStringsByTypeName(baseAssembly, baseType.FullName!); | ||||
|                 _ = localizerStrings?.TryGetValue(name, out ret); | ||||
|             } | ||||
|         } | ||||
|         return ret; | ||||
|     } | ||||
|  | ||||
|     private List<LocalizedString> MegerResolveLocalizers(IEnumerable<LocalizedString>? localizerStrings) | ||||
|     { | ||||
|         var localizers = new List<LocalizedString>(CacheManager.GetTypeStringsFromResolve(typeName)); | ||||
|  | ||||
|         if (localizerStrings != null) | ||||
|         { | ||||
|             localizers.AddRange(localizerStrings); | ||||
|         } | ||||
|         return localizers; | ||||
|     } | ||||
|  | ||||
|     private void HandleMissingResourceItem(string name) | ||||
|     { | ||||
|         localizationMissingItemHandler.HandleMissingItem(name, typeName, CultureInfo.CurrentUICulture.Name); | ||||
| @@ -183,7 +134,7 @@ internal class JsonStringLocalizer(Assembly assembly, string typeName, string ba | ||||
|         _missingManifestCache.TryAdd($"name={name}&culture={CultureInfo.CurrentUICulture.Name}"); | ||||
|     } | ||||
|  | ||||
|     private List<LocalizedString>? _allLocalizerdStrings; | ||||
|     private LocalizedString[]? _allLocalizerdStrings; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获取当前语言的所有资源信息 | ||||
| @@ -198,7 +149,7 @@ internal class JsonStringLocalizer(Assembly assembly, string typeName, string ba | ||||
|                 ?? GetAllStringsFromBase() | ||||
|                 ?? GetAllStringsFromJson(); | ||||
|  | ||||
|             _allLocalizerdStrings = MegerResolveLocalizers(items); | ||||
|             _allLocalizerdStrings = items.ToArray(); | ||||
|         } | ||||
|         return _allLocalizerdStrings; | ||||
|  | ||||
|   | ||||
| @@ -14,7 +14,7 @@ | ||||
| 	<ItemGroup> | ||||
| 		<PackageReference Include="BootstrapBlazor.TableExport" Version="9.2.7" /> | ||||
| 		<PackageReference Include="Yitter.IdGenerator" Version="1.0.14" /> | ||||
| 		<PackageReference Include="BootstrapBlazor" Version="9.11.2" /> | ||||
| 		<PackageReference Include="BootstrapBlazor" Version="9.11.4" /> | ||||
| 	</ItemGroup> | ||||
|  | ||||
| 	<ItemGroup> | ||||
|   | ||||
| @@ -7,17 +7,22 @@ using System.Runtime.Versioning; | ||||
| using System.Security; | ||||
|  | ||||
| using ThingsGateway.NewLife.Collections; | ||||
| using ThingsGateway.NewLife.Data; | ||||
| using ThingsGateway.NewLife.Log; | ||||
| using ThingsGateway.NewLife.Model; | ||||
| using ThingsGateway.NewLife.Reflection; | ||||
| using ThingsGateway.NewLife.Serialization; | ||||
| using ThingsGateway.NewLife.Windows; | ||||
|  | ||||
| using System.Diagnostics; | ||||
| using System.Runtime; | ||||
|  | ||||
|  | ||||
| #if NETFRAMEWORK | ||||
| using System.Management; | ||||
|  | ||||
| using Microsoft.VisualBasic.Devices; | ||||
|  | ||||
|  | ||||
| #endif | ||||
| #if NETFRAMEWORK || NET6_0_OR_GREATER | ||||
| using Microsoft.Win32; | ||||
| @@ -42,7 +47,7 @@ public interface IMachineInfo | ||||
| ///  | ||||
| /// 刷新信息成本较高,建议采用单例模式 | ||||
| /// </remarks> | ||||
| public class MachineInfo : IExtend | ||||
| public class MachineInfo | ||||
| { | ||||
|     #region 属性 | ||||
|     /// <summary>系统名称</summary> | ||||
| @@ -88,11 +93,11 @@ public class MachineInfo : IExtend | ||||
|     [DisplayName("磁盘序列号")] | ||||
|     public String? DiskID { get; set; } | ||||
|  | ||||
|     /// <summary>内存总量。单位KB</summary> | ||||
|     /// <summary>内存总量。单位MB</summary> | ||||
|     [DisplayName("内存总量")] | ||||
|     public UInt64 Memory { get; set; } | ||||
|  | ||||
|     /// <summary>可用内存。单位KB</summary> | ||||
|     /// <summary>可用内存。单位MB</summary> | ||||
|     [DisplayName("可用内存")] | ||||
|     public UInt64 AvailableMemory { get; set; } | ||||
|  | ||||
| @@ -116,13 +121,98 @@ public class MachineInfo : IExtend | ||||
|     [DisplayName("电池剩余")] | ||||
|     public Double Battery { get; set; } | ||||
|  | ||||
|     private readonly Dictionary<String, Object?> _items = []; | ||||
|     IDictionary<String, Object?> IExtend.Items => _items; | ||||
| #if NET6_0_OR_GREATER | ||||
|     #region GC与进程内存信息 | ||||
|  | ||||
|     /// <summary>GC 认为“内存吃紧”的阈值。单位:MB</summary> | ||||
|     [DisplayName("GC高内存阈值")] | ||||
|     public UInt64 HighMemoryLoadThreshold { get; set; } | ||||
|  | ||||
|     /// <summary>GC 可用内存上限。单位:MB</summary> | ||||
|     [DisplayName("GC可用内存上限")] | ||||
|     public UInt64 TotalAvailableMemory { get; set; } | ||||
|  | ||||
|     /// <summary>当前托管堆容量。单位:MB</summary> | ||||
|     [DisplayName("托管堆容量")] | ||||
|     public UInt64 HeapSize { get; set; } | ||||
|  | ||||
|     /// <summary>托管堆已用内存。单位:MB</summary> | ||||
|     [DisplayName("托管堆已用")] | ||||
|     public UInt64 TotalMemory { get; set; } | ||||
|  | ||||
|     /// <summary>托管堆碎片大小。单位:MB</summary> | ||||
|     [DisplayName("托管堆碎片")] | ||||
|     public UInt64 FragmentedBytes { get; set; } | ||||
|  | ||||
|     /// <summary>GC识别可用内存。单位:MB</summary> | ||||
|     [DisplayName("GC识别可用内存")] | ||||
|     public UInt64 GCAvailableMemory { get; set; } | ||||
|  | ||||
|     /// <summary>GC 已提交的内存。单位:MB</summary> | ||||
|     [DisplayName("GC已提交内存")] | ||||
|     public UInt64 CommittedBytes { get; set; } | ||||
|  | ||||
|     /// <summary>GC 累计分配的托管内存。单位:MB</summary> | ||||
|     [DisplayName("GC累计分配")] | ||||
|     public UInt64 TotalAllocatedBytes { get; set; } | ||||
| #if NET8_0_OR_GREATER | ||||
|     /// <summary>GC 暂停累计时间。单位:毫秒</summary> | ||||
|     [DisplayName("GC累计暂停时间")] | ||||
|     public UInt64 TotalPauseDurationMs { get; set; } | ||||
| #endif | ||||
|     /// <summary>GC 代0收集次数</summary> | ||||
|     [DisplayName("GC Gen0 次数")] | ||||
|     public Int32 GcGen0Count { get; set; } | ||||
|  | ||||
|     /// <summary>GC 代1收集次数</summary> | ||||
|     [DisplayName("GC Gen1 次数")] | ||||
|     public Int32 GcGen1Count { get; set; } | ||||
|  | ||||
|     /// <summary>GC 代2收集次数</summary> | ||||
|     [DisplayName("GC Gen2 次数")] | ||||
|     public Int32 GcGen2Count { get; set; } | ||||
|  | ||||
|     /// <summary>Server GC 是否启用</summary> | ||||
|     [DisplayName("是否使用Server GC")] | ||||
|     public Boolean IsServerGC { get; set; } | ||||
|  | ||||
|     /// <summary>GC 延迟模式</summary> | ||||
|     [DisplayName("GC延迟模式")] | ||||
|     public GCLatencyMode? GCLatencyMode { get; set; } | ||||
|  | ||||
|     /// <summary>GC 固定对象数</summary> | ||||
|     [DisplayName("固定对象数")] | ||||
|     public Int64 PinnedObjectsCount { get; set; } | ||||
|  | ||||
|     /// <summary>终结队列挂起对象数</summary> | ||||
|     [DisplayName("终结挂起数")] | ||||
|     public Int64 FinalizationPendingCount { get; set; } | ||||
|  | ||||
|     #endregion | ||||
|  | ||||
|     #region 进程内存信息 | ||||
|  | ||||
|     /// <summary>进程虚拟内存使用量。单位:MB</summary> | ||||
|     [DisplayName("虚拟内存")] | ||||
|     public UInt64 VirtualMemory { get; set; } | ||||
|  | ||||
|     /// <summary>进程私有内存使用量。单位:MB</summary> | ||||
|     [DisplayName("私有内存")] | ||||
|     public UInt64 PrivateMemory { get; set; } | ||||
|  | ||||
|     /// <summary>进程峰值工作集。单位:MB</summary> | ||||
|     [DisplayName("峰值工作集")] | ||||
|     public UInt64 PeakWorkingSet { get; set; } | ||||
|  | ||||
|     /// <summary>进程当前工作集。单位:MB</summary> | ||||
|     [DisplayName("当前工作集")] | ||||
|     public UInt64 WorkingSet { get; set; } | ||||
|  | ||||
|     #endregion | ||||
|  | ||||
| #endif | ||||
|  | ||||
|  | ||||
|     /// <summary>获取 或 设置 扩展属性数据</summary> | ||||
|     /// <param name="key"></param> | ||||
|     /// <returns></returns> | ||||
|     public Object? this[String key] { get => _items.TryGetValue(key, out var obj) ? obj : null; set => _items[key] = value; } | ||||
|     #endregion | ||||
|  | ||||
|     #region 全局静态 | ||||
| @@ -569,10 +659,50 @@ public class MachineInfo : IExtend | ||||
|         else if (Runtime.Linux) | ||||
|             RefreshLinux(); | ||||
|  | ||||
|         // 刷新 GC 与进程内存信息 | ||||
|         RefreshMemoryInfo(); | ||||
|  | ||||
|         RefreshSpeed(); | ||||
|  | ||||
|         Provider?.Refresh(this); | ||||
|     } | ||||
|     /// <summary> | ||||
|     /// 刷新 GC 与进程内存相关信息 | ||||
|     /// </summary> | ||||
|     private void RefreshMemoryInfo() | ||||
|     { | ||||
| #if NET6_0_OR_GREATER | ||||
|         var info = GC.GetGCMemoryInfo(); | ||||
|         var proc = Process.GetCurrentProcess(); | ||||
|  | ||||
|         // GC 信息(单位:MB) | ||||
|         HighMemoryLoadThreshold = (ulong)(info.HighMemoryLoadThresholdBytes / 1024 / 1024); | ||||
|         TotalAvailableMemory = (ulong)(info.TotalAvailableMemoryBytes / 1024 / 1024); | ||||
|         HeapSize = (ulong)(info.HeapSizeBytes / 1024 / 1024); | ||||
|         TotalMemory = (ulong)(GC.GetTotalMemory(false) / 1024 / 1024); | ||||
|         FragmentedBytes = (ulong)(info.FragmentedBytes / 1024 / 1024); | ||||
|         GCAvailableMemory = (ulong)Math.Max(0, (info.TotalAvailableMemoryBytes - info.MemoryLoadBytes) / 1024 / 1024); | ||||
|         CommittedBytes = (ulong)(info.TotalCommittedBytes / 1024 / 1024); | ||||
|         TotalAllocatedBytes = (ulong)(GC.GetTotalAllocatedBytes(false) / 1024 / 1024); | ||||
| #if NET8_0_OR_GREATER | ||||
|         TotalPauseDurationMs = (ulong)GC.GetTotalPauseDuration().TotalMilliseconds; | ||||
| #endif | ||||
|         GcGen0Count = GC.CollectionCount(0); | ||||
|         GcGen1Count = GC.CollectionCount(1); | ||||
|         GcGen2Count = GC.CollectionCount(2); | ||||
|         IsServerGC = System.Runtime.GCSettings.IsServerGC; | ||||
|         GCLatencyMode = System.Runtime.GCSettings.LatencyMode; | ||||
|         PinnedObjectsCount = info.PinnedObjectsCount; | ||||
|         FinalizationPendingCount = info.FinalizationPendingCount; | ||||
|  | ||||
|         // 进程信息(单位:MB) | ||||
|         VirtualMemory = (ulong)(proc.VirtualMemorySize64 / 1024 / 1024); | ||||
|         PrivateMemory = (ulong)(proc.PrivateMemorySize64 / 1024 / 1024); | ||||
|         PeakWorkingSet = (ulong)(proc.PeakWorkingSet64 / 1024 / 1024); | ||||
|         WorkingSet = (ulong)(proc.WorkingSet64 / 1024 / 1024); | ||||
|  | ||||
| #endif | ||||
|     } | ||||
|  | ||||
|     private void RefreshWindows() | ||||
|     { | ||||
| @@ -580,8 +710,8 @@ public class MachineInfo : IExtend | ||||
|         ms.Init(); | ||||
|         if (GlobalMemoryStatusEx(ref ms)) | ||||
|         { | ||||
|             Memory = (ulong)(ms.ullTotalPhys / 1024.0); | ||||
|             AvailableMemory = (ulong)(ms.ullAvailPhys / 1024.0); | ||||
|             Memory = ms.ullTotalPhys / 1024 / 1024; | ||||
|             AvailableMemory = ms.ullAvailPhys / 1024 / 1024; | ||||
|         } | ||||
|  | ||||
|         GetSystemTimes(out var idleTime, out var kernelTime, out var userTime); | ||||
| @@ -675,28 +805,87 @@ public class MachineInfo : IExtend | ||||
| #endif | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 🐳 容器内存使用 | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     private static (ulong Total, ulong Used) GetCGroupMemoryUsage() | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|  | ||||
|             string[] limitPaths = { | ||||
|             "/sys/fs/cgroup/memory/memory.limit_in_bytes", // cgroup v1 | ||||
|             "/sys/fs/cgroup/memory.max"                    // cgroup v2 | ||||
|         }; | ||||
|  | ||||
|             string[] usagePaths = { | ||||
|             "/sys/fs/cgroup/memory/memory.usage_in_bytes",  // cgroup v1 | ||||
|             "/sys/fs/cgroup/memory.current"                 // cgroup v2 | ||||
|         }; | ||||
|  | ||||
|             ulong total = ReadFirstAvailable(limitPaths); | ||||
|             ulong used = ReadFirstAvailable(usagePaths); | ||||
|  | ||||
|             // 容器无内存限制时 total 通常是 2^63-1,忽略 | ||||
|             if (total > (1UL << 60)) total = 0; | ||||
|  | ||||
|             return (total, used); | ||||
|  | ||||
|  | ||||
|         } | ||||
|         catch (Exception) | ||||
|         { | ||||
|             return (0, 0); | ||||
|         } | ||||
|         static ulong ReadFirstAvailable(string[] paths) | ||||
|         { | ||||
|             foreach (var path in paths) | ||||
|             { | ||||
|                 if (File.Exists(path)) | ||||
|                 { | ||||
|                     var content = File.ReadAllText(path).Trim(); | ||||
|                     if (content == "max") return ulong.MaxValue; | ||||
|                     if (ulong.TryParse(content, out var value)) return value; | ||||
|                 } | ||||
|             } | ||||
|             return 0; | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     private void RefreshLinux() | ||||
|     { | ||||
|         var dic = ReadInfo("/proc/meminfo"); | ||||
|         if (dic != null) | ||||
|         var (totalMemory, usedMemory) = GetCGroupMemoryUsage(); | ||||
|         if (totalMemory > 0 && usedMemory > 0 && totalMemory < ulong.MaxValue / 2) | ||||
|         { | ||||
|             if (dic.TryGetValue("MemTotal", out var str) && !str.IsNullOrEmpty()) | ||||
|                 Memory = (UInt64)str.TrimEnd(" kB").ToLong(); | ||||
|  | ||||
|             ulong ma = 0; | ||||
|             if (dic.TryGetValue("MemAvailable", out str) && !str.IsNullOrEmpty()) | ||||
|             Memory = totalMemory / 1024 / 1024; | ||||
|             AvailableMemory = (totalMemory - usedMemory) / 1024 / 1024; | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             var dic = ReadInfo("/proc/meminfo"); | ||||
|             if (dic != null) | ||||
|             { | ||||
|                 ma = (UInt64)(str.TrimEnd(" kB").ToLong()); | ||||
|                 if (dic.TryGetValue("MemTotal", out var str) && !str.IsNullOrEmpty()) | ||||
|                     Memory = (UInt64)str.TrimEnd(" kB").ToLong(); | ||||
|  | ||||
|                 ulong ma = 0; | ||||
|                 if (dic.TryGetValue("MemAvailable", out str) && !str.IsNullOrEmpty()) | ||||
|                 { | ||||
|                     ma = (UInt64)(str.TrimEnd(" kB").ToLong() / 1024); | ||||
|                 } | ||||
|  | ||||
|                 //低于3.14内核的版本用 free+cache | ||||
|                 var mf = (UInt64)(dic["MemFree"]?.TrimEnd(" kB").ToLong() / 1024 ?? 0); | ||||
|                 var mc = (UInt64)(dic["Cached"]?.TrimEnd(" kB").ToLong() / 1024 ?? 0); | ||||
|                 var bf = (UInt64)(dic["Buffers"]?.TrimEnd(" kB").ToLong() / 1024 ?? 0); | ||||
|  | ||||
|                 var free = mf + mc + bf; | ||||
|  | ||||
|                 AvailableMemory = ma > free ? ma : free; | ||||
|             } | ||||
|  | ||||
|             //低于3.14内核的版本用 free+cache | ||||
|             var mf = (UInt64)(dic["MemFree"]?.TrimEnd(" kB").ToLong() ?? 0); | ||||
|             var mc = (UInt64)(dic["Cached"]?.TrimEnd(" kB").ToLong() ?? 0); | ||||
|             var bf = (UInt64)(dic["Buffers"]?.TrimEnd(" kB").ToLong() ?? 0); | ||||
|  | ||||
|             var free = mf + mc + bf; | ||||
|  | ||||
|             AvailableMemory = ma > free ? ma : free; | ||||
|         } | ||||
|  | ||||
|         // A2/A4温度获取,Buildroot,CPU温度和主板温度 | ||||
|   | ||||
| @@ -180,6 +180,12 @@ public static class Runtime | ||||
|     #endregion | ||||
|  | ||||
|     #region 扩展 | ||||
|  | ||||
|     public static Int64 AppStartTick = TickCount64; | ||||
|  | ||||
|     /// <summary>软件启动以来的毫秒数</summary> | ||||
|     public static Int64 AppTickCount64 => TickCount64-AppStartTick; | ||||
|  | ||||
| #if NETCOREAPP3_1_OR_GREATER | ||||
|     /// <summary>系统启动以来的毫秒数</summary> | ||||
|     public static Int64 TickCount64 => Environment.TickCount64; | ||||
| @@ -196,6 +202,8 @@ public static class Runtime | ||||
|     } | ||||
| #endif | ||||
|  | ||||
|  | ||||
|  | ||||
|     /// <summary>获取当前UTC时间。基于全局时间提供者,在星尘应用中会屏蔽服务器时间差</summary> | ||||
|     /// <returns></returns> | ||||
|     public static DateTimeOffset UtcNow => TimerScheduler.GlobalTimeProvider.GetUtcNow(); | ||||
| @@ -248,7 +256,7 @@ public static class Runtime | ||||
|  | ||||
|         return dic; | ||||
|     } | ||||
|     #endregion | ||||
| #endregion | ||||
|  | ||||
|     #region 设置 | ||||
|     private static Boolean? _createConfigOnMissing; | ||||
|   | ||||
| @@ -8,8 +8,6 @@ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| using ThingsGateway.NewLife.Collections; | ||||
|  | ||||
| namespace ThingsGateway.NewLife; | ||||
|  | ||||
| /// <summary> | ||||
|   | ||||
| @@ -61,7 +61,8 @@ public class TextFileLog : Logger, IDisposable | ||||
|         MaxBytes = set.LogFileMaxBytes; | ||||
|         Backups = set.LogFileBackups; | ||||
|  | ||||
|         _Timer = new TimerX(DoWriteAndClose, null, 0_000, 5_000) { Async = true }; | ||||
|         _Timer = new TimerX(DoWriteAndClose, null, 0_000, 60_000, nameof(TextFileLog)) { Async = true }; | ||||
|         _WriteTimer = new TimerX(DoWrite, null, 0_000, 1000, nameof(TextFileLog)) { Async = true }; | ||||
|     } | ||||
|  | ||||
|     private static readonly NonBlockingDictionary<String, TextFileLog> cache = new(StringComparer.OrdinalIgnoreCase); | ||||
| @@ -96,6 +97,7 @@ public class TextFileLog : Logger, IDisposable | ||||
|     protected virtual void Dispose(Boolean disposing) | ||||
|     { | ||||
|         _Timer.TryDispose(); | ||||
|         _WriteTimer.TryDispose(); | ||||
|  | ||||
|         // 销毁前把队列日志输出 | ||||
|         if (Interlocked.CompareExchange(ref _writing, 1, 0) == 0) WriteAndClose(DateTime.MinValue); | ||||
| @@ -147,35 +149,72 @@ public class TextFileLog : Logger, IDisposable | ||||
|  | ||||
|     /// <summary>获取日志文件路径</summary> | ||||
|     /// <returns></returns> | ||||
|     private String? GetLogFile() | ||||
|     private string? GetLogFile() | ||||
|     { | ||||
|         // 单日志文件 | ||||
|         if (_isFile) return LogPath.GetBasePath(); | ||||
|  | ||||
|         Directory.CreateDirectory(LogPath); | ||||
|         // 目录多日志文件 | ||||
|         var logfile = LogPath.CombinePath(String.Format(FileFormat, TimerX.Now.AddHours(Setting.Current.UtcIntervalHours), Level)).GetBasePath(); | ||||
|         var baseFile = LogPath.CombinePath( | ||||
|             string.Format(FileFormat, TimerX.Now.AddHours(Setting.Current.UtcIntervalHours), Level) | ||||
|         ).GetBasePath(); | ||||
|  | ||||
|         // 是否限制文件大小 | ||||
|         if (MaxBytes == 0) return logfile; | ||||
|         // 不限制大小 | ||||
|         if (MaxBytes == 0) return baseFile; | ||||
|  | ||||
|         // 找到今天第一个未达到最大上限的文件 | ||||
|         var max = MaxBytes * 1024L * 1024L; | ||||
|         var ext = Path.GetExtension(logfile); | ||||
|         var name = logfile.TrimEnd(ext); | ||||
|         var ext = Path.GetExtension(FileFormat); | ||||
|  | ||||
|         string? latestFile = null; | ||||
|         DateTime latestTime = DateTime.MinValue; | ||||
|  | ||||
|         foreach (var path in Directory.EnumerateFiles(LogPath, $"*{ext}", SearchOption.TopDirectoryOnly)) | ||||
|         { | ||||
|             try | ||||
|             { | ||||
|                 var lastWrite = File.GetLastWriteTimeUtc(path); | ||||
|                 if (lastWrite > latestTime) | ||||
|                 { | ||||
|                     latestTime = lastWrite; | ||||
|                     latestFile = path; | ||||
|                 } | ||||
|             } | ||||
|             catch { } | ||||
|         } | ||||
|  | ||||
|         if (latestFile != null) | ||||
|         { | ||||
|             try | ||||
|             { | ||||
|                 var len = new FileInfo(latestFile).Length; | ||||
|                 if (len < max) | ||||
|                     return latestFile; | ||||
|             } | ||||
|             catch { } | ||||
|         } | ||||
|  | ||||
|         var fileNameWithoutExt = Path.Combine( | ||||
|             Path.GetDirectoryName(baseFile)!, | ||||
|             Path.GetFileNameWithoutExtension(baseFile) | ||||
|         ); | ||||
|  | ||||
|         // 依序找下一个可用文件 | ||||
|         for (var i = 1; i < 1024; i++) | ||||
|         { | ||||
|             if (i > 1) logfile = $"{name}_{i}{ext}"; | ||||
|             var nextFile = i == 1 ? $"{fileNameWithoutExt}{ext}" : $"{fileNameWithoutExt}_{i}{ext}"; | ||||
|             if (!File.Exists(nextFile)) | ||||
|                 return nextFile; | ||||
|  | ||||
|             var fi = logfile.AsFile(); | ||||
|             if (!fi.Exists || fi.Length < max) return logfile; | ||||
|         } | ||||
|  | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|     #endregion | ||||
|  | ||||
|     #region 异步写日志 | ||||
|     private readonly TimerX? _Timer; | ||||
|     private readonly TimerX? _WriteTimer; | ||||
|     private readonly ConcurrentQueue<String> _Logs = new(); | ||||
|     private volatile Int32 _logCount; | ||||
|     private Int32 _writing; | ||||
| @@ -186,9 +225,9 @@ public class TextFileLog : Logger, IDisposable | ||||
|     { | ||||
|         var writer = LogWriter; | ||||
|  | ||||
|         var now = TimerX.Now.AddHours(Setting.Current.UtcIntervalHours); | ||||
|         var logFile = GetLogFile(); | ||||
|         if (logFile.IsNullOrEmpty()) return; | ||||
|         var now = TimerX.Now.AddHours(Setting.Current.UtcIntervalHours); | ||||
|  | ||||
|         if (!_isFile && logFile != CurrentLogFile) | ||||
|         { | ||||
| @@ -223,7 +262,10 @@ public class TextFileLog : Logger, IDisposable | ||||
|         // 连续5秒没日志,就关闭 | ||||
|         _NextClose = now.AddSeconds(5); | ||||
|     } | ||||
|  | ||||
|     private void DoWrite(Object? state) | ||||
|     { | ||||
|         WriteLog(); | ||||
|     } | ||||
|     /// <summary>关闭文件</summary> | ||||
|     private void DoWriteAndClose(Object? state) | ||||
|     { | ||||
| @@ -237,43 +279,36 @@ public class TextFileLog : Logger, IDisposable | ||||
|             DirectoryInfo? di = new DirectoryInfo(LogPath); | ||||
|             if (di.Exists) | ||||
|             { | ||||
|                 // 删除*.del | ||||
|                 // 删除 *.del | ||||
|                 try | ||||
|                 { | ||||
|                     var dels = di.GetFiles("*.del"); | ||||
|                     if (dels?.Length > 0) | ||||
|                     foreach (var item in di.EnumerateFiles("*.del")) | ||||
|                     { | ||||
|                         foreach (var item in dels) | ||||
|                         { | ||||
|                             item.Delete(); | ||||
|                         } | ||||
|                         item.Delete(); | ||||
|                     } | ||||
|                 } | ||||
|                 catch { } | ||||
|  | ||||
|                 var ext = Path.GetExtension(FileFormat); | ||||
|                 var fis = di.GetFiles("*" + ext); | ||||
|                 if (fis != null && fis.Length > Backups) | ||||
|                 var fis = di.EnumerateFiles($"*{ext}") | ||||
|                            .OrderByDescending(e => e.LastWriteTimeUtc) | ||||
|                            .Skip(Backups); | ||||
|  | ||||
|                 foreach (var item in fis) | ||||
|                 { | ||||
|                     // 删除最旧的文件 | ||||
|                     var retain = fis.Length - Backups; | ||||
|                     fis = fis.OrderBy(e => e.LastWriteTimeUtc).Take(retain).ToArray(); | ||||
|                     foreach (var item in fis) | ||||
|                     OnWrite(LogLevel.Info, "The log file has reached the maximum limit of {0}, delete {1}, size {2: n0} Byte", Backups, item.Name, item.Length); | ||||
|                     try | ||||
|                     { | ||||
|                         item.Delete(); | ||||
|                     } | ||||
|                     catch | ||||
|                     { | ||||
|                         OnWrite(LogLevel.Info, "The log file has reached the maximum limit of {0}, delete {1}, size {2: n0} Byte", Backups, item.Name, item.Length); | ||||
|                         try | ||||
|                         { | ||||
|                             item.Delete(); | ||||
|                             item.MoveTo(item.FullName + ".del"); | ||||
|                         } | ||||
|                         catch | ||||
|                         { | ||||
|                             try | ||||
|                             { | ||||
|                                 item.MoveTo(item.FullName + ".del"); | ||||
|                             } | ||||
|                             catch | ||||
|                             { | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
| @@ -323,7 +358,6 @@ public class TextFileLog : Logger, IDisposable | ||||
|         // 推入队列 | ||||
|         Enqueue($"{e.GetAndReset()}"); | ||||
|  | ||||
|         WriteLog(); | ||||
|     } | ||||
|  | ||||
|     protected bool Check() | ||||
| @@ -340,35 +374,17 @@ public class TextFileLog : Logger, IDisposable | ||||
|     } | ||||
|     protected void WriteLog() | ||||
|     { | ||||
|         // 异步写日志,实时。即使这里错误,定时器那边仍然会补上 | ||||
|         // 写日志,实时。即使这里错误,定时器那边仍然会补上 | ||||
|         if (Interlocked.CompareExchange(ref _writing, 1, 0) == 0) | ||||
|         { | ||||
|             // 调试级别 或 致命错误 同步写日志 | ||||
|             if (Setting.Current.LogLevel <= LogLevel.Debug || Level >= LogLevel.Error) | ||||
|             try | ||||
|             { | ||||
|                 try | ||||
|                 { | ||||
|                     WriteFile(); | ||||
|                 } | ||||
|                 finally | ||||
|                 { | ||||
|                     _writing = 0; | ||||
|                 } | ||||
|                 if (!_Logs.IsEmpty) WriteFile(); | ||||
|             } | ||||
|             else | ||||
|             finally | ||||
|             { | ||||
|                 ThreadPool.UnsafeQueueUserWorkItem(s => | ||||
|                 { | ||||
|                     try | ||||
|                     { | ||||
|                         WriteFile(); | ||||
|                     } | ||||
|                     catch { } | ||||
|                     finally | ||||
|                     { | ||||
|                         _writing = 0; | ||||
|                     } | ||||
|                 }, null); | ||||
|                 _writing = 0; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -80,13 +80,21 @@ public static class XTrace | ||||
|  | ||||
|         Log.Error("{0}", ex); | ||||
|     } | ||||
|     public static void WriteException(Exception ex, string message) | ||||
|     { | ||||
|         if (!InitLog()) return; | ||||
|  | ||||
|         WriteVersion(); | ||||
|  | ||||
|         Log.Error("{0}, {1}", message, ex); | ||||
|     } | ||||
|     #endregion 写日志 | ||||
|  | ||||
|     #region 构造 | ||||
|  | ||||
|     static XTrace() | ||||
|     { | ||||
|         _ = Runtime.AppTickCount64; | ||||
|         AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; | ||||
|         TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException; | ||||
|         AppDomain.CurrentDomain.ProcessExit += OnProcessExit; | ||||
|   | ||||
| @@ -65,8 +65,21 @@ public class TimerScheduler : IDisposable, ILogFeature | ||||
|     public static TimeProvider GlobalTimeProvider { get; set; } = TimeProvider.System; | ||||
|     #endregion | ||||
|     #region 构造 | ||||
|     private TimerScheduler(String name) => Name = name; | ||||
|  | ||||
|     private TimerScheduler(String name) | ||||
|     { | ||||
|         Name = name; | ||||
|         _processCallback = state => | ||||
|         { | ||||
|             try | ||||
|             { | ||||
|                 Execute(state); | ||||
|             } | ||||
|             catch (Exception ex) | ||||
|             { | ||||
|                 XTrace.WriteException(ex, "Timer执行错误"); | ||||
|             } | ||||
|         }; | ||||
|     } | ||||
|     /// <summary>销毁</summary> | ||||
|     public void Dispose() | ||||
|     { | ||||
| @@ -258,17 +271,7 @@ public class TimerScheduler : IDisposable, ILogFeature | ||||
|                         else | ||||
|                             //Task.Factory.StartNew(() => ProcessItem(timer)); | ||||
|                             // 不需要上下文流动,捕获所有异常 | ||||
|                             ThreadPool.UnsafeQueueUserWorkItem(s => | ||||
|                             { | ||||
|                                 try | ||||
|                                 { | ||||
|                                     Execute(s); | ||||
|                                 } | ||||
|                                 catch (Exception ex) | ||||
|                                 { | ||||
|                                     XTrace.WriteException(ex); | ||||
|                                 } | ||||
|                             }, timer); | ||||
|                             ThreadPool.UnsafeQueueUserWorkItem(_processCallback, timer); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| @@ -283,7 +286,7 @@ public class TimerScheduler : IDisposable, ILogFeature | ||||
|  | ||||
|         WriteLog("调度线程已退出:{0}", Name); | ||||
|     } | ||||
|  | ||||
|     private readonly WaitCallback _processCallback; | ||||
|     /// <summary>检查定时器是否到期</summary> | ||||
|     /// <param name="timer"></param> | ||||
|     /// <param name="now"></param> | ||||
| @@ -325,9 +328,9 @@ public class TimerScheduler : IDisposable, ILogFeature | ||||
|  | ||||
|         timer.hasSetNext = false; | ||||
|  | ||||
|         string tracerName = timer.TracerName ?? "timer:ExecuteAsync"; | ||||
|         string timerArg = timer.Timers.ToString(); | ||||
|         using var span = timer.Tracer?.NewSpan(tracerName, timerArg); | ||||
|         //string tracerName = timer.TracerName ?? "timer:ExecuteAsync"; | ||||
|         //string timerArg = timer.Timers.ToString(); | ||||
|         //using var span = timer.Tracer?.NewSpan(tracerName, timerArg); | ||||
|         var sw = ValueStopwatch.StartNew(); | ||||
|         try | ||||
|         { | ||||
| @@ -351,7 +354,7 @@ public class TimerScheduler : IDisposable, ILogFeature | ||||
|         // 如果用户代码没有拦截错误,则这里拦截,避免出错了都不知道怎么回事 | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             span?.SetError(ex, null); | ||||
|             //span?.SetError(ex, null); | ||||
|             XTrace.WriteException(ex); | ||||
|         } | ||||
|         finally | ||||
| @@ -377,9 +380,9 @@ public class TimerScheduler : IDisposable, ILogFeature | ||||
|  | ||||
|             timer.hasSetNext = false; | ||||
|  | ||||
|             string tracerName = timer.TracerName ?? "timer:ExecuteAsync"; | ||||
|             string timerArg = timer.Timers.ToString(); | ||||
|             using var span = timer.Tracer?.NewSpan(tracerName, timerArg); | ||||
|             //string tracerName = timer.TracerName ?? "timer:ExecuteAsync"; | ||||
|             //string timerArg = timer.Timers.ToString(); | ||||
|             //using var span = timer.Tracer?.NewSpan(tracerName, timerArg); | ||||
|             var sw = ValueStopwatch.StartNew(); | ||||
|             try | ||||
|             { | ||||
| @@ -427,7 +430,7 @@ public class TimerScheduler : IDisposable, ILogFeature | ||||
|             // 如果用户代码没有拦截错误,则这里拦截,避免出错了都不知道怎么回事 | ||||
|             catch (Exception ex) | ||||
|             { | ||||
|                 span?.SetError(ex, null); | ||||
|                 //span?.SetError(ex, null); | ||||
|                 XTrace.WriteException(ex); | ||||
|             } | ||||
|             finally | ||||
|   | ||||
| @@ -694,8 +694,12 @@ namespace ThingsGateway.SqlSugar | ||||
|             var enumerator = table.GetEnumerator(); | ||||
|             while (enumerator.MoveNext()) | ||||
|             { | ||||
|                 var cur = enumerator.Current; | ||||
|                 yield return cur.Value.Item2[rowIndex]; | ||||
|                 var kvp = enumerator.Current; | ||||
|                 var list = kvp.Value.Item2; | ||||
|                 if (list != null && rowIndex < list.Count) | ||||
|                     yield return list[rowIndex]; | ||||
|                 else | ||||
|                     yield return new DataInfos { ColumnName = kvp.Key, Value = DBNull.Value }; | ||||
|             } | ||||
|  | ||||
|         } | ||||
|   | ||||
| @@ -201,6 +201,7 @@ namespace ThingsGateway.SqlSugar | ||||
|             { | ||||
|                 foreach (var column in columns) | ||||
|                 { | ||||
|  | ||||
|                     if (column.IsIgnore) | ||||
|                     { | ||||
|                         continue; | ||||
| @@ -210,6 +211,12 @@ namespace ThingsGateway.SqlSugar | ||||
|                     { | ||||
|                         name = column.PropertyName; | ||||
|                     } | ||||
|                     if (!results.TryGetValue(name, out var tuple) || tuple.Item2 == null) | ||||
|                     { | ||||
|                         // 某些列可能不在 DataTable 中(例如数据库多了列) | ||||
|                         continue; | ||||
|                     } | ||||
|  | ||||
|                     var value = ValueConverter(column, GetValue(item, column)); | ||||
|                     if (column.SqlParameterDbType != null && column.SqlParameterDbType is Type && UtilMethods.HasInterface((Type)column.SqlParameterDbType, typeof(ISugarDataConverter))) | ||||
|                     { | ||||
|   | ||||
| @@ -37,7 +37,7 @@ | ||||
| 		<PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.2" /> | ||||
| 		<PackageReference Include="System.Reflection.Emit.Lightweight" Version="4.7.0" /> | ||||
| 		<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" /> | ||||
| 		<PackageReference Include="System.Formats.Asn1" Version="8.0.2" /> | ||||
| 		<PackageReference Include="System.Formats.Asn1" Version="9.0.10" /> | ||||
| 	</ItemGroup> | ||||
|  | ||||
| </Project> | ||||
|   | ||||
| @@ -1,18 +1,18 @@ | ||||
| <Project> | ||||
|  | ||||
| 	<PropertyGroup> | ||||
| 		<PluginVersion>10.12.1</PluginVersion> | ||||
| 		<ProPluginVersion>10.12.1</ProPluginVersion> | ||||
| 		<DefaultVersion>10.12.1</DefaultVersion> | ||||
| 		<AuthenticationVersion>10.11.6</AuthenticationVersion> | ||||
| 		<SourceGeneratorVersion>10.11.6</SourceGeneratorVersion> | ||||
| 		<PluginVersion>10.12.12</PluginVersion> | ||||
| 		<ProPluginVersion>10.12.12</ProPluginVersion> | ||||
| 		<DefaultVersion>10.12.12</DefaultVersion> | ||||
| 		<AuthenticationVersion>10.11.7</AuthenticationVersion> | ||||
| 		<SourceGeneratorVersion>10.11.7</SourceGeneratorVersion> | ||||
| 		<NET8Version>8.0.21</NET8Version> | ||||
| 		<NET10Version>10.0.0-rc.2.25502.107</NET10Version> | ||||
| 		<SatelliteResourceLanguages>zh-Hans;en-US</SatelliteResourceLanguages> | ||||
| 		<IsTrimmable>false</IsTrimmable> | ||||
| 		<ManagementProPluginVersion>10.11.87</ManagementProPluginVersion> | ||||
| 		<ManagementPluginVersion>10.11.87</ManagementPluginVersion> | ||||
| 		<TSVersion>4.0.0-beta.140</TSVersion> | ||||
| 		<TSVersion>4.0.0-rc.1</TSVersion> | ||||
| 		 | ||||
| 		 | ||||
| 	</PropertyGroup> | ||||
|   | ||||
| @@ -10,53 +10,53 @@ | ||||
| <div class="w-100" style=@($"height:{HeightString}")> | ||||
|  | ||||
|     <Card HeaderText=@HeaderText class=@("w-100 h-100")> | ||||
|     <HeaderTemplate> | ||||
|         <div class="flex-fill"> | ||||
|         </div> | ||||
|         <HeaderTemplate> | ||||
|             <div class="flex-fill"> | ||||
|             </div> | ||||
|  | ||||
|         @if (LogLevelChanged.HasDelegate) | ||||
|         { | ||||
|             <Select Value="@LogLevel" ValueChanged="LogLevelChanged" IsPopover></Select> | ||||
|         } | ||||
|         <Tooltip class=" col-auto" Title="@RazorLocalizer[Pause?"Play":"Pause"]" Placement="Placement.Bottom"> | ||||
|             @if (LogLevelChanged.HasDelegate) | ||||
|             { | ||||
|                 <Select Value="@LogLevel" ValueChanged="LogLevelChanged" IsPopover></Select> | ||||
|             } | ||||
|             <Tooltip class=" col-auto" Title=@(Pause? PlayText:PauseText) Placement="Placement.Bottom"> | ||||
|  | ||||
|             <Button Color="Color.None" style="color: var(--bs-card-title-color);" Icon=@(Pause?"fa fa-play":"fa fa-pause") OnClick="OnPause" /> | ||||
|                 <Button Color="Color.None" style="color: var(--bs-card-title-color);" Icon=@(Pause ? "fa fa-play" : "fa fa-pause") OnClick="OnPause" /> | ||||
|  | ||||
|         </Tooltip> | ||||
|             </Tooltip> | ||||
|  | ||||
|         <Tooltip class=" col-auto" Title="@RazorLocalizer["Export"]" Placement="Placement.Bottom"> | ||||
|             <Tooltip class=" col-auto" Title="@ExportText" Placement="Placement.Bottom"> | ||||
|  | ||||
|             <Button IsAsync Color="Color.None" style="color: var(--bs-card-title-color);" Icon=@("fa fa-sign-out") OnClick="HandleOnExportClick" /> | ||||
|                 <Button IsAsync Color="Color.None" style="color: var(--bs-card-title-color);" Icon=@("fa fa-sign-out") OnClick="HandleOnExportClick" /> | ||||
|  | ||||
|         </Tooltip> | ||||
|             </Tooltip> | ||||
|  | ||||
|         <Tooltip class=" col-auto" Title="@RazorLocalizer["Delete"]" Placement="Placement.Bottom"> | ||||
|             <Tooltip class=" col-auto" Title="@DeleteText" Placement="Placement.Bottom"> | ||||
|  | ||||
|             <Button IsAsync Color="Color.None" style="color: var(--bs-card-title-color);" Icon=@("far fa-trash-alt") OnClick="Delete" /> | ||||
|                 <Button IsAsync Color="Color.None" style="color: var(--bs-card-title-color);" Icon=@("far fa-trash-alt") OnClick="Delete" /> | ||||
|  | ||||
|         </Tooltip> | ||||
|             </Tooltip> | ||||
|  | ||||
|  | ||||
|     </HeaderTemplate> | ||||
|     <BodyTemplate> | ||||
|                 <div style=@($"height:calc(100% - 50px);overflow-y:scroll;flex-fill;")> | ||||
|             <Virtualize Items="CurrentMessages??new  List<LogMessage>()" Context="itemMessage" ItemSize="60" OverscanCount=2> | ||||
|                 <ItemContent> | ||||
|                     @*       <Tooltip Placement="Placement.Bottom" Title=@itemMessage.Message.Substring(0, Math.Min(itemMessage.Message.Length, 500))> *@ | ||||
|                     <div class=@(itemMessage.Level<(byte)Microsoft.Extensions.Logging.LogLevel.Information?"": | ||||
|                          itemMessage.Level>=(byte)Microsoft.Extensions.Logging.LogLevel.Warning? " red--text text-truncate":"green--text text-truncate") | ||||
|                          title=@itemMessage.Message.Substring(0, Math.Min(itemMessage.Message.Length, 500))> | ||||
|         </HeaderTemplate> | ||||
|         <BodyTemplate> | ||||
|             <div style=@($"height:calc(100% - 50px);overflow-y:scroll;flex-fill;")> | ||||
|                 <Virtualize Items="CurrentMessages ?? new List<LogMessage>()" Context="itemMessage" ItemSize="60" OverscanCount=2> | ||||
|                     <ItemContent> | ||||
|                         @*       <Tooltip Placement="Placement.Bottom" Title=@itemMessage.Message.Substring(0, Math.Min(itemMessage.Message.Length, 500))> *@ | ||||
|                         <div class=@(itemMessage.Level<(byte)Microsoft.Extensions.Logging.LogLevel.Information?"": | ||||
|                                                           itemMessage.Level >= (byte)Microsoft.Extensions.Logging.LogLevel.Warning ? " red--text text-truncate" : "green--text text-truncate") | ||||
|                              title=@itemMessage.Message.Substring(0, Math.Min(itemMessage.Message.Length, 500))> | ||||
|  | ||||
|                         @itemMessage.Message.Substring(0, Math.Min(itemMessage.Message.Length, 150)) | ||||
|                             @itemMessage.Message.Substring(0, Math.Min(itemMessage.Message.Length, 150)) | ||||
|  | ||||
|                     </div> | ||||
|                     @* </Tooltip> *@ | ||||
|                 </ItemContent> | ||||
|             </Virtualize> | ||||
|         </div> | ||||
|                         </div> | ||||
|                         @* </Tooltip> *@ | ||||
|                     </ItemContent> | ||||
|                 </Virtualize> | ||||
|             </div> | ||||
|  | ||||
|     </BodyTemplate> | ||||
| </Card> | ||||
|         </BodyTemplate> | ||||
|     </Card> | ||||
|  | ||||
|  | ||||
| </div> | ||||
|   | ||||
| @@ -10,12 +10,11 @@ | ||||
|  | ||||
| using Microsoft.AspNetCore.Components.Web; | ||||
|  | ||||
| using System.Diagnostics; | ||||
| using System.Text.RegularExpressions; | ||||
|  | ||||
| using ThingsGateway.Extension; | ||||
| using ThingsGateway.Foundation; | ||||
| using ThingsGateway.NewLife; | ||||
| using ThingsGateway.NewLife.Threading; | ||||
|  | ||||
| using TouchSocket.Core; | ||||
|  | ||||
| @@ -23,6 +22,24 @@ namespace ThingsGateway.Debug; | ||||
|  | ||||
| public partial class LogConsole : IDisposable | ||||
| { | ||||
|  | ||||
|     private string PlayText { get; set; } | ||||
|     private string PauseText { get; set; } | ||||
|     private string ExportText { get; set; } | ||||
|     private string DeleteText { get; set; } | ||||
|  | ||||
|     protected override void OnInitialized() | ||||
|     { | ||||
|         PlayText = RazorLocalizer["Play"]; | ||||
|         PauseText = RazorLocalizer["Pause"]; | ||||
|         ExportText = RazorLocalizer["Export"]; | ||||
|         DeleteText = RazorLocalizer["Delete"]; | ||||
|  | ||||
|         _Timer = new TimerX(RunTimerAsync, null, 1_000, 1_000, nameof(LogConsole)) { Async = true }; | ||||
|         base.OnInitialized(); | ||||
|     } | ||||
|     private TimerX _Timer; | ||||
|  | ||||
|     private bool Pause; | ||||
|  | ||||
|     public bool Disposed { get; set; } | ||||
| @@ -69,7 +86,7 @@ public partial class LogConsole : IDisposable | ||||
|         { | ||||
|             logPath = LogPath; | ||||
|             Messages = new List<LogMessage>(); | ||||
|             await ExecuteAsync(); | ||||
|             _Timer?.SetNext(0); | ||||
|         } | ||||
|  | ||||
|         await base.OnParametersSetAsync(); | ||||
| @@ -82,63 +99,38 @@ public partial class LogConsole : IDisposable | ||||
|     public void Dispose() | ||||
|     { | ||||
|         Disposed = true; | ||||
|         _Timer?.SafeDispose(); | ||||
|         GC.SuppressFinalize(this); | ||||
|     } | ||||
|     private WaitLock WaitLock = new(nameof(LogConsole)); | ||||
|     protected async Task ExecuteAsync() | ||||
|     protected async ValueTask ExecuteAsync() | ||||
|     { | ||||
|         if (WaitLock.Waited) return; | ||||
|         try | ||||
|         { | ||||
|             await WaitLock.WaitAsync(); | ||||
|             await Task.Delay(1000); | ||||
|  | ||||
|             if (LogPath != null) | ||||
|         if (LogPath != null) | ||||
|         { | ||||
|             var files = await TextFileReadService.GetLogFilesAsync(LogPath); | ||||
|             if (!files.IsSuccess) | ||||
|             { | ||||
|                 var files = await TextFileReadService.GetLogFilesAsync(LogPath); | ||||
|                 if (!files.IsSuccess) | ||||
|                 Messages = new List<LogMessage>(); | ||||
|                 await Task.Delay(1000); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|  | ||||
|                 var result = await TextFileReadService.LastLogDataAsync(files.Content.FirstOrDefault()); | ||||
|                 if (result.IsSuccess) | ||||
|                 { | ||||
|                     Messages = new List<LogMessage>(); | ||||
|                     await Task.Delay(1000); | ||||
|                     Messages = result.Content.Where(a => a.LogLevel >= LogLevel).Select(a => new LogMessage((int)a.LogLevel, $"{a.LogTime} - {a.Message}{(a.ExceptionString.IsNullOrWhiteSpace() ? null : $"{Environment.NewLine}{a.ExceptionString}")}")).ToArray(); | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     await Task.Run(async () => | ||||
|                     { | ||||
|                         Stopwatch sw = Stopwatch.StartNew(); | ||||
|                         var result = await TextFileReadService.LastLogDataAsync(files.Content.FirstOrDefault()); | ||||
|                         if (result.IsSuccess) | ||||
|                         { | ||||
|                             Messages = result.Content.Where(a => a.LogLevel >= LogLevel).Select(a => new LogMessage((int)a.LogLevel, $"{a.LogTime} - {a.Message}{(a.ExceptionString.IsNullOrWhiteSpace() ? null : $"{Environment.NewLine}{a.ExceptionString}")}")).ToList(); | ||||
|                         } | ||||
|                         else | ||||
|                         { | ||||
|                             Messages = new List<LogMessage>(); | ||||
|                         } | ||||
|                         sw.Stop(); | ||||
|                         if (sw.ElapsedMilliseconds > 500) | ||||
|                         { | ||||
|                             await Task.Delay(1000); | ||||
|                         } | ||||
|                     }); | ||||
|                     Messages = Array.Empty<LogMessage>(); | ||||
|                 } | ||||
|  | ||||
|             } | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             NewLife.Log.XTrace.WriteException(ex); | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|             WaitLock.Release(); | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     protected override void OnInitialized() | ||||
|     { | ||||
|         _ = RunTimerAsync(); | ||||
|         base.OnInitialized(); | ||||
|     } | ||||
|  | ||||
|     private async Task Delete() | ||||
|     { | ||||
| @@ -185,19 +177,9 @@ public partial class LogConsole : IDisposable | ||||
|         return Task.CompletedTask; | ||||
|     } | ||||
|  | ||||
|     private async Task RunTimerAsync() | ||||
|     private async Task RunTimerAsync(object? state) | ||||
|     { | ||||
|         while (!Disposed) | ||||
|         { | ||||
|             try | ||||
|             { | ||||
|                 await ExecuteAsync(); | ||||
|                 await InvokeAsync(StateHasChanged); | ||||
|             } | ||||
|             catch (Exception ex) | ||||
|             { | ||||
|                 NewLife.Log.XTrace.WriteException(ex); | ||||
|             } | ||||
|         } | ||||
|         await ExecuteAsync(); | ||||
|         await InvokeAsync(StateHasChanged); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -28,7 +28,7 @@ public static class ChannelOptionsExtensions | ||||
|     /// <param name="e">接收数据</param> | ||||
|     /// <param name="funcs">事件</param> | ||||
|     /// <returns></returns> | ||||
|     internal static  ValueTask OnChannelReceivedEvent(this IClientChannel clientChannel, ReceivedDataEventArgs e, ChannelReceivedEventHandler funcs) | ||||
|     internal static ValueTask OnChannelReceivedEvent(this IClientChannel clientChannel, ReceivedDataEventArgs e, ChannelReceivedEventHandler funcs) | ||||
|     { | ||||
|         clientChannel.ThrowIfNull(nameof(IClientChannel)); | ||||
|         e.ThrowIfNull(nameof(ReceivedDataEventArgs)); | ||||
| @@ -44,8 +44,8 @@ public static class ChannelOptionsExtensions | ||||
|                 { | ||||
|                     var func = funcs[i]; | ||||
|                     if (func == null) continue; | ||||
|                     var taskResult= func.Invoke(clientChannel, e, i == funcs.Count - 1); | ||||
|                     if(!taskResult.IsCompletedSuccessfully) | ||||
|                     var taskResult = func.Invoke(clientChannel, e, i == funcs.Count - 1); | ||||
|                     if (!taskResult.IsCompletedSuccessfully) | ||||
|                     { | ||||
|                         await taskResult.ConfigureAwait(false); | ||||
|                     } | ||||
|   | ||||
| @@ -30,7 +30,6 @@ public interface IClientChannel : IChannel, ISender, IClient, IClientSender, IOn | ||||
|     WaitHandlePool<MessageBase> WaitHandlePool { get; } | ||||
|  | ||||
|     WaitLock GetLock(string key); | ||||
|     void LogSeted(bool logSeted); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 设置数据处理适配器 | ||||
|   | ||||
| @@ -80,13 +80,11 @@ public class OtherChannel : SetupConfigObject, IClientChannel | ||||
|  | ||||
|  | ||||
|  | ||||
|     private bool logSet; | ||||
|     /// <inheritdoc/> | ||||
|     public void SetDataHandlingAdapterLogger(ILog log) | ||||
|     { | ||||
|         if (!logSet && ReadOnlyDataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter) | ||||
|         if (ReadOnlyDataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter) | ||||
|         { | ||||
|             logSet = true; | ||||
|             handleAdapter.Logger = log; | ||||
|         } | ||||
|     } | ||||
| @@ -96,12 +94,8 @@ public class OtherChannel : SetupConfigObject, IClientChannel | ||||
|         if (adapter is SingleStreamDataHandlingAdapter singleStreamDataHandlingAdapter) | ||||
|             SetAdapter(singleStreamDataHandlingAdapter); | ||||
|  | ||||
|         logSet = false; | ||||
|     } | ||||
|     public void LogSeted(bool logSeted) | ||||
|     { | ||||
|         logSet = logSeted; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 设置数据处理适配器。 | ||||
|     /// </summary> | ||||
|   | ||||
| @@ -97,13 +97,15 @@ public static class PluginUtil | ||||
|         { | ||||
|             action += a => | ||||
|             { | ||||
|                 a.UseTcpSessionCheckClear() | ||||
|         .SetCheckClearType(CheckClearType.All) | ||||
|         .SetTick(TimeSpan.FromMilliseconds(channelOptions.CheckClearTime)) | ||||
|         .SetOnClose((c, t) => | ||||
|         { | ||||
|             return c.CloseAsync($"{channelOptions.CheckClearTime}ms Timeout"); | ||||
|         }); | ||||
|                 a.UseTcpSessionCheckClear(options => | ||||
|                 { | ||||
|                     options.CheckClearType = CheckClearType.All; | ||||
|                     options.Tick = TimeSpan.FromMilliseconds(channelOptions.CheckClearTime); | ||||
|                     options.OnClose = (c, t) => | ||||
|                     { | ||||
|                         return c.CloseAsync($"{channelOptions.CheckClearTime}ms Timeout"); | ||||
|                     }; | ||||
|                 }); | ||||
|             }; | ||||
|         } | ||||
|         return action; | ||||
|   | ||||
| @@ -52,13 +52,11 @@ public class SerialPortChannel : SerialPortClient, IClientChannel | ||||
|     /// <inheritdoc/> | ||||
|     public DataHandlingAdapter ReadOnlyDataHandlingAdapter => ProtectedDataHandlingAdapter; | ||||
|  | ||||
|     private bool logSet; | ||||
|     /// <inheritdoc/> | ||||
|     public void SetDataHandlingAdapterLogger(ILog log) | ||||
|     { | ||||
|         if (!logSet && ProtectedDataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter) | ||||
|         if (ProtectedDataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter) | ||||
|         { | ||||
|             logSet = true; | ||||
|             handleAdapter.Logger = log; | ||||
|         } | ||||
|     } | ||||
| @@ -68,13 +66,9 @@ public class SerialPortChannel : SerialPortClient, IClientChannel | ||||
|         if (adapter is SingleStreamDataHandlingAdapter singleStreamDataHandlingAdapter) | ||||
|             SetAdapter(singleStreamDataHandlingAdapter); | ||||
|  | ||||
|         logSet = false; | ||||
|     } | ||||
|  | ||||
|     public void LogSeted(bool logSeted) | ||||
|     { | ||||
|         logSet = logSeted; | ||||
|     } | ||||
|  | ||||
|  | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|   | ||||
| @@ -34,12 +34,10 @@ public class TcpClientChannel : TcpClient, IClientChannel | ||||
|         WaitHandlePool = new WaitHandlePool<MessageBase>(minSign, maxSign); | ||||
|         pool?.CancelAll(); | ||||
|     } | ||||
|     private bool logSet; | ||||
|     public void SetDataHandlingAdapterLogger(ILog log) | ||||
|     { | ||||
|         if (!logSet && DataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter) | ||||
|         if (DataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter) | ||||
|         { | ||||
|             logSet = true; | ||||
|             handleAdapter.Logger = log; | ||||
|         } | ||||
|     } | ||||
| @@ -49,12 +47,8 @@ public class TcpClientChannel : TcpClient, IClientChannel | ||||
|         if (adapter is SingleStreamDataHandlingAdapter singleStreamDataHandlingAdapter) | ||||
|             SetAdapter(singleStreamDataHandlingAdapter); | ||||
|  | ||||
|         logSet = false; | ||||
|     } | ||||
|     public void LogSeted(bool logSeted) | ||||
|     { | ||||
|         logSet = logSeted; | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public ChannelReceivedEventHandler ChannelReceived { get; } = new(); | ||||
|  | ||||
|   | ||||
| @@ -25,13 +25,11 @@ public class TcpSessionClientChannel : TcpSessionClient, IClientChannel | ||||
|     public TcpSessionClientChannel() | ||||
|     { | ||||
|     } | ||||
|     private bool logSet; | ||||
|     /// <inheritdoc/> | ||||
|     public void SetDataHandlingAdapterLogger(ILog log) | ||||
|     { | ||||
|         if (!logSet && DataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter) | ||||
|         if (DataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter) | ||||
|         { | ||||
|             logSet = true; | ||||
|             handleAdapter.Logger = log; | ||||
|         } | ||||
|     } | ||||
| @@ -41,12 +39,8 @@ public class TcpSessionClientChannel : TcpSessionClient, IClientChannel | ||||
|         if (adapter is SingleStreamDataHandlingAdapter singleStreamDataHandlingAdapter) | ||||
|             SetAdapter(singleStreamDataHandlingAdapter); | ||||
|  | ||||
|         logSet = false; | ||||
|     } | ||||
|     public void LogSeted(bool logSeted) | ||||
|     { | ||||
|         logSet = logSeted; | ||||
|     } | ||||
|  | ||||
|     public void ResetSign(int minSign = 1, int maxSign = ushort.MaxValue - 1) | ||||
|     { | ||||
|         var pool = WaitHandlePool; | ||||
|   | ||||
| @@ -30,27 +30,21 @@ public class UdpSessionChannel : UdpSession, IClientChannel | ||||
|         ResetSign(); | ||||
|     } | ||||
|     public override TouchSocketConfig Config => base.Config ?? ChannelOptions.Config; | ||||
|     private bool logSet; | ||||
|     /// <inheritdoc/> | ||||
|     public void SetDataHandlingAdapterLogger(ILog log) | ||||
|     { | ||||
|         if (!logSet && DataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter) | ||||
|         if (DataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter) | ||||
|         { | ||||
|             logSet = true; | ||||
|             handleAdapter.Logger = log; | ||||
|         } | ||||
|     } | ||||
|     public void LogSeted(bool logSeted) | ||||
|     { | ||||
|         logSet = logSeted; | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public void SetDataHandlingAdapter(DataHandlingAdapter adapter) | ||||
|     { | ||||
|         if (adapter is UdpDataHandlingAdapter udpDataHandlingAdapter) | ||||
|             SetAdapter(udpDataHandlingAdapter); | ||||
|  | ||||
|         logSet = false; | ||||
|     } | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -1060,10 +1060,6 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice | ||||
|                 } | ||||
|  | ||||
|                 Channel.Collects.Remove(this); | ||||
|                 if (Channel is IClientChannel clientChannel) | ||||
|                 { | ||||
|                     clientChannel.LogSeted(false); | ||||
|                 } | ||||
|  | ||||
|             } | ||||
|         } | ||||
| @@ -1118,10 +1114,6 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice | ||||
|  | ||||
|             Channel.Collects.Remove(this); | ||||
|  | ||||
|             if (Channel is IClientChannel clientChannel) | ||||
|             { | ||||
|                 clientChannel.LogSeted(false); | ||||
|             } | ||||
|  | ||||
|         } | ||||
|  | ||||
|   | ||||
| @@ -128,7 +128,6 @@ public class TextFileLogger : ThingsGateway.NewLife.Log.TextFileLog, TouchSocket | ||||
|         // 推入队列 | ||||
|         Enqueue(stringBuilder.ToString()); | ||||
|  | ||||
|         WriteLog(); | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|   | ||||
| @@ -139,8 +139,15 @@ public struct OperResult<T> : IOperResult<T> | ||||
|     /// <param name="operResult"></param> | ||||
|     public static implicit operator OperResult(OperResult<T> operResult) | ||||
|     { | ||||
|         return new OperResult(operResult); | ||||
|         return new OperResult | ||||
|         { | ||||
|             OperCode = operResult.OperCode, | ||||
|             ErrorMessage = operResult.ErrorMessage, | ||||
|             Exception = operResult.Exception, | ||||
|             ErrorType = operResult.ErrorType | ||||
|         }; | ||||
|     } | ||||
|  | ||||
| } | ||||
|  | ||||
| /// <inheritdoc/> | ||||
|   | ||||
| @@ -15,7 +15,6 @@ using Microsoft.AspNetCore.Http; | ||||
| using Microsoft.AspNetCore.Mvc; | ||||
|  | ||||
| using System.ComponentModel; | ||||
| using ThingsGateway.NewLife.DictionaryExtensions; | ||||
|  | ||||
| using ThingsGateway.FriendlyException; | ||||
|  | ||||
|   | ||||
| @@ -327,9 +327,12 @@ public static class GlobalData | ||||
|         { | ||||
|             if (deviceRuntime.RedundantEnable && deviceRuntime.RedundantDeviceId != null) | ||||
|                 return true; | ||||
|             else if (GlobalData.IdDevices.Any(a => a.Value.RedundantDeviceId == deviceRuntime.Id)) | ||||
|  | ||||
|             var id = deviceRuntime.Id; | ||||
|             foreach (var kv in GlobalData.IdDevices) | ||||
|             { | ||||
|                 return true; | ||||
|                 if (kv.Value.RedundantDeviceId == id) | ||||
|                     return true; | ||||
|             } | ||||
|         } | ||||
|         return false; | ||||
|   | ||||
| @@ -386,7 +386,7 @@ internal sealed class ChannelService : BaseService<Channel>, IChannelService | ||||
|         ManageHelper.CheckChannelCount(insertData.Count); | ||||
|  | ||||
|         using var db = GetDB(); | ||||
|         if (GlobalData.HardwareJob.HardwareInfo.MachineInfo.AvailableMemory < 2 * 1024 * 1024 || WebEnableVariable.WebEnable == false) | ||||
|         if (GlobalData.HardwareJob.HardwareInfo.AvailableMemory < 2 * 1024 * 1024 || WebEnableVariable.WebEnable == false) | ||||
|         { | ||||
|             await db.BulkCopyAsync(insertData, 10000).ConfigureAwait(false); | ||||
|             await db.BulkUpdateAsync(upData, 10000).ConfigureAwait(false); | ||||
|   | ||||
| @@ -387,7 +387,7 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService | ||||
|         ManageHelper.CheckDeviceCount(insertData.Count); | ||||
|  | ||||
|         using var db = GetDB(); | ||||
|         if (GlobalData.HardwareJob.HardwareInfo.MachineInfo.AvailableMemory < 2 * 1024 * 1024 || WebEnableVariable.WebEnable == false) | ||||
|         if (GlobalData.HardwareJob.HardwareInfo.AvailableMemory < 2 * 1024 * 1024 || WebEnableVariable.WebEnable == false) | ||||
|         { | ||||
|             await db.BulkCopyAsync(insertData, 10000).ConfigureAwait(false); | ||||
|             await db.BulkUpdateAsync(upData, 10000).ConfigureAwait(false); | ||||
|   | ||||
| @@ -113,6 +113,28 @@ public partial class ManagementTask : AsyncDisposableObject | ||||
|                .ConfigurePlugins(a => | ||||
|                { | ||||
|                    a.UseTcpSessionCheckClear(); | ||||
|  | ||||
|  | ||||
|                    a.UseReconnection<TcpDmtpClient>(options => | ||||
|                    { | ||||
|                        options.TryCount = -1; | ||||
|                        options.PollingInterval = TimeSpan.FromMilliseconds(_managementOptions.HeartbeatInterval); | ||||
|                        options.PrintLog = true; | ||||
|                        options.CheckAction = async (c, count) => | ||||
|                        { | ||||
|                            using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); | ||||
|                            if ((await c.PingAsync(cts.Token).ConfigureAwait(false)).IsSuccess) | ||||
|                            { | ||||
|                                return ConnectionCheckResult.Alive; | ||||
|                            } | ||||
|                            else | ||||
|                            { | ||||
|                                return ConnectionCheckResult.Dead; | ||||
|                            } | ||||
|                        }; | ||||
|  | ||||
|                    }); | ||||
|  | ||||
|                    a.UseDmtpRpc(a => a.ConfigureDefaultSerializationSelector(b => | ||||
|                    { | ||||
|                        b.UseSystemTextJson(json => | ||||
| @@ -131,10 +153,6 @@ public partial class ManagementTask : AsyncDisposableObject | ||||
|                    a.Add<FilePlugin>(); | ||||
|  | ||||
|  | ||||
|                    a.UseDmtpHeartbeat()//使用Dmtp心跳 | ||||
|                    .SetTick(TimeSpan.FromMilliseconds(_managementOptions.HeartbeatInterval)) | ||||
|                    .SetMaxFailCount(3); | ||||
|  | ||||
|                    a.AddDmtpCreatedChannelPlugin(async () => | ||||
|                    { | ||||
|                        try | ||||
| @@ -194,9 +212,6 @@ public partial class ManagementTask : AsyncDisposableObject | ||||
|  | ||||
|                    a.Add<FilePlugin>(); | ||||
|  | ||||
|                    a.UseDmtpHeartbeat()//使用Dmtp心跳 | ||||
|                    .SetTick(TimeSpan.FromMilliseconds(_managementOptions.HeartbeatInterval)) | ||||
|                    .SetMaxFailCount(3); | ||||
|                }); | ||||
|  | ||||
|         await tcpDmtpService.SetupAsync(config).ConfigureAwait(false); | ||||
|   | ||||
| @@ -63,7 +63,7 @@ internal sealed class RedundancyTask : IRpcDriver, IAsyncDisposable | ||||
|         const int highMemorySize = 100000; | ||||
|         const long memoryThreshold = 2L * 1024 * 1024; // 2GB,单位KB | ||||
|  | ||||
|         return (GlobalData.HardwareJob.HardwareInfo.MachineInfo.AvailableMemory > memoryThreshold && WebEnableVariable.WebEnable == true) | ||||
|         return (GlobalData.HardwareJob.HardwareInfo.AvailableMemory > memoryThreshold && WebEnableVariable.WebEnable == true) | ||||
|             ? highMemorySize | ||||
|             : defaultSize; | ||||
|     } | ||||
| @@ -345,7 +345,25 @@ internal sealed class RedundancyTask : IRpcDriver, IAsyncDisposable | ||||
|                .ConfigurePlugins(a => | ||||
|                { | ||||
|                    a.UseTcpSessionCheckClear(); | ||||
|                    a.UseReconnection<TcpDmtpClient>(options => | ||||
|                    { | ||||
|                        options.TryCount = -1; | ||||
|                        options.PollingInterval = TimeSpan.FromMilliseconds(redundancy.HeartbeatInterval); | ||||
|                        options.PrintLog = true; | ||||
|                        options.CheckAction = async (c, count) => | ||||
|                        { | ||||
|                            using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); | ||||
|                            if ((await c.PingAsync(cts.Token).ConfigureAwait(false)).IsSuccess) | ||||
|                            { | ||||
|                                return ConnectionCheckResult.Alive; | ||||
|                            } | ||||
|                            else | ||||
|                            { | ||||
|                                return ConnectionCheckResult.Dead; | ||||
|                            } | ||||
|                        }; | ||||
|  | ||||
|                    }); | ||||
|                    a.UseDmtpRpc(a => a.ConfigureDefaultSerializationSelector(b => | ||||
|                    { | ||||
|                        b.UseSystemTextJson(json => | ||||
| @@ -358,9 +376,7 @@ internal sealed class RedundancyTask : IRpcDriver, IAsyncDisposable | ||||
|                            json.Converters.Add(new JArraySystemTextJsonConverter()); | ||||
|                        }); | ||||
|                    })); | ||||
|                    a.UseDmtpHeartbeat()//使用Dmtp心跳 | ||||
|                    .SetTick(TimeSpan.FromMilliseconds(redundancy.HeartbeatInterval)) | ||||
|                    .SetMaxFailCount(redundancy.MaxErrorCount); | ||||
|  | ||||
|  | ||||
|                }); | ||||
|  | ||||
| @@ -405,9 +421,7 @@ internal sealed class RedundancyTask : IRpcDriver, IAsyncDisposable | ||||
|                            json.Converters.Add(new JArraySystemTextJsonConverter()); | ||||
|                        }); | ||||
|                    })); | ||||
|                    a.UseDmtpHeartbeat()//使用Dmtp心跳 | ||||
|                    .SetTick(TimeSpan.FromMilliseconds(redundancy.HeartbeatInterval)) | ||||
|                    .SetMaxFailCount(redundancy.MaxErrorCount); | ||||
|  | ||||
|                }); | ||||
|  | ||||
|         await tcpDmtpService.SetupAsync(config).ConfigureAwait(false); | ||||
|   | ||||
| @@ -207,7 +207,7 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService | ||||
|  | ||||
|         var result = await db.UseTranAsync(async () => | ||||
|         { | ||||
|             if (GlobalData.HardwareJob.HardwareInfo.MachineInfo.AvailableMemory < 2 * 1024 * 1024 || WebEnableVariable.WebEnable == false) | ||||
|             if (GlobalData.HardwareJob.HardwareInfo.AvailableMemory < 2 * 1024 * 1024 || WebEnableVariable.WebEnable == false) | ||||
|             { | ||||
|                 await db.BulkCopyAsync(newChannels, 10000).ConfigureAwait(false); | ||||
|                 await db.BulkCopyAsync(newDevices, 10000).ConfigureAwait(false); | ||||
| @@ -342,7 +342,7 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService | ||||
|  | ||||
|         var result = await db.UseTranAsync(async () => | ||||
|         { | ||||
|             if (GlobalData.HardwareJob.HardwareInfo.MachineInfo.AvailableMemory < 2 * 1024 * 1024 || WebEnableVariable.WebEnable == false) | ||||
|             if (GlobalData.HardwareJob.HardwareInfo.AvailableMemory < 2 * 1024 * 1024 || WebEnableVariable.WebEnable == false) | ||||
|             { | ||||
|                 await db.BulkCopyAsync(newChannels, 10000).ConfigureAwait(false); | ||||
|                 await db.BulkCopyAsync(newDevices, 10000).ConfigureAwait(false); | ||||
| @@ -424,7 +424,7 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService | ||||
|         if (differences?.Count > 0) | ||||
|         { | ||||
|             var data = models.ToList(); | ||||
|             await  GlobalData.CheckByDeviceIds(data.Select(a => a.DeviceId)).ConfigureAwait(false); | ||||
|             await GlobalData.CheckByDeviceIds(data.Select(a => a.DeviceId)).ConfigureAwait(false); | ||||
|             using var db = GetDB(); | ||||
|  | ||||
|             var result = (await db.Updateable(data).UpdateColumns(differences.Select(a => a.Key).ToArray()).ExecuteCommandAsync().ConfigureAwait(false)) > 0; | ||||
| @@ -493,10 +493,10 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService | ||||
|     private async Task<Func<ISugarQueryable<Variable>, ISugarQueryable<Variable>>> GetWhereQueryFunc(GatewayExportFilter exportFilter) | ||||
|     { | ||||
|         var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false); | ||||
|         List<long>? filterDeviceIds= null; | ||||
|         if(dataScope!=null) | ||||
|         List<long>? filterDeviceIds = null; | ||||
|         if (dataScope != null) | ||||
|         { | ||||
|             filterDeviceIds= GlobalData.GetCurrentUserDeviceIds(dataScope).ToList(); | ||||
|             filterDeviceIds = GlobalData.GetCurrentUserDeviceIds(dataScope).ToList(); | ||||
|         } | ||||
|         HashSet<long>? deviceId = null; | ||||
|         if (!exportFilter.PluginName.IsNullOrWhiteSpace()) | ||||
| @@ -513,7 +513,7 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService | ||||
|         .WhereIF(exportFilter.PluginType == PluginTypeEnum.Collect, a => a.DeviceId == exportFilter.DeviceId) | ||||
|         .WhereIF(deviceId != null, a => deviceId.Contains(a.DeviceId)) | ||||
|  | ||||
|         .WhereIF(filterDeviceIds != null , u => filterDeviceIds.Contains(u.DeviceId))//在指定机构列表查询 | ||||
|         .WhereIF(filterDeviceIds != null, u => filterDeviceIds.Contains(u.DeviceId))//在指定机构列表查询 | ||||
|  | ||||
|         .WhereIF(exportFilter.PluginType == PluginTypeEnum.Business, u => SqlFunc.JsonLike(u.VariablePropertys, exportFilter.DeviceId.ToString())); | ||||
|         return whereQuery; | ||||
| @@ -527,8 +527,8 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService | ||||
|         { | ||||
|             filterDeviceIds = GlobalData.GetCurrentUserDeviceIds(dataScope).ToList(); | ||||
|         } | ||||
|          | ||||
|          | ||||
|  | ||||
|  | ||||
|         HashSet<long>? deviceId = null; | ||||
|         if (!exportFilter.PluginName.IsNullOrWhiteSpace()) | ||||
|         { | ||||
| @@ -620,7 +620,7 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService | ||||
|     [OperDesc("ExportVariable", isRecordPar: false, localizerType: typeof(Variable))] | ||||
|     public async Task<Dictionary<string, object>> ExportVariableAsync(GatewayExportFilter exportFilter) | ||||
|     { | ||||
|         if (GlobalData.HardwareJob.HardwareInfo.MachineInfo.AvailableMemory < 4 * 1024 * 1024 || WebEnableVariable.WebEnable == false) | ||||
|         if (GlobalData.HardwareJob.HardwareInfo.AvailableMemory < 4 * 1024 * 1024 || WebEnableVariable.WebEnable == false) | ||||
|         { | ||||
|             var whereQuery = await GetWhereEnumerableFunc(exportFilter).ConfigureAwait(false); | ||||
|             //导出 | ||||
| @@ -694,7 +694,7 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService | ||||
|     { | ||||
|         ManageHelper.CheckVariableCount(insertData.Count); | ||||
|         using var db = GetDB(); | ||||
|         if (GlobalData.HardwareJob.HardwareInfo.MachineInfo.AvailableMemory < 2 * 1024 * 1024 || WebEnableVariable.WebEnable == false) | ||||
|         if (GlobalData.HardwareJob.HardwareInfo.AvailableMemory < 2 * 1024 * 1024 || WebEnableVariable.WebEnable == false) | ||||
|         { | ||||
|             await db.BulkCopyAsync(insertData, 10000).ConfigureAwait(false); | ||||
|             await db.BulkUpdateAsync(upData, 10000).ConfigureAwait(false); | ||||
| @@ -850,7 +850,7 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService | ||||
|                     { | ||||
|                         variable.IsUp = false; | ||||
|                     } | ||||
|             | ||||
|  | ||||
|                     if (device.IsUp && (filterDeviceIds?.Contains(variable.DeviceId) != false)) | ||||
|                     { | ||||
|                         importPreviewOutput.Results.Add(new(Interlocked.Increment(ref row), false, "Operation not permitted")); | ||||
|   | ||||
| @@ -8,7 +8,9 @@ | ||||
| 	</PropertyGroup> | ||||
| 	<ItemGroup> | ||||
| 		<PackageReference Include="Portable.BouncyCastle" Version="1.9.0" /> | ||||
| 		<PackageReference Include="Riok.Mapperly" Version="4.2.1" ExcludeAssets="runtime" PrivateAssets="all" /> | ||||
| 		<PackageReference Include="Riok.Mapperly" Version="4.3.0" ExcludeAssets="runtime" PrivateAssets="all"> | ||||
| 		  <IncludeAssets>compile; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||||
| 		</PackageReference> | ||||
| 		<PackageReference Include="Rougamo.Fody" Version="5.0.2" /> | ||||
| 		<PackageReference Include="TouchSocket.Dmtp" Version="$(TSVersion)" /> | ||||
| 		<!--<PackageReference Include="TouchSocket.WebApi.Swagger" Version="$(TSVersion)" />--> | ||||
|   | ||||
| @@ -1347,27 +1347,7 @@ EventCallback.Factory.Create<MouseEventArgs>(this, async e => | ||||
|     private TreeViewItem<ChannelDeviceTreeItem> UnknownTreeViewItem; | ||||
|  | ||||
|     SmartTriggerScheduler? scheduler; | ||||
|     private bool _initialized; | ||||
|     public override async Task SetParametersAsync(ParameterView parameters) | ||||
|     { | ||||
|         parameters.SetParameterProperties(this); | ||||
|         if (!_initialized) | ||||
|         { | ||||
|             _initialized = true; | ||||
|  | ||||
|             OnInitialized(); | ||||
|             await OnInitializedAsync(); | ||||
|             OnParametersSet(); | ||||
|             StateHasChanged(); | ||||
|             await OnParametersSetAsync(); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             OnParametersSet(); | ||||
|             StateHasChanged(); | ||||
|             await OnParametersSetAsync(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     protected override async Task OnInitializedAsync() | ||||
|     { | ||||
|   | ||||
| @@ -10,7 +10,9 @@ | ||||
| 	<ItemGroup> | ||||
| 		<ProjectReference Include="..\ThingsGateway.Blazor.Diagrams\ThingsGateway.Blazor.Diagrams.csproj" /> | ||||
| 		<ProjectReference Include="..\ThingsGateway.Gateway.Application\ThingsGateway.Gateway.Application.csproj" /> | ||||
| 		<PackageReference Include="Riok.Mapperly" Version="4.2.1" ExcludeAssets="runtime" PrivateAssets="all" /> | ||||
| 		<PackageReference Include="Riok.Mapperly" Version="4.3.0" ExcludeAssets="runtime" PrivateAssets="all"> | ||||
| 		  <IncludeAssets>compile; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||||
| 		</PackageReference> | ||||
| 		<ProjectReference Include="..\..\Admin\ThingsGateway.Admin.Razor\ThingsGateway.Admin.Razor.csproj" /> | ||||
| 		<ProjectReference Include="..\..\Foundation\ThingsGateway.Foundation.Razor\ThingsGateway.Foundation.Razor.csproj" /> | ||||
| 	</ItemGroup> | ||||
|   | ||||
| @@ -7,15 +7,13 @@ | ||||
| 		<OutputType>WinExe</OutputType> | ||||
| 		<ApplicationIcon>favicon.ico</ApplicationIcon> | ||||
| 		<TargetFrameworks>net8.0;$(OtherTargetFrameworks);</TargetFrameworks> | ||||
|  | ||||
| 		<!--动态适用GC--> | ||||
| 		<GarbageCollectionAdaptationMode>true</GarbageCollectionAdaptationMode> | ||||
| 		 | ||||
| 		<!--使用自托管线程池--> | ||||
| 		<!--<UseWindowsThreadPool>false</UseWindowsThreadPool> --> | ||||
| 		 | ||||
| 		<CETCompat>false</CETCompat> | ||||
| 		<ServerGarbageCollection>true</ServerGarbageCollection> | ||||
| 		<!--动态适用GC--> | ||||
| 		<GarbageCollectionAdaptationMode>1</GarbageCollectionAdaptationMode> | ||||
|  | ||||
| 		<!--使用工作站GC--> | ||||
| 		<!--<ServerGarbageCollection>true</ServerGarbageCollection>--> | ||||
| 	</PropertyGroup> | ||||
|  | ||||
| 	<ItemGroup> | ||||
|   | ||||
| @@ -225,97 +225,97 @@ public class ModbusSlave : DeviceBase, IModbusAddress | ||||
|             return; | ||||
|         else | ||||
|             ModbusServer02ByteBlocks.GetOrAdd(mAddress.Station, a => | ||||
|         { | ||||
|             var bytes = new ByteBlock(256, | ||||
|             (c) => | ||||
|             { | ||||
|                 var data = ArrayPool<byte>.Shared.Rent(c); | ||||
|                 for (int i = 0; i < data.Length; i++) | ||||
|                 var bytes = new ByteBlock(256, | ||||
|                 (c) => | ||||
|                 { | ||||
|                     data[i] = 0; | ||||
|                 } | ||||
|                 return data; | ||||
|             }, | ||||
|             (m) => | ||||
|             { | ||||
|                 if (MemoryMarshal.TryGetArray((ReadOnlyMemory<byte>)m, out var result)) | ||||
|                     var data = ArrayPool<byte>.Shared.Rent(c); | ||||
|                     for (int i = 0; i < data.Length; i++) | ||||
|                     { | ||||
|                         data[i] = 0; | ||||
|                     } | ||||
|                     return data; | ||||
|                 }, | ||||
|                 (m) => | ||||
|                 { | ||||
|                     ArrayPool<byte>.Shared.Return(result.Array); | ||||
|                     if (MemoryMarshal.TryGetArray((ReadOnlyMemory<byte>)m, out var result)) | ||||
|                     { | ||||
|                         ArrayPool<byte>.Shared.Return(result.Array); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             ); | ||||
|             bytes.SetLength(256); | ||||
|             for (int i = 0; i < bytes.Length; i++) | ||||
|             { | ||||
|                 bytes.WriteByte(0); | ||||
|             } | ||||
|             bytes.Position = 0; | ||||
|             return bytes; | ||||
|         }); | ||||
|                 ); | ||||
|                 bytes.SetLength(256); | ||||
|                 for (int i = 0; i < bytes.Length; i++) | ||||
|                 { | ||||
|                     bytes.WriteByte(0); | ||||
|                 } | ||||
|                 bytes.Position = 0; | ||||
|                 return bytes; | ||||
|             }); | ||||
|  | ||||
|         if (ModbusServer03ByteBlocks.ContainsKey(mAddress.Station)) | ||||
|             return; | ||||
|         else | ||||
|             ModbusServer03ByteBlocks.GetOrAdd(mAddress.Station, a => | ||||
|         { | ||||
|             var bytes = new ByteBlock(256, | ||||
|             (c) => | ||||
|             { | ||||
|                 var data = ArrayPool<byte>.Shared.Rent(c); | ||||
|                 for (int i = 0; i < data.Length; i++) | ||||
|                 var bytes = new ByteBlock(256, | ||||
|                 (c) => | ||||
|                 { | ||||
|                     data[i] = 0; | ||||
|                 } | ||||
|                 return data; | ||||
|             }, | ||||
|             (m) => | ||||
|             { | ||||
|                 if (MemoryMarshal.TryGetArray((ReadOnlyMemory<byte>)m, out var result)) | ||||
|                     var data = ArrayPool<byte>.Shared.Rent(c); | ||||
|                     for (int i = 0; i < data.Length; i++) | ||||
|                     { | ||||
|                         data[i] = 0; | ||||
|                     } | ||||
|                     return data; | ||||
|                 }, | ||||
|                 (m) => | ||||
|                 { | ||||
|                     ArrayPool<byte>.Shared.Return(result.Array); | ||||
|                     if (MemoryMarshal.TryGetArray((ReadOnlyMemory<byte>)m, out var result)) | ||||
|                     { | ||||
|                         ArrayPool<byte>.Shared.Return(result.Array); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             ); | ||||
|             bytes.SetLength(256); | ||||
|             for (int i = 0; i < bytes.Length; i++) | ||||
|             { | ||||
|                 bytes.WriteByte(0); | ||||
|             } | ||||
|             bytes.Position = 0; | ||||
|             return bytes; | ||||
|         }); | ||||
|                 ); | ||||
|                 bytes.SetLength(256); | ||||
|                 for (int i = 0; i < bytes.Length; i++) | ||||
|                 { | ||||
|                     bytes.WriteByte(0); | ||||
|                 } | ||||
|                 bytes.Position = 0; | ||||
|                 return bytes; | ||||
|             }); | ||||
|  | ||||
|         if (ModbusServer04ByteBlocks.ContainsKey(mAddress.Station)) | ||||
|             return; | ||||
|         else | ||||
|             ModbusServer04ByteBlocks.GetOrAdd(mAddress.Station, a => | ||||
|         { | ||||
|             var bytes = new ByteBlock(256, | ||||
|             (c) => | ||||
|             { | ||||
|                 var data = ArrayPool<byte>.Shared.Rent(c); | ||||
|                 for (int i = 0; i < data.Length; i++) | ||||
|                 var bytes = new ByteBlock(256, | ||||
|                 (c) => | ||||
|                 { | ||||
|                     data[i] = 0; | ||||
|                 } | ||||
|                 return data; | ||||
|             }, | ||||
|             (m) => | ||||
|             { | ||||
|                 if (MemoryMarshal.TryGetArray((ReadOnlyMemory<byte>)m, out var result)) | ||||
|                     var data = ArrayPool<byte>.Shared.Rent(c); | ||||
|                     for (int i = 0; i < data.Length; i++) | ||||
|                     { | ||||
|                         data[i] = 0; | ||||
|                     } | ||||
|                     return data; | ||||
|                 }, | ||||
|                 (m) => | ||||
|                 { | ||||
|                     ArrayPool<byte>.Shared.Return(result.Array); | ||||
|                     if (MemoryMarshal.TryGetArray((ReadOnlyMemory<byte>)m, out var result)) | ||||
|                     { | ||||
|                         ArrayPool<byte>.Shared.Return(result.Array); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             ); | ||||
|             bytes.SetLength(256); | ||||
|             for (int i = 0; i < bytes.Length; i++) | ||||
|             { | ||||
|                 bytes.WriteByte(0); | ||||
|             } | ||||
|             bytes.Position = 0; | ||||
|             return bytes; | ||||
|         }); | ||||
|                 ); | ||||
|                 bytes.SetLength(256); | ||||
|                 for (int i = 0; i < bytes.Length; i++) | ||||
|                 { | ||||
|                     bytes.WriteByte(0); | ||||
|                 } | ||||
|                 bytes.Position = 0; | ||||
|                 return bytes; | ||||
|             }); | ||||
|     } | ||||
|  | ||||
|     public override Action<IPluginManager> ConfigurePlugins(TouchSocketConfig config) | ||||
|   | ||||
| @@ -15,7 +15,9 @@ | ||||
| 		<ProjectReference Include="..\..\Gateway\ThingsGateway.Gateway.Razor\ThingsGateway.Gateway.Razor.csproj"> | ||||
| 		</ProjectReference> | ||||
| 		 | ||||
| 		<PackageReference Include="Riok.Mapperly" Version="4.2.1" ExcludeAssets="runtime" PrivateAssets="all" /> | ||||
| 		<PackageReference Include="Riok.Mapperly" Version="4.3.0" ExcludeAssets="runtime" PrivateAssets="all"> | ||||
| 		  <IncludeAssets>compile; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||||
| 		</PackageReference> | ||||
|  | ||||
| 	</ItemGroup> | ||||
|  | ||||
|   | ||||
| @@ -17,6 +17,8 @@ using Photino.Blazor; | ||||
|  | ||||
| using System.Text; | ||||
|  | ||||
| using ThingsGateway.NewLife; | ||||
| using ThingsGateway.NewLife.Json.Extension; | ||||
| using ThingsGateway.NewLife.Log; | ||||
|  | ||||
| namespace ThingsGateway.Server; | ||||
| @@ -28,6 +30,23 @@ internal sealed class Program | ||||
|     [STAThread] | ||||
|     private static void Main(string[] args) | ||||
|     { | ||||
|         AppDomain.CurrentDomain.UnhandledException += (sender, e) => | ||||
|         { | ||||
|             if (e.ExceptionObject is OutOfMemoryException) | ||||
|             { | ||||
|                 try | ||||
|                 { | ||||
|                     XTrace.WriteLine($"[OOM DETECTED]"); | ||||
|                     XTrace.WriteLine(MachineInfo.GetCurrent().ToJsonNetString()); | ||||
|                 } | ||||
|                 catch | ||||
|                 { | ||||
|  | ||||
|                 } | ||||
|  | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         //当前工作目录设为程序集的基目录 | ||||
|         System.IO.Directory.SetCurrentDirectory(AppContext.BaseDirectory); | ||||
|         // 增加中文编码支持 | ||||
|   | ||||
| @@ -64,12 +64,10 @@ | ||||
| 		<ApplicationIcon>favicon.ico</ApplicationIcon> | ||||
| 		<!--<PublishAot>true</PublishAot>--> | ||||
| 		<CETCompat>false</CETCompat> | ||||
|  | ||||
| 		<ServerGarbageCollection>true</ServerGarbageCollection> | ||||
| 		<!--动态适用GC--> | ||||
| 		<GarbageCollectionAdaptationMode>1</GarbageCollectionAdaptationMode> | ||||
|  | ||||
| 		<!--使用工作站GC--> | ||||
| 		<!--<ServerGarbageCollection>true</ServerGarbageCollection>--> | ||||
| 		<!--<PlatformTarget>x86</PlatformTarget>--> | ||||
| 	</PropertyGroup> | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| <Project Sdk="Microsoft.NET.Sdk.Razor"> | ||||
| <Project Sdk="Microsoft.NET.Sdk.Razor"> | ||||
|  | ||||
| 	<Import Project="..\Version.props" /> | ||||
| 	 | ||||
| @@ -37,17 +37,15 @@ | ||||
| 		<ApplicationIcon>favicon.ico</ApplicationIcon> | ||||
| 		<!--<PublishAot>true</PublishAot>--> | ||||
| 		<CETCompat>false</CETCompat> | ||||
| 		<ServerGarbageCollection>true</ServerGarbageCollection> | ||||
| 		<!--动态适用GC--> | ||||
| 		<GarbageCollectionAdaptationMode>1</GarbageCollectionAdaptationMode> | ||||
|  | ||||
| 		<PublishAot>true</PublishAot> | ||||
| 		<DebugType>none</DebugType> | ||||
| 		<EmbedAllSources>false</EmbedAllSources> | ||||
| 		<EmitDebugInformation>false</EmitDebugInformation> | ||||
| 		 | ||||
| 		<!--动态适用GC--> | ||||
| 		<GarbageCollectionAdaptationMode>1</GarbageCollectionAdaptationMode> | ||||
| 		 | ||||
| 		<!--使用工作站GC--> | ||||
| 		<!--<ServerGarbageCollection>true</ServerGarbageCollection>--> | ||||
| 		<!--<PlatformTarget>x86</PlatformTarget>--> | ||||
| 	</PropertyGroup> | ||||
|  | ||||
|   | ||||
| @@ -32,26 +32,14 @@ | ||||
| 		<PublishReadyToRunComposite>true</PublishReadyToRunComposite> | ||||
| 		<ApplicationIcon>favicon.ico</ApplicationIcon> | ||||
|  | ||||
| 		<CETCompat>false</CETCompat> | ||||
| 		<ServerGarbageCollection>true</ServerGarbageCollection> | ||||
| 		<!--动态适用GC--> | ||||
| 		<GarbageCollectionAdaptationMode>1</GarbageCollectionAdaptationMode> | ||||
|  | ||||
|  | ||||
| 		<CETCompat>false</CETCompat> | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| 		<!--<TieredCompilation>false</TieredCompilation>--> | ||||
|  | ||||
| 		<!--使用自托管线程池--> | ||||
| 		<!--<UseWindowsThreadPool>false</UseWindowsThreadPool> --> | ||||
|  | ||||
| 		<!--使用工作站GC--> | ||||
| 		<!--<ServerGarbageCollection>true</ServerGarbageCollection>--> | ||||
|  | ||||
| 		<!--<PlatformTarget>x86</PlatformTarget>--> | ||||
| 		<!--editbin /LARGEADDRESSAWARE:NO ThingsGateway.Server.exe--> | ||||
|  | ||||
| 	</PropertyGroup> | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -3,6 +3,6 @@ | ||||
|     "CheckInterval": 1800000, //检查间隔 | ||||
|     "MaxChannelCount": 50, //最大通道数量 | ||||
|     "MaxDeviceCount": 50, //最大设备数量 | ||||
|     "MaxVariableCount": 10000 //最大变量数量 | ||||
|     "MaxVariableCount": 5000 //最大变量数量 | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -16,6 +16,7 @@ using System.Text; | ||||
| using ThingsGateway.Admin.Application; | ||||
| using ThingsGateway.DB; | ||||
| using ThingsGateway.NewLife; | ||||
| using ThingsGateway.NewLife.Json.Extension; | ||||
| using ThingsGateway.NewLife.Log; | ||||
| using ThingsGateway.SqlSugar; | ||||
|  | ||||
| @@ -27,6 +28,24 @@ public class Program | ||||
|  | ||||
|     public static async Task Main(string[] args) | ||||
|     { | ||||
|         AppDomain.CurrentDomain.UnhandledException += (sender, e) => | ||||
|         { | ||||
|             if (e.ExceptionObject is OutOfMemoryException) | ||||
|             { | ||||
|                 try | ||||
|                 { | ||||
|                     XTrace.WriteLine($"[OOM DETECTED]"); | ||||
|                     XTrace.WriteLine(MachineInfo.GetCurrent().ToJsonNetString()); | ||||
|                 } | ||||
|                 catch | ||||
|                 { | ||||
|  | ||||
|                 } | ||||
|                  | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|  | ||||
|         await Task.Delay(2000).ConfigureAwait(false); | ||||
|         //当前工作目录设为程序集的基目录 | ||||
|         System.IO.Directory.SetCurrentDirectory(AppContext.BaseDirectory); | ||||
|   | ||||
| @@ -109,9 +109,10 @@ public class Startup : AppStartup | ||||
|              .AddInteractiveServerComponents(options => | ||||
|              { | ||||
|                  options.RootComponents.MaxJSRootComponents = 500; | ||||
|                  options.JSInteropDefaultCallTimeout = TimeSpan.FromMinutes(2); | ||||
|                  options.MaxBufferedUnacknowledgedRenderBatches = 20; | ||||
|                  options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromMinutes(10); | ||||
|                  options.JSInteropDefaultCallTimeout = TimeSpan.FromSeconds(30); | ||||
|                  options.MaxBufferedUnacknowledgedRenderBatches = 5; | ||||
|                  options.DisconnectedCircuitMaxRetained = 1; | ||||
|                  options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromSeconds(10); | ||||
|              }) | ||||
|              .AddHubOptions(options => | ||||
|              { | ||||
| @@ -119,29 +120,31 @@ public class Startup : AppStartup | ||||
|                  options.MaximumReceiveMessageSize = 32 * 1024 * 1024; | ||||
|                  //可为客户端上载流缓冲的最大项数。 如果达到此限制,则会阻止处理调用,直到服务器处理流项。 | ||||
|                  options.StreamBufferCapacity = 30; | ||||
|                  options.ClientTimeoutInterval = TimeSpan.FromMinutes(2); | ||||
|                  options.ClientTimeoutInterval = TimeSpan.FromSeconds(30); | ||||
|                  options.KeepAliveInterval = TimeSpan.FromSeconds(15); | ||||
|                  options.HandshakeTimeout = TimeSpan.FromSeconds(30); | ||||
|                  options.HandshakeTimeout = TimeSpan.FromSeconds(15); | ||||
|              }); | ||||
|  | ||||
| #else | ||||
|  | ||||
|                     services.AddServerSideBlazor(options => | ||||
|                     { | ||||
|                         options.RootComponents.MaxJSRootComponents = 500; | ||||
|                         options.JSInteropDefaultCallTimeout = TimeSpan.FromMinutes(2); | ||||
|                         options.MaxBufferedUnacknowledgedRenderBatches = 20; | ||||
|                         options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromMinutes(10); | ||||
|                     }).AddHubOptions(options => | ||||
|                     { | ||||
|                         //单个传入集线器消息的最大大小。默认 32 KB | ||||
|                         options.MaximumReceiveMessageSize =32 * 1024 * 1024; | ||||
|                         //可为客户端上载流缓冲的最大项数。 如果达到此限制,则会阻止处理调用,直到服务器处理流项。 | ||||
|                         options.StreamBufferCapacity = 30; | ||||
|                         options.ClientTimeoutInterval = TimeSpan.FromMinutes(2); | ||||
|                         options.KeepAliveInterval = TimeSpan.FromSeconds(15); | ||||
|                         options.HandshakeTimeout = TimeSpan.FromSeconds(30); | ||||
|                     }); | ||||
|                      { | ||||
|                          options.RootComponents.MaxJSRootComponents = 500; | ||||
|                          options.JSInteropDefaultCallTimeout = TimeSpan.FromSeconds(30); | ||||
|                          options.MaxBufferedUnacknowledgedRenderBatches = 5; | ||||
|                          options.DisconnectedCircuitMaxRetained = 1; | ||||
|                          options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromSeconds(10); | ||||
|                      }) | ||||
|                      .AddHubOptions(options => | ||||
|                      { | ||||
|                          //单个传入集线器消息的最大大小。默认 32 KB | ||||
|                          options.MaximumReceiveMessageSize = 32 * 1024 * 1024; | ||||
|                          //可为客户端上载流缓冲的最大项数。 如果达到此限制,则会阻止处理调用,直到服务器处理流项。 | ||||
|                          options.StreamBufferCapacity = 30; | ||||
|                          options.ClientTimeoutInterval = TimeSpan.FromSeconds(30); | ||||
|                          options.KeepAliveInterval = TimeSpan.FromSeconds(15); | ||||
|                          options.HandshakeTimeout = TimeSpan.FromSeconds(15); | ||||
|                      }); | ||||
|  | ||||
| #endif | ||||
|  | ||||
| @@ -205,10 +208,7 @@ public class Startup : AppStartup | ||||
|  | ||||
|             var websiteOptions = App.GetConfig<WebsiteOptions>("Website"); | ||||
|  | ||||
|             if (websiteOptions.BlazorConnectionLimitEnable) | ||||
|             { | ||||
|                 services.AddSingleton<CircuitHandler, ConnectionLimiterCircuitHandler>(); | ||||
|             } | ||||
|              services.AddSingleton<CircuitHandler, ConnectionLimiterCircuitHandler>(); | ||||
|             if (websiteOptions.Demo) | ||||
|             { | ||||
|                 authenticationBuilder.AddOAuth<GiteeOAuthOptions, AdminOAuthHandler<GiteeOAuthOptions>>("Gitee", "Gitee", options => | ||||
|   | ||||
| @@ -10,6 +10,8 @@ | ||||
|  | ||||
| using Microsoft.AspNetCore.Components.Server.Circuits; | ||||
|  | ||||
| using System.Runtime; | ||||
|  | ||||
| using ThingsGateway.Common; | ||||
|  | ||||
| namespace ThingsGateway.Server; | ||||
| @@ -22,6 +24,12 @@ public class ConnectionLimiterCircuitHandler : CircuitHandler | ||||
|  | ||||
|     public override Task OnCircuitOpenedAsync(Circuit circuit, CancellationToken cancellationToken) | ||||
|     { | ||||
|         //主动触发垃圾回收,释放上个链路资源 | ||||
|         GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce; | ||||
|         GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true, true); | ||||
|         GC.WaitForPendingFinalizers(); | ||||
|         GC.Collect(1, GCCollectionMode.Optimized, blocking: false, compacting: false); | ||||
|  | ||||
|         WebsiteOptions ??= App.GetOptions<WebsiteOptions>(); | ||||
|  | ||||
|         if (!WebsiteOptions.BlazorConnectionLimitEnable) | ||||
|   | ||||
| @@ -51,26 +51,11 @@ | ||||
| 		<PublishReadyToRunComposite>true</PublishReadyToRunComposite> | ||||
| 		<ApplicationIcon>favicon.ico</ApplicationIcon> | ||||
|  | ||||
| 		<CETCompat>false</CETCompat> | ||||
| 		<ServerGarbageCollection>true</ServerGarbageCollection> | ||||
| 		<!--动态适用GC--> | ||||
| 		<GarbageCollectionAdaptationMode>1</GarbageCollectionAdaptationMode> | ||||
|  | ||||
|  | ||||
| 		<CETCompat>false</CETCompat> | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| 		<!--<TieredCompilation>false</TieredCompilation>--> | ||||
|  | ||||
| 		<!--使用自托管线程池--> | ||||
| 		<!--<UseWindowsThreadPool>false</UseWindowsThreadPool> --> | ||||
|  | ||||
| 		<!--使用工作站GC--> | ||||
| 		<!--<ServerGarbageCollection>true</ServerGarbageCollection>--> | ||||
|  | ||||
| 		<!--<PlatformTarget>x86</PlatformTarget>--> | ||||
| 		<!--editbin /LARGEADDRESSAWARE:NO ThingsGateway.Server.exe--> | ||||
|  | ||||
| 	</PropertyGroup> | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,10 @@ | ||||
| { | ||||
|   "configProperties": { | ||||
|     "System.Runtime.EnableWriteXorExecute": false | ||||
|     "System.Runtime.EnableWriteXorExecute": false, | ||||
|     "System.GC.HeapHardLimitPercent": 95, //堆限制百分比 | ||||
|     "System.GC.HighMemoryPercent": 90, //高内存百分比 | ||||
|     "System.GC.DynamicAdaptationMode": 1, //动态适应模式 | ||||
|     "System.GC.ConserveMemory": 5 //节省内存模式,0-9 | ||||
|     //"System.GC.RegionRange": 549755813888 //8GB, 区域范围,保留的虚拟内存,如DOCKER内出现OOM,可以调大,一般是进程内存限制的2倍 | ||||
|   } | ||||
| } | ||||
|   | ||||
							
								
								
									
										302
									
								
								wiki.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										302
									
								
								wiki.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,302 @@ | ||||
| { | ||||
|   "repo_notes": [ | ||||
|     { | ||||
|       "content": "" | ||||
|     } | ||||
|   ], | ||||
|   "pages": [ | ||||
|     { | ||||
|       "title": "Overview", | ||||
|       "purpose": "Introduce ThingsGateway, explaining what it is, its core purpose as an industrial IoT gateway, and the high-level architecture", | ||||
|       "page_notes": [ | ||||
|         { | ||||
|           "content": "" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "title": "Key Features", | ||||
|       "purpose": "List and describe the main features including multi-protocol support, plugin architecture, data persistence options, and deployment models", | ||||
|       "parent": "Overview", | ||||
|       "page_notes": [ | ||||
|         { | ||||
|           "content": "" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "title": "System Requirements and Dependencies", | ||||
|       "purpose": "Document the .NET versions, target frameworks, required NuGet packages, and system requirements", | ||||
|       "parent": "Overview", | ||||
|       "page_notes": [ | ||||
|         { | ||||
|           "content": "" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "title": "Getting Started", | ||||
|       "purpose": "Provide quick-start guide for installing and running ThingsGateway", | ||||
|       "page_notes": [ | ||||
|         { | ||||
|           "content": "" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "title": "Installation and Deployment", | ||||
|       "purpose": "Explain deployment options including Docker (x64/ARM64), web server, desktop application, and configuration basics", | ||||
|       "parent": "Getting Started", | ||||
|       "page_notes": [ | ||||
|         { | ||||
|           "content": "" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "title": "Initial Configuration", | ||||
|       "purpose": "Guide users through initial setup including appsettings.json, environment variables, logging configuration, and first channel/device setup", | ||||
|       "parent": "Getting Started", | ||||
|       "page_notes": [ | ||||
|         { | ||||
|           "content": "" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "title": "Architecture Overview", | ||||
|       "purpose": "Explain the overall system architecture including layered design, separation of concerns, and component relationships", | ||||
|       "page_notes": [ | ||||
|         { | ||||
|           "content": "" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "title": "Layered Architecture", | ||||
|       "purpose": "Describe the Foundation, Gateway Application, and UI/Admin layers and their responsibilities", | ||||
|       "parent": "Architecture Overview", | ||||
|       "page_notes": [ | ||||
|         { | ||||
|           "content": "" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "title": "Plugin System Architecture", | ||||
|       "purpose": "Explain the plugin architecture including Foundation vs Plugin layer separation, dynamic loading with AssemblyLoadContext, and plugin types", | ||||
|       "parent": "Architecture Overview", | ||||
|       "page_notes": [ | ||||
|         { | ||||
|           "content": "" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "title": "Build System and Conditional Compilation", | ||||
|       "purpose": "Document the MSBuild-based build system including Directory.Build.props, conditional plugin loading, multi-targeting, and NuGet package generation", | ||||
|       "parent": "Architecture Overview", | ||||
|       "page_notes": [ | ||||
|         { | ||||
|           "content": "" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "title": "Foundation Layer", | ||||
|       "purpose": "Document the core foundation libraries that provide protocol implementations and communication primitives", | ||||
|       "page_notes": [ | ||||
|         { | ||||
|           "content": "" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "title": "Device Communication", | ||||
|       "purpose": "Explain DeviceBase abstraction, IDevice interface, protocol implementations, read/write operations, and data parsing", | ||||
|       "parent": "Foundation Layer", | ||||
|       "page_notes": [ | ||||
|         { | ||||
|           "content": "" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "title": "Channel Architecture", | ||||
|       "purpose": "Document IChannel interface, channel implementations (TCP/UDP/Serial), channel options, and connection lifecycle", | ||||
|       "parent": "Foundation Layer", | ||||
|       "page_notes": [ | ||||
|         { | ||||
|           "content": "" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "title": "Data Handling and Transformation", | ||||
|       "purpose": "Describe DataHandlingAdapter, message parsing, ThingsGatewayBitConverter for endianness/format conversion, and WaitHandlePool for request-response correlation", | ||||
|       "parent": "Foundation Layer", | ||||
|       "page_notes": [ | ||||
|         { | ||||
|           "content": "" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "title": "Core Utilities", | ||||
|       "purpose": "Document TimerX scheduling, ExpiringDictionary caching, WaitLock concurrency primitives, and Reflect utilities", | ||||
|       "parent": "Foundation Layer", | ||||
|       "page_notes": [ | ||||
|         { | ||||
|           "content": "" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "title": "Gateway Application", | ||||
|       "purpose": "Explain the main gateway application layer that orchestrates device communication and data flow", | ||||
|       "page_notes": [ | ||||
|         { | ||||
|           "content": "" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "title": "Runtime Management System", | ||||
|       "purpose": "Document the entity-to-runtime conversion, GlobalData static registry, RuntimeServiceHelper, and runtime synchronization", | ||||
|       "parent": "Gateway Application", | ||||
|       "page_notes": [ | ||||
|         { | ||||
|           "content": "" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "title": "Device Lifecycle Management", | ||||
|       "purpose": "Explain DeviceThreadManage, driver initialization, StartAsync/StopAsync lifecycle, TaskSchedulerLoop, and thread management", | ||||
|       "parent": "Gateway Application", | ||||
|       "page_notes": [ | ||||
|         { | ||||
|           "content": "" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "title": "Variable Management", | ||||
|       "purpose": "Document VariableRuntime, VariableSourceRead, variable packing optimization, and data collection workflow", | ||||
|       "parent": "Gateway Application", | ||||
|       "page_notes": [ | ||||
|         { | ||||
|           "content": "" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "title": "Concurrency and Read/Write Coordination", | ||||
|       "purpose": "Explain AsyncReadWriteLock, reader/writer priority, duty cycle control, and concurrent operation management", | ||||
|       "parent": "Gateway Application", | ||||
|       "page_notes": [ | ||||
|         { | ||||
|           "content": "" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "title": "Event System", | ||||
|       "purpose": "Document GlobalData event dispatchers, VariableValueChangeEvent, DeviceStatusChangeEvent, AlarmChangedEvent, and event flow", | ||||
|       "parent": "Gateway Application", | ||||
|       "page_notes": [ | ||||
|         { | ||||
|           "content": "" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "title": "Redundancy and Failover", | ||||
|       "purpose": "Explain device redundancy system, master/slave configuration, failover triggers, and variable state transfer", | ||||
|       "parent": "Gateway Application", | ||||
|       "page_notes": [ | ||||
|         { | ||||
|           "content": "" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "title": "Plugin Development", | ||||
|       "purpose": "Guide for developing custom plugins for data collection and business logic integration", | ||||
|       "page_notes": [ | ||||
|         { | ||||
|           "content": "" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "title": "Collection Plugins (CollectBase)", | ||||
|       "purpose": "Document how to create data collection plugins including protocol implementation, variable loading, and scheduled tasks", | ||||
|       "parent": "Plugin Development", | ||||
|       "page_notes": [ | ||||
|         { | ||||
|           "content": "" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "title": "Business Plugins (BusinessBase)", | ||||
|       "purpose": "Explain business plugin development for data persistence, distribution, and serving protocols", | ||||
|       "parent": "Plugin Development", | ||||
|       "page_notes": [ | ||||
|         { | ||||
|           "content": "" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "title": "Plugin Service and Dynamic Loading", | ||||
|       "purpose": "Document PluginService, AssemblyLoadContext usage, plugin discovery, driver instantiation, and property configuration", | ||||
|       "parent": "Plugin Development", | ||||
|       "page_notes": [ | ||||
|         { | ||||
|           "content": "" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "title": "Plugin Packaging and Distribution", | ||||
|       "purpose": "Explain how to package plugins as NuGet packages, versioning, and deployment via Directory.build.targets", | ||||
|       "parent": "Plugin Development", | ||||
|       "page_notes": [ | ||||
|         { | ||||
|           "content": "" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "title": "Built-in Protocols", | ||||
|       "purpose": "Document the built-in protocol implementations including Modbus, Siemens S7, OPC UA, OPC DA, and Dlt645", | ||||
|       "page_notes": [ | ||||
|         { | ||||
|           "content": "" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "title": "Modbus Protocol", | ||||
|       "purpose": "Document Modbus Master/Slave implementations, RTU/TCP/UDP support, function codes, and address parsing", | ||||
|       "parent": "Built-in Protocols", | ||||
|       "page_notes": [ | ||||
|         { | ||||
|           "content": "" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "title": "Siemens S7 Protocol", | ||||
|       "purpose": "Explain S7 Master implementation, PLC types, area addressing, and data block access", | ||||
|       "parent": "Built-in Protocols", | ||||
|       "page_notes": [ | ||||
|         { | ||||
|           "content": "" | ||||
|         } | ||||
|       ] | ||||
|     } | ||||
|   ] | ||||
| } | ||||
		Reference in New Issue
	
	Block a user