mirror of
				https://gitee.com/ThingsGateway/ThingsGateway.git
				synced 2025-10-31 07:33:58 +08:00 
			
		
		
		
	Compare commits
	
		
			81 Commits
		
	
	
		
			10.11.43.0
			...
			10.12.11.0
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | aaa459ebe0 | ||
|   | 51a8acbc3e | ||
|   | dc132a1999 | ||
|   | 0c31cfcbc5 | ||
|   | 0e44bc67cd | ||
|   | 12dfbba42c | ||
|   | 96b4287f3a | ||
|   | 7d406de29f | ||
|   | 81f0ef466a | ||
|   | 3f2d6b133c | ||
|   | e776dc67eb | ||
| ![devin-ai-integration[bot]](/assets/img/avatar_default.png)  | bc5827d140 | ||
|   | 21838bf4af | ||
|   | 6090108597 | ||
|   | b47b9e6f43 | ||
|   | 18d1cffb2d | ||
|   | 516fd7f235 | ||
|   | 2ee16c3533 | ||
|   | 7d22f5c78e | ||
|   | 3e604ee2fd | ||
|   | 47e442874c | ||
|   | 2a8c0cbab1 | ||
|   | c26898b49d | ||
|   | 00c24d06a3 | ||
|   | 3461f34240 | ||
|   | aa1ce08c02 | ||
|   | 9c230c2da9 | ||
|   | 21215d0379 | ||
|   | 7448183791 | ||
|   | 35edd7dc43 | ||
|   | bd178831e3 | ||
|   | fe9ec6ad10 | ||
|   | 6f9ec2e24b | ||
|   | c0337e2b19 | ||
|   | 8a95f48f5a | ||
|   | 14f3c31265 | ||
|   | 1bad65378f | ||
|   | db3affc67e | ||
|   | 5ee8b50a92 | ||
|   | 301beda2a2 | ||
|   | 628b51a353 | ||
|   | f03445bc83 | ||
|   | 55a2ff5487 | ||
|   | 0fef7dcf3b | ||
|   | 19d9702606 | ||
|   | a8a9774932 | ||
|   | aad0f0e8c3 | ||
|   | e74eae50a7 | ||
|   | 3b16d7019f | ||
|   | 3e038028c2 | ||
|   | b1d8041f7e | ||
|   | 53a98b26cd | ||
|   | 42c740fa1b | ||
|   | 556819c90c | ||
|   | 2522333a9c | ||
|   | bd4ce7c09b | ||
|   | 156ed88bd6 | ||
|   | 2416226eb0 | ||
|   | 976323a716 | ||
|   | 3c9e397403 | ||
|   | 79406ad4a0 | ||
|   | 20c44f10ca | ||
|   | 31d6b2a9e6 | ||
|   | 68e5a9c546 | ||
|   | b2ea9f99b9 | ||
|   | 6d7d0e468a | ||
|   | ff1f632de2 | ||
|   | fc3d7015ee | ||
|   | 40c5acb522 | ||
|   | f9cc1cbb05 | ||
|   | cf6e8b58f0 | ||
|   | 615e3bb24c | ||
|   | 4a7534b210 | ||
|   | 58e099cb93 | ||
|   | a94a9c953c | ||
|   | 35e1ffa3e9 | ||
|   | 4921642151 | ||
|   | d71ee29da8 | ||
|   | 901aa2d59f | ||
|   | 764957c014 | ||
|   | 0e3898218b | 
							
								
								
									
										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 | ||||
|  | ||||
|   | ||||
| @@ -45,6 +45,7 @@ public class VerificatInfo : PrimaryIdEntity | ||||
|     /// 登录IP | ||||
|     /// </summary> | ||||
|     [AutoGenerateColumn(Filterable = true, Sortable = true, Width = 200)] | ||||
|     [SugarColumn(IsNullable = true)] | ||||
|     public string LoginIp { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
| @@ -78,5 +79,6 @@ public class VerificatInfo : PrimaryIdEntity | ||||
|     /// 登录设备 | ||||
|     /// </summary> | ||||
|     [AutoGenerateColumn(Filterable = true, Sortable = true, Width = 100)] | ||||
|     [SugarColumn(IsNullable = true)] | ||||
|     public string Device { get; set; } | ||||
| } | ||||
|   | ||||
| @@ -251,11 +251,13 @@ public class RequestAuditFilter : IAsyncActionFilter, IOrderedFilter | ||||
|  | ||||
|         if (exception == null) | ||||
|         { | ||||
|             logger.Log(LogLevel.Information, $"{logData.Method}:{logData.Path}-{logData.Operation}"); | ||||
|             if (logger.IsEnabled(LogLevel.Information)) | ||||
|                 logger.Log(LogLevel.Information, $"{logData.Method}:{logData.Path}-{logData.Operation}"); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             logger.Log(LogLevel.Warning, $"{logData.Method}:{logData.Path}-{logData.Operation}{Environment.NewLine}{logData.Exception?.ToSystemTextJsonString()}{Environment.NewLine}{logData.Validation?.ToSystemTextJsonString()}"); | ||||
|             if (logger.IsEnabled(LogLevel.Warning)) | ||||
|                 logger.Log(LogLevel.Warning, $"{logData.Method}:{logData.Path}-{logData.Operation}{Environment.NewLine}{logData.Exception?.ToSystemTextJsonString()}{Environment.NewLine}{logData.Validation?.ToSystemTextJsonString()}"); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -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,12 +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); | ||||
|   | ||||
| @@ -145,7 +145,7 @@ public class AdminOAuthHandler<TOptions>( | ||||
|         var loginEvent = new LoginEvent | ||||
|         { | ||||
|             Ip = appService.RemoteIpAddress, | ||||
|             Device = appService.UserAgent?.Platform, | ||||
|             Device = appService.UserAgent?.Platform ?? "Unknown", | ||||
|             Expire = expire, | ||||
|             SysUser = sysUser, | ||||
|             VerificatId = CommonUtils.GetSingleId() | ||||
| @@ -156,7 +156,7 @@ public class AdminOAuthHandler<TOptions>( | ||||
|         //生成verificat信息 | ||||
|         var verificatInfo = new VerificatInfo | ||||
|         { | ||||
|             Device = loginEvent.Device, | ||||
|             Device = loginEvent.Device ?? "Unknown", | ||||
|             Expire = loginEvent.Expire, | ||||
|             VerificatTimeout = tokenTimeout, | ||||
|             Id = loginEvent.VerificatId, | ||||
|   | ||||
| @@ -26,7 +26,7 @@ | ||||
|       "Module": 2, | ||||
|       "Title": "权限管理", | ||||
|       "Code": "System", | ||||
|       "NavLinkMatch": "All", | ||||
|       "NavLinkMatch": "Prefix", | ||||
|       "Category": "MENU", | ||||
|       "Target": "_self", | ||||
|       "Href": null, | ||||
| @@ -47,7 +47,7 @@ | ||||
|       "ParentId": 0, | ||||
|       "Module": 2, | ||||
|       "Title": "系统运维", | ||||
|       "NavLinkMatch": "All", | ||||
|       "NavLinkMatch": "Prefix", | ||||
|       "Code": "System", | ||||
|       "Category": "MENU", | ||||
|       "Target": "_self", | ||||
|   | ||||
| @@ -235,7 +235,7 @@ public class AuthService : IAuthService | ||||
|         var logingEvent = new LoginEvent | ||||
|         { | ||||
|             Ip = _appService.RemoteIpAddress, | ||||
|             Device = _appService.UserAgent?.Platform, | ||||
|             Device = _appService.UserAgent?.Platform ?? "Unknown", | ||||
|             Expire = expire, | ||||
|             SysUser = sysUser, | ||||
|             VerificatId = verificatId | ||||
| @@ -344,7 +344,7 @@ public class AuthService : IAuthService | ||||
|         //生成verificat信息 | ||||
|         var verificatInfo = new VerificatInfo | ||||
|         { | ||||
|             Device = loginEvent.Device, | ||||
|             Device = loginEvent.Device ?? "Unknown", | ||||
|             Expire = loginEvent.Expire, | ||||
|             VerificatTimeout = tokenTimeout, | ||||
|             Id = loginEvent.VerificatId, | ||||
|   | ||||
| @@ -20,7 +20,7 @@ namespace ThingsGateway.Admin.Application; | ||||
| /// <typeparam name="TEntry"></typeparam> | ||||
| public class EventService<TEntry> : IEventService<TEntry>, IDisposable | ||||
| { | ||||
|     private ConcurrentDictionary<string, Func<TEntry, Task>> Cache = new(); | ||||
|     private NonBlockingDictionary<string, Func<TEntry, Task>> Cache = new(); | ||||
|  | ||||
|     public void Dispose() | ||||
|     { | ||||
|   | ||||
| @@ -9,6 +9,7 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| namespace ThingsGateway.Admin.Application; | ||||
|  | ||||
| internal sealed class NoticeService : INoticeService | ||||
| { | ||||
|     private IEventService<AppMessage>? MessageDispatchService { get; set; } | ||||
|   | ||||
| @@ -282,7 +282,7 @@ internal sealed class SysRoleService : BaseService<SysRole>, ISysRoleService | ||||
|         if (sysRole != null) | ||||
|         { | ||||
|             var resources = await _sysResourceService.GetAllAsync().ConfigureAwait(false); | ||||
|             var menusList = resources.Where(a => a.Category == ResourceCategoryEnum.Menu).Where(a => menuIds.Contains(a.Id)); | ||||
|             var menusList = resources.Where(a => a.Category == ResourceCategoryEnum.Menu && menuIds.Contains(a.Id)); | ||||
|  | ||||
|             #region 角色模块处理 | ||||
|  | ||||
|   | ||||
| @@ -550,7 +550,7 @@ internal sealed class SysUserService : BaseService<SysUser>, ISysUserService | ||||
|         if (sysUser != null) | ||||
|         { | ||||
|             var resources = await _sysResourceService.GetAllAsync().ConfigureAwait(false); | ||||
|             var menusList = resources.Where(a => a.Category == ResourceCategoryEnum.Menu).Where(a => menuIds.Contains(a.Id)); | ||||
|             var menusList = resources.Where(a => a.Category == ResourceCategoryEnum.Menu && menuIds.Contains(a.Id)); | ||||
|  | ||||
|             #region 用户模块处理 | ||||
|  | ||||
|   | ||||
| @@ -119,7 +119,7 @@ internal sealed class VerificatInfoService : BaseService<VerificatInfo>, IVerifi | ||||
|     public void Add(VerificatInfo verificatInfo) | ||||
|     { | ||||
|         using var db = GetDB(); | ||||
|         db.Insertable<VerificatInfo>(verificatInfo).ExecuteCommand(); | ||||
|         db.InsertableT<VerificatInfo>(verificatInfo).ExecuteCommand(); | ||||
|         VerificatInfoService.RemoveCache(verificatInfo.Id); | ||||
|         if (verificatInfo != null) | ||||
|             VerificatInfoService.SetCahce(verificatInfo); | ||||
| @@ -132,7 +132,7 @@ internal sealed class VerificatInfoService : BaseService<VerificatInfo>, IVerifi | ||||
|     public void Update(VerificatInfo verificatInfo) | ||||
|     { | ||||
|         using var db = GetDB(); | ||||
|         db.Updateable<VerificatInfo>(verificatInfo).ExecuteCommand(); | ||||
|         db.UpdateableT<VerificatInfo>(verificatInfo).ExecuteCommand(); | ||||
|         VerificatInfoService.RemoveCache(verificatInfo.Id); | ||||
|         if (verificatInfo != null) | ||||
|             VerificatInfoService.SetCahce(verificatInfo); | ||||
|   | ||||
| @@ -8,7 +8,7 @@ | ||||
| 		 | ||||
| 	</PropertyGroup> | ||||
| 	<PropertyGroup> | ||||
| 		<TargetFrameworks>net8.0;net9.0;</TargetFrameworks> | ||||
| 		<TargetFrameworks>net8.0;$(OtherTargetFrameworks);</TargetFrameworks> | ||||
| 	</PropertyGroup> | ||||
|  | ||||
| 	<ItemGroup> | ||||
| @@ -19,19 +19,21 @@ | ||||
| 	</ItemGroup> | ||||
| 	 | ||||
| 	<ItemGroup> | ||||
| 		<PackageReference Include="Riok.Mapperly" Version="4.2.1" ExcludeAssets="runtime" PrivateAssets="all" /> | ||||
| 		<PackageReference Include="Rougamo.Fody" Version="5.0.1" /> | ||||
| 		<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> | ||||
| 	<ItemGroup Condition=" '$(TargetFramework)' == 'net9.0' "> | ||||
| 		<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="$(NET9Version)" /> | ||||
| 		<PackageReference Include="System.Formats.Asn1" Version="$(NET9Version)" /> | ||||
| 		<PackageReference Include="System.Threading.RateLimiting" Version="$(NET9Version)" /> | ||||
| 	<ItemGroup Condition=" '$(TargetFramework)' == 'net10.0' "> | ||||
| 		<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="$(NET10Version)" /> | ||||
| 		<PackageReference Include="System.Formats.Asn1" Version="$(NET10Version)" /> | ||||
| 		<PackageReference Include="System.Threading.RateLimiting" Version="$(NET10Version)" /> | ||||
| 	</ItemGroup> | ||||
| 	<ItemGroup> | ||||
| 		<Content Remove="SeedData\Admin\*.json" /> | ||||
|   | ||||
| @@ -4,8 +4,8 @@ | ||||
|  | ||||
| <div class="tg-table h-100"> | ||||
|  | ||||
|     <Table TItem="TItem" IsBordered="true" IsStriped="true" TableSize="TableSize.Compact" SelectedRows=SelectedRows SelectedRowsChanged=privateSelectedRowsChanged IsMultipleSelect="IsMultipleSelect" @ref="Instance" SearchTemplate="SearchTemplate" | ||||
|            DataService="DataService" CreateItemCallback="CreateItemCallback!" | ||||
|     <Table Id=@Id TItem="TItem" IsBordered="true" IsStriped="true" TableSize="TableSize.Compact" SelectedRows=SelectedRows SelectedRowsChanged=privateSelectedRowsChanged IsMultipleSelect="IsMultipleSelect" @ref="Instance" SearchTemplate="SearchTemplate" | ||||
|            DataService="DataService" CreateItemCallback="CreateItemCallback!" RenderMode=RenderMode OnColumnCreating=OnColumnCreating | ||||
|            IsPagination="IsPagination" PageItemsSource="PageItemsSource" IsFixedHeader="IsFixedHeader" IndentSize=24 RowHeight=RowHeight ShowSearchText="ShowSearchText" ShowSearchButton="ShowSearchButton" DisableEditButtonCallback="DisableEditButtonCallback" DisableDeleteButtonCallback="DisableDeleteButtonCallback" BeforeShowEditDialogCallback=" BeforeShowEditDialogCallback!" | ||||
|            IsTree="IsTree" OnTreeExpand="OnTreeExpand!" TreeNodeConverter="TreeNodeConverter!" TreeIcon="fa-solid fa-circle-chevron-right" TreeExpandIcon="fa-solid fa-circle-chevron-right fa-rotate-90" IsAutoQueryFirstRender=IsAutoQueryFirstRender | ||||
|            ShowDefaultButtons="ShowDefaultButtons" ShowAdvancedSearch="ShowAdvancedSearch" ShowResetButton=ShowResetButton | ||||
| @@ -14,7 +14,7 @@ | ||||
|            ShowSkeleton="true" ShowLoading="ShowLoading" ShowSearch="ShowSearch" SearchModel=@SearchModel ShowLineNo | ||||
|            SearchMode=SearchMode ShowExportPdfButton=ShowExportPdfButton ExportButtonText=@ExportButtonText | ||||
|            ShowExportButton=@ShowExportButton Items=Items ClickToSelect=ClickToSelect ScrollMode=ScrollMode | ||||
|            ShowExportCsvButton=@ShowExportCsvButton ShowCardView=ShowCardView | ||||
|            ShowExportCsvButton=@ShowExportCsvButton ShowCardView=ShowCardView OnColumnVisibleChanged=OnColumnVisibleChanged | ||||
|            FixedExtendButtonsColumn=FixedExtendButtonsColumn FixedMultipleColumn=FixedMultipleColumn FixedDetailRowHeaderColumn=FixedDetailRowHeaderColumn FixedLineNoColumn=FixedLineNoColumn | ||||
|            IsAutoRefresh=IsAutoRefresh AutoRefreshInterval=AutoRefreshInterval | ||||
|            AllowDragColumn=@AllowDragColumn Height=@Height ShowRefresh=ShowRefresh | ||||
| @@ -41,6 +41,7 @@ | ||||
|            DoubleClickToEdit="DoubleClickToEdit" | ||||
|            OnDoubleClickCellCallback="OnDoubleClickCellCallback" | ||||
|            OnDoubleClickRowCallback="OnDoubleClickRowCallback" | ||||
|            RowContentTemplate="RowContentTemplate" | ||||
|            OnClickRowCallback="OnClickRowCallback"> | ||||
|     </Table> | ||||
| </div> | ||||
|   | ||||
| @@ -13,6 +13,23 @@ namespace ThingsGateway.Admin.Razor; | ||||
| [CascadingTypeParameter(nameof(TItem))] | ||||
| public partial class AdminTable<TItem> where TItem : class, new() | ||||
| { | ||||
|     /// <inheritdoc cref="Table{TItem}.OnColumnVisibleChanged"/> | ||||
|     [Parameter] | ||||
|     public Func<string, bool, Task> OnColumnVisibleChanged { get; set; } | ||||
|  | ||||
|     /// <inheritdoc cref="Table{TItem}.OnColumnCreating"/> | ||||
|     [Parameter] | ||||
|     public Func<List<ITableColumn>, Task> OnColumnCreating { get; set; } | ||||
|     /// <inheritdoc cref="Table{TItem}.RenderMode"/> | ||||
|     [Parameter] | ||||
|     public TableRenderMode RenderMode { get; set; } | ||||
|  | ||||
|     public List<ITableColumn> Columns => Instance?.Columns; | ||||
|  | ||||
|     public IEnumerable<ITableColumn> GetVisibleColumns => Instance?.GetVisibleColumns(); | ||||
|     public List<TItem> Rows => Instance?.Rows; | ||||
|  | ||||
|  | ||||
|     /// <inheritdoc cref="Table{TItem}.SelectedRowsChanged"/> | ||||
|     [Parameter] | ||||
|     public EventCallback<List<TItem>> SelectedRowsChanged { get; set; } | ||||
| @@ -40,6 +57,10 @@ public partial class AdminTable<TItem> where TItem : class, new() | ||||
|     /// <inheritdoc cref="Table{TItem}.OnDoubleClickRowCallback"/> | ||||
|     [Parameter] | ||||
|     public Func<TItem, Task>? OnDoubleClickRowCallback { get; set; } | ||||
|     /// <inheritdoc cref="Table{TItem}.RowContentTemplate"/> | ||||
|     [Parameter] | ||||
|     public RenderFragment<TableRowContext<TItem>>? RowContentTemplate { get; set; } | ||||
|  | ||||
|     /// <inheritdoc cref="Table{TItem}.OnClickRowCallback"/> | ||||
|     [Parameter] | ||||
|     public Func<TItem, Task>? OnClickRowCallback { get; set; } | ||||
| @@ -146,6 +167,9 @@ public partial class AdminTable<TItem> where TItem : class, new() | ||||
|     [Parameter] | ||||
|     public IDataService<TItem> DataService { get; set; } | ||||
|  | ||||
|     [Parameter] | ||||
|     public string? Id { get; set; } | ||||
|  | ||||
|     /// <inheritdoc cref="Table{TItem}.CreateItemCallback"/> | ||||
|     [Parameter] | ||||
|     public Func<TItem> CreateItemCallback { get; set; } | ||||
|   | ||||
| @@ -125,13 +125,22 @@ public class BlazorAppContext | ||||
|             var ownMenus = OwnMenus.Where(a => a.Module == CurrentModuleId); | ||||
|             OwnMenuItems = AdminResourceUtil.BuildMenuTrees(ownMenus).ToList(); | ||||
|             AllOwnMenuItems = AdminResourceUtil.BuildMenuTrees(OwnMenus).ToList(); | ||||
|             OwnSameLevelMenuItems = ownMenus.Where(a => !a.Href.IsNullOrWhiteSpace()).Select(item => new MenuItem() | ||||
|             OwnSameLevelMenuItems = ownMenus.Where(a => !a.Href.IsNullOrWhiteSpace()).Select(item => | ||||
|             { | ||||
|                 Match = item.NavLinkMatch ?? Microsoft.AspNetCore.Components.Routing.NavLinkMatch.All, | ||||
|                 Text = item.Title, | ||||
|                 Icon = item.Icon, | ||||
|                 Url = item.Href, | ||||
|                 Target = item.Target.ToString(), | ||||
|                 var menu = new MenuItem() | ||||
|                 { | ||||
|                     Match = item.NavLinkMatch ?? Microsoft.AspNetCore.Components.Routing.NavLinkMatch.Prefix, | ||||
|                     Text = item.Title, | ||||
|                     Icon = item.Icon, | ||||
|                     Url = item.Href, | ||||
|                     Target = item.Target.ToString(), | ||||
|                 }; | ||||
|                 if (menu.Url.IsNullOrEmpty()) | ||||
|                 { | ||||
|                     menu.Match = Microsoft.AspNetCore.Components.Routing.NavLinkMatch.Prefix; | ||||
|                 } | ||||
|                 return menu; | ||||
|  | ||||
|             }).ToList(); | ||||
|             UserWorkbenchOutputs = AllMenus.Where(it => UserWorkBench.Shortcuts.Contains(it.Id)).ToList(); | ||||
|         } | ||||
|   | ||||
| @@ -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> | ||||
|   | ||||
| @@ -72,7 +72,7 @@ public partial class HardwareInfoPage : IDisposable | ||||
|             ChartDataSource.Options.Title = Localizer[nameof(HistoryHardwareInfo)]; | ||||
|             ChartDataSource.Options.X.Title = Localizer["DateTime"]; | ||||
|             ChartDataSource.Options.Y.Title = Localizer["Data"]; | ||||
|             ChartDataSource.Labels = hisHardwareInfos.Select(a => a.Date.ToString("dd HH:mm zz")); | ||||
|             ChartDataSource.Labels = hisHardwareInfos.Select(a => a.Date.ToString("dd-HH:mm")); | ||||
|             ChartDataSource.Data.Add(new ChartDataset() | ||||
|             { | ||||
|                 Tension = 0.4f, | ||||
| @@ -116,7 +116,7 @@ public partial class HardwareInfoPage : IDisposable | ||||
|         else | ||||
|         { | ||||
|             var hisHardwareInfos = await HardwareJob.GetHistoryHardwareInfos(); | ||||
|             ChartDataSource.Labels = hisHardwareInfos.Select(a => a.Date.ToString("dd HH:mm zz")); | ||||
|             ChartDataSource.Labels = hisHardwareInfos.Select(a => a.Date.ToString("dd-HH:mm")); | ||||
|             ChartDataSource.Data[0].Data = hisHardwareInfos.Select(a => (object)a.CpuUsage); | ||||
|             ChartDataSource.Data[1].Data = hisHardwareInfos.Select(a => (object)a.MemoryUsage); | ||||
|             ChartDataSource.Data[2].Data = hisHardwareInfos.Select(a => (object)a.DriveUsage); | ||||
|   | ||||
| @@ -15,7 +15,7 @@ | ||||
|         </Card> | ||||
|  | ||||
|     </div> | ||||
|     <div class="col-12 col-sm-10 h-100"> | ||||
|     <div class="col-12 col-sm-10 h-100 ps-2"> | ||||
|     <AdminTable @ref=table TItem="SysUser" | ||||
|         AutoGenerateColumns="true" | ||||
|         ShowAdvancedSearch=false | ||||
|   | ||||
| @@ -20,5 +20,6 @@ public class Startup : AppStartup | ||||
|         services.AddScoped<IMenuService, MenuService>(); | ||||
|         services.AddScoped<IAuthRazorService, AuthRazorService>(); | ||||
|         services.AddBootstrapBlazorTableExportService(); | ||||
|         services.AddBootstrapBlazorWinBoxService(); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -5,19 +5,21 @@ | ||||
|  | ||||
| 	<ItemGroup> | ||||
| 		<ProjectReference Include="..\ThingsGateway.Admin.Application\ThingsGateway.Admin.Application.csproj" /> | ||||
| 		<PackageReference Include="BootstrapBlazor.Chart" Version="9.0.0" /> | ||||
| 		<PackageReference Include="BootstrapBlazor.Chart" Version="9.0.4" /> | ||||
| 		<PackageReference Include="BootstrapBlazor.UniverSheet" Version="9.0.5" /> | ||||
| 		<PackageReference Include="BootstrapBlazor.WinBox" Version="9.0.7" /> | ||||
| 		<PackageReference Include="BootstrapBlazor.CodeEditor" Version="9.0.3" /> | ||||
| 	</ItemGroup> | ||||
|  | ||||
| 	<ItemGroup Condition="'$(TargetFramework)'=='net8.0'"> | ||||
| 		<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="$(NET8Version)" /> | ||||
| 	</ItemGroup> | ||||
| 	<ItemGroup Condition="'$(TargetFramework)'=='net9.0'"> | ||||
| 		<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="$(NET9Version)" /> | ||||
| 	<ItemGroup Condition="'$(TargetFramework)'=='net10.0'"> | ||||
| 		<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="$(NET10Version)" /> | ||||
| 	</ItemGroup> | ||||
|  | ||||
| 	<PropertyGroup> | ||||
| 		<TargetFrameworks>net8.0;net9.0</TargetFrameworks> | ||||
| 		<TargetFrameworks>net8.0;$(OtherTargetFrameworks);</TargetFrameworks> | ||||
| 		 | ||||
| 		<!--<UseRazorSourceGenerator>false</UseRazorSourceGenerator>--> | ||||
| 	</PropertyGroup> | ||||
|   | ||||
| @@ -41,15 +41,22 @@ public static class AdminResourceUtil | ||||
|         return items | ||||
|         .Where(it => it.ParentId == parentId) | ||||
|         .Select((item, index) => | ||||
|             new MenuItem() | ||||
|         { | ||||
|             var menu = new MenuItem() | ||||
|             { | ||||
|                 Match = item.NavLinkMatch ?? Microsoft.AspNetCore.Components.Routing.NavLinkMatch.All, | ||||
|                 Match = item.NavLinkMatch ?? Microsoft.AspNetCore.Components.Routing.NavLinkMatch.Prefix, | ||||
|                 Text = item.Title, | ||||
|                 Icon = item.Icon, | ||||
|                 Url = item.Href, | ||||
|                 Target = item.Target.ToString(), | ||||
|                 Items = BuildMenuTrees(items, item.Id).ToList() | ||||
|             }; | ||||
|             if (menu.Url.IsNullOrEmpty()) | ||||
|             { | ||||
|                 menu.Match = Microsoft.AspNetCore.Components.Routing.NavLinkMatch.Prefix; | ||||
|             } | ||||
|             return menu; | ||||
|         } | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -21,11 +21,13 @@ | ||||
|     <link rel="apple-touch-icon" href="favicon.png"> | ||||
|     <base href="/" /> | ||||
|     <title>ThingsGateway</title> | ||||
|     <link rel="stylesheet" href=@($"_content/BootstrapBlazor.FontAwesome/css/font-awesome.min.css?v={this.GetType().Assembly.GetName().Version}") /> | ||||
|     <link rel="stylesheet" href=@($"_content/BootstrapBlazor/css/bootstrap.blazor.bundle.min.css?v={this.GetType().Assembly.GetName().Version}") /> | ||||
|     <link rel="stylesheet" href=@($"_content/BootstrapBlazor/css/motronic.min.css?v={this.GetType().Assembly.GetName().Version}") /> | ||||
|     <link rel="stylesheet" href=@($"ThingsGateway.AdminServer.styles.css?v={this.GetType().Assembly.GetName().Version}") /> | ||||
|     <link rel="stylesheet" href=@($"{WebsiteConst.DefaultResourceUrl}css/site.css?v={this.GetType().Assembly.GetName().Version}") /> | ||||
|     <link rel="stylesheet" href=@($"_content/BootstrapBlazor.FontAwesome/css/font-awesome.min.css") /> | ||||
|     <link rel="stylesheet" href=@($"_content/BootstrapBlazor/css/bootstrap.blazor.bundle.min.css") /> | ||||
|     <link rel="stylesheet" href=@($"_content/BootstrapBlazor/css/motronic.min.css") /> | ||||
|     <link rel="stylesheet" href=@($"ThingsGateway.AdminServer.styles.css") /> | ||||
|     <link rel="stylesheet" href=@($"{WebsiteConst.DefaultResourceUrl}css/site.css") /> | ||||
|     <link rel="stylesheet" href=@($"{WebsiteConst.DefaultResourceUrl}css/devui.css") /> | ||||
|  | ||||
|     @* <script src=@($"{WebsiteConst.DefaultResourceUrl}js/theme.js") type="module"></script><!-- 初始主题 --> *@ | ||||
|     <!-- PWA Manifest --> | ||||
|     <link rel="manifest" href="./manifest.json" /> | ||||
| @@ -38,12 +40,13 @@ | ||||
|  | ||||
|     <BlazorReconnector @rendermode="new InteractiveServerRenderMode(false)" /> | ||||
|  | ||||
|     <script src=@($"_content/BootstrapBlazor/js/bootstrap.blazor.bundle.min.js?v={this.GetType().Assembly.GetName().Version}")></script> | ||||
|     <script src=@($"{WebsiteConst.DefaultResourceUrl}js/localStorageUtil.js?v={this.GetType().Assembly.GetName().Version}")></script> | ||||
|     <script src=@($"_content/BootstrapBlazor/js/bootstrap.blazor.bundle.min.js")></script> | ||||
|     <script src=@($"{WebsiteConst.DefaultResourceUrl}js/localStorageUtil.js")></script> | ||||
|     <script src="_framework/blazor.web.js"></script> | ||||
|     <!-- PWA Service Worker --> | ||||
|     <script type="text/javascript">'serviceWorker' in navigator && navigator.serviceWorker.register('./service-worker.js')</script> | ||||
|  | ||||
|     <script src="pwa-install.js"></script> | ||||
| </body> | ||||
|  | ||||
| </html> | ||||
|   | ||||
| @@ -70,7 +70,7 @@ | ||||
|                         <Button @onclick="ShowAbout" class="layout-header-bar d-none d-lg-flex px-2" Icon="fa fa-info" Color="Color.None" TooltipText="@Localizer[nameof(About)]" /> | ||||
|                     } | ||||
|                     @* 版本号 *@ | ||||
|                     <div class="px-1 navbar-header-text d-none d-lg-block">@_versionString</div> | ||||
|                     <div class="px-1 navbar-header-text text-nowrap d-none d-lg-block">@_versionString</div> | ||||
|  | ||||
|                     @* 主题切换 *@ | ||||
|                     @* <ThemeToggle /> *@ | ||||
| @@ -89,12 +89,19 @@ | ||||
|                     </div> | ||||
|                 </Side> | ||||
|                 <Main> | ||||
|                         <Tab @ref=_tab ClickTabToNavigation="true" ShowToolbar="true" ShowContextMenu="true" ShowExtendButtons="false" ShowClose="true" AllowDrag=true | ||||
|                     <Tab @ref=_tab ClickTabToNavigation="true" ShowToolbar="true" ShowContextMenu="true" ShowExtendButtons="false" ShowClose="true" AllowDrag=true | ||||
|                          ShowFullscreenToolbarButton=false ShowContextMenuFullScreen=false ShowFullScreen=false AdditionalAssemblies="@App.RazorAssemblies" Menus="@MenuService.AllOwnMenuItems" | ||||
|                              DefaultUrl=@("/") Body=@(Body!) OnCloseTabItemAsync=@((a)=> | ||||
|                              { | ||||
|                              return Task.FromResult(!(a.Url=="/"||a.Url.IsNullOrEmpty())); | ||||
|                              })> | ||||
|                          DefaultUrl=@("/") Body=@(Body!) OnCloseTabItemAsync=@((a)=> | ||||
|                                                                                   { | ||||
|                                                                                       return Task.FromResult(!(a.Url == "/" || a.Url.IsNullOrEmpty())); | ||||
|                                                                                   }) | ||||
|                                                                                   > | ||||
|                         <BeforeContextMenuTemplate> | ||||
|  | ||||
|                                                      <ContextMenuItem Icon="fa fa-window-restore" Text="@Localizer["WindowRestore"]" OnClick="WinboxRender"></ContextMenuItem> | ||||
|                                                      <ContextMenuDivider></ContextMenuDivider> | ||||
|  | ||||
|                                                  </BeforeContextMenuTemplate> | ||||
|                         </Tab> | ||||
|                 </Main> | ||||
|                 <NotAuthorized> | ||||
|   | ||||
| @@ -120,6 +120,38 @@ public partial class MainLayout : IDisposable | ||||
|  | ||||
|     #endregion 注销 | ||||
|  | ||||
|  | ||||
|     private async Task WinboxRender(ContextMenuItem item, object? context) | ||||
|     { | ||||
|         if (context is TabItem tabItem) | ||||
|         { | ||||
|             await WinboxRender(tabItem.ChildContent, tabItem.Text); | ||||
|             //await _tab.RemoveTab(tabItem); | ||||
|         } | ||||
|     } | ||||
|     [Inject] | ||||
|     [NotNull] | ||||
|     private WinBoxService? WinBoxService { get; set; } | ||||
|  | ||||
|     private async Task WinboxRender(RenderFragment item, string title) | ||||
|     { | ||||
|         if (item != null) | ||||
|         { | ||||
|             var option = new WinBoxOption() | ||||
|             { | ||||
|                 Title = title, | ||||
|                 ContentTemplate = item, | ||||
|                 Max = false, | ||||
|                 Width = "80%", | ||||
|                 Height = "80%", | ||||
|                 Top = "0%", | ||||
|                 Left = "10%", | ||||
|                 Background = "var(--bb-primary-color)", | ||||
|                 Overflow = true | ||||
|             }; | ||||
|             await WinBoxService.Show(option); | ||||
|         } | ||||
|     } | ||||
|     private string _versionString = string.Empty; | ||||
|     [Inject] | ||||
|     [NotNull] | ||||
|   | ||||
| @@ -92,7 +92,8 @@ public class Startup : AppStartup | ||||
|              options.RootComponents.MaxJSRootComponents = 500; | ||||
|              options.JSInteropDefaultCallTimeout = TimeSpan.FromMinutes(2); | ||||
|              options.MaxBufferedUnacknowledgedRenderBatches = 20; | ||||
|              options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromMinutes(10); | ||||
|              options.DisconnectedCircuitMaxRetained = 1; | ||||
|              options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromSeconds(10); | ||||
|          }) | ||||
|          .AddHubOptions(options => | ||||
|          { | ||||
| @@ -103,6 +104,7 @@ public class Startup : AppStartup | ||||
|              options.ClientTimeoutInterval = TimeSpan.FromMinutes(2); | ||||
|              options.KeepAliveInterval = TimeSpan.FromSeconds(15); | ||||
|              options.HandshakeTimeout = TimeSpan.FromSeconds(30); | ||||
|  | ||||
|          }); | ||||
|  | ||||
| #else | ||||
| @@ -112,7 +114,8 @@ public class Startup : AppStartup | ||||
|             options.RootComponents.MaxJSRootComponents = 500; | ||||
|             options.JSInteropDefaultCallTimeout = TimeSpan.FromMinutes(2); | ||||
|             options.MaxBufferedUnacknowledgedRenderBatches = 20; | ||||
|             options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromMinutes(10); | ||||
|              options.DisconnectedCircuitMaxRetained = 1; | ||||
|              options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromSeconds(10); | ||||
|         }).AddHubOptions(options => | ||||
|         { | ||||
|             //单个传入集线器消息的最大大小。默认 32 KB | ||||
| @@ -132,7 +135,11 @@ public class Startup : AppStartup | ||||
|         services.Configure<ForwardedHeadersOptions>(options => | ||||
|         { | ||||
|             options.ForwardedHeaders = ForwardedHeaders.All; | ||||
| #if NET10_0_OR_GREATER | ||||
|             options.KnownIPNetworks.Clear(); | ||||
| #else | ||||
|             options.KnownNetworks.Clear(); | ||||
| #endif | ||||
|             options.KnownProxies.Clear(); | ||||
|         }); | ||||
|  | ||||
| @@ -204,7 +211,16 @@ public class Startup : AppStartup | ||||
|     public void Use(IApplicationBuilder applicationBuilder, IWebHostEnvironment env) | ||||
|     { | ||||
|         var app = (WebApplication)applicationBuilder; | ||||
|         app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.All, KnownNetworks = { }, KnownProxies = { } }); | ||||
|         app.UseForwardedHeaders(new ForwardedHeadersOptions | ||||
|         { | ||||
|             ForwardedHeaders = ForwardedHeaders.All, | ||||
| #if NET10_0_OR_GREATER | ||||
|             KnownIPNetworks = { }, | ||||
| #else | ||||
|             KnownNetworks = { }, | ||||
| #endif | ||||
|             KnownProxies = { } | ||||
|         }); | ||||
|         app.UseBootstrapBlazor(); | ||||
|  | ||||
|         // 启用本地化 | ||||
|   | ||||
| @@ -4,7 +4,7 @@ | ||||
|  | ||||
|  | ||||
| 	<PropertyGroup> | ||||
| 		<TargetFrameworks>net8.0;net9.0;</TargetFrameworks> | ||||
| 		<TargetFrameworks>net8.0;$(OtherTargetFrameworks);</TargetFrameworks> | ||||
| 		 | ||||
| 	</PropertyGroup> | ||||
|  | ||||
| @@ -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> | ||||
|  | ||||
| @@ -53,9 +49,9 @@ | ||||
| 		<PackageReference Include="Microsoft.Extensions.Hosting.Systemd" Version="8.0.1" /> | ||||
| 		<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="8.0.1" /> | ||||
| 	</ItemGroup> | ||||
| 	<ItemGroup Condition=" '$(TargetFramework)' == 'net9.0' "> | ||||
| 		<PackageReference Include="Microsoft.Extensions.Hosting.Systemd" Version="$(NET9Version)" /> | ||||
| 		<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="$(NET9Version)" /> | ||||
| 	<ItemGroup Condition=" '$(TargetFramework)' == 'net10.0' "> | ||||
| 		<PackageReference Include="Microsoft.Extensions.Hosting.Systemd" Version="$(NET10Version)" /> | ||||
| 		<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="$(NET10Version)" /> | ||||
| 	</ItemGroup> | ||||
| 	 | ||||
| 	<ItemGroup> | ||||
|   | ||||
							
								
								
									
										34
									
								
								src/Admin/ThingsGateway.AdminServer/wwwroot/pwa-install.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/Admin/ThingsGateway.AdminServer/wwwroot/pwa-install.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | ||||
| let installPromptTriggered = false; | ||||
|  | ||||
| function getCookie(name) { | ||||
|     const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)')); | ||||
|     return match ? match[2] : null; | ||||
| } | ||||
|  | ||||
| function hasShownInstallPrompt() { | ||||
|     return getCookie("tgPWAInstallPromptShown") === "true"; | ||||
| } | ||||
|  | ||||
| function markInstallPromptShown() { | ||||
|     document.cookie = "tgPWAInstallPromptShown=true; max-age=31536000; path=/"; | ||||
| } | ||||
|  | ||||
| window.addEventListener('beforeinstallprompt', (e) => { | ||||
|     e.preventDefault(); | ||||
|  | ||||
|     if (!hasShownInstallPrompt() && !installPromptTriggered) { | ||||
|         installPromptTriggered = true; | ||||
|         setTimeout(() => { | ||||
|             e.prompt() | ||||
|                 .then(() => e.userChoice) | ||||
|                 .then(choiceResult => { | ||||
|                     markInstallPromptShown(); | ||||
|                 }) | ||||
|                 .catch(err => { | ||||
|                     // 可选错误处理 | ||||
|                 }); | ||||
|         }, 2000); // 延迟 2 秒提示 | ||||
|     } else { | ||||
|         // console.log("已提示过安装,不再弹出"); | ||||
|     } | ||||
| }); | ||||
| @@ -1,4 +1,4 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| @@ -477,7 +477,7 @@ public class ConcurrentList<T> : IList<T>, IReadOnlyList<T> | ||||
|     { | ||||
|         lock (((ICollection)m_list).SyncRoot) | ||||
|         { | ||||
|             return m_list.IndexOf(item); | ||||
|             return m_list.LastIndexOf(item); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -31,8 +31,8 @@ public static class GenericExtensions | ||||
|  | ||||
|         // 比较oldModel和model的属性,找出差异 | ||||
|         var differences = properties | ||||
|             .Where(prop => prop.CanRead && prop.CanWrite) // 确保属性可读可写 | ||||
|             .Where(prop => !Equals(prop.GetValue(oldModel), prop.GetValue(model))) // 找出值不同的属性 | ||||
|             .Where(prop => prop.CanRead && prop.CanWrite && !Equals(prop.GetValue(oldModel), prop.GetValue(model))) // 确保属性可读可写 | ||||
|                                                                                                                     // 找出值不同的属性 | ||||
|             .ToDictionary(prop => prop.Name, prop => prop.GetValue(model)); // 将属性名和新值存储到字典中 | ||||
|  | ||||
|         // 应用差异到channels列表中的每个Channel对象 | ||||
|   | ||||
| @@ -16,6 +16,8 @@ using System.Runtime.CompilerServices; | ||||
| using System.Text.Json; | ||||
| using System.Text.RegularExpressions; | ||||
|  | ||||
| using ThingsGateway.Extension; | ||||
|  | ||||
| namespace ThingsGateway.Common.Extension; | ||||
| /// <summary> | ||||
| /// 对象拓展类 | ||||
| @@ -48,113 +50,7 @@ public static class ObjectExtensions | ||||
|         bool IsTheRawGenericType(Type type) => generic == (type.IsGenericType ? type.GetGenericTypeDefinition() : type); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 将 DateTimeOffset 转换成本地 DateTime | ||||
|     /// </summary> | ||||
|     /// <param name="dateTime"></param> | ||||
|     /// <returns></returns> | ||||
|     public static DateTime ConvertToDateTime(this DateTimeOffset dateTime) | ||||
|     { | ||||
|         if (dateTime.Offset.Equals(TimeSpan.Zero)) | ||||
|             return dateTime.UtcDateTime; | ||||
|         if (dateTime.Offset.Equals(TimeZoneInfo.Local.GetUtcOffset(dateTime.DateTime))) | ||||
|             return dateTime.ToLocalTime().DateTime; | ||||
|         else | ||||
|             return dateTime.DateTime; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 将 DateTimeOffset? 转换成本地 DateTime? | ||||
|     /// </summary> | ||||
|     /// <param name="dateTime"></param> | ||||
|     /// <returns></returns> | ||||
|     public static DateTime? ConvertToDateTime(this DateTimeOffset? dateTime) | ||||
|     { | ||||
|         return dateTime.HasValue ? dateTime.Value.ConvertToDateTime() : null; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 将 DateTime 转换成 DateTimeOffset | ||||
|     /// </summary> | ||||
|     /// <param name="dateTime"></param> | ||||
|     /// <returns></returns> | ||||
|     public static DateTimeOffset ConvertToDateTimeOffset(this DateTime dateTime) | ||||
|     { | ||||
|         return DateTime.SpecifyKind(dateTime, DateTimeKind.Local); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 将 DateTime? 转换成 DateTimeOffset? | ||||
|     /// </summary> | ||||
|     /// <param name="dateTime"></param> | ||||
|     /// <returns></returns> | ||||
|     public static DateTimeOffset? ConvertToDateTimeOffset(this DateTime? dateTime) | ||||
|     { | ||||
|         return dateTime.HasValue ? dateTime.Value.ConvertToDateTimeOffset() : null; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 将流保存到本地磁盘 | ||||
|     /// </summary> | ||||
|     /// <param name="stream"></param> | ||||
|     /// <param name="path"></param> | ||||
|     /// <returns></returns> | ||||
|     public static void CopyToSave(this Stream stream, string path) | ||||
|     { | ||||
|         // 空检查 | ||||
|         if (string.IsNullOrWhiteSpace(path)) throw new ArgumentNullException(nameof(path)); | ||||
|  | ||||
|         using var fileStream = File.Create(path); | ||||
|         stream.CopyTo(fileStream); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 将字节数组保存到本地磁盘 | ||||
|     /// </summary> | ||||
|     /// <param name="bytes"></param> | ||||
|     /// <param name="path"></param> | ||||
|     /// <returns></returns> | ||||
|     public static void CopyToSave(this byte[] bytes, string path) | ||||
|     { | ||||
|         using var stream = new MemoryStream(bytes); | ||||
|         stream.CopyToSave(path); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 将流保存到本地磁盘 | ||||
|     /// </summary> | ||||
|     /// <param name="stream"></param> | ||||
|     /// <param name="path">需包含文件名完整路径</param> | ||||
|     /// <returns></returns> | ||||
|     public static async Task CopyToSaveAsync(this Stream stream, string path) | ||||
|     { | ||||
|         // 空检查 | ||||
|         if (string.IsNullOrWhiteSpace(path)) | ||||
|         { | ||||
|             throw new ArgumentNullException(nameof(path)); | ||||
|         } | ||||
|  | ||||
|         // 文件名判断 | ||||
|         if (string.IsNullOrWhiteSpace(Path.GetFileName(path))) | ||||
|         { | ||||
|             throw new ArgumentException("The parameter of <path> parameter must include the complete file name."); | ||||
|         } | ||||
|  | ||||
|         using var fileStream = File.Create(path); | ||||
|         await stream.CopyToAsync(fileStream).ConfigureAwait(false); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 将字节数组保存到本地磁盘 | ||||
|     /// </summary> | ||||
|     /// <param name="bytes"></param> | ||||
|     /// <param name="path"></param> | ||||
|     /// <returns></returns> | ||||
|     public static async Task CopyToSaveAsync(this byte[] bytes, string path) | ||||
|     { | ||||
|         using var stream = new MemoryStream(bytes); | ||||
|         await stream.CopyToSaveAsync(path).ConfigureAwait(false); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 合并两个字典 | ||||
| @@ -186,7 +82,7 @@ public static class ObjectExtensions | ||||
|     /// <typeparam name="T"></typeparam> | ||||
|     /// <param name="dic">字典</param> | ||||
|     /// <param name="newDic">新字典</param> | ||||
|     internal static void AddOrUpdate<T>(this ConcurrentDictionary<string, T> dic, Dictionary<string, T> newDic) | ||||
|     internal static void AddOrUpdate<T>(this NonBlockingDictionary<string, T> dic, Dictionary<string, T> newDic) | ||||
|     { | ||||
|         foreach (var (key, value) in newDic) | ||||
|         { | ||||
|   | ||||
| @@ -24,7 +24,7 @@ public static class ParallelExtensions | ||||
|     /// <typeparam name="T">集合元素类型</typeparam> | ||||
|     /// <param name="source">要操作的集合</param> | ||||
|     /// <param name="body">要执行的操作</param> | ||||
|     public static void ParallelForEach<T>(this IList<T> source, Action<T> body) | ||||
|     public static void ParallelForEach<T>(this IEnumerable<T> source, Action<T> body) | ||||
|     { | ||||
|         ParallelOptions options = new(); | ||||
|         options.MaxDegreeOfParallelism = Environment.ProcessorCount; | ||||
| @@ -38,7 +38,7 @@ public static class ParallelExtensions | ||||
|     /// <typeparam name="T">集合元素类型</typeparam> | ||||
|     /// <param name="source">要操作的集合</param> | ||||
|     /// <param name="body">要执行的操作</param> | ||||
|     public static void ParallelForEach<T>(this IList<T> source, Action<T, ParallelLoopState, long> body) | ||||
|     public static void ParallelForEach<T>(this IEnumerable<T> source, Action<T, ParallelLoopState, long> body) | ||||
|     { | ||||
|         ParallelOptions options = new(); | ||||
|         options.MaxDegreeOfParallelism = Environment.ProcessorCount; | ||||
| @@ -53,7 +53,7 @@ public static class ParallelExtensions | ||||
|     /// <param name="source">要操作的集合</param> | ||||
|     /// <param name="body">要执行的操作</param> | ||||
|     /// <param name="parallelCount">最大并行度</param> | ||||
|     public static void ParallelForEach<T>(this IList<T> source, Action<T> body, int parallelCount) | ||||
|     public static void ParallelForEach<T>(this IEnumerable<T> source, Action<T> body, int parallelCount) | ||||
|     { | ||||
|         // 创建并行操作的选项对象,设置最大并行度为指定的值 | ||||
|         var options = new ParallelOptions(); | ||||
| @@ -109,7 +109,7 @@ public static class ParallelExtensions | ||||
|     /// <param name="parallelCount">最大并行度</param> | ||||
|     /// <param name="cancellationToken">取消操作的标志</param> | ||||
|     /// <returns>表示异步操作的任务</returns> | ||||
|     public static Task ParallelForEachAsync<T>(this IList<T> source, Func<T, CancellationToken, ValueTask> body, int parallelCount, CancellationToken cancellationToken = default) | ||||
|     public static Task ParallelForEachAsync<T>(this IEnumerable<T> source, Func<T, CancellationToken, ValueTask> body, int parallelCount, CancellationToken cancellationToken = default) | ||||
|     { | ||||
|         // 创建并行操作的选项对象,设置最大并行度和取消标志 | ||||
|         var options = new ParallelOptions(); | ||||
| @@ -126,7 +126,7 @@ public static class ParallelExtensions | ||||
|     /// <param name="body">异步执行的操作</param> | ||||
|     /// <param name="cancellationToken">取消操作的标志</param> | ||||
|     /// <returns>表示异步操作的任务</returns> | ||||
|     public static Task ParallelForEachAsync<T>(this IList<T> source, Func<T, CancellationToken, ValueTask> body, CancellationToken cancellationToken = default) | ||||
|     public static Task ParallelForEachAsync<T>(this IEnumerable<T> source, Func<T, CancellationToken, ValueTask> body, CancellationToken cancellationToken = default) | ||||
|     { | ||||
|         return ParallelForEachAsync(source, body, Environment.ProcessorCount, cancellationToken); | ||||
|     } | ||||
|   | ||||
| @@ -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 | ||||
| @@ -27,7 +30,7 @@ internal class CacheManager | ||||
| { | ||||
|     private IMemoryCache Cache { get; set; } | ||||
|  | ||||
|     private IServiceProvider Provider { get; set; } | ||||
|     private static IServiceProvider Provider => App.RootServices; | ||||
|  | ||||
|     [NotNull] | ||||
|     private static CacheManager? Instance { get; set; } | ||||
| @@ -40,8 +43,7 @@ internal class CacheManager | ||||
|     static CacheManager() | ||||
|     { | ||||
|         Instance = new(); | ||||
|         Instance.Provider = App.RootServices; | ||||
|         Instance.Cache = Instance.Provider.GetRequiredService<IMemoryCache>(); | ||||
|         Instance.Cache = Provider.GetRequiredService<IMemoryCache>(); | ||||
|         Options = App.RootServices.GetRequiredService<IOptions<BootstrapBlazorOptions>>().Value; | ||||
|     } | ||||
|  | ||||
| @@ -105,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> | ||||
| @@ -236,7 +207,7 @@ internal class CacheManager | ||||
|     /// <returns></returns> | ||||
|     public static IStringLocalizer? CreateLocalizerByType(Type resourceSource) => resourceSource.Assembly.IsDynamic | ||||
|         ? null | ||||
|         : Instance.Provider.GetRequiredService<IStringLocalizerFactory>().Create(resourceSource); | ||||
|         : Provider.GetRequiredService<IStringLocalizerFactory>().Create(resourceSource); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获得 <see cref="JsonLocalizationOptions"/> 值 | ||||
| @@ -244,7 +215,7 @@ internal class CacheManager | ||||
|     /// <returns></returns> | ||||
|     private static JsonLocalizationOptions GetJsonLocalizationOption() | ||||
|     { | ||||
|         var localizationOptions = Instance.Provider.GetRequiredService<IOptions<JsonLocalizationOptions>>(); | ||||
|         var localizationOptions = Provider.GetRequiredService<IOptions<JsonLocalizationOptions>>(); | ||||
|         return localizationOptions.Value; | ||||
|     } | ||||
|     /// <summary> | ||||
| @@ -253,7 +224,7 @@ internal class CacheManager | ||||
|     /// <returns></returns> | ||||
|     private static BootstrapBlazorOptions GetBootstrapBlazorOption() | ||||
|     { | ||||
|         var localizationOptions = Instance.Provider.GetRequiredService<IOptions<BootstrapBlazorOptions>>(); | ||||
|         var localizationOptions = Provider.GetRequiredService<IOptions<BootstrapBlazorOptions>>(); | ||||
|         return localizationOptions.Value; | ||||
|     } | ||||
|     /// <summary> | ||||
| @@ -269,7 +240,7 @@ internal class CacheManager | ||||
|             return null; | ||||
|         } | ||||
|         IStringLocalizer? ret = null; | ||||
|         var factories = Instance.Provider.GetServices<IStringLocalizerFactory>(); | ||||
|         var factories = Provider.GetServices<IStringLocalizerFactory>(); | ||||
|         var factory = factories.LastOrDefault(a => a is not JsonStringLocalizerFactory); | ||||
|         if (factory != null) | ||||
|         { | ||||
| @@ -287,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> | ||||
|     /// 通过指定程序集获取所有本地化信息键值集合 | ||||
| @@ -299,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) | ||||
|         { | ||||
| @@ -309,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 => | ||||
| @@ -336,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) => Instance.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 | ||||
|   | ||||
| @@ -66,7 +66,8 @@ internal class JsonStringLocalizer(Assembly assembly, string typeName, string ba | ||||
|                 } | ||||
|                 catch (Exception ex) | ||||
|                 { | ||||
|                     Logger.LogError(ex, "{JsonStringLocalizerName} searched for '{Name}' in '{typeName}' with culture '{CultureName}' throw exception.", nameof(JsonStringLocalizer), name, typeName, CultureInfo.CurrentUICulture.Name); | ||||
|                     if (Logger?.IsEnabled(LogLevel.Error) == true) | ||||
|                         Logger.LogError(ex, "{JsonStringLocalizerName} searched for '{Name}' in '{typeName}' with culture '{CultureName}' throw exception.", nameof(JsonStringLocalizer), name, typeName, CultureInfo.CurrentUICulture.Name); | ||||
|                 } | ||||
|                 return ret; | ||||
|             } | ||||
| @@ -80,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); | ||||
| @@ -149,39 +116,25 @@ 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); | ||||
|         if (jsonLocalizationOptions.IgnoreLocalizerMissing == false) | ||||
|         { | ||||
|             Logger.LogInformation("{JsonStringLocalizerName} searched for '{Name}' in '{TypeName}' with culture '{CultureName}' not found.", nameof(JsonStringLocalizer), name, typeName, CultureInfo.CurrentUICulture.Name); | ||||
|             if (Logger?.IsEnabled(LogLevel.Information) == true) | ||||
|                 Logger.LogInformation("{JsonStringLocalizerName} searched for '{Name}' in '{TypeName}' with culture '{CultureName}' not found.", nameof(JsonStringLocalizer), name, typeName, CultureInfo.CurrentUICulture.Name); | ||||
|         } | ||||
|         _missingManifestCache.TryAdd($"name={name}&culture={CultureInfo.CurrentUICulture.Name}"); | ||||
|     } | ||||
|  | ||||
|     private List<LocalizedString>? _allLocalizerdStrings; | ||||
|     private LocalizedString[]? _allLocalizerdStrings; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获取当前语言的所有资源信息 | ||||
| @@ -196,7 +149,7 @@ internal class JsonStringLocalizer(Assembly assembly, string typeName, string ba | ||||
|                 ?? GetAllStringsFromBase() | ||||
|                 ?? GetAllStringsFromJson(); | ||||
|  | ||||
|             _allLocalizerdStrings = MegerResolveLocalizers(items); | ||||
|             _allLocalizerdStrings = items.ToArray(); | ||||
|         } | ||||
|         return _allLocalizerdStrings; | ||||
|  | ||||
|   | ||||
| @@ -8,13 +8,13 @@ | ||||
| 		 | ||||
| 	</PropertyGroup> | ||||
| 	<PropertyGroup> | ||||
| 		<TargetFrameworks>net8.0;net9.0;</TargetFrameworks> | ||||
| 		<TargetFrameworks>net8.0;$(OtherTargetFrameworks);</TargetFrameworks> | ||||
| 	</PropertyGroup> | ||||
| 	 | ||||
| 	<ItemGroup> | ||||
| 		<PackageReference Include="BootstrapBlazor.TableExport" Version="9.2.6" /> | ||||
| 		<PackageReference Include="BootstrapBlazor.TableExport" Version="9.2.7" /> | ||||
| 		<PackageReference Include="Yitter.IdGenerator" Version="1.0.14" /> | ||||
| 		<PackageReference Include="BootstrapBlazor" Version="9.10.1" /> | ||||
| 		<PackageReference Include="BootstrapBlazor" Version="9.11.4" /> | ||||
| 	</ItemGroup> | ||||
|  | ||||
| 	<ItemGroup> | ||||
|   | ||||
| @@ -27,10 +27,15 @@ public abstract class PrimaryIdEntity : IPrimaryIdEntity | ||||
|     public virtual long Id { get; set; } | ||||
| } | ||||
|  | ||||
| public interface IPrimaryKeyEntity | ||||
| { | ||||
|     string ExtJson { get; set; } | ||||
| } | ||||
|  | ||||
| /// <summary> | ||||
| /// 主键实体基类 | ||||
| /// </summary> | ||||
| public abstract class PrimaryKeyEntity : PrimaryIdEntity | ||||
| public abstract class PrimaryKeyEntity : PrimaryIdEntity, IPrimaryKeyEntity | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 拓展信息 | ||||
|   | ||||
| @@ -8,7 +8,7 @@ | ||||
| 		 | ||||
| 	</PropertyGroup> | ||||
| 	<PropertyGroup> | ||||
| 		<TargetFrameworks>net8.0;net9.0;</TargetFrameworks> | ||||
| 		<TargetFrameworks>net8.0;$(OtherTargetFrameworks);</TargetFrameworks> | ||||
| 	</PropertyGroup> | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -12,7 +12,7 @@ | ||||
| using Microsoft.AspNetCore.Hosting; | ||||
|  | ||||
| using ThingsGateway; | ||||
| using ThingsGateway.Extensions; | ||||
| using ThingsGateway.Extension; | ||||
| using ThingsGateway.Reflection; | ||||
|  | ||||
| namespace Microsoft.Extensions.Hosting; | ||||
|   | ||||
| @@ -20,7 +20,7 @@ using System.Text.RegularExpressions; | ||||
|  | ||||
| using ThingsGateway.NewLife; | ||||
|  | ||||
| namespace ThingsGateway.Extensions; | ||||
| namespace ThingsGateway.Extension; | ||||
|  | ||||
| /// <summary> | ||||
| /// 对象拓展类 | ||||
| @@ -28,70 +28,10 @@ namespace ThingsGateway.Extensions; | ||||
| [SuppressSniffer] | ||||
| public static class ObjectExtensions | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 将 DateTimeOffset 转换成本地 DateTime | ||||
|     /// </summary> | ||||
|     /// <param name="dateTime"></param> | ||||
|     /// <returns></returns> | ||||
|     public static DateTime ConvertToDateTime(this DateTimeOffset dateTime) | ||||
|     { | ||||
|         if (dateTime.Offset.Equals(TimeSpan.Zero)) | ||||
|             return dateTime.UtcDateTime; | ||||
|         if (dateTime.Offset.Equals(TimeZoneInfo.Local.GetUtcOffset(dateTime.DateTime))) | ||||
|             return dateTime.ToLocalTime().DateTime; | ||||
|         else | ||||
|             return dateTime.DateTime; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 将 DateTimeOffset? 转换成本地 DateTime? | ||||
|     /// </summary> | ||||
|     /// <param name="dateTime"></param> | ||||
|     /// <returns></returns> | ||||
|     public static DateTime? ConvertToDateTime(this DateTimeOffset? dateTime) | ||||
|     { | ||||
|         return dateTime.HasValue ? dateTime.Value.ConvertToDateTime() : null; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 将 DateTime 转换成 DateTimeOffset | ||||
|     /// </summary> | ||||
|     /// <param name="dateTime"></param> | ||||
|     /// <returns></returns> | ||||
|     public static DateTimeOffset ConvertToDateTimeOffset(this DateTime dateTime) | ||||
|     { | ||||
|         return DateTime.SpecifyKind(dateTime, DateTimeKind.Local); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 将 DateTime? 转换成 DateTimeOffset? | ||||
|     /// </summary> | ||||
|     /// <param name="dateTime"></param> | ||||
|     /// <returns></returns> | ||||
|     public static DateTimeOffset? ConvertToDateTimeOffset(this DateTime? dateTime) | ||||
|     { | ||||
|         return dateTime.HasValue ? dateTime.Value.ConvertToDateTimeOffset() : null; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 将时间戳转换为 DateTime | ||||
|     /// </summary> | ||||
|     /// <param name="timestamp"></param> | ||||
|     /// <returns></returns> | ||||
|     internal static DateTime ConvertToDateTime(this long timestamp) | ||||
|     { | ||||
|         var timeStampDateTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); | ||||
|         var digitCount = (int)Math.Floor(Math.Log10(timestamp) + 1); | ||||
|  | ||||
|         if (digitCount != 13 && digitCount != 10) | ||||
|         { | ||||
|             throw new ArgumentException("Data is not a valid timestamp format."); | ||||
|         } | ||||
|  | ||||
|         return (digitCount == 13 | ||||
|             ? timeStampDateTime.AddMilliseconds(timestamp)  // 13 位时间戳 | ||||
|             : timeStampDateTime.AddSeconds(timestamp)).ToLocalTime();   // 10 位时间戳 | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 将 IFormFile 转换成 byte[] | ||||
| @@ -265,7 +205,7 @@ public static class ObjectExtensions | ||||
|     /// <typeparam name="T"></typeparam> | ||||
|     /// <param name="dic">字典</param> | ||||
|     /// <param name="newDic">新字典</param> | ||||
|     internal static void AddOrUpdate<T>(this ConcurrentDictionary<string, T> dic, Dictionary<string, T> newDic) | ||||
|     internal static void AddOrUpdate<T>(this NonBlockingDictionary<string, T> dic, Dictionary<string, T> newDic) | ||||
|     { | ||||
|         foreach (var (key, value) in newDic) | ||||
|         { | ||||
|   | ||||
| @@ -94,7 +94,7 @@ public static class AspNetCoreBuilderServiceCollectionExtensions | ||||
|     /// <param name="mvcBuilder"></param> | ||||
|     /// <param name="configure"></param> | ||||
|     /// <returns></returns> | ||||
|     public static IMvcBuilder AddFromConvertBinding(this IMvcBuilder mvcBuilder, Action<ConcurrentDictionary<Type, Type>> configure = default) | ||||
|     public static IMvcBuilder AddFromConvertBinding(this IMvcBuilder mvcBuilder, Action<NonBlockingDictionary<Type, Type>> configure = default) | ||||
|     { | ||||
|         mvcBuilder.Services.AddFromConvertBinding(configure); | ||||
|  | ||||
| @@ -107,13 +107,13 @@ public static class AspNetCoreBuilderServiceCollectionExtensions | ||||
|     /// <param name="services"></param> | ||||
|     /// <param name="configure"></param> | ||||
|     /// <returns></returns> | ||||
|     public static IServiceCollection AddFromConvertBinding(this IServiceCollection services, Action<ConcurrentDictionary<Type, Type>> configure = default) | ||||
|     public static IServiceCollection AddFromConvertBinding(this IServiceCollection services, Action<NonBlockingDictionary<Type, Type>> configure = default) | ||||
|     { | ||||
|         // 非 Web 环境跳过注册 | ||||
|         if (App.WebHostEnvironment == default) return services; | ||||
|  | ||||
|         // 定义模型绑定转换器集合 | ||||
|         var modelBinderConverts = new ConcurrentDictionary<Type, Type>(); | ||||
|         var modelBinderConverts = new NonBlockingDictionary<Type, Type>(); | ||||
|         modelBinderConverts.TryAdd(typeof(DateTime), typeof(DateTimeModelConvertBinder)); | ||||
|         modelBinderConverts.TryAdd(typeof(DateTimeOffset), typeof(DateTimeOffsetModelConvertBinder)); | ||||
|  | ||||
|   | ||||
| @@ -11,7 +11,7 @@ | ||||
|  | ||||
| using Microsoft.AspNetCore.Mvc.ModelBinding; | ||||
|  | ||||
| using ThingsGateway.Extensions; | ||||
| using ThingsGateway.Extension; | ||||
|  | ||||
| namespace ThingsGateway.AspNetCore; | ||||
|  | ||||
|   | ||||
| @@ -27,13 +27,13 @@ public class FromConvertBinder : IModelBinder | ||||
|     /// <summary> | ||||
|     /// 定义模型绑定转换器集合 | ||||
|     /// </summary> | ||||
|     private readonly ConcurrentDictionary<Type, Type> _modelBinderConverts; | ||||
|     private readonly NonBlockingDictionary<Type, Type> _modelBinderConverts; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 构造函数 | ||||
|     /// </summary> | ||||
|     /// <param name="modelBinderConverts">定义模型绑定转换器集合</param> | ||||
|     public FromConvertBinder(ConcurrentDictionary<Type, Type> modelBinderConverts) | ||||
|     public FromConvertBinder(NonBlockingDictionary<Type, Type> modelBinderConverts) | ||||
|     { | ||||
|         _modelBinderConverts = modelBinderConverts; | ||||
|     } | ||||
|   | ||||
| @@ -28,13 +28,13 @@ public class FromConvertBinderProvider : IModelBinderProvider | ||||
|     /// <summary> | ||||
|     /// 定义模型绑定转换器集合 | ||||
|     /// </summary> | ||||
|     private readonly ConcurrentDictionary<Type, Type> _modelBinderConverts; | ||||
|     private readonly NonBlockingDictionary<Type, Type> _modelBinderConverts; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 构造函数 | ||||
|     /// </summary> | ||||
|     /// <param name="modelBinderConverts">定义模型绑定转换器集合</param> | ||||
|     public FromConvertBinderProvider(ConcurrentDictionary<Type, Type> modelBinderConverts) | ||||
|     public FromConvertBinderProvider(NonBlockingDictionary<Type, Type> modelBinderConverts) | ||||
|     { | ||||
|         _modelBinderConverts = modelBinderConverts; | ||||
|     } | ||||
|   | ||||
| @@ -11,7 +11,7 @@ | ||||
|  | ||||
| using Microsoft.AspNetCore.Mvc.ModelBinding; | ||||
|  | ||||
| using ThingsGateway.Extensions; | ||||
| using ThingsGateway.Extension; | ||||
|  | ||||
| namespace ThingsGateway.AspNetCore; | ||||
|  | ||||
|   | ||||
| @@ -12,7 +12,7 @@ | ||||
| using Microsoft.AspNetCore.Mvc.ModelBinding; | ||||
| using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; | ||||
|  | ||||
| using ThingsGateway.Extensions; | ||||
| using ThingsGateway.Extension; | ||||
|  | ||||
| namespace ThingsGateway.AspNetCore; | ||||
|  | ||||
|   | ||||
| @@ -18,7 +18,7 @@ using System.Reflection; | ||||
|  | ||||
| using ThingsGateway; | ||||
| using ThingsGateway.ConfigurableOptions; | ||||
| using ThingsGateway.Extensions; | ||||
| using ThingsGateway.Extension; | ||||
|  | ||||
| namespace Microsoft.Extensions.DependencyInjection; | ||||
|  | ||||
|   | ||||
| @@ -34,9 +34,12 @@ public static class PBKDF2Encryption | ||||
|         using var rng = RandomNumberGenerator.Create(); | ||||
|         var salt = new byte[saltSize]; | ||||
|         rng.GetBytes(salt); | ||||
|  | ||||
| #if NET10_0_OR_GREATER | ||||
|         var hash = Rfc2898DeriveBytes.Pbkdf2(System.Text.Encoding.UTF8.GetBytes(text), salt, iterationCount, HashAlgorithmName.SHA256, derivedKeyLength); | ||||
| #else | ||||
|         using var pbkdf2 = new Rfc2898DeriveBytes(text, salt, iterationCount, HashAlgorithmName.SHA256); | ||||
|         var hash = pbkdf2.GetBytes(derivedKeyLength); | ||||
| #endif | ||||
|  | ||||
|         // 分别编码盐和哈希,并用分隔符拼接 | ||||
|         return Convert.ToBase64String(salt) + SaltHashSeparator + Convert.ToBase64String(hash); | ||||
| @@ -65,8 +68,12 @@ public static class PBKDF2Encryption | ||||
|             if (saltBytes.Length != saltSize || storedHashBytes.Length != derivedKeyLength) | ||||
|                 return false; | ||||
|  | ||||
| #if NET10_0_OR_GREATER | ||||
|             var computedHash = Rfc2898DeriveBytes.Pbkdf2(System.Text.Encoding.UTF8.GetBytes(text), saltBytes, iterationCount, HashAlgorithmName.SHA256, derivedKeyLength); | ||||
| #else | ||||
|             using var pbkdf2 = new Rfc2898DeriveBytes(text, saltBytes, iterationCount, HashAlgorithmName.SHA256); | ||||
|             var computedHash = pbkdf2.GetBytes(derivedKeyLength); | ||||
| #endif | ||||
|  | ||||
|             return computedHash.SequenceEqual(storedHashBytes); | ||||
|         } | ||||
|   | ||||
| @@ -16,7 +16,7 @@ using System.ComponentModel.DataAnnotations; | ||||
| using System.Reflection; | ||||
| using System.Text.RegularExpressions; | ||||
|  | ||||
| using ThingsGateway.Extensions; | ||||
| using ThingsGateway.Extension; | ||||
| using ThingsGateway.Templates.Extensions; | ||||
|  | ||||
| namespace ThingsGateway.DataValidation; | ||||
| @@ -40,7 +40,7 @@ public static class DataValidator | ||||
|     /// <summary> | ||||
|     /// 验证类型正则表达式 | ||||
|     /// </summary> | ||||
|     private static readonly ConcurrentDictionary<string, ValidationItemMetadataAttribute> ValidationItemMetadatas; | ||||
|     private static readonly NonBlockingDictionary<string, ValidationItemMetadataAttribute> ValidationItemMetadatas; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 构造函数 | ||||
| @@ -57,7 +57,7 @@ public static class DataValidator | ||||
|         ValidationItemMetadatas = GetValidationValidationItemMetadatas(); | ||||
|  | ||||
|         // 缓存所有正则表达式 | ||||
|         GetValidationTypeValidationItemMetadataCached = new ConcurrentDictionary<object, (string, ValidationItemMetadataAttribute)>(); | ||||
|         GetValidationTypeValidationItemMetadataCached = new NonBlockingDictionary<object, (string, ValidationItemMetadataAttribute)>(); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
| @@ -203,7 +203,7 @@ public static class DataValidator | ||||
|     /// <summary> | ||||
|     /// 获取验证类型验证Item集合 | ||||
|     /// </summary> | ||||
|     private static readonly ConcurrentDictionary<object, (string, ValidationItemMetadataAttribute)> GetValidationTypeValidationItemMetadataCached; | ||||
|     private static readonly NonBlockingDictionary<object, (string, ValidationItemMetadataAttribute)> GetValidationTypeValidationItemMetadataCached; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获取验证类型正则表达式(需要缓存) | ||||
| @@ -267,9 +267,9 @@ public static class DataValidator | ||||
|     /// 获取验证类型所有有效的正则表达式 | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     private static ConcurrentDictionary<string, ValidationItemMetadataAttribute> GetValidationValidationItemMetadatas() | ||||
|     private static NonBlockingDictionary<string, ValidationItemMetadataAttribute> GetValidationValidationItemMetadatas() | ||||
|     { | ||||
|         var vaidationItems = new ConcurrentDictionary<string, ValidationItemMetadataAttribute>(); | ||||
|         var vaidationItems = new NonBlockingDictionary<string, ValidationItemMetadataAttribute>(); | ||||
|  | ||||
|         // 查找所有 [ValidationMessageType] 类型中的 [ValidationMessage] 消息定义 | ||||
|         var customErrorMessages = ValidationMessageTypes.SelectMany(u => u.GetFields() | ||||
|   | ||||
| @@ -353,7 +353,7 @@ public static class DependencyInjectionServiceCollectionExtensions | ||||
|     /// <summary> | ||||
|     /// 类型名称集合 | ||||
|     /// </summary> | ||||
|     private static readonly ConcurrentDictionary<string, Type> TypeNamedCollection; | ||||
|     private static readonly NonBlockingDictionary<string, Type> TypeNamedCollection; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 创建代理方法 | ||||
| @@ -374,7 +374,7 @@ public static class DependencyInjectionServiceCollectionExtensions | ||||
|         GlobalServiceProxyType = App.EffectiveTypes | ||||
|             .FirstOrDefault(u => typeof(AspectDispatchProxy).IsAssignableFrom(u) && typeof(IGlobalDispatchProxy).IsAssignableFrom(u) && u.IsClass && !u.IsInterface && !u.IsAbstract); | ||||
|  | ||||
|         TypeNamedCollection = new ConcurrentDictionary<string, Type>(); | ||||
|         TypeNamedCollection = new NonBlockingDictionary<string, Type>(); | ||||
|         DispatchCreateMethod = typeof(AspectDispatchProxy).GetMethod(nameof(AspectDispatchProxy.Create)); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -21,7 +21,7 @@ using System.Collections.Concurrent; | ||||
| using System.Reflection; | ||||
| using System.Text.RegularExpressions; | ||||
|  | ||||
| using ThingsGateway.Extensions; | ||||
| using ThingsGateway.Extension; | ||||
| using ThingsGateway.UnifyResult; | ||||
|  | ||||
| namespace ThingsGateway.DynamicApiController; | ||||
|   | ||||
| @@ -28,21 +28,21 @@ internal static class Penetrates | ||||
|     /// <summary> | ||||
|     /// 请求动词映射字典 | ||||
|     /// </summary> | ||||
|     internal static ConcurrentDictionary<string, string> VerbToHttpMethods { get; private set; } | ||||
|     internal static NonBlockingDictionary<string, string> VerbToHttpMethods { get; private set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 控制器排序集合 | ||||
|     /// </summary> | ||||
|     internal static ConcurrentDictionary<string, (string, int, Type)> ControllerOrderCollection { get; set; } | ||||
|     internal static NonBlockingDictionary<string, (string, int, Type)> ControllerOrderCollection { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 构造函数 | ||||
|     /// </summary> | ||||
|     static Penetrates() | ||||
|     { | ||||
|         ControllerOrderCollection = new ConcurrentDictionary<string, (string, int, Type)>(); | ||||
|         ControllerOrderCollection = new NonBlockingDictionary<string, (string, int, Type)>(); | ||||
|  | ||||
|         VerbToHttpMethods = new ConcurrentDictionary<string, string> | ||||
|         VerbToHttpMethods = new NonBlockingDictionary<string, string> | ||||
|         { | ||||
|             ["post"] = "POST", | ||||
|             ["add"] = "POST", | ||||
| @@ -67,13 +67,13 @@ internal static class Penetrates | ||||
|             ["patch"] = "PATCH" | ||||
|         }; | ||||
|  | ||||
|         //IsApiControllerCached = new ConcurrentDictionary<Type, bool>(); | ||||
|         //IsApiControllerCached = new NonBlockingDictionary<Type, bool>(); | ||||
|     } | ||||
|  | ||||
|     ///// <summary> | ||||
|     ///// <see cref="IsApiController(Type)"/> 缓存集合 | ||||
|     ///// </summary> | ||||
|     //private static readonly ConcurrentDictionary<Type, bool> IsApiControllerCached; | ||||
|     //private static readonly NonBlockingDictionary<Type, bool> IsApiControllerCached; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 是否是Api控制器 | ||||
|   | ||||
| @@ -61,7 +61,7 @@ internal sealed class EventBusHostedService : BackgroundService | ||||
|     /// <summary> | ||||
|     /// 事件处理程序集合 | ||||
|     /// </summary> | ||||
|     private readonly ConcurrentDictionary<EventHandlerWrapper, EventHandlerWrapper> _eventHandlers = new(); | ||||
|     private readonly NonBlockingDictionary<EventHandlerWrapper, EventHandlerWrapper> _eventHandlers = new(); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 构造函数 | ||||
| @@ -295,7 +295,8 @@ internal sealed class EventBusHostedService : BackgroundService | ||||
|                         , retryAction: (total, times) => | ||||
|                         { | ||||
|                             // 输出重试日志 | ||||
|                             _logger.LogWarning("Retrying {times}/{total} times for {EventId}", times, total, eventSource.EventId); | ||||
|                             if (_logger?.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Warning) == true) | ||||
|                                 _logger.LogWarning("Retrying {times}/{total} times for {EventId}", times, total, eventSource.EventId); | ||||
|                         }).ConfigureAwait(false); | ||||
|                     } | ||||
|                     else | ||||
|   | ||||
| @@ -17,7 +17,7 @@ using System.ComponentModel.DataAnnotations; | ||||
| using System.Diagnostics; | ||||
| using System.Reflection; | ||||
|  | ||||
| using ThingsGateway.Extensions; | ||||
| using ThingsGateway.Extension; | ||||
| using ThingsGateway.Templates.Extensions; | ||||
|  | ||||
| namespace ThingsGateway.FriendlyException; | ||||
| @@ -31,7 +31,7 @@ public static class Oops | ||||
|     /// <summary> | ||||
|     /// 方法错误异常特性 | ||||
|     /// </summary> | ||||
|     private static readonly ConcurrentDictionary<MethodBase, MethodIfException> _errorMethods; | ||||
|     private static readonly NonBlockingDictionary<MethodBase, MethodIfException> _errorMethods; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 错误代码类型 | ||||
| @@ -41,7 +41,7 @@ public static class Oops | ||||
|     /// <summary> | ||||
|     /// 错误消息字典 | ||||
|     /// </summary> | ||||
|     private static readonly ConcurrentDictionary<string, string> _errorCodeMessages; | ||||
|     private static readonly NonBlockingDictionary<string, string> _errorCodeMessages; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 友好异常设置 | ||||
| @@ -53,7 +53,7 @@ public static class Oops | ||||
|     /// </summary> | ||||
|     static Oops() | ||||
|     { | ||||
|         _errorMethods = new ConcurrentDictionary<MethodBase, MethodIfException>(); | ||||
|         _errorMethods = new NonBlockingDictionary<MethodBase, MethodIfException>(); | ||||
|         _friendlyExceptionSettings = App.GetConfig<FriendlyExceptionSettingsOptions>("FriendlyExceptionSettings", true); | ||||
|         _errorCodeTypes = GetErrorCodeTypes(); | ||||
|         _errorCodeMessages = GetErrorCodeMessages(); | ||||
| @@ -258,9 +258,9 @@ public static class Oops | ||||
|     /// 获取所有错误消息 | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     private static ConcurrentDictionary<string, string> GetErrorCodeMessages() | ||||
|     private static NonBlockingDictionary<string, string> GetErrorCodeMessages() | ||||
|     { | ||||
|         var defaultErrorCodeMessages = new ConcurrentDictionary<string, string>(); | ||||
|         var defaultErrorCodeMessages = new NonBlockingDictionary<string, string>(); | ||||
|  | ||||
|         // 查找所有 [ErrorCodeType] 类型中的 [ErrorCodeMetadata] 元数据定义 | ||||
|         var errorCodeMessages = _errorCodeTypes.SelectMany(u => u.GetFields().Where(u => u.IsDefined(typeof(ErrorCodeItemMetadataAttribute)))) | ||||
|   | ||||
| @@ -16,7 +16,7 @@ using Microsoft.AspNetCore.SignalR; | ||||
| using System.Reflection; | ||||
|  | ||||
| using ThingsGateway; | ||||
| using ThingsGateway.Extensions; | ||||
| using ThingsGateway.Extension; | ||||
| using ThingsGateway.InstantMessaging; | ||||
|  | ||||
| namespace Microsoft.AspNetCore.Builder; | ||||
|   | ||||
| @@ -23,7 +23,7 @@ public static class ILoggerExtensions | ||||
|     /// 设置日志上下文 | ||||
|     /// </summary> | ||||
|     /// <param name="logger"></param> | ||||
|     /// <param name="properties">建议使用 ConcurrentDictionary 类型</param> | ||||
|     /// <param name="properties">建议使用 NonBlockingDictionary 类型</param> | ||||
|     /// <returns></returns> | ||||
|     public static IDisposable ScopeContext(this ILogger logger, IDictionary<string, object> properties) | ||||
|     { | ||||
|   | ||||
| @@ -9,7 +9,7 @@ | ||||
| // 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。 | ||||
| // ------------------------------------------------------------------------ | ||||
|  | ||||
| using ThingsGateway.Extensions; | ||||
| using ThingsGateway.Extension; | ||||
|  | ||||
| namespace ThingsGateway.Logging; | ||||
|  | ||||
|   | ||||
| @@ -82,7 +82,7 @@ public static class StringLoggingExtensions | ||||
|     /// 配置日志上下文 | ||||
|     /// </summary> | ||||
|     /// <param name="message"></param> | ||||
|     /// <param name="properties">建议使用 ConcurrentDictionary 类型</param> | ||||
|     /// <param name="properties">建议使用 NonBlockingDictionary 类型</param> | ||||
|     /// <returns></returns> | ||||
|     public static StringLoggingPart ScopeContext(this string message, IDictionary<string, object> properties) | ||||
|     { | ||||
|   | ||||
| @@ -44,7 +44,7 @@ public sealed class FileLoggerProvider : ILoggerProvider, ISupportExternalScope | ||||
|     /// 记录日志所有滚动文件名 | ||||
|     /// </summary> | ||||
|     /// <remarks>只有 MaxRollingFiles 和 FileSizeLimitBytes 大于 0 有效</remarks> | ||||
|     internal readonly ConcurrentDictionary<string, FileInfo> _rollingFileNames = new(); | ||||
|     internal readonly NonBlockingDictionary<string, FileInfo> _rollingFileNames = new(); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 文件日志写入器 | ||||
|   | ||||
| @@ -36,7 +36,7 @@ using System.Text.Json; | ||||
|  | ||||
| using ThingsGateway; | ||||
| using ThingsGateway.DataValidation; | ||||
| using ThingsGateway.Extensions; | ||||
| using ThingsGateway.Extension; | ||||
| using ThingsGateway.FriendlyException; | ||||
| using ThingsGateway.JsonSerialization; | ||||
| using ThingsGateway.Logging; | ||||
| @@ -151,7 +151,7 @@ public sealed class LoggingMonitorAttribute : Attribute, IAsyncActionFilter, IAs | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         await MonitorAsync(actionMethod, context.ActionArguments, context, next).ConfigureAwait(false); | ||||
|         await MonitorAsync(actionMethod, context.ActionArguments, context, () => next()).ConfigureAwait(false); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
| @@ -183,7 +183,7 @@ public sealed class LoggingMonitorAttribute : Attribute, IAsyncActionFilter, IAs | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         await MonitorAsync(actionMethod, context.HandlerArguments, context, next).ConfigureAwait(false); | ||||
|         await MonitorAsync(actionMethod, context.HandlerArguments, context, () => next()).ConfigureAwait(false); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
| @@ -789,12 +789,12 @@ public sealed class LoggingMonitorAttribute : Attribute, IAsyncActionFilter, IAs | ||||
|         return typeName; | ||||
|     } | ||||
|  | ||||
|     private async Task MonitorAsync(MethodInfo actionMethod, IDictionary<string, object> parameterValues, FilterContext context, dynamic next) | ||||
|     private async Task MonitorAsync<T>(MethodInfo actionMethod, IDictionary<string, object> parameterValues, FilterContext context, Func<Task<T>> next) | ||||
|     { | ||||
|         // 排除 WebSocket 请求处理 | ||||
|         if (context.HttpContext.IsWebSocketRequest()) | ||||
|         { | ||||
|             _ = await next(); | ||||
|             _ = await next().ConfigureAwait(false); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
| @@ -805,14 +805,14 @@ public sealed class LoggingMonitorAttribute : Attribute, IAsyncActionFilter, IAs | ||||
|         if (actionMethod.IsDefined(typeof(SuppressMonitorAttribute), true) | ||||
|             || actionMethod.DeclaringType.IsDefined(typeof(SuppressMonitorAttribute), true)) | ||||
|         { | ||||
|             _ = await next(); | ||||
|             _ = await next().ConfigureAwait(false); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // 判断是否自定义了日志筛选器,如果是则检查是否符合条件 | ||||
|         if (LoggingMonitorSettings.InternalWriteFilter?.Invoke(context) == false) | ||||
|         { | ||||
|             _ = await next(); | ||||
|             _ = await next().ConfigureAwait(false); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
| @@ -825,7 +825,7 @@ public sealed class LoggingMonitorAttribute : Attribute, IAsyncActionFilter, IAs | ||||
|         // 解决局部和全局触发器同时配置触发两次问题 | ||||
|         if (isDefinedScopedAttribute && Settings.FromGlobalFilter == true) | ||||
|         { | ||||
|             _ = await next(); | ||||
|             _ = await next().ConfigureAwait(false); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
| @@ -839,7 +839,7 @@ public sealed class LoggingMonitorAttribute : Attribute, IAsyncActionFilter, IAs | ||||
|                     && !Settings.IncludeOfMethods.Contains(methodFullName, StringComparer.OrdinalIgnoreCase)) | ||||
|                 { | ||||
|                     // 查找是否包含匹配,忽略大小写 | ||||
|                     _ = await next(); | ||||
|                     _ = await next().ConfigureAwait(false); | ||||
|                     return; | ||||
|                 } | ||||
|  | ||||
| @@ -847,7 +847,7 @@ public sealed class LoggingMonitorAttribute : Attribute, IAsyncActionFilter, IAs | ||||
|                 if (Settings.GlobalEnabled | ||||
|                     && Settings.ExcludeOfMethods.Contains(methodFullName, StringComparer.OrdinalIgnoreCase)) | ||||
|                 { | ||||
|                     _ = await next(); | ||||
|                     _ = await next().ConfigureAwait(false); | ||||
|                     return; | ||||
|                 } | ||||
|             } | ||||
| @@ -958,7 +958,8 @@ public sealed class LoggingMonitorAttribute : Attribute, IAsyncActionFilter, IAs | ||||
|  | ||||
|         // 计算接口执行时间 | ||||
|         var timeOperation = Stopwatch.StartNew(); | ||||
|         var resultContext = await next(); | ||||
|  | ||||
|         var resultContext = await next().ConfigureAwait(false); | ||||
|         timeOperation.Stop(); | ||||
|         writer.WriteNumber("timeOperationElapsedMilliseconds", timeOperation.ElapsedMilliseconds); | ||||
|  | ||||
| @@ -1014,8 +1015,13 @@ public sealed class LoggingMonitorAttribute : Attribute, IAsyncActionFilter, IAs | ||||
|         var environment = httpContext.RequestServices.GetRequiredService<IWebHostEnvironment>().EnvironmentName; | ||||
|         writer.WriteString(nameof(environment), environment); | ||||
|  | ||||
|         Exception exception = null; | ||||
|         // 获取异常对象情况 | ||||
|         Exception exception = resultContext.Exception; | ||||
|         if (resultContext is PageHandlerExecutedContext pageHandlerExecutedContext) | ||||
|             exception = pageHandlerExecutedContext.Exception; | ||||
|         else if (resultContext is ActionExecutedContext actionExecutedContext) | ||||
|             exception = actionExecutedContext.Exception; | ||||
|  | ||||
|         if (exception == null) | ||||
|         { | ||||
|             // 解析存储的验证信息 | ||||
|   | ||||
| @@ -94,7 +94,7 @@ public sealed partial class StringLoggingPart | ||||
|     /// <summary> | ||||
|     /// 配置日志上下文 | ||||
|     /// </summary> | ||||
|     /// <param name="properties">建议使用 ConcurrentDictionary 类型</param> | ||||
|     /// <param name="properties">建议使用 NonBlockingDictionary 类型</param> | ||||
|     /// <returns></returns> | ||||
|     public StringLoggingPart ScopeContext(IDictionary<string, object> properties) | ||||
|     { | ||||
|   | ||||
| @@ -57,7 +57,7 @@ public static class Log | ||||
|     /// <summary> | ||||
|     /// 配置日志上下文 | ||||
|     /// </summary> | ||||
|     /// <param name="properties">建议使用 ConcurrentDictionary 类型</param> | ||||
|     /// <param name="properties">建议使用 NonBlockingDictionary 类型</param> | ||||
|     /// <returns></returns> | ||||
|     public static (ILogger logger, IDisposable scope) ScopeContext(IDictionary<string, object> properties) | ||||
|     { | ||||
|   | ||||
| @@ -16,7 +16,7 @@ using Microsoft.Extensions.DependencyInjection.Extensions; | ||||
| using System.Linq.Expressions; | ||||
| using System.Reflection; | ||||
|  | ||||
| using ThingsGateway.Extensions; | ||||
| using ThingsGateway.Extension; | ||||
| using ThingsGateway.Options; | ||||
|  | ||||
| namespace Microsoft.Extensions.Options; | ||||
|   | ||||
| @@ -21,7 +21,7 @@ internal sealed class JobCancellationToken : IJobCancellationToken | ||||
|     /// <summary> | ||||
|     /// 取消作业执行 Token 集合 | ||||
|     /// </summary> | ||||
|     private readonly ConcurrentDictionary<string, CancellationTokenSource> _cancellationTokenSources; | ||||
|     private readonly NonBlockingDictionary<string, CancellationTokenSource> _cancellationTokenSources; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 作业调度器日志服务 | ||||
|   | ||||
| @@ -167,7 +167,7 @@ public partial class JobDetail | ||||
|     /// <summary> | ||||
|     /// 带命名规则的数据库列名 | ||||
|     /// </summary> | ||||
|     private readonly ConcurrentDictionary<NamingConventions, string[]> _namingColumnNames = new(); | ||||
|     private readonly NonBlockingDictionary<NamingConventions, string[]> _namingColumnNames = new(); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获取数据库列名 | ||||
|   | ||||
| @@ -65,7 +65,7 @@ internal sealed partial class SchedulerFactory : ISchedulerFactory | ||||
|     /// <summary> | ||||
|     /// 作业计划集合 | ||||
|     /// </summary> | ||||
|     private readonly ConcurrentDictionary<string, Scheduler> _schedulers = new(); | ||||
|     private readonly NonBlockingDictionary<string, Scheduler> _schedulers = new(); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 作业计划构建器集合 | ||||
| @@ -204,7 +204,7 @@ internal sealed partial class SchedulerFactory : ISchedulerFactory | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 _logger.LogWarning("Schedule hosted service preload completed, and a total of <{Count}> schedulers are appended.", _schedulers.Count); | ||||
|                 _logger.LogInformation("Schedule hosted service preload completed, and a total of <{Count}> schedulers are appended.", _schedulers.Count); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -369,11 +369,13 @@ internal sealed class ScheduleHostedService : BackgroundService | ||||
|                             // 写入作业执行详细日志 | ||||
|                             if (executionException == null) | ||||
|                             { | ||||
|                                 jobLogger?.LogInformation("{jobExecutingContext}", jobExecutingContext); | ||||
|                                 if (jobLogger?.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Information) == true) | ||||
|                                     jobLogger?.LogInformation("{jobExecutingContext}", jobExecutingContext); | ||||
|                             } | ||||
|                             else | ||||
|                             { | ||||
|                                 jobLogger?.LogError(executionException, "{jobExecutingContext}", jobExecutingContext); | ||||
|                                 if (jobLogger?.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Error) == true) | ||||
|                                     jobLogger?.LogError(executionException, "{jobExecutingContext}", jobExecutingContext); | ||||
|                             } | ||||
|  | ||||
|                             // 记录作业触发器运行信息 | ||||
|   | ||||
| @@ -380,7 +380,7 @@ public partial class Trigger | ||||
|     /// <summary> | ||||
|     /// 带命名规则的数据库列名 | ||||
|     /// </summary> | ||||
|     private readonly ConcurrentDictionary<NamingConventions, string[]> _namingColumnNames = new(); | ||||
|     private readonly NonBlockingDictionary<NamingConventions, string[]> _namingColumnNames = new(); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获取数据库列名 | ||||
|   | ||||
| @@ -31,7 +31,7 @@ using System.Xml.Linq; | ||||
| using System.Xml.XPath; | ||||
|  | ||||
| using ThingsGateway.DynamicApiController; | ||||
| using ThingsGateway.Extensions; | ||||
| using ThingsGateway.Extension; | ||||
| using ThingsGateway.Reflection; | ||||
|  | ||||
| namespace ThingsGateway.SpecificationDocument; | ||||
| @@ -83,11 +83,11 @@ public static class SpecificationDocumentBuilder | ||||
|  | ||||
|         // 初始化常量 | ||||
|         _groupOrderRegex = new Regex(@"@(?<order>[0-9]+$)"); | ||||
|         GetActionGroupsCached = new ConcurrentDictionary<MethodInfo, IEnumerable<GroupExtraInfo>>(); | ||||
|         GetControllerGroupsCached = new ConcurrentDictionary<Type, IEnumerable<GroupExtraInfo>>(); | ||||
|         GetGroupOpenApiInfoCached = new ConcurrentDictionary<string, SpecificationOpenApiInfo>(); | ||||
|         GetControllerTagCached = new ConcurrentDictionary<ControllerActionDescriptor, string>(); | ||||
|         GetActionTagCached = new ConcurrentDictionary<ApiDescription, string>(); | ||||
|         GetActionGroupsCached = new NonBlockingDictionary<MethodInfo, IEnumerable<GroupExtraInfo>>(); | ||||
|         GetControllerGroupsCached = new NonBlockingDictionary<Type, IEnumerable<GroupExtraInfo>>(); | ||||
|         GetGroupOpenApiInfoCached = new NonBlockingDictionary<string, SpecificationOpenApiInfo>(); | ||||
|         GetControllerTagCached = new NonBlockingDictionary<ControllerActionDescriptor, string>(); | ||||
|         GetActionTagCached = new NonBlockingDictionary<ApiDescription, string>(); | ||||
|  | ||||
|         // 默认分组,支持多个逗号分割 | ||||
|         DocumentGroupExtras = new List<GroupExtraInfo> { ResolveGroupExtraInfo(_specificationDocumentSettings.DefaultGroupName) }; | ||||
| @@ -143,7 +143,7 @@ public static class SpecificationDocumentBuilder | ||||
|     /// <summary> | ||||
|     /// 获取分组信息缓存集合 | ||||
|     /// </summary> | ||||
|     private static readonly ConcurrentDictionary<string, SpecificationOpenApiInfo> GetGroupOpenApiInfoCached; | ||||
|     private static readonly NonBlockingDictionary<string, SpecificationOpenApiInfo> GetGroupOpenApiInfoCached; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获取分组配置信息 | ||||
| @@ -738,7 +738,7 @@ public static class SpecificationDocumentBuilder | ||||
|     /// <summary> | ||||
|     /// 获取控制器组缓存集合 | ||||
|     /// </summary> | ||||
|     private static readonly ConcurrentDictionary<Type, IEnumerable<GroupExtraInfo>> GetControllerGroupsCached; | ||||
|     private static readonly NonBlockingDictionary<Type, IEnumerable<GroupExtraInfo>> GetControllerGroupsCached; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获取控制器分组列表 | ||||
| @@ -773,7 +773,7 @@ public static class SpecificationDocumentBuilder | ||||
|     /// <summary> | ||||
|     /// <see cref="GetActionGroups(MethodInfo)"/> 缓存集合 | ||||
|     /// </summary> | ||||
|     private static readonly ConcurrentDictionary<MethodInfo, IEnumerable<GroupExtraInfo>> GetActionGroupsCached; | ||||
|     private static readonly NonBlockingDictionary<MethodInfo, IEnumerable<GroupExtraInfo>> GetActionGroupsCached; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获取动作方法分组列表 | ||||
| @@ -808,7 +808,7 @@ public static class SpecificationDocumentBuilder | ||||
|     /// <summary> | ||||
|     /// <see cref="GetActionTag(ApiDescription)"/> 缓存集合 | ||||
|     /// </summary> | ||||
|     private static readonly ConcurrentDictionary<ControllerActionDescriptor, string> GetControllerTagCached; | ||||
|     private static readonly NonBlockingDictionary<ControllerActionDescriptor, string> GetControllerTagCached; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获取控制器标签 | ||||
| @@ -835,7 +835,7 @@ public static class SpecificationDocumentBuilder | ||||
|     /// <summary> | ||||
|     /// <see cref="GetActionTag(ApiDescription)"/> 缓存集合 | ||||
|     /// </summary> | ||||
|     private static readonly ConcurrentDictionary<ApiDescription, string> GetActionTagCached; | ||||
|     private static readonly NonBlockingDictionary<ApiDescription, string> GetActionTagCached; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获取动作方法标签 | ||||
|   | ||||
| @@ -20,7 +20,7 @@ using System.Reflection; | ||||
| using System.Text; | ||||
| using System.Text.RegularExpressions; | ||||
|  | ||||
| using ThingsGateway.Extensions; | ||||
| using ThingsGateway.Extension; | ||||
|  | ||||
| namespace ThingsGateway.SpecificationDocument; | ||||
|  | ||||
|   | ||||
| @@ -11,7 +11,7 @@ | ||||
|  | ||||
| using System.Text.RegularExpressions; | ||||
|  | ||||
| using ThingsGateway.Extensions; | ||||
| using ThingsGateway.Extension; | ||||
|  | ||||
| namespace ThingsGateway.Templates.Extensions; | ||||
|  | ||||
|   | ||||
| @@ -4,7 +4,7 @@ | ||||
| 	<Import Project="..\..\PackNuget.props" /> | ||||
|  | ||||
| 	<PropertyGroup> | ||||
| 		<TargetFrameworks>net8.0;net9.0;</TargetFrameworks> | ||||
| 		<TargetFrameworks>net8.0;$(OtherTargetFrameworks);</TargetFrameworks> | ||||
| 		 | ||||
| 	</PropertyGroup> | ||||
| 	 | ||||
| @@ -29,8 +29,8 @@ | ||||
| 	</ItemGroup> | ||||
|  | ||||
| 	<ItemGroup> | ||||
| 		<PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.4" /> | ||||
| 		<PackageReference Include="System.Text.Encoding.CodePages" Version="$(NET9Version)" /> | ||||
| 		<PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.6" /> | ||||
| 		<PackageReference Include="System.Text.Encoding.CodePages" Version="$(NET10Version)" /> | ||||
| 	</ItemGroup> | ||||
|  | ||||
| 	<ItemGroup Condition=" '$(TargetFramework)' == 'net8.0' "> | ||||
| @@ -39,10 +39,10 @@ | ||||
| 		<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="8.0.2" /> | ||||
| 	</ItemGroup> | ||||
| 	 | ||||
| 	<ItemGroup Condition=" '$(TargetFramework)' == 'net9.0' "> | ||||
| 		<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="$(NET9Version)" /> | ||||
| 		<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="$(NET9Version)" /> | ||||
| 		<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="$(NET9Version)" /> | ||||
| 	<ItemGroup Condition=" '$(TargetFramework)' == 'net10.0' "> | ||||
| 		<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="$(NET10Version)" /> | ||||
| 		<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="$(NET10Version)" /> | ||||
| 		<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="$(NET10Version)" /> | ||||
| 		 | ||||
|  | ||||
| 	</ItemGroup> | ||||
|   | ||||
| @@ -13,7 +13,7 @@ using Microsoft.AspNetCore.Http; | ||||
|  | ||||
| using System.Reflection; | ||||
|  | ||||
| using ThingsGateway.Extensions; | ||||
| using ThingsGateway.Extension; | ||||
| using ThingsGateway.UnifyResult; | ||||
|  | ||||
| namespace Microsoft.AspNetCore.Mvc; | ||||
|   | ||||
| @@ -22,7 +22,7 @@ using Microsoft.Extensions.Options; | ||||
| using System.Collections.Concurrent; | ||||
| using System.Reflection; | ||||
|  | ||||
| using ThingsGateway.Extensions; | ||||
| using ThingsGateway.Extension; | ||||
| using ThingsGateway.FriendlyException; | ||||
|  | ||||
| namespace ThingsGateway.UnifyResult; | ||||
| @@ -51,12 +51,12 @@ public static class UnifyContext | ||||
|     /// <summary> | ||||
|     /// 规范化结果提供器 | ||||
|     /// </summary> | ||||
|     internal static ConcurrentDictionary<string, UnifyMetadata> UnifyProviders = new(); | ||||
|     internal static NonBlockingDictionary<string, UnifyMetadata> UnifyProviders = new(); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 规范化序列化配置 | ||||
|     /// </summary> | ||||
|     internal static ConcurrentDictionary<string, object> UnifySerializerSettings = new(); | ||||
|     internal static NonBlockingDictionary<string, object> UnifySerializerSettings = new(); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获取异常元数据 | ||||
|   | ||||
| @@ -12,7 +12,7 @@ | ||||
| using System.Text.Json; | ||||
| using System.Text.Json.Serialization; | ||||
|  | ||||
| using ThingsGateway.Extensions; | ||||
| using ThingsGateway.Extension; | ||||
|  | ||||
| namespace ThingsGateway.Converters.Json; | ||||
|  | ||||
|   | ||||
| @@ -11,7 +11,7 @@ | ||||
|  | ||||
| using System.Reflection; | ||||
|  | ||||
| namespace ThingsGateway.Extensions; | ||||
| namespace ThingsGateway.Extension; | ||||
|  | ||||
| /// <summary> | ||||
| ///     <see cref="Assembly" /> 拓展类 | ||||
|   | ||||
| @@ -11,10 +11,10 @@ | ||||
|  | ||||
| using System.Collections.Concurrent; | ||||
|  | ||||
| namespace ThingsGateway.Extensions; | ||||
| namespace ThingsGateway.Extension; | ||||
|  | ||||
| /// <summary> | ||||
| ///     <see cref="ConcurrentDictionary{TKey, TValue}" /> 拓展类 | ||||
| ///     <see cref="NonBlockingDictionary{TKey, TValue}" /> 拓展类 | ||||
| /// </summary> | ||||
| internal static class ConcurrentDictionaryExtensions | ||||
| { | ||||
| @@ -24,7 +24,7 @@ internal static class ConcurrentDictionaryExtensions | ||||
|     /// <typeparam name="TKey">字典键类型</typeparam> | ||||
|     /// <typeparam name="TValue">字典值类型</typeparam> | ||||
|     /// <param name="dictionary"> | ||||
|     ///     <see cref="ConcurrentDictionary{TKey, TValue}" /> | ||||
|     ///     <see cref="NonBlockingDictionary{TKey, TValue}" /> | ||||
|     /// </param> | ||||
|     /// <param name="key"> | ||||
|     ///     <typeparamref name="TKey" /> | ||||
| @@ -36,7 +36,7 @@ internal static class ConcurrentDictionaryExtensions | ||||
|     /// <returns> | ||||
|     ///     <see cref="bool" /> | ||||
|     /// </returns> | ||||
|     internal static bool TryUpdate<TKey, TValue>(this ConcurrentDictionary<TKey, TValue> dictionary | ||||
|     internal static bool TryUpdate<TKey, TValue>(this NonBlockingDictionary<TKey, TValue> dictionary | ||||
|         , TKey key | ||||
|         , Func<TValue, TValue> updateFactory | ||||
|         , out TValue? value) | ||||
|   | ||||
| @@ -15,7 +15,7 @@ using Microsoft.Extensions.Hosting; | ||||
|  | ||||
| using System.Reflection; | ||||
|  | ||||
| namespace ThingsGateway.Extensions; | ||||
| namespace ThingsGateway.Extension; | ||||
|  | ||||
| /// <summary> | ||||
| ///     核心模块 <see cref="IServiceCollection" /> 拓展类 | ||||
|   | ||||
| @@ -11,7 +11,7 @@ | ||||
|  | ||||
| using System.Data; | ||||
|  | ||||
| namespace ThingsGateway.Extensions; | ||||
| namespace ThingsGateway.Extension; | ||||
|  | ||||
| /// <summary> | ||||
| ///     <see cref="DataTable" /> 和 <see cref="DataSet" /> 拓展类 | ||||
|   | ||||
| @@ -9,7 +9,7 @@ | ||||
| // 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。 | ||||
| // ------------------------------------------------------------------------ | ||||
|  | ||||
| namespace ThingsGateway.Extensions; | ||||
| namespace ThingsGateway.Extension; | ||||
|  | ||||
| /// <summary> | ||||
| ///     委托拓展类 | ||||
|   | ||||
| @@ -12,7 +12,7 @@ | ||||
| using System.ComponentModel; | ||||
| using System.Reflection; | ||||
|  | ||||
| namespace ThingsGateway.Extensions; | ||||
| namespace ThingsGateway.Extension; | ||||
|  | ||||
| /// <summary> | ||||
| ///     枚举拓展类 | ||||
|   | ||||
| @@ -9,7 +9,7 @@ | ||||
| // 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。 | ||||
| // ------------------------------------------------------------------------ | ||||
|  | ||||
| namespace ThingsGateway.Extensions; | ||||
| namespace ThingsGateway.Extension; | ||||
|  | ||||
| /// <summary> | ||||
| ///     <see cref="EventHandler{TEventArgs}" /> 拓展类 | ||||
|   | ||||
| @@ -11,7 +11,7 @@ | ||||
|  | ||||
| using System.Diagnostics.CodeAnalysis; | ||||
|  | ||||
| namespace ThingsGateway.Extensions; | ||||
| namespace ThingsGateway.Extension; | ||||
|  | ||||
| /// <summary> | ||||
| ///     <see cref="ICollection{T}" /> 拓展类 | ||||
|   | ||||
| @@ -11,7 +11,7 @@ | ||||
|  | ||||
| using System.Collections.Concurrent; | ||||
|  | ||||
| namespace ThingsGateway.Extensions; | ||||
| namespace ThingsGateway.Extension; | ||||
|  | ||||
| /// <summary> | ||||
| ///     <see cref="IDictionary{TKey, TValue}" /> 拓展类 | ||||
| @@ -241,7 +241,7 @@ internal static class IDictionaryExtensions | ||||
|     /// </summary> | ||||
|     /// <remarks>其中键是由值通过给定的选择器函数生成的。</remarks> | ||||
|     /// <param name="dictionary"> | ||||
|     ///     <see cref="ConcurrentDictionary{TKey, TValue}" /> | ||||
|     ///     <see cref="NonBlockingDictionary{TKey, TValue}" /> | ||||
|     /// </param> | ||||
|     /// <param name="values"> | ||||
|     ///     <see cref="IEnumerable{T}" /> | ||||
| @@ -249,7 +249,7 @@ internal static class IDictionaryExtensions | ||||
|     /// <param name="keySelector">键选择器</param> | ||||
|     /// <typeparam name="TKey">字典键类型</typeparam> | ||||
|     /// <typeparam name="TValue">字典值类型</typeparam> | ||||
|     internal static void TryAdd<TKey, TValue>(this ConcurrentDictionary<TKey, TValue> dictionary, | ||||
|     internal static void TryAdd<TKey, TValue>(this NonBlockingDictionary<TKey, TValue> dictionary, | ||||
|         IEnumerable<TValue>? values, Func<TValue, TKey> keySelector) | ||||
|         where TKey : notnull | ||||
|     { | ||||
|   | ||||
| @@ -9,7 +9,7 @@ | ||||
| // 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。 | ||||
| // ------------------------------------------------------------------------ | ||||
|  | ||||
| namespace ThingsGateway.Extensions; | ||||
| namespace ThingsGateway.Extension; | ||||
|  | ||||
| /// <summary> | ||||
| ///     <see cref="IEnumerable" /> 拓展类 | ||||
|   | ||||
| @@ -18,7 +18,7 @@ using System.Text.RegularExpressions; | ||||
| using System.Xml; | ||||
| using System.Xml.Linq; | ||||
|  | ||||
| namespace ThingsGateway.Extensions; | ||||
| namespace ThingsGateway.Extension; | ||||
|  | ||||
| /// <summary> | ||||
| ///     System.Text.Json 拓展类 | ||||
|   | ||||
| @@ -12,7 +12,7 @@ | ||||
| using System.Linq.Expressions; | ||||
| using System.Reflection; | ||||
|  | ||||
| namespace ThingsGateway.Extensions; | ||||
| namespace ThingsGateway.Extension; | ||||
|  | ||||
| /// <summary> | ||||
| ///     <see cref="Expression" /> 拓展类 | ||||
|   | ||||
| @@ -12,7 +12,7 @@ | ||||
| using System.Diagnostics.CodeAnalysis; | ||||
| using System.Reflection; | ||||
|  | ||||
| namespace ThingsGateway.Extensions; | ||||
| namespace ThingsGateway.Extension; | ||||
|  | ||||
| /// <summary> | ||||
| ///     <see cref="MethodInfo" /> 拓展类 | ||||
|   | ||||
| @@ -9,7 +9,7 @@ | ||||
| // 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。 | ||||
| // ------------------------------------------------------------------------ | ||||
|  | ||||
| namespace ThingsGateway.Extensions; | ||||
| namespace ThingsGateway.Extension; | ||||
|  | ||||
| /// <summary> | ||||
| ///     数值类型拓展类 | ||||
|   | ||||
| @@ -17,7 +17,7 @@ using System.Reflection; | ||||
| using System.Text; | ||||
| using System.Text.RegularExpressions; | ||||
|  | ||||
| namespace ThingsGateway.Extensions; | ||||
| namespace ThingsGateway.Extension; | ||||
|  | ||||
| /// <summary> | ||||
| ///     <see cref="string" /> 拓展类 | ||||
|   | ||||
| @@ -15,7 +15,7 @@ using System.Reflection; | ||||
| using System.Reflection.Emit; | ||||
| using System.Runtime.CompilerServices; | ||||
|  | ||||
| namespace ThingsGateway.Extensions; | ||||
| namespace ThingsGateway.Extension; | ||||
|  | ||||
| /// <summary> | ||||
| ///     <see cref="Type" /> 拓展类 | ||||
|   | ||||
| @@ -13,7 +13,7 @@ using System.Buffers; | ||||
| using System.Text; | ||||
| using System.Text.Json; | ||||
|  | ||||
| namespace ThingsGateway.Extensions; | ||||
| namespace ThingsGateway.Extension; | ||||
|  | ||||
| /// <summary> | ||||
| ///     <see cref="Utf8JsonReader" /> 拓展类 | ||||
|   | ||||
| @@ -16,7 +16,7 @@ using System.Text.Json; | ||||
|  | ||||
| using ThingsGateway.Utilities; | ||||
|  | ||||
| namespace ThingsGateway.Extensions; | ||||
| namespace ThingsGateway.Extension; | ||||
|  | ||||
| /// <summary> | ||||
| ///     <see cref="object" /> 拓展类 | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user