Compare commits
	
		
			82 Commits
		
	
	
		
			10.11.56.0
			...
			10.12.12.0
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					b42f8afa35 | ||
| 
						 | 
					facf8bd401 | ||
| 
						 | 
					feeb17eca3 | ||
| 
						 | 
					b3405cd674 | ||
| 
						 | 
					c35f9cef93 | ||
| 
						 | 
					3f382202db | ||
| 
						 | 
					2a3493cc82 | ||
| 
						 | 
					aaa459ebe0 | ||
| 
						 | 
					51a8acbc3e | ||
| 
						 | 
					dc132a1999 | ||
| 
						 | 
					0c31cfcbc5 | ||
| 
						 | 
					0e44bc67cd | ||
| 
						 | 
					12dfbba42c | ||
| 
						 | 
					96b4287f3a | ||
| 
						 | 
					7d406de29f | ||
| 
						 | 
					81f0ef466a | ||
| 
						 | 
					3f2d6b133c | ||
| 
						 | 
					e776dc67eb | ||
| 
						 | 
					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 | 
							
								
								
									
										14
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								README.md
									
									
									
									
									
								
							@@ -1,11 +1,18 @@
 | 
				
			|||||||
# ThingsGateway
 | 
					# 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
 | 
					## 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
 | 
					## Documentation
 | 
				
			||||||
@@ -29,7 +36,6 @@ Account: **SuperAdmin**
 | 
				
			|||||||
Password: **111111**
 | 
					Password: **111111**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**In the upper-right corner, switch to the IoT Gateway module in the personal popup box**
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Docker
 | 
					## Docker
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,8 +1,18 @@
 | 
				
			|||||||
# ThingsGateway
 | 
					# 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**
 | 
					密码 : **111111**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**右上角个人弹出框中,切换到物联网关模块**
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Docker
 | 
					## Docker
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -45,6 +45,7 @@ public class VerificatInfo : PrimaryIdEntity
 | 
				
			|||||||
    /// 登录IP
 | 
					    /// 登录IP
 | 
				
			||||||
    /// </summary>
 | 
					    /// </summary>
 | 
				
			||||||
    [AutoGenerateColumn(Filterable = true, Sortable = true, Width = 200)]
 | 
					    [AutoGenerateColumn(Filterable = true, Sortable = true, Width = 200)]
 | 
				
			||||||
 | 
					    [SugarColumn(IsNullable = true)]
 | 
				
			||||||
    public string LoginIp { get; set; }
 | 
					    public string LoginIp { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
@@ -78,5 +79,6 @@ public class VerificatInfo : PrimaryIdEntity
 | 
				
			|||||||
    /// 登录设备
 | 
					    /// 登录设备
 | 
				
			||||||
    /// </summary>
 | 
					    /// </summary>
 | 
				
			||||||
    [AutoGenerateColumn(Filterable = true, Sortable = true, Width = 100)]
 | 
					    [AutoGenerateColumn(Filterable = true, Sortable = true, Width = 100)]
 | 
				
			||||||
 | 
					    [SugarColumn(IsNullable = true)]
 | 
				
			||||||
    public string Device { get; set; }
 | 
					    public string Device { get; set; }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -251,11 +251,13 @@ public class RequestAuditFilter : IAsyncActionFilter, IOrderedFilter
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        if (exception == null)
 | 
					        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
 | 
					        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
 | 
					//  QQ群:605534569
 | 
				
			||||||
//------------------------------------------------------------------------------
 | 
					//------------------------------------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using System.ComponentModel;
 | 
				
			||||||
 | 
					using System.Runtime;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
using ThingsGateway.NewLife;
 | 
					using ThingsGateway.NewLife;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace ThingsGateway.Admin.Application;
 | 
					namespace ThingsGateway.Admin.Application;
 | 
				
			||||||
@@ -19,11 +22,7 @@ public class HardwareInfo
 | 
				
			|||||||
    /// 当前磁盘信息
 | 
					    /// 当前磁盘信息
 | 
				
			||||||
    /// </summary>
 | 
					    /// </summary>
 | 
				
			||||||
    public DriveInfo DriveInfo { get; set; }
 | 
					    public DriveInfo DriveInfo { get; set; }
 | 
				
			||||||
 | 
					 
 | 
				
			||||||
    /// <summary>
 | 
					 | 
				
			||||||
    /// 硬件信息获取
 | 
					 | 
				
			||||||
    /// </summary>
 | 
					 | 
				
			||||||
    public MachineInfo? MachineInfo { get; set; }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
    /// 主机环境
 | 
					    /// 主机环境
 | 
				
			||||||
@@ -40,19 +39,118 @@ public class HardwareInfo
 | 
				
			|||||||
    /// </summary>
 | 
					    /// </summary>
 | 
				
			||||||
    public string OsArchitecture { get; set; }
 | 
					    public string OsArchitecture { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// <summary>
 | 
					 | 
				
			||||||
    /// 唯一编码
 | 
					 | 
				
			||||||
    /// </summary>
 | 
					 | 
				
			||||||
    public string UUID { get; set; }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// <summary>
 | 
					    /// <summary>系统名称</summary>
 | 
				
			||||||
    /// 进程占用内存
 | 
					    public String OSName { get; set; }
 | 
				
			||||||
    /// </summary>
 | 
					
 | 
				
			||||||
    [AutoGenerateColumn(Ignore = true)]
 | 
					    /// <summary>系统版本</summary>
 | 
				
			||||||
    public int WorkingSet { get; set; }
 | 
					    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>
 | 
				
			||||||
    /// 更新时间
 | 
					    /// 更新时间
 | 
				
			||||||
    /// </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
 | 
					// QQ群:605534569
 | 
				
			||||||
// ------------------------------------------------------------------------------
 | 
					// ------------------------------------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
using Microsoft.Extensions.Hosting;
 | 
					 | 
				
			||||||
using Microsoft.Extensions.Logging;
 | 
					using Microsoft.Extensions.Logging;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
using System.Runtime.InteropServices;
 | 
					using System.Runtime.InteropServices;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
using ThingsGateway.Extension;
 | 
					 | 
				
			||||||
using ThingsGateway.NewLife;
 | 
					using ThingsGateway.NewLife;
 | 
				
			||||||
using ThingsGateway.NewLife.Caching;
 | 
					using ThingsGateway.NewLife.Caching;
 | 
				
			||||||
using ThingsGateway.NewLife.Threading;
 | 
					using ThingsGateway.NewLife.Threading;
 | 
				
			||||||
@@ -43,7 +41,7 @@ public class HardwareJob : IJob, IHardwareJob
 | 
				
			|||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
    /// 运行信息获取
 | 
					    /// 运行信息获取
 | 
				
			||||||
    /// </summary>
 | 
					    /// </summary>
 | 
				
			||||||
    public HardwareInfo HardwareInfo { get; } = new();
 | 
					    public HardwareInfo HardwareInfo { get; private set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// <inheritdoc/>
 | 
					    /// <inheritdoc/>
 | 
				
			||||||
    public HardwareInfoOptions HardwareInfoOptions { get; private set; }
 | 
					    public HardwareInfoOptions HardwareInfoOptions { get; private set; }
 | 
				
			||||||
@@ -76,9 +74,10 @@ public class HardwareJob : IJob, IHardwareJob
 | 
				
			|||||||
        {
 | 
					        {
 | 
				
			||||||
            try
 | 
					            try
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                if (HardwareInfo.MachineInfo == null)
 | 
					                    var machine = MachineInfo.GetCurrent();
 | 
				
			||||||
 | 
					                if (HardwareInfo == null)
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    HardwareInfo.MachineInfo = MachineInfo.GetCurrent();
 | 
					                    HardwareInfo=machine.AdaptHardwareInfo();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    string currentPath = Directory.GetCurrentDirectory();
 | 
					                    string currentPath = Directory.GetCurrentDirectory();
 | 
				
			||||||
                    DriveInfo drive = new(Path.GetPathRoot(currentPath));
 | 
					                    DriveInfo drive = new(Path.GetPathRoot(currentPath));
 | 
				
			||||||
@@ -88,10 +87,9 @@ public class HardwareJob : IJob, IHardwareJob
 | 
				
			|||||||
                    HardwareInfo.DriveInfo = drive;
 | 
					                    HardwareInfo.DriveInfo = drive;
 | 
				
			||||||
                    HardwareInfo.OsArchitecture = Environment.OSVersion.Platform.ToString() + " " + RuntimeInformation.OSArchitecture.ToString(); // 系统架构
 | 
					                    HardwareInfo.OsArchitecture = Environment.OSVersion.Platform.ToString() + " " + RuntimeInformation.OSArchitecture.ToString(); // 系统架构
 | 
				
			||||||
                    HardwareInfo.FrameworkDescription = RuntimeInformation.FrameworkDescription; // NET框架
 | 
					                    HardwareInfo.FrameworkDescription = RuntimeInformation.FrameworkDescription; // NET框架
 | 
				
			||||||
                    HardwareInfo.Environment = App.HostEnvironment.IsDevelopment() ? "Development" : "Production";
 | 
					                    HardwareInfo.Environment = App.HostEnvironment.EnvironmentName;
 | 
				
			||||||
                    HardwareInfo.UUID = HardwareInfo.MachineInfo.UUID;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    HardwareInfo.UpdateTime = TimerX.Now.ToDefaultDateTimeFormat();
 | 
					                    HardwareInfo.UpdateTime = TimerX.Now;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            catch
 | 
					            catch
 | 
				
			||||||
@@ -99,9 +97,12 @@ public class HardwareJob : IJob, IHardwareJob
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
            try
 | 
					            try
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                HardwareInfo.MachineInfo.Refresh();
 | 
					                var machine = MachineInfo.GetCurrent();
 | 
				
			||||||
                HardwareInfo.UpdateTime = TimerX.Now.ToDefaultDateTimeFormat();
 | 
					                machine.Refresh();
 | 
				
			||||||
                HardwareInfo.WorkingSet = (Environment.WorkingSet / 1024.0 / 1024.0).ToInt();
 | 
					                machine.AdaptHardwareInfo(HardwareInfo);
 | 
				
			||||||
 | 
					                HardwareInfo.AppRunTotalMinute = (ulong)Runtime.AppTickCount64 / 1000 /60;
 | 
				
			||||||
 | 
					                HardwareInfo.SystemRunTotalMinute = (ulong)Runtime.TickCount64 / 1000 /60;
 | 
				
			||||||
 | 
					                HardwareInfo.UpdateTime = TimerX.Now;
 | 
				
			||||||
                error = false;
 | 
					                error = false;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            catch (Exception ex)
 | 
					            catch (Exception ex)
 | 
				
			||||||
@@ -123,10 +124,10 @@ public class HardwareJob : IJob, IHardwareJob
 | 
				
			|||||||
                            {
 | 
					                            {
 | 
				
			||||||
                                Date = TimerX.Now,
 | 
					                                Date = TimerX.Now,
 | 
				
			||||||
                                DriveUsage = (100 - (HardwareInfo.DriveInfo.TotalFreeSpace * 100.00 / HardwareInfo.DriveInfo.TotalSize)).ToInt(),
 | 
					                                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),
 | 
					                                MemoryUsage = (HardwareInfo.WorkingSet),
 | 
				
			||||||
                                CpuUsage = (HardwareInfo.MachineInfo.CpuRate * 100).ToInt(),
 | 
					                                CpuUsage = (HardwareInfo.CpuRate * 100).ToInt(),
 | 
				
			||||||
                                Temperature = (HardwareInfo.MachineInfo.Temperature).ToInt(),
 | 
					                                Temperature = (HardwareInfo.Temperature).ToInt(),
 | 
				
			||||||
                            };
 | 
					                            };
 | 
				
			||||||
                            await _db.InsertableT(his).ExecuteCommandAsync(stoppingToken).ConfigureAwait(false);
 | 
					                            await _db.InsertableT(his).ExecuteCommandAsync(stoppingToken).ConfigureAwait(false);
 | 
				
			||||||
                            MemoryCache.Remove(CacheKey);
 | 
					                            MemoryCache.Remove(CacheKey);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -21,7 +21,7 @@ public class HistoryHardwareInfo
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    /// <inheritdoc/>
 | 
					    /// <inheritdoc/>
 | 
				
			||||||
    [SugarColumn(ColumnDescription = "内存")]
 | 
					    [SugarColumn(ColumnDescription = "内存")]
 | 
				
			||||||
    public int MemoryUsage { get; set; }
 | 
					    public UInt64 MemoryUsage { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// <inheritdoc/>
 | 
					    /// <inheritdoc/>
 | 
				
			||||||
    [SugarColumn(ColumnDescription = "CPU使用率")]
 | 
					    [SugarColumn(ColumnDescription = "CPU使用率")]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -21,7 +21,7 @@
 | 
				
			|||||||
    "UserNoModule": "This account has not been assigned a module. Please contact the administrator",
 | 
					    "UserNoModule": "This account has not been assigned a module. Please contact the administrator",
 | 
				
			||||||
    "UserNull": "User {0} does not exist"
 | 
					    "UserNull": "User {0} does not exist"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  
 | 
					
 | 
				
			||||||
  "ThingsGateway.Admin.Application.BlazorAuthenticationHandler": {
 | 
					  "ThingsGateway.Admin.Application.BlazorAuthenticationHandler": {
 | 
				
			||||||
    "UserExpire": "User expired, please login again"
 | 
					    "UserExpire": "User expired, please login again"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
@@ -46,12 +46,44 @@
 | 
				
			|||||||
    "FileTypeError": "Not supported format {0}"
 | 
					    "FileTypeError": "Not supported format {0}"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "ThingsGateway.Admin.Application.HardwareInfo": {
 | 
					  "ThingsGateway.Admin.Application.HardwareInfo": {
 | 
				
			||||||
    "Environment": "HostEnvironment",
 | 
					    "DriveInfo": "Current Disk Info",
 | 
				
			||||||
    "FrameworkDescription": ".NETFramework",
 | 
					    "AppRunTotalMinute": "AppRunTotalMinute(min)",
 | 
				
			||||||
    "OsArchitecture": "System Architecture",
 | 
					    "SystemRunTotalMinute": "SystemRunTotalMinute(min)",
 | 
				
			||||||
    "UpdateTime": "UpdateTime",
 | 
					    "Environment": "Host Environment",
 | 
				
			||||||
    "UUID": "UUID"
 | 
					    "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": {
 | 
					  "ThingsGateway.Admin.Application.HardwareJob": {
 | 
				
			||||||
    "GetHardwareInfoFail": "Get Hardwareinfo Fail"
 | 
					    "GetHardwareInfoFail": "Get Hardwareinfo Fail"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -46,11 +46,42 @@
 | 
				
			|||||||
    "FileTypeError": "不支持 {0} 格式"
 | 
					    "FileTypeError": "不支持 {0} 格式"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "ThingsGateway.Admin.Application.HardwareInfo": {
 | 
					  "ThingsGateway.Admin.Application.HardwareInfo": {
 | 
				
			||||||
 | 
					    "DriveInfo": "当前磁盘信息",
 | 
				
			||||||
 | 
					    "AppRunTotalMinute": "软件运行时长(min)",
 | 
				
			||||||
 | 
					    "SystemRunTotalMinute": "系统运行时长(min)",
 | 
				
			||||||
    "Environment": "主机环境",
 | 
					    "Environment": "主机环境",
 | 
				
			||||||
    "FrameworkDescription": "NET框架",
 | 
					    "FrameworkDescription": ".NET 框架",
 | 
				
			||||||
    "OsArchitecture": "系统架构",
 | 
					    "OsArchitecture": "系统架构",
 | 
				
			||||||
    "UpdateTime": "更新时间",
 | 
					    "OSName": "系统名称",
 | 
				
			||||||
    "UUID": "唯一编码"
 | 
					    "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": {
 | 
					  "ThingsGateway.Admin.Application.HardwareJob": {
 | 
				
			||||||
    "GetHardwareInfoFail": "获取硬件信息出错"
 | 
					    "GetHardwareInfoFail": "获取硬件信息出错"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,12 +8,20 @@
 | 
				
			|||||||
//  QQ群:605534569
 | 
					//  QQ群:605534569
 | 
				
			||||||
//------------------------------------------------------------------------------
 | 
					//------------------------------------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using BootstrapBlazor.Components;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
using Riok.Mapperly.Abstractions;
 | 
					using Riok.Mapperly.Abstractions;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using ThingsGateway.NewLife;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace ThingsGateway.Admin.Application;
 | 
					namespace ThingsGateway.Admin.Application;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[Mapper(UseDeepCloning = true, EnumMappingStrategy = EnumMappingStrategy.ByName, RequiredMappingStrategy = RequiredMappingStrategy.None)]
 | 
					[Mapper(UseDeepCloning = true, EnumMappingStrategy = EnumMappingStrategy.ByName, RequiredMappingStrategy = RequiredMappingStrategy.None)]
 | 
				
			||||||
public static partial class AdminMapper
 | 
					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 LoginInput AdaptLoginInput(this OpenApiLoginInput src);
 | 
				
			||||||
    public static partial OpenApiLoginOutput AdaptOpenApiLoginOutput(this LoginOutput src);
 | 
					    public static partial OpenApiLoginOutput AdaptOpenApiLoginOutput(this LoginOutput src);
 | 
				
			||||||
    public static partial SessionOutput AdaptSessionOutput(this SysUser src);
 | 
					    public static partial SessionOutput AdaptSessionOutput(this SysUser src);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -145,7 +145,7 @@ public class AdminOAuthHandler<TOptions>(
 | 
				
			|||||||
        var loginEvent = new LoginEvent
 | 
					        var loginEvent = new LoginEvent
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            Ip = appService.RemoteIpAddress,
 | 
					            Ip = appService.RemoteIpAddress,
 | 
				
			||||||
            Device = appService.UserAgent?.Platform,
 | 
					            Device = appService.UserAgent?.Platform ?? "Unknown",
 | 
				
			||||||
            Expire = expire,
 | 
					            Expire = expire,
 | 
				
			||||||
            SysUser = sysUser,
 | 
					            SysUser = sysUser,
 | 
				
			||||||
            VerificatId = CommonUtils.GetSingleId()
 | 
					            VerificatId = CommonUtils.GetSingleId()
 | 
				
			||||||
@@ -156,7 +156,7 @@ public class AdminOAuthHandler<TOptions>(
 | 
				
			|||||||
        //生成verificat信息
 | 
					        //生成verificat信息
 | 
				
			||||||
        var verificatInfo = new VerificatInfo
 | 
					        var verificatInfo = new VerificatInfo
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            Device = loginEvent.Device,
 | 
					            Device = loginEvent.Device ?? "Unknown",
 | 
				
			||||||
            Expire = loginEvent.Expire,
 | 
					            Expire = loginEvent.Expire,
 | 
				
			||||||
            VerificatTimeout = tokenTimeout,
 | 
					            VerificatTimeout = tokenTimeout,
 | 
				
			||||||
            Id = loginEvent.VerificatId,
 | 
					            Id = loginEvent.VerificatId,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -26,7 +26,7 @@
 | 
				
			|||||||
      "Module": 2,
 | 
					      "Module": 2,
 | 
				
			||||||
      "Title": "权限管理",
 | 
					      "Title": "权限管理",
 | 
				
			||||||
      "Code": "System",
 | 
					      "Code": "System",
 | 
				
			||||||
      "NavLinkMatch": "All",
 | 
					      "NavLinkMatch": "Prefix",
 | 
				
			||||||
      "Category": "MENU",
 | 
					      "Category": "MENU",
 | 
				
			||||||
      "Target": "_self",
 | 
					      "Target": "_self",
 | 
				
			||||||
      "Href": null,
 | 
					      "Href": null,
 | 
				
			||||||
@@ -47,7 +47,7 @@
 | 
				
			|||||||
      "ParentId": 0,
 | 
					      "ParentId": 0,
 | 
				
			||||||
      "Module": 2,
 | 
					      "Module": 2,
 | 
				
			||||||
      "Title": "系统运维",
 | 
					      "Title": "系统运维",
 | 
				
			||||||
      "NavLinkMatch": "All",
 | 
					      "NavLinkMatch": "Prefix",
 | 
				
			||||||
      "Code": "System",
 | 
					      "Code": "System",
 | 
				
			||||||
      "Category": "MENU",
 | 
					      "Category": "MENU",
 | 
				
			||||||
      "Target": "_self",
 | 
					      "Target": "_self",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -235,7 +235,7 @@ public class AuthService : IAuthService
 | 
				
			|||||||
        var logingEvent = new LoginEvent
 | 
					        var logingEvent = new LoginEvent
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            Ip = _appService.RemoteIpAddress,
 | 
					            Ip = _appService.RemoteIpAddress,
 | 
				
			||||||
            Device = _appService.UserAgent?.Platform,
 | 
					            Device = _appService.UserAgent?.Platform ?? "Unknown",
 | 
				
			||||||
            Expire = expire,
 | 
					            Expire = expire,
 | 
				
			||||||
            SysUser = sysUser,
 | 
					            SysUser = sysUser,
 | 
				
			||||||
            VerificatId = verificatId
 | 
					            VerificatId = verificatId
 | 
				
			||||||
@@ -344,7 +344,7 @@ public class AuthService : IAuthService
 | 
				
			|||||||
        //生成verificat信息
 | 
					        //生成verificat信息
 | 
				
			||||||
        var verificatInfo = new VerificatInfo
 | 
					        var verificatInfo = new VerificatInfo
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            Device = loginEvent.Device,
 | 
					            Device = loginEvent.Device ?? "Unknown",
 | 
				
			||||||
            Expire = loginEvent.Expire,
 | 
					            Expire = loginEvent.Expire,
 | 
				
			||||||
            VerificatTimeout = tokenTimeout,
 | 
					            VerificatTimeout = tokenTimeout,
 | 
				
			||||||
            Id = loginEvent.VerificatId,
 | 
					            Id = loginEvent.VerificatId,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,7 +20,7 @@ namespace ThingsGateway.Admin.Application;
 | 
				
			|||||||
/// <typeparam name="TEntry"></typeparam>
 | 
					/// <typeparam name="TEntry"></typeparam>
 | 
				
			||||||
public class EventService<TEntry> : IEventService<TEntry>, IDisposable
 | 
					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()
 | 
					    public void Dispose()
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,6 +9,7 @@
 | 
				
			|||||||
//------------------------------------------------------------------------------
 | 
					//------------------------------------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace ThingsGateway.Admin.Application;
 | 
					namespace ThingsGateway.Admin.Application;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
internal sealed class NoticeService : INoticeService
 | 
					internal sealed class NoticeService : INoticeService
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    private IEventService<AppMessage>? MessageDispatchService { get; set; }
 | 
					    private IEventService<AppMessage>? MessageDispatchService { get; set; }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -282,7 +282,7 @@ internal sealed class SysRoleService : BaseService<SysRole>, ISysRoleService
 | 
				
			|||||||
        if (sysRole != null)
 | 
					        if (sysRole != null)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var resources = await _sysResourceService.GetAllAsync().ConfigureAwait(false);
 | 
					            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 角色模块处理
 | 
					            #region 角色模块处理
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -550,7 +550,7 @@ internal sealed class SysUserService : BaseService<SysUser>, ISysUserService
 | 
				
			|||||||
        if (sysUser != null)
 | 
					        if (sysUser != null)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var resources = await _sysResourceService.GetAllAsync().ConfigureAwait(false);
 | 
					            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 用户模块处理
 | 
					            #region 用户模块处理
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -119,7 +119,7 @@ internal sealed class VerificatInfoService : BaseService<VerificatInfo>, IVerifi
 | 
				
			|||||||
    public void Add(VerificatInfo verificatInfo)
 | 
					    public void Add(VerificatInfo verificatInfo)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        using var db = GetDB();
 | 
					        using var db = GetDB();
 | 
				
			||||||
        db.Insertable<VerificatInfo>(verificatInfo).ExecuteCommand();
 | 
					        db.InsertableT<VerificatInfo>(verificatInfo).ExecuteCommand();
 | 
				
			||||||
        VerificatInfoService.RemoveCache(verificatInfo.Id);
 | 
					        VerificatInfoService.RemoveCache(verificatInfo.Id);
 | 
				
			||||||
        if (verificatInfo != null)
 | 
					        if (verificatInfo != null)
 | 
				
			||||||
            VerificatInfoService.SetCahce(verificatInfo);
 | 
					            VerificatInfoService.SetCahce(verificatInfo);
 | 
				
			||||||
@@ -132,7 +132,7 @@ internal sealed class VerificatInfoService : BaseService<VerificatInfo>, IVerifi
 | 
				
			|||||||
    public void Update(VerificatInfo verificatInfo)
 | 
					    public void Update(VerificatInfo verificatInfo)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        using var db = GetDB();
 | 
					        using var db = GetDB();
 | 
				
			||||||
        db.Updateable<VerificatInfo>(verificatInfo).ExecuteCommand();
 | 
					        db.UpdateableT<VerificatInfo>(verificatInfo).ExecuteCommand();
 | 
				
			||||||
        VerificatInfoService.RemoveCache(verificatInfo.Id);
 | 
					        VerificatInfoService.RemoveCache(verificatInfo.Id);
 | 
				
			||||||
        if (verificatInfo != null)
 | 
					        if (verificatInfo != null)
 | 
				
			||||||
            VerificatInfoService.SetCahce(verificatInfo);
 | 
					            VerificatInfoService.SetCahce(verificatInfo);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,7 +8,7 @@
 | 
				
			|||||||
		
 | 
							
 | 
				
			||||||
	</PropertyGroup>
 | 
						</PropertyGroup>
 | 
				
			||||||
	<PropertyGroup>
 | 
						<PropertyGroup>
 | 
				
			||||||
		<TargetFrameworks>net8.0;net9.0;</TargetFrameworks>
 | 
							<TargetFrameworks>net8.0;$(OtherTargetFrameworks);</TargetFrameworks>
 | 
				
			||||||
	</PropertyGroup>
 | 
						</PropertyGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<ItemGroup>
 | 
						<ItemGroup>
 | 
				
			||||||
@@ -19,19 +19,21 @@
 | 
				
			|||||||
	</ItemGroup>
 | 
						</ItemGroup>
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	<ItemGroup>
 | 
						<ItemGroup>
 | 
				
			||||||
		<PackageReference Include="Riok.Mapperly" Version="4.2.1" ExcludeAssets="runtime" PrivateAssets="all" />
 | 
							<PackageReference Include="Riok.Mapperly" Version="4.3.0" ExcludeAssets="runtime" PrivateAssets="all">
 | 
				
			||||||
		<PackageReference Include="Rougamo.Fody" Version="5.0.1" />
 | 
							  <IncludeAssets>compile; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
 | 
				
			||||||
 | 
							</PackageReference>
 | 
				
			||||||
 | 
							<PackageReference Include="Rougamo.Fody" Version="5.0.2" />
 | 
				
			||||||
	</ItemGroup>
 | 
						</ItemGroup>
 | 
				
			||||||
	<ItemGroup Condition=" '$(TargetFramework)' == 'net8.0' ">
 | 
						<ItemGroup Condition=" '$(TargetFramework)' == 'net8.0' ">
 | 
				
			||||||
		<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.1" />
 | 
							<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" />
 | 
							<PackageReference Include="System.Threading.RateLimiting" Version="8.0.0" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	</ItemGroup>
 | 
						</ItemGroup>
 | 
				
			||||||
	<ItemGroup Condition=" '$(TargetFramework)' == 'net9.0' ">
 | 
						<ItemGroup Condition=" '$(TargetFramework)' == 'net10.0' ">
 | 
				
			||||||
		<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="$(NET9Version)" />
 | 
							<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="$(NET10Version)" />
 | 
				
			||||||
		<PackageReference Include="System.Formats.Asn1" Version="$(NET9Version)" />
 | 
							<PackageReference Include="System.Formats.Asn1" Version="$(NET10Version)" />
 | 
				
			||||||
		<PackageReference Include="System.Threading.RateLimiting" Version="$(NET9Version)" />
 | 
							<PackageReference Include="System.Threading.RateLimiting" Version="$(NET10Version)" />
 | 
				
			||||||
	</ItemGroup>
 | 
						</ItemGroup>
 | 
				
			||||||
	<ItemGroup>
 | 
						<ItemGroup>
 | 
				
			||||||
		<Content Remove="SeedData\Admin\*.json" />
 | 
							<Content Remove="SeedData\Admin\*.json" />
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,8 +4,8 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<div class="tg-table h-100">
 | 
					<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"
 | 
					    <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!"
 | 
					           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!"
 | 
					           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
 | 
					           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
 | 
					           ShowDefaultButtons="ShowDefaultButtons" ShowAdvancedSearch="ShowAdvancedSearch" ShowResetButton=ShowResetButton
 | 
				
			||||||
@@ -14,7 +14,7 @@
 | 
				
			|||||||
           ShowSkeleton="true" ShowLoading="ShowLoading" ShowSearch="ShowSearch" SearchModel=@SearchModel ShowLineNo
 | 
					           ShowSkeleton="true" ShowLoading="ShowLoading" ShowSearch="ShowSearch" SearchModel=@SearchModel ShowLineNo
 | 
				
			||||||
           SearchMode=SearchMode ShowExportPdfButton=ShowExportPdfButton ExportButtonText=@ExportButtonText
 | 
					           SearchMode=SearchMode ShowExportPdfButton=ShowExportPdfButton ExportButtonText=@ExportButtonText
 | 
				
			||||||
           ShowExportButton=@ShowExportButton Items=Items ClickToSelect=ClickToSelect ScrollMode=ScrollMode
 | 
					           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
 | 
					           FixedExtendButtonsColumn=FixedExtendButtonsColumn FixedMultipleColumn=FixedMultipleColumn FixedDetailRowHeaderColumn=FixedDetailRowHeaderColumn FixedLineNoColumn=FixedLineNoColumn
 | 
				
			||||||
           IsAutoRefresh=IsAutoRefresh AutoRefreshInterval=AutoRefreshInterval
 | 
					           IsAutoRefresh=IsAutoRefresh AutoRefreshInterval=AutoRefreshInterval
 | 
				
			||||||
           AllowDragColumn=@AllowDragColumn Height=@Height ShowRefresh=ShowRefresh
 | 
					           AllowDragColumn=@AllowDragColumn Height=@Height ShowRefresh=ShowRefresh
 | 
				
			||||||
@@ -41,6 +41,7 @@
 | 
				
			|||||||
           DoubleClickToEdit="DoubleClickToEdit"
 | 
					           DoubleClickToEdit="DoubleClickToEdit"
 | 
				
			||||||
           OnDoubleClickCellCallback="OnDoubleClickCellCallback"
 | 
					           OnDoubleClickCellCallback="OnDoubleClickCellCallback"
 | 
				
			||||||
           OnDoubleClickRowCallback="OnDoubleClickRowCallback"
 | 
					           OnDoubleClickRowCallback="OnDoubleClickRowCallback"
 | 
				
			||||||
 | 
					           RowContentTemplate="RowContentTemplate"
 | 
				
			||||||
           OnClickRowCallback="OnClickRowCallback">
 | 
					           OnClickRowCallback="OnClickRowCallback">
 | 
				
			||||||
    </Table>
 | 
					    </Table>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,6 +13,23 @@ namespace ThingsGateway.Admin.Razor;
 | 
				
			|||||||
[CascadingTypeParameter(nameof(TItem))]
 | 
					[CascadingTypeParameter(nameof(TItem))]
 | 
				
			||||||
public partial class AdminTable<TItem> where TItem : class, new()
 | 
					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"/>
 | 
					    /// <inheritdoc cref="Table{TItem}.SelectedRowsChanged"/>
 | 
				
			||||||
    [Parameter]
 | 
					    [Parameter]
 | 
				
			||||||
    public EventCallback<List<TItem>> SelectedRowsChanged { get; set; }
 | 
					    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"/>
 | 
					    /// <inheritdoc cref="Table{TItem}.OnDoubleClickRowCallback"/>
 | 
				
			||||||
    [Parameter]
 | 
					    [Parameter]
 | 
				
			||||||
    public Func<TItem, Task>? OnDoubleClickRowCallback { get; set; }
 | 
					    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"/>
 | 
					    /// <inheritdoc cref="Table{TItem}.OnClickRowCallback"/>
 | 
				
			||||||
    [Parameter]
 | 
					    [Parameter]
 | 
				
			||||||
    public Func<TItem, Task>? OnClickRowCallback { get; set; }
 | 
					    public Func<TItem, Task>? OnClickRowCallback { get; set; }
 | 
				
			||||||
@@ -146,6 +167,9 @@ public partial class AdminTable<TItem> where TItem : class, new()
 | 
				
			|||||||
    [Parameter]
 | 
					    [Parameter]
 | 
				
			||||||
    public IDataService<TItem> DataService { get; set; }
 | 
					    public IDataService<TItem> DataService { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    [Parameter]
 | 
				
			||||||
 | 
					    public string? Id { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// <inheritdoc cref="Table{TItem}.CreateItemCallback"/>
 | 
					    /// <inheritdoc cref="Table{TItem}.CreateItemCallback"/>
 | 
				
			||||||
    [Parameter]
 | 
					    [Parameter]
 | 
				
			||||||
    public Func<TItem> CreateItemCallback { get; set; }
 | 
					    public Func<TItem> CreateItemCallback { get; set; }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -125,13 +125,22 @@ public class BlazorAppContext
 | 
				
			|||||||
            var ownMenus = OwnMenus.Where(a => a.Module == CurrentModuleId);
 | 
					            var ownMenus = OwnMenus.Where(a => a.Module == CurrentModuleId);
 | 
				
			||||||
            OwnMenuItems = AdminResourceUtil.BuildMenuTrees(ownMenus).ToList();
 | 
					            OwnMenuItems = AdminResourceUtil.BuildMenuTrees(ownMenus).ToList();
 | 
				
			||||||
            AllOwnMenuItems = 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,
 | 
					                var menu = new MenuItem()
 | 
				
			||||||
                Text = item.Title,
 | 
					                {
 | 
				
			||||||
                Icon = item.Icon,
 | 
					                    Match = item.NavLinkMatch ?? Microsoft.AspNetCore.Components.Routing.NavLinkMatch.Prefix,
 | 
				
			||||||
                Url = item.Href,
 | 
					                    Text = item.Title,
 | 
				
			||||||
                Target = item.Target.ToString(),
 | 
					                    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();
 | 
					            }).ToList();
 | 
				
			||||||
            UserWorkbenchOutputs = AllMenus.Where(it => UserWorkBench.Shortcuts.Contains(it.Id)).ToList();
 | 
					            UserWorkbenchOutputs = AllMenus.Where(it => UserWorkBench.Shortcuts.Contains(it.Id)).ToList();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -22,6 +22,9 @@
 | 
				
			|||||||
    "SetDefaultModule": "Set as default module"
 | 
					    "SetDefaultModule": "Set as default module"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "ThingsGateway.Admin.Razor.HardwareInfoPage": {
 | 
					  "ThingsGateway.Admin.Razor.HardwareInfoPage": {
 | 
				
			||||||
 | 
					    "GCMemoryInfoConfig": "GCMemoryInfoConfig(MB)",
 | 
				
			||||||
 | 
					    "GCMemoryInfo": "GCMemoryInfo(MB)",
 | 
				
			||||||
 | 
					    "GCAnalyzeInfo": "GCAnalyzeInfo",
 | 
				
			||||||
    "AvailableDisk": "Available Disk",
 | 
					    "AvailableDisk": "Available Disk",
 | 
				
			||||||
    "AvailableMemory": "Available Memory",
 | 
					    "AvailableMemory": "Available Memory",
 | 
				
			||||||
    "CpuUsage": "CPU Usage",
 | 
					    "CpuUsage": "CPU Usage",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -22,6 +22,10 @@
 | 
				
			|||||||
    "SetDefaultModule": "设置为默认模块"
 | 
					    "SetDefaultModule": "设置为默认模块"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "ThingsGateway.Admin.Razor.HardwareInfoPage": {
 | 
					  "ThingsGateway.Admin.Razor.HardwareInfoPage": {
 | 
				
			||||||
 | 
					    "GCMemoryInfoConfig": "GC配置信息(MB)",
 | 
				
			||||||
 | 
					    "GCMemoryInfo": "GC内存信息(MB)",
 | 
				
			||||||
 | 
					    "GCAnalyzeInfo": "GC统计",
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    "AvailableDisk": "可用磁盘",
 | 
					    "AvailableDisk": "可用磁盘",
 | 
				
			||||||
    "AvailableMemory": "可用内存",
 | 
					    "AvailableMemory": "可用内存",
 | 
				
			||||||
    "CpuUsage": "CPU使用率",
 | 
					    "CpuUsage": "CPU使用率",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,131 +5,189 @@
 | 
				
			|||||||
@using ThingsGateway.Admin.Application
 | 
					@using ThingsGateway.Admin.Application
 | 
				
			||||||
@namespace ThingsGateway.Admin.Razor
 | 
					@namespace ThingsGateway.Admin.Razor
 | 
				
			||||||
<div class="row g-2 mx-1 form-inline">
 | 
					<div class="row g-2 mx-1 form-inline">
 | 
				
			||||||
 | 
					    <div class="col-12 col-md-12">
 | 
				
			||||||
    <div class="col-12 col-md-4">
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <Card IsShadow=true class="m-2 flex-fill" Color="Color.Primary">
 | 
					        <Card IsShadow=true class="m-2 flex-fill" Color="Color.Primary">
 | 
				
			||||||
            <HeaderTemplate>
 | 
					            <HeaderTemplate>
 | 
				
			||||||
                @Localizer["SystemInfo"]
 | 
					                <div class="d-flex justify-content-between align-items-center w-100">
 | 
				
			||||||
            </HeaderTemplate>
 | 
					                    <span>@Localizer["SystemInfo"]</span>
 | 
				
			||||||
 | 
					                    <small class="text-muted">
 | 
				
			||||||
            <BodyTemplate>
 | 
					                        @HardwareJob.HardwareInfo.UpdateTime.ToString("yyyy-MM-dd HH:mm:ss")
 | 
				
			||||||
                <EditorFormObject IsDisplay Model="HardwareJob.HardwareInfo" ItemsPerRow="1" RowType=RowType.Inline LabelWidth="160">
 | 
					                    </small>
 | 
				
			||||||
                    <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>
 | 
					                </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>
 | 
					            </BodyTemplate>
 | 
				
			||||||
        </Card>
 | 
					        </Card>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
</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="row g-2 mx-1 form-inline">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <div class="col-12 col-md-12">
 | 
					    <div class="col-12 col-md-12">
 | 
				
			||||||
@@ -140,7 +198,7 @@
 | 
				
			|||||||
            </HeaderTemplate>
 | 
					            </HeaderTemplate>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <BodyTemplate>
 | 
					            <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>
 | 
					            </BodyTemplate>
 | 
				
			||||||
        </Card>
 | 
					        </Card>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -72,7 +72,7 @@ public partial class HardwareInfoPage : IDisposable
 | 
				
			|||||||
            ChartDataSource.Options.Title = Localizer[nameof(HistoryHardwareInfo)];
 | 
					            ChartDataSource.Options.Title = Localizer[nameof(HistoryHardwareInfo)];
 | 
				
			||||||
            ChartDataSource.Options.X.Title = Localizer["DateTime"];
 | 
					            ChartDataSource.Options.X.Title = Localizer["DateTime"];
 | 
				
			||||||
            ChartDataSource.Options.Y.Title = Localizer["Data"];
 | 
					            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()
 | 
					            ChartDataSource.Data.Add(new ChartDataset()
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                Tension = 0.4f,
 | 
					                Tension = 0.4f,
 | 
				
			||||||
@@ -116,7 +116,7 @@ public partial class HardwareInfoPage : IDisposable
 | 
				
			|||||||
        else
 | 
					        else
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var hisHardwareInfos = await HardwareJob.GetHistoryHardwareInfos();
 | 
					            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[0].Data = hisHardwareInfos.Select(a => (object)a.CpuUsage);
 | 
				
			||||||
            ChartDataSource.Data[1].Data = hisHardwareInfos.Select(a => (object)a.MemoryUsage);
 | 
					            ChartDataSource.Data[1].Data = hisHardwareInfos.Select(a => (object)a.MemoryUsage);
 | 
				
			||||||
            ChartDataSource.Data[2].Data = hisHardwareInfos.Select(a => (object)a.DriveUsage);
 | 
					            ChartDataSource.Data[2].Data = hisHardwareInfos.Select(a => (object)a.DriveUsage);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,7 +15,7 @@
 | 
				
			|||||||
        </Card>
 | 
					        </Card>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    </div>
 | 
					    </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"
 | 
					    <AdminTable @ref=table TItem="SysUser"
 | 
				
			||||||
        AutoGenerateColumns="true"
 | 
					        AutoGenerateColumns="true"
 | 
				
			||||||
        ShowAdvancedSearch=false
 | 
					        ShowAdvancedSearch=false
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,5 +20,6 @@ public class Startup : AppStartup
 | 
				
			|||||||
        services.AddScoped<IMenuService, MenuService>();
 | 
					        services.AddScoped<IMenuService, MenuService>();
 | 
				
			||||||
        services.AddScoped<IAuthRazorService, AuthRazorService>();
 | 
					        services.AddScoped<IAuthRazorService, AuthRazorService>();
 | 
				
			||||||
        services.AddBootstrapBlazorTableExportService();
 | 
					        services.AddBootstrapBlazorTableExportService();
 | 
				
			||||||
 | 
					        services.AddBootstrapBlazorWinBoxService();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,19 +5,21 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	<ItemGroup>
 | 
						<ItemGroup>
 | 
				
			||||||
		<ProjectReference Include="..\ThingsGateway.Admin.Application\ThingsGateway.Admin.Application.csproj" />
 | 
							<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.UniverSheet" Version="9.0.5" />
 | 
				
			||||||
 | 
							<PackageReference Include="BootstrapBlazor.WinBox" Version="9.0.7" />
 | 
				
			||||||
 | 
							<PackageReference Include="BootstrapBlazor.CodeEditor" Version="9.0.3" />
 | 
				
			||||||
	</ItemGroup>
 | 
						</ItemGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<ItemGroup Condition="'$(TargetFramework)'=='net8.0'">
 | 
						<ItemGroup Condition="'$(TargetFramework)'=='net8.0'">
 | 
				
			||||||
		<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="$(NET8Version)" />
 | 
							<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="$(NET8Version)" />
 | 
				
			||||||
	</ItemGroup>
 | 
						</ItemGroup>
 | 
				
			||||||
	<ItemGroup Condition="'$(TargetFramework)'=='net9.0'">
 | 
						<ItemGroup Condition="'$(TargetFramework)'=='net10.0'">
 | 
				
			||||||
		<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="$(NET9Version)" />
 | 
							<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="$(NET10Version)" />
 | 
				
			||||||
	</ItemGroup>
 | 
						</ItemGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<PropertyGroup>
 | 
						<PropertyGroup>
 | 
				
			||||||
		<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
 | 
							<TargetFrameworks>net8.0;$(OtherTargetFrameworks);</TargetFrameworks>
 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
		<!--<UseRazorSourceGenerator>false</UseRazorSourceGenerator>-->
 | 
							<!--<UseRazorSourceGenerator>false</UseRazorSourceGenerator>-->
 | 
				
			||||||
	</PropertyGroup>
 | 
						</PropertyGroup>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -41,15 +41,22 @@ public static class AdminResourceUtil
 | 
				
			|||||||
        return items
 | 
					        return items
 | 
				
			||||||
        .Where(it => it.ParentId == parentId)
 | 
					        .Where(it => it.ParentId == parentId)
 | 
				
			||||||
        .Select((item, index) =>
 | 
					        .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,
 | 
					                Text = item.Title,
 | 
				
			||||||
                Icon = item.Icon,
 | 
					                Icon = item.Icon,
 | 
				
			||||||
                Url = item.Href,
 | 
					                Url = item.Href,
 | 
				
			||||||
                Target = item.Target.ToString(),
 | 
					                Target = item.Target.ToString(),
 | 
				
			||||||
                Items = BuildMenuTrees(items, item.Id).ToList()
 | 
					                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">
 | 
					    <link rel="apple-touch-icon" href="favicon.png">
 | 
				
			||||||
    <base href="/" />
 | 
					    <base href="/" />
 | 
				
			||||||
    <title>ThingsGateway</title>
 | 
					    <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.FontAwesome/css/font-awesome.min.css") />
 | 
				
			||||||
    <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/bootstrap.blazor.bundle.min.css") />
 | 
				
			||||||
    <link rel="stylesheet" href=@($"_content/BootstrapBlazor/css/motronic.min.css?v={this.GetType().Assembly.GetName().Version}") />
 | 
					    <link rel="stylesheet" href=@($"_content/BootstrapBlazor/css/motronic.min.css") />
 | 
				
			||||||
    <link rel="stylesheet" href=@($"ThingsGateway.AdminServer.styles.css?v={this.GetType().Assembly.GetName().Version}") />
 | 
					    <link rel="stylesheet" href=@($"ThingsGateway.AdminServer.styles.css") />
 | 
				
			||||||
    <link rel="stylesheet" href=@($"{WebsiteConst.DefaultResourceUrl}css/site.css?v={this.GetType().Assembly.GetName().Version}") />
 | 
					    <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><!-- 初始主题 --> *@
 | 
					    @* <script src=@($"{WebsiteConst.DefaultResourceUrl}js/theme.js") type="module"></script><!-- 初始主题 --> *@
 | 
				
			||||||
    <!-- PWA Manifest -->
 | 
					    <!-- PWA Manifest -->
 | 
				
			||||||
    <link rel="manifest" href="./manifest.json" />
 | 
					    <link rel="manifest" href="./manifest.json" />
 | 
				
			||||||
@@ -38,12 +40,13 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    <BlazorReconnector @rendermode="new InteractiveServerRenderMode(false)" />
 | 
					    <BlazorReconnector @rendermode="new InteractiveServerRenderMode(false)" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <script src=@($"_content/BootstrapBlazor/js/bootstrap.blazor.bundle.min.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?v={this.GetType().Assembly.GetName().Version}")></script>
 | 
					    <script src=@($"{WebsiteConst.DefaultResourceUrl}js/localStorageUtil.js")></script>
 | 
				
			||||||
    <script src="_framework/blazor.web.js"></script>
 | 
					    <script src="_framework/blazor.web.js"></script>
 | 
				
			||||||
    <!-- PWA Service Worker -->
 | 
					    <!-- PWA Service Worker -->
 | 
				
			||||||
    <script type="text/javascript">'serviceWorker' in navigator && navigator.serviceWorker.register('./service-worker.js')</script>
 | 
					    <script type="text/javascript">'serviceWorker' in navigator && navigator.serviceWorker.register('./service-worker.js')</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <script src="pwa-install.js"></script>
 | 
				
			||||||
</body>
 | 
					</body>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
</html>
 | 
					</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)]" />
 | 
					                        <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 /> *@
 | 
					                    @* <ThemeToggle /> *@
 | 
				
			||||||
@@ -89,12 +89,19 @@
 | 
				
			|||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                </Side>
 | 
					                </Side>
 | 
				
			||||||
                <Main>
 | 
					                <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"
 | 
					                         ShowFullscreenToolbarButton=false ShowContextMenuFullScreen=false ShowFullScreen=false AdditionalAssemblies="@App.RazorAssemblies" Menus="@MenuService.AllOwnMenuItems"
 | 
				
			||||||
                             DefaultUrl=@("/") Body=@(Body!) OnCloseTabItemAsync=@((a)=>
 | 
					                         DefaultUrl=@("/") Body=@(Body!) OnCloseTabItemAsync=@((a)=>
 | 
				
			||||||
                             {
 | 
					                                                                                  {
 | 
				
			||||||
                             return Task.FromResult(!(a.Url=="/"||a.Url.IsNullOrEmpty()));
 | 
					                                                                                      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>
 | 
					                        </Tab>
 | 
				
			||||||
                </Main>
 | 
					                </Main>
 | 
				
			||||||
                <NotAuthorized>
 | 
					                <NotAuthorized>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -120,6 +120,38 @@ public partial class MainLayout : IDisposable
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    #endregion 注销
 | 
					    #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;
 | 
					    private string _versionString = string.Empty;
 | 
				
			||||||
    [Inject]
 | 
					    [Inject]
 | 
				
			||||||
    [NotNull]
 | 
					    [NotNull]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -87,42 +87,45 @@ public class Startup : AppStartup
 | 
				
			|||||||
#if NET8_0_OR_GREATER
 | 
					#if NET8_0_OR_GREATER
 | 
				
			||||||
        services
 | 
					        services
 | 
				
			||||||
         .AddRazorComponents(options => options.TemporaryRedirectionUrlValidityDuration = TimeSpan.FromMinutes(10))
 | 
					         .AddRazorComponents(options => options.TemporaryRedirectionUrlValidityDuration = TimeSpan.FromMinutes(10))
 | 
				
			||||||
         .AddInteractiveServerComponents(options =>
 | 
					            .AddInteractiveServerComponents(options =>
 | 
				
			||||||
         {
 | 
					            {
 | 
				
			||||||
             options.RootComponents.MaxJSRootComponents = 500;
 | 
					                options.RootComponents.MaxJSRootComponents = 500;
 | 
				
			||||||
             options.JSInteropDefaultCallTimeout = TimeSpan.FromMinutes(2);
 | 
					                options.JSInteropDefaultCallTimeout = TimeSpan.FromSeconds(30);
 | 
				
			||||||
             options.MaxBufferedUnacknowledgedRenderBatches = 20;
 | 
					                options.MaxBufferedUnacknowledgedRenderBatches = 5;
 | 
				
			||||||
             options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromMinutes(10);
 | 
					                options.DisconnectedCircuitMaxRetained = 1;
 | 
				
			||||||
         })
 | 
					                options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromSeconds(10);
 | 
				
			||||||
         .AddHubOptions(options =>
 | 
					            })
 | 
				
			||||||
         {
 | 
					            .AddHubOptions(options =>
 | 
				
			||||||
             //单个传入集线器消息的最大大小。默认 32 KB
 | 
					            {
 | 
				
			||||||
             options.MaximumReceiveMessageSize = 32 * 1024 * 1024;
 | 
					                //单个传入集线器消息的最大大小。默认 32 KB
 | 
				
			||||||
             //可为客户端上载流缓冲的最大项数。 如果达到此限制,则会阻止处理调用,直到服务器处理流项。
 | 
					                options.MaximumReceiveMessageSize = 32 * 1024 * 1024;
 | 
				
			||||||
             options.StreamBufferCapacity = 30;
 | 
					                //可为客户端上载流缓冲的最大项数。 如果达到此限制,则会阻止处理调用,直到服务器处理流项。
 | 
				
			||||||
             options.ClientTimeoutInterval = TimeSpan.FromMinutes(2);
 | 
					                options.StreamBufferCapacity = 30;
 | 
				
			||||||
             options.KeepAliveInterval = TimeSpan.FromSeconds(15);
 | 
					                options.ClientTimeoutInterval = TimeSpan.FromSeconds(30);
 | 
				
			||||||
             options.HandshakeTimeout = TimeSpan.FromSeconds(30);
 | 
					                options.KeepAliveInterval = TimeSpan.FromSeconds(15);
 | 
				
			||||||
         });
 | 
					                options.HandshakeTimeout = TimeSpan.FromSeconds(15);
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#else
 | 
					#else
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        services.AddServerSideBlazor(options =>
 | 
					        services.AddServerSideBlazor(options =>
 | 
				
			||||||
        {
 | 
					             {
 | 
				
			||||||
            options.RootComponents.MaxJSRootComponents = 500;
 | 
					                 options.RootComponents.MaxJSRootComponents = 500;
 | 
				
			||||||
            options.JSInteropDefaultCallTimeout = TimeSpan.FromMinutes(2);
 | 
					                 options.JSInteropDefaultCallTimeout = TimeSpan.FromSeconds(30);
 | 
				
			||||||
            options.MaxBufferedUnacknowledgedRenderBatches = 20;
 | 
					                 options.MaxBufferedUnacknowledgedRenderBatches = 5;
 | 
				
			||||||
            options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromMinutes(10);
 | 
					                 options.DisconnectedCircuitMaxRetained = 1;
 | 
				
			||||||
        }).AddHubOptions(options =>
 | 
					                 options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromSeconds(10);
 | 
				
			||||||
        {
 | 
					             })
 | 
				
			||||||
            //单个传入集线器消息的最大大小。默认 32 KB
 | 
					             .AddHubOptions(options =>
 | 
				
			||||||
            options.MaximumReceiveMessageSize = 32 * 1024 * 1024;
 | 
					             {
 | 
				
			||||||
            //可为客户端上载流缓冲的最大项数。 如果达到此限制,则会阻止处理调用,直到服务器处理流项。
 | 
					                 //单个传入集线器消息的最大大小。默认 32 KB
 | 
				
			||||||
            options.StreamBufferCapacity = 30;
 | 
					                 options.MaximumReceiveMessageSize = 32 * 1024 * 1024;
 | 
				
			||||||
            options.ClientTimeoutInterval = TimeSpan.FromMinutes(2);
 | 
					                 //可为客户端上载流缓冲的最大项数。 如果达到此限制,则会阻止处理调用,直到服务器处理流项。
 | 
				
			||||||
            options.KeepAliveInterval = TimeSpan.FromSeconds(15);
 | 
					                 options.StreamBufferCapacity = 30;
 | 
				
			||||||
            options.HandshakeTimeout = TimeSpan.FromSeconds(30);
 | 
					                 options.ClientTimeoutInterval = TimeSpan.FromSeconds(30);
 | 
				
			||||||
        });
 | 
					                 options.KeepAliveInterval = TimeSpan.FromSeconds(15);
 | 
				
			||||||
 | 
					                 options.HandshakeTimeout = TimeSpan.FromSeconds(15);
 | 
				
			||||||
 | 
					             });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -132,7 +135,11 @@ public class Startup : AppStartup
 | 
				
			|||||||
        services.Configure<ForwardedHeadersOptions>(options =>
 | 
					        services.Configure<ForwardedHeadersOptions>(options =>
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            options.ForwardedHeaders = ForwardedHeaders.All;
 | 
					            options.ForwardedHeaders = ForwardedHeaders.All;
 | 
				
			||||||
 | 
					#if NET10_0_OR_GREATER
 | 
				
			||||||
 | 
					            options.KnownIPNetworks.Clear();
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
            options.KnownNetworks.Clear();
 | 
					            options.KnownNetworks.Clear();
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
            options.KnownProxies.Clear();
 | 
					            options.KnownProxies.Clear();
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -204,7 +211,16 @@ public class Startup : AppStartup
 | 
				
			|||||||
    public void Use(IApplicationBuilder applicationBuilder, IWebHostEnvironment env)
 | 
					    public void Use(IApplicationBuilder applicationBuilder, IWebHostEnvironment env)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var app = (WebApplication)applicationBuilder;
 | 
					        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();
 | 
					        app.UseBootstrapBlazor();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // 启用本地化
 | 
					        // 启用本地化
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,7 +4,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<PropertyGroup>
 | 
						<PropertyGroup>
 | 
				
			||||||
		<TargetFrameworks>net8.0;net9.0;</TargetFrameworks>
 | 
							<TargetFrameworks>net8.0;$(OtherTargetFrameworks);</TargetFrameworks>
 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
	</PropertyGroup>
 | 
						</PropertyGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -15,15 +15,11 @@
 | 
				
			|||||||
		<PublishReadyToRunComposite>true</PublishReadyToRunComposite>
 | 
							<PublishReadyToRunComposite>true</PublishReadyToRunComposite>
 | 
				
			||||||
		<ApplicationIcon>wwwroot\favicon.ico</ApplicationIcon>
 | 
							<ApplicationIcon>wwwroot\favicon.ico</ApplicationIcon>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							<CETCompat>false</CETCompat>
 | 
				
			||||||
 | 
							<ServerGarbageCollection>true</ServerGarbageCollection>
 | 
				
			||||||
		<!--动态适用GC-->
 | 
							<!--动态适用GC-->
 | 
				
			||||||
		<GarbageCollectionAdaptationMode>1</GarbageCollectionAdaptationMode>
 | 
							<GarbageCollectionAdaptationMode>1</GarbageCollectionAdaptationMode>
 | 
				
			||||||
		<CETCompat>false</CETCompat>
 | 
							
 | 
				
			||||||
		<!--使用自托管线程池-->
 | 
					 | 
				
			||||||
		<!--<UseWindowsThreadPool>false</UseWindowsThreadPool> -->
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		<!--使用工作站GC-->
 | 
					 | 
				
			||||||
		<!--<ServerGarbageCollection>true</ServerGarbageCollection>-->
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		<!--<PlatformTarget>x86</PlatformTarget>-->
 | 
							<!--<PlatformTarget>x86</PlatformTarget>-->
 | 
				
			||||||
	</PropertyGroup>
 | 
						</PropertyGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -53,9 +49,9 @@
 | 
				
			|||||||
		<PackageReference Include="Microsoft.Extensions.Hosting.Systemd" Version="8.0.1" />
 | 
							<PackageReference Include="Microsoft.Extensions.Hosting.Systemd" Version="8.0.1" />
 | 
				
			||||||
		<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="8.0.1" />
 | 
							<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="8.0.1" />
 | 
				
			||||||
	</ItemGroup>
 | 
						</ItemGroup>
 | 
				
			||||||
	<ItemGroup Condition=" '$(TargetFramework)' == 'net9.0' ">
 | 
						<ItemGroup Condition=" '$(TargetFramework)' == 'net10.0' ">
 | 
				
			||||||
		<PackageReference Include="Microsoft.Extensions.Hosting.Systemd" Version="$(NET9Version)" />
 | 
							<PackageReference Include="Microsoft.Extensions.Hosting.Systemd" Version="$(NET10Version)" />
 | 
				
			||||||
		<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="$(NET9Version)" />
 | 
							<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="$(NET10Version)" />
 | 
				
			||||||
	</ItemGroup>
 | 
						</ItemGroup>
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	<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所有
 | 
					//  此代码版权(除特别声明外的代码)归作者本人Diego所有
 | 
				
			||||||
//  源代码使用协议遵循本仓库的开源协议及附加协议
 | 
					//  源代码使用协议遵循本仓库的开源协议及附加协议
 | 
				
			||||||
@@ -477,7 +477,7 @@ public class ConcurrentList<T> : IList<T>, IReadOnlyList<T>
 | 
				
			|||||||
    {
 | 
					    {
 | 
				
			||||||
        lock (((ICollection)m_list).SyncRoot)
 | 
					        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的属性,找出差异
 | 
					        // 比较oldModel和model的属性,找出差异
 | 
				
			||||||
        var differences = properties
 | 
					        var differences = properties
 | 
				
			||||||
            .Where(prop => prop.CanRead && prop.CanWrite) // 确保属性可读可写
 | 
					            .Where(prop => prop.CanRead && prop.CanWrite && !Equals(prop.GetValue(oldModel), prop.GetValue(model))) // 确保属性可读可写
 | 
				
			||||||
            .Where(prop => !Equals(prop.GetValue(oldModel), prop.GetValue(model))) // 找出值不同的属性
 | 
					                                                                                                                    // 找出值不同的属性
 | 
				
			||||||
            .ToDictionary(prop => prop.Name, prop => prop.GetValue(model)); // 将属性名和新值存储到字典中
 | 
					            .ToDictionary(prop => prop.Name, prop => prop.GetValue(model)); // 将属性名和新值存储到字典中
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // 应用差异到channels列表中的每个Channel对象
 | 
					        // 应用差异到channels列表中的每个Channel对象
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,6 +16,8 @@ using System.Runtime.CompilerServices;
 | 
				
			|||||||
using System.Text.Json;
 | 
					using System.Text.Json;
 | 
				
			||||||
using System.Text.RegularExpressions;
 | 
					using System.Text.RegularExpressions;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using ThingsGateway.Extension;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace ThingsGateway.Common.Extension;
 | 
					namespace ThingsGateway.Common.Extension;
 | 
				
			||||||
/// <summary>
 | 
					/// <summary>
 | 
				
			||||||
/// 对象拓展类
 | 
					/// 对象拓展类
 | 
				
			||||||
@@ -48,113 +50,7 @@ public static class ObjectExtensions
 | 
				
			|||||||
        bool IsTheRawGenericType(Type type) => generic == (type.IsGenericType ? type.GetGenericTypeDefinition() : type);
 | 
					        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>
 | 
					    /// <summary>
 | 
				
			||||||
    /// 合并两个字典
 | 
					    /// 合并两个字典
 | 
				
			||||||
@@ -186,7 +82,7 @@ public static class ObjectExtensions
 | 
				
			|||||||
    /// <typeparam name="T"></typeparam>
 | 
					    /// <typeparam name="T"></typeparam>
 | 
				
			||||||
    /// <param name="dic">字典</param>
 | 
					    /// <param name="dic">字典</param>
 | 
				
			||||||
    /// <param name="newDic">新字典</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)
 | 
					        foreach (var (key, value) in newDic)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -24,7 +24,7 @@ public static class ParallelExtensions
 | 
				
			|||||||
    /// <typeparam name="T">集合元素类型</typeparam>
 | 
					    /// <typeparam name="T">集合元素类型</typeparam>
 | 
				
			||||||
    /// <param name="source">要操作的集合</param>
 | 
					    /// <param name="source">要操作的集合</param>
 | 
				
			||||||
    /// <param name="body">要执行的操作</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();
 | 
					        ParallelOptions options = new();
 | 
				
			||||||
        options.MaxDegreeOfParallelism = Environment.ProcessorCount;
 | 
					        options.MaxDegreeOfParallelism = Environment.ProcessorCount;
 | 
				
			||||||
@@ -38,7 +38,7 @@ public static class ParallelExtensions
 | 
				
			|||||||
    /// <typeparam name="T">集合元素类型</typeparam>
 | 
					    /// <typeparam name="T">集合元素类型</typeparam>
 | 
				
			||||||
    /// <param name="source">要操作的集合</param>
 | 
					    /// <param name="source">要操作的集合</param>
 | 
				
			||||||
    /// <param name="body">要执行的操作</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();
 | 
					        ParallelOptions options = new();
 | 
				
			||||||
        options.MaxDegreeOfParallelism = Environment.ProcessorCount;
 | 
					        options.MaxDegreeOfParallelism = Environment.ProcessorCount;
 | 
				
			||||||
@@ -53,7 +53,7 @@ public static class ParallelExtensions
 | 
				
			|||||||
    /// <param name="source">要操作的集合</param>
 | 
					    /// <param name="source">要操作的集合</param>
 | 
				
			||||||
    /// <param name="body">要执行的操作</param>
 | 
					    /// <param name="body">要执行的操作</param>
 | 
				
			||||||
    /// <param name="parallelCount">最大并行度</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();
 | 
					        var options = new ParallelOptions();
 | 
				
			||||||
@@ -109,7 +109,7 @@ public static class ParallelExtensions
 | 
				
			|||||||
    /// <param name="parallelCount">最大并行度</param>
 | 
					    /// <param name="parallelCount">最大并行度</param>
 | 
				
			||||||
    /// <param name="cancellationToken">取消操作的标志</param>
 | 
					    /// <param name="cancellationToken">取消操作的标志</param>
 | 
				
			||||||
    /// <returns>表示异步操作的任务</returns>
 | 
					    /// <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();
 | 
					        var options = new ParallelOptions();
 | 
				
			||||||
@@ -126,7 +126,7 @@ public static class ParallelExtensions
 | 
				
			|||||||
    /// <param name="body">异步执行的操作</param>
 | 
					    /// <param name="body">异步执行的操作</param>
 | 
				
			||||||
    /// <param name="cancellationToken">取消操作的标志</param>
 | 
					    /// <param name="cancellationToken">取消操作的标志</param>
 | 
				
			||||||
    /// <returns>表示异步操作的任务</returns>
 | 
					    /// <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);
 | 
					        return ParallelForEachAsync(source, body, Environment.ProcessorCount, cancellationToken);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,6 +14,9 @@ using System.ComponentModel.DataAnnotations;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
using ThingsGateway.Common.Extension;
 | 
					using ThingsGateway.Common.Extension;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using Swashbuckle.AspNetCore.SwaggerGen;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#if NET8_0_OR_GREATER
 | 
					#if NET8_0_OR_GREATER
 | 
				
			||||||
using System.Collections.Frozen;
 | 
					using System.Collections.Frozen;
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
@@ -27,7 +30,7 @@ internal class CacheManager
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
    private IMemoryCache Cache { get; set; }
 | 
					    private IMemoryCache Cache { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private IServiceProvider Provider { get; set; }
 | 
					    private static IServiceProvider Provider => App.RootServices;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [NotNull]
 | 
					    [NotNull]
 | 
				
			||||||
    private static CacheManager? Instance { get; set; }
 | 
					    private static CacheManager? Instance { get; set; }
 | 
				
			||||||
@@ -40,8 +43,7 @@ internal class CacheManager
 | 
				
			|||||||
    static CacheManager()
 | 
					    static CacheManager()
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        Instance = new();
 | 
					        Instance = new();
 | 
				
			||||||
        Instance.Provider = App.RootServices;
 | 
					        Instance.Cache = Provider.GetRequiredService<IMemoryCache>();
 | 
				
			||||||
        Instance.Cache = Instance.Provider.GetRequiredService<IMemoryCache>();
 | 
					 | 
				
			||||||
        Options = App.RootServices.GetRequiredService<IOptions<BootstrapBlazorOptions>>().Value;
 | 
					        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>
 | 
				
			||||||
    /// 获得 缓存数量
 | 
					    /// 获得 缓存数量
 | 
				
			||||||
    /// </summary>
 | 
					    /// </summary>
 | 
				
			||||||
@@ -236,7 +207,7 @@ internal class CacheManager
 | 
				
			|||||||
    /// <returns></returns>
 | 
					    /// <returns></returns>
 | 
				
			||||||
    public static IStringLocalizer? CreateLocalizerByType(Type resourceSource) => resourceSource.Assembly.IsDynamic
 | 
					    public static IStringLocalizer? CreateLocalizerByType(Type resourceSource) => resourceSource.Assembly.IsDynamic
 | 
				
			||||||
        ? null
 | 
					        ? null
 | 
				
			||||||
        : Instance.Provider.GetRequiredService<IStringLocalizerFactory>().Create(resourceSource);
 | 
					        : Provider.GetRequiredService<IStringLocalizerFactory>().Create(resourceSource);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
    /// 获得 <see cref="JsonLocalizationOptions"/> 值
 | 
					    /// 获得 <see cref="JsonLocalizationOptions"/> 值
 | 
				
			||||||
@@ -244,7 +215,7 @@ internal class CacheManager
 | 
				
			|||||||
    /// <returns></returns>
 | 
					    /// <returns></returns>
 | 
				
			||||||
    private static JsonLocalizationOptions GetJsonLocalizationOption()
 | 
					    private static JsonLocalizationOptions GetJsonLocalizationOption()
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var localizationOptions = Instance.Provider.GetRequiredService<IOptions<JsonLocalizationOptions>>();
 | 
					        var localizationOptions = Provider.GetRequiredService<IOptions<JsonLocalizationOptions>>();
 | 
				
			||||||
        return localizationOptions.Value;
 | 
					        return localizationOptions.Value;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
@@ -253,7 +224,7 @@ internal class CacheManager
 | 
				
			|||||||
    /// <returns></returns>
 | 
					    /// <returns></returns>
 | 
				
			||||||
    private static BootstrapBlazorOptions GetBootstrapBlazorOption()
 | 
					    private static BootstrapBlazorOptions GetBootstrapBlazorOption()
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var localizationOptions = Instance.Provider.GetRequiredService<IOptions<BootstrapBlazorOptions>>();
 | 
					        var localizationOptions = Provider.GetRequiredService<IOptions<BootstrapBlazorOptions>>();
 | 
				
			||||||
        return localizationOptions.Value;
 | 
					        return localizationOptions.Value;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
@@ -269,7 +240,7 @@ internal class CacheManager
 | 
				
			|||||||
            return null;
 | 
					            return null;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        IStringLocalizer? ret = null;
 | 
					        IStringLocalizer? ret = null;
 | 
				
			||||||
        var factories = Instance.Provider.GetServices<IStringLocalizerFactory>();
 | 
					        var factories = Provider.GetServices<IStringLocalizerFactory>();
 | 
				
			||||||
        var factory = factories.LastOrDefault(a => a is not JsonStringLocalizerFactory);
 | 
					        var factory = factories.LastOrDefault(a => a is not JsonStringLocalizerFactory);
 | 
				
			||||||
        if (factory != null)
 | 
					        if (factory != null)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
@@ -287,8 +258,15 @@ internal class CacheManager
 | 
				
			|||||||
    /// </summary>
 | 
					    /// </summary>
 | 
				
			||||||
    /// <param name="assembly">Assembly 程序集实例</param>
 | 
					    /// <param name="assembly">Assembly 程序集实例</param>
 | 
				
			||||||
    /// <param name="typeName">类型名称</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);
 | 
					        => 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>
 | 
					    /// <summary>
 | 
				
			||||||
    /// 通过指定程序集获取所有本地化信息键值集合
 | 
					    /// 通过指定程序集获取所有本地化信息键值集合
 | 
				
			||||||
@@ -299,7 +277,7 @@ internal class CacheManager
 | 
				
			|||||||
    /// <param name="cultureName">cultureName 未空时使用 CultureInfo.CurrentUICulture.Name</param>
 | 
					    /// <param name="cultureName">cultureName 未空时使用 CultureInfo.CurrentUICulture.Name</param>
 | 
				
			||||||
    /// <param name="forceLoad">默认 false 使用缓存值 设置 true 时内部强制重新加载</param>
 | 
					    /// <param name="forceLoad">默认 false 使用缓存值 设置 true 时内部强制重新加载</param>
 | 
				
			||||||
    /// <returns></returns>
 | 
					    /// <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)
 | 
					        if (assembly.IsDynamic)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
@@ -309,13 +287,15 @@ internal class CacheManager
 | 
				
			|||||||
        cultureName ??= CultureInfo.CurrentUICulture.Name;
 | 
					        cultureName ??= CultureInfo.CurrentUICulture.Name;
 | 
				
			||||||
        if (string.IsNullOrEmpty(cultureName))
 | 
					        if (string.IsNullOrEmpty(cultureName))
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return [];
 | 
					            return null;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var key = $"{CacheKeyPrefix}-{nameof(GetJsonStringByTypeName)}-{assembly.GetUniqueName()}-{cultureName}";
 | 
					        var key = $"{CacheKeyPrefix}-{nameof(GetJsonStringByTypeName)}-{assembly.GetUniqueName()}-{cultureName}";
 | 
				
			||||||
 | 
					        var typeKey = $"{CacheKeyPrefix}-{nameof(GetJsonStringByTypeName)}-{assembly.GetUniqueName()}-{typeName}-{cultureName}";
 | 
				
			||||||
        if (forceLoad)
 | 
					        if (forceLoad)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            Instance.Cache.Remove(key);
 | 
					            Instance.Cache.Remove(key);
 | 
				
			||||||
 | 
					            Instance.Cache.Remove(typeKey);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var localizedItems = Instance.GetOrCreate(key, entry =>
 | 
					        var localizedItems = Instance.GetOrCreate(key, entry =>
 | 
				
			||||||
@@ -336,16 +316,77 @@ internal class CacheManager
 | 
				
			|||||||
            return items.ToHashSet();
 | 
					            return items.ToHashSet();
 | 
				
			||||||
#endif
 | 
					#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>
 | 
					    /// <summary>
 | 
				
			||||||
    /// 通过 ILocalizationResolve 接口实现类获得本地化键值集合
 | 
					    /// 通过指定程序集获取所有本地化信息键值集合
 | 
				
			||||||
    /// </summary>
 | 
					    /// </summary>
 | 
				
			||||||
    /// <param name="typeName"></param>
 | 
					    /// <param name="option">JsonLocalizationOptions 实例</param>
 | 
				
			||||||
    /// <param name="includeParentCultures"></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>
 | 
					    /// <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
 | 
					    #endregion
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    #region DisplayName
 | 
					    #region DisplayName
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -66,7 +66,8 @@ internal class JsonStringLocalizer(Assembly assembly, string typeName, string ba
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
                catch (Exception ex)
 | 
					                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;
 | 
					                return ret;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@@ -80,50 +81,16 @@ internal class JsonStringLocalizer(Assembly assembly, string typeName, string ba
 | 
				
			|||||||
    /// <returns></returns>
 | 
					    /// <returns></returns>
 | 
				
			||||||
    private string? GetStringSafely(string name) => GetStringFromJson(name);
 | 
					    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 readonly ConcurrentHashSet<string> _missingManifestCache = [];
 | 
				
			||||||
    private string? GetStringFromJson(string name)
 | 
					    private string? GetStringFromJson(string name)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        // get string from json localization file
 | 
					        // get string from json localization file
 | 
				
			||||||
        var localizerStrings = MegerResolveLocalizers(CacheManager.GetAllStringsByTypeName(Assembly, typeName));
 | 
					 | 
				
			||||||
        var cacheKey = $"name={name}&culture={CultureInfo.CurrentUICulture.Name}";
 | 
					        var cacheKey = $"name={name}&culture={CultureInfo.CurrentUICulture.Name}";
 | 
				
			||||||
        string? ret = null;
 | 
					        string? ret = null;
 | 
				
			||||||
        if (!_missingManifestCache.Contain(cacheKey))
 | 
					        if (!_missingManifestCache.Contain(cacheKey))
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var l = localizerStrings.Find(i => i.Name == name);
 | 
					            var localizerStrings = CacheManager.GetAllHasValueStringsByTypeName(Assembly, typeName);
 | 
				
			||||||
            if (l is { ResourceNotFound: false })
 | 
					            if (localizerStrings?.TryGetValue(name, out ret) != true)
 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                ret = l.Value;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            else
 | 
					 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                // 如果没有找到资源信息则尝试从父类中查找
 | 
					                // 如果没有找到资源信息则尝试从父类中查找
 | 
				
			||||||
                ret ??= GetStringFromBaseType(name);
 | 
					                ret ??= GetStringFromBaseType(name);
 | 
				
			||||||
@@ -149,39 +116,25 @@ internal class JsonStringLocalizer(Assembly assembly, string typeName, string ba
 | 
				
			|||||||
            if (baseType != type)
 | 
					            if (baseType != type)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                var baseAssembly = baseType.Assembly;
 | 
					                var baseAssembly = baseType.Assembly;
 | 
				
			||||||
                var localizerStrings = MegerResolveLocalizers(CacheManager.GetAllStringsByTypeName(baseAssembly, baseType.FullName!));
 | 
					                var localizerStrings = CacheManager.GetAllHasValueStringsByTypeName(baseAssembly, baseType.FullName!);
 | 
				
			||||||
                var l = localizerStrings.Find(i => i.Name == name);
 | 
					                _ = localizerStrings?.TryGetValue(name, out ret);
 | 
				
			||||||
                if (l is { ResourceNotFound: false })
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    ret = l.Value;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return 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)
 | 
					    private void HandleMissingResourceItem(string name)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        localizationMissingItemHandler.HandleMissingItem(name, typeName, CultureInfo.CurrentUICulture.Name);
 | 
					        localizationMissingItemHandler.HandleMissingItem(name, typeName, CultureInfo.CurrentUICulture.Name);
 | 
				
			||||||
        if (jsonLocalizationOptions.IgnoreLocalizerMissing == false)
 | 
					        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}");
 | 
					        _missingManifestCache.TryAdd($"name={name}&culture={CultureInfo.CurrentUICulture.Name}");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private List<LocalizedString>? _allLocalizerdStrings;
 | 
					    private LocalizedString[]? _allLocalizerdStrings;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
    /// 获取当前语言的所有资源信息
 | 
					    /// 获取当前语言的所有资源信息
 | 
				
			||||||
@@ -196,7 +149,7 @@ internal class JsonStringLocalizer(Assembly assembly, string typeName, string ba
 | 
				
			|||||||
                ?? GetAllStringsFromBase()
 | 
					                ?? GetAllStringsFromBase()
 | 
				
			||||||
                ?? GetAllStringsFromJson();
 | 
					                ?? GetAllStringsFromJson();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            _allLocalizerdStrings = MegerResolveLocalizers(items);
 | 
					            _allLocalizerdStrings = items.ToArray();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return _allLocalizerdStrings;
 | 
					        return _allLocalizerdStrings;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,13 +8,13 @@
 | 
				
			|||||||
		
 | 
							
 | 
				
			||||||
	</PropertyGroup>
 | 
						</PropertyGroup>
 | 
				
			||||||
	<PropertyGroup>
 | 
						<PropertyGroup>
 | 
				
			||||||
		<TargetFrameworks>net8.0;net9.0;</TargetFrameworks>
 | 
							<TargetFrameworks>net8.0;$(OtherTargetFrameworks);</TargetFrameworks>
 | 
				
			||||||
	</PropertyGroup>
 | 
						</PropertyGroup>
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	<ItemGroup>
 | 
						<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="Yitter.IdGenerator" Version="1.0.14" />
 | 
				
			||||||
		<PackageReference Include="BootstrapBlazor" Version="9.10.1" />
 | 
							<PackageReference Include="BootstrapBlazor" Version="9.11.4" />
 | 
				
			||||||
	</ItemGroup>
 | 
						</ItemGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<ItemGroup>
 | 
						<ItemGroup>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -27,10 +27,15 @@ public abstract class PrimaryIdEntity : IPrimaryIdEntity
 | 
				
			|||||||
    public virtual long Id { get; set; }
 | 
					    public virtual long Id { get; set; }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public interface IPrimaryKeyEntity
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    string ExtJson { get; set; }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// <summary>
 | 
					/// <summary>
 | 
				
			||||||
/// 主键实体基类
 | 
					/// 主键实体基类
 | 
				
			||||||
/// </summary>
 | 
					/// </summary>
 | 
				
			||||||
public abstract class PrimaryKeyEntity : PrimaryIdEntity
 | 
					public abstract class PrimaryKeyEntity : PrimaryIdEntity, IPrimaryKeyEntity
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
    /// 拓展信息
 | 
					    /// 拓展信息
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,7 +8,7 @@
 | 
				
			|||||||
		
 | 
							
 | 
				
			||||||
	</PropertyGroup>
 | 
						</PropertyGroup>
 | 
				
			||||||
	<PropertyGroup>
 | 
						<PropertyGroup>
 | 
				
			||||||
		<TargetFrameworks>net8.0;net9.0;</TargetFrameworks>
 | 
							<TargetFrameworks>net8.0;$(OtherTargetFrameworks);</TargetFrameworks>
 | 
				
			||||||
	</PropertyGroup>
 | 
						</PropertyGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,7 +12,7 @@
 | 
				
			|||||||
using Microsoft.AspNetCore.Hosting;
 | 
					using Microsoft.AspNetCore.Hosting;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
using ThingsGateway;
 | 
					using ThingsGateway;
 | 
				
			||||||
using ThingsGateway.Extensions;
 | 
					using ThingsGateway.Extension;
 | 
				
			||||||
using ThingsGateway.Reflection;
 | 
					using ThingsGateway.Reflection;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Microsoft.Extensions.Hosting;
 | 
					namespace Microsoft.Extensions.Hosting;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,7 +20,7 @@ using System.Text.RegularExpressions;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
using ThingsGateway.NewLife;
 | 
					using ThingsGateway.NewLife;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace ThingsGateway.Extensions;
 | 
					namespace ThingsGateway.Extension;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// <summary>
 | 
					/// <summary>
 | 
				
			||||||
/// 对象拓展类
 | 
					/// 对象拓展类
 | 
				
			||||||
@@ -28,70 +28,10 @@ namespace ThingsGateway.Extensions;
 | 
				
			|||||||
[SuppressSniffer]
 | 
					[SuppressSniffer]
 | 
				
			||||||
public static class ObjectExtensions
 | 
					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>
 | 
					    /// <summary>
 | 
				
			||||||
    /// 将 IFormFile 转换成 byte[]
 | 
					    /// 将 IFormFile 转换成 byte[]
 | 
				
			||||||
@@ -265,7 +205,7 @@ public static class ObjectExtensions
 | 
				
			|||||||
    /// <typeparam name="T"></typeparam>
 | 
					    /// <typeparam name="T"></typeparam>
 | 
				
			||||||
    /// <param name="dic">字典</param>
 | 
					    /// <param name="dic">字典</param>
 | 
				
			||||||
    /// <param name="newDic">新字典</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)
 | 
					        foreach (var (key, value) in newDic)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -94,7 +94,7 @@ public static class AspNetCoreBuilderServiceCollectionExtensions
 | 
				
			|||||||
    /// <param name="mvcBuilder"></param>
 | 
					    /// <param name="mvcBuilder"></param>
 | 
				
			||||||
    /// <param name="configure"></param>
 | 
					    /// <param name="configure"></param>
 | 
				
			||||||
    /// <returns></returns>
 | 
					    /// <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);
 | 
					        mvcBuilder.Services.AddFromConvertBinding(configure);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -107,13 +107,13 @@ public static class AspNetCoreBuilderServiceCollectionExtensions
 | 
				
			|||||||
    /// <param name="services"></param>
 | 
					    /// <param name="services"></param>
 | 
				
			||||||
    /// <param name="configure"></param>
 | 
					    /// <param name="configure"></param>
 | 
				
			||||||
    /// <returns></returns>
 | 
					    /// <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 环境跳过注册
 | 
					        // 非 Web 环境跳过注册
 | 
				
			||||||
        if (App.WebHostEnvironment == default) return services;
 | 
					        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(DateTime), typeof(DateTimeModelConvertBinder));
 | 
				
			||||||
        modelBinderConverts.TryAdd(typeof(DateTimeOffset), typeof(DateTimeOffsetModelConvertBinder));
 | 
					        modelBinderConverts.TryAdd(typeof(DateTimeOffset), typeof(DateTimeOffsetModelConvertBinder));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,7 +11,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
 | 
					using Microsoft.AspNetCore.Mvc.ModelBinding;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
using ThingsGateway.Extensions;
 | 
					using ThingsGateway.Extension;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace ThingsGateway.AspNetCore;
 | 
					namespace ThingsGateway.AspNetCore;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -27,13 +27,13 @@ public class FromConvertBinder : IModelBinder
 | 
				
			|||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
    /// 定义模型绑定转换器集合
 | 
					    /// 定义模型绑定转换器集合
 | 
				
			||||||
    /// </summary>
 | 
					    /// </summary>
 | 
				
			||||||
    private readonly ConcurrentDictionary<Type, Type> _modelBinderConverts;
 | 
					    private readonly NonBlockingDictionary<Type, Type> _modelBinderConverts;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
    /// 构造函数
 | 
					    /// 构造函数
 | 
				
			||||||
    /// </summary>
 | 
					    /// </summary>
 | 
				
			||||||
    /// <param name="modelBinderConverts">定义模型绑定转换器集合</param>
 | 
					    /// <param name="modelBinderConverts">定义模型绑定转换器集合</param>
 | 
				
			||||||
    public FromConvertBinder(ConcurrentDictionary<Type, Type> modelBinderConverts)
 | 
					    public FromConvertBinder(NonBlockingDictionary<Type, Type> modelBinderConverts)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        _modelBinderConverts = modelBinderConverts;
 | 
					        _modelBinderConverts = modelBinderConverts;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -28,13 +28,13 @@ public class FromConvertBinderProvider : IModelBinderProvider
 | 
				
			|||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
    /// 定义模型绑定转换器集合
 | 
					    /// 定义模型绑定转换器集合
 | 
				
			||||||
    /// </summary>
 | 
					    /// </summary>
 | 
				
			||||||
    private readonly ConcurrentDictionary<Type, Type> _modelBinderConverts;
 | 
					    private readonly NonBlockingDictionary<Type, Type> _modelBinderConverts;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
    /// 构造函数
 | 
					    /// 构造函数
 | 
				
			||||||
    /// </summary>
 | 
					    /// </summary>
 | 
				
			||||||
    /// <param name="modelBinderConverts">定义模型绑定转换器集合</param>
 | 
					    /// <param name="modelBinderConverts">定义模型绑定转换器集合</param>
 | 
				
			||||||
    public FromConvertBinderProvider(ConcurrentDictionary<Type, Type> modelBinderConverts)
 | 
					    public FromConvertBinderProvider(NonBlockingDictionary<Type, Type> modelBinderConverts)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        _modelBinderConverts = modelBinderConverts;
 | 
					        _modelBinderConverts = modelBinderConverts;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,7 +11,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
 | 
					using Microsoft.AspNetCore.Mvc.ModelBinding;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
using ThingsGateway.Extensions;
 | 
					using ThingsGateway.Extension;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace ThingsGateway.AspNetCore;
 | 
					namespace ThingsGateway.AspNetCore;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,7 +12,7 @@
 | 
				
			|||||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
 | 
					using Microsoft.AspNetCore.Mvc.ModelBinding;
 | 
				
			||||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
 | 
					using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
using ThingsGateway.Extensions;
 | 
					using ThingsGateway.Extension;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace ThingsGateway.AspNetCore;
 | 
					namespace ThingsGateway.AspNetCore;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -18,7 +18,7 @@ using System.Reflection;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
using ThingsGateway;
 | 
					using ThingsGateway;
 | 
				
			||||||
using ThingsGateway.ConfigurableOptions;
 | 
					using ThingsGateway.ConfigurableOptions;
 | 
				
			||||||
using ThingsGateway.Extensions;
 | 
					using ThingsGateway.Extension;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Microsoft.Extensions.DependencyInjection;
 | 
					namespace Microsoft.Extensions.DependencyInjection;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -34,9 +34,12 @@ public static class PBKDF2Encryption
 | 
				
			|||||||
        using var rng = RandomNumberGenerator.Create();
 | 
					        using var rng = RandomNumberGenerator.Create();
 | 
				
			||||||
        var salt = new byte[saltSize];
 | 
					        var salt = new byte[saltSize];
 | 
				
			||||||
        rng.GetBytes(salt);
 | 
					        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);
 | 
					        using var pbkdf2 = new Rfc2898DeriveBytes(text, salt, iterationCount, HashAlgorithmName.SHA256);
 | 
				
			||||||
        var hash = pbkdf2.GetBytes(derivedKeyLength);
 | 
					        var hash = pbkdf2.GetBytes(derivedKeyLength);
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // 分别编码盐和哈希,并用分隔符拼接
 | 
					        // 分别编码盐和哈希,并用分隔符拼接
 | 
				
			||||||
        return Convert.ToBase64String(salt) + SaltHashSeparator + Convert.ToBase64String(hash);
 | 
					        return Convert.ToBase64String(salt) + SaltHashSeparator + Convert.ToBase64String(hash);
 | 
				
			||||||
@@ -65,8 +68,12 @@ public static class PBKDF2Encryption
 | 
				
			|||||||
            if (saltBytes.Length != saltSize || storedHashBytes.Length != derivedKeyLength)
 | 
					            if (saltBytes.Length != saltSize || storedHashBytes.Length != derivedKeyLength)
 | 
				
			||||||
                return false;
 | 
					                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);
 | 
					            using var pbkdf2 = new Rfc2898DeriveBytes(text, saltBytes, iterationCount, HashAlgorithmName.SHA256);
 | 
				
			||||||
            var computedHash = pbkdf2.GetBytes(derivedKeyLength);
 | 
					            var computedHash = pbkdf2.GetBytes(derivedKeyLength);
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return computedHash.SequenceEqual(storedHashBytes);
 | 
					            return computedHash.SequenceEqual(storedHashBytes);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,7 +16,7 @@ using System.ComponentModel.DataAnnotations;
 | 
				
			|||||||
using System.Reflection;
 | 
					using System.Reflection;
 | 
				
			||||||
using System.Text.RegularExpressions;
 | 
					using System.Text.RegularExpressions;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
using ThingsGateway.Extensions;
 | 
					using ThingsGateway.Extension;
 | 
				
			||||||
using ThingsGateway.Templates.Extensions;
 | 
					using ThingsGateway.Templates.Extensions;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace ThingsGateway.DataValidation;
 | 
					namespace ThingsGateway.DataValidation;
 | 
				
			||||||
@@ -40,7 +40,7 @@ public static class DataValidator
 | 
				
			|||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
    /// 验证类型正则表达式
 | 
					    /// 验证类型正则表达式
 | 
				
			||||||
    /// </summary>
 | 
					    /// </summary>
 | 
				
			||||||
    private static readonly ConcurrentDictionary<string, ValidationItemMetadataAttribute> ValidationItemMetadatas;
 | 
					    private static readonly NonBlockingDictionary<string, ValidationItemMetadataAttribute> ValidationItemMetadatas;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
    /// 构造函数
 | 
					    /// 构造函数
 | 
				
			||||||
@@ -57,7 +57,7 @@ public static class DataValidator
 | 
				
			|||||||
        ValidationItemMetadatas = GetValidationValidationItemMetadatas();
 | 
					        ValidationItemMetadatas = GetValidationValidationItemMetadatas();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // 缓存所有正则表达式
 | 
					        // 缓存所有正则表达式
 | 
				
			||||||
        GetValidationTypeValidationItemMetadataCached = new ConcurrentDictionary<object, (string, ValidationItemMetadataAttribute)>();
 | 
					        GetValidationTypeValidationItemMetadataCached = new NonBlockingDictionary<object, (string, ValidationItemMetadataAttribute)>();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
@@ -203,7 +203,7 @@ public static class DataValidator
 | 
				
			|||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
    /// 获取验证类型验证Item集合
 | 
					    /// 获取验证类型验证Item集合
 | 
				
			||||||
    /// </summary>
 | 
					    /// </summary>
 | 
				
			||||||
    private static readonly ConcurrentDictionary<object, (string, ValidationItemMetadataAttribute)> GetValidationTypeValidationItemMetadataCached;
 | 
					    private static readonly NonBlockingDictionary<object, (string, ValidationItemMetadataAttribute)> GetValidationTypeValidationItemMetadataCached;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
    /// 获取验证类型正则表达式(需要缓存)
 | 
					    /// 获取验证类型正则表达式(需要缓存)
 | 
				
			||||||
@@ -267,9 +267,9 @@ public static class DataValidator
 | 
				
			|||||||
    /// 获取验证类型所有有效的正则表达式
 | 
					    /// 获取验证类型所有有效的正则表达式
 | 
				
			||||||
    /// </summary>
 | 
					    /// </summary>
 | 
				
			||||||
    /// <returns></returns>
 | 
					    /// <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] 消息定义
 | 
					        // 查找所有 [ValidationMessageType] 类型中的 [ValidationMessage] 消息定义
 | 
				
			||||||
        var customErrorMessages = ValidationMessageTypes.SelectMany(u => u.GetFields()
 | 
					        var customErrorMessages = ValidationMessageTypes.SelectMany(u => u.GetFields()
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -353,7 +353,7 @@ public static class DependencyInjectionServiceCollectionExtensions
 | 
				
			|||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
    /// 类型名称集合
 | 
					    /// 类型名称集合
 | 
				
			||||||
    /// </summary>
 | 
					    /// </summary>
 | 
				
			||||||
    private static readonly ConcurrentDictionary<string, Type> TypeNamedCollection;
 | 
					    private static readonly NonBlockingDictionary<string, Type> TypeNamedCollection;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
    /// 创建代理方法
 | 
					    /// 创建代理方法
 | 
				
			||||||
@@ -374,7 +374,7 @@ public static class DependencyInjectionServiceCollectionExtensions
 | 
				
			|||||||
        GlobalServiceProxyType = App.EffectiveTypes
 | 
					        GlobalServiceProxyType = App.EffectiveTypes
 | 
				
			||||||
            .FirstOrDefault(u => typeof(AspectDispatchProxy).IsAssignableFrom(u) && typeof(IGlobalDispatchProxy).IsAssignableFrom(u) && u.IsClass && !u.IsInterface && !u.IsAbstract);
 | 
					            .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));
 | 
					        DispatchCreateMethod = typeof(AspectDispatchProxy).GetMethod(nameof(AspectDispatchProxy.Create));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -21,7 +21,7 @@ using System.Collections.Concurrent;
 | 
				
			|||||||
using System.Reflection;
 | 
					using System.Reflection;
 | 
				
			||||||
using System.Text.RegularExpressions;
 | 
					using System.Text.RegularExpressions;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
using ThingsGateway.Extensions;
 | 
					using ThingsGateway.Extension;
 | 
				
			||||||
using ThingsGateway.UnifyResult;
 | 
					using ThingsGateway.UnifyResult;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace ThingsGateway.DynamicApiController;
 | 
					namespace ThingsGateway.DynamicApiController;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -28,21 +28,21 @@ internal static class Penetrates
 | 
				
			|||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
    /// 请求动词映射字典
 | 
					    /// 请求动词映射字典
 | 
				
			||||||
    /// </summary>
 | 
					    /// </summary>
 | 
				
			||||||
    internal static ConcurrentDictionary<string, string> VerbToHttpMethods { get; private set; }
 | 
					    internal static NonBlockingDictionary<string, string> VerbToHttpMethods { get; private set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
    /// 控制器排序集合
 | 
					    /// 控制器排序集合
 | 
				
			||||||
    /// </summary>
 | 
					    /// </summary>
 | 
				
			||||||
    internal static ConcurrentDictionary<string, (string, int, Type)> ControllerOrderCollection { get; set; }
 | 
					    internal static NonBlockingDictionary<string, (string, int, Type)> ControllerOrderCollection { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
    /// 构造函数
 | 
					    /// 构造函数
 | 
				
			||||||
    /// </summary>
 | 
					    /// </summary>
 | 
				
			||||||
    static Penetrates()
 | 
					    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",
 | 
					            ["post"] = "POST",
 | 
				
			||||||
            ["add"] = "POST",
 | 
					            ["add"] = "POST",
 | 
				
			||||||
@@ -67,13 +67,13 @@ internal static class Penetrates
 | 
				
			|||||||
            ["patch"] = "PATCH"
 | 
					            ["patch"] = "PATCH"
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        //IsApiControllerCached = new ConcurrentDictionary<Type, bool>();
 | 
					        //IsApiControllerCached = new NonBlockingDictionary<Type, bool>();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ///// <summary>
 | 
					    ///// <summary>
 | 
				
			||||||
    ///// <see cref="IsApiController(Type)"/> 缓存集合
 | 
					    ///// <see cref="IsApiController(Type)"/> 缓存集合
 | 
				
			||||||
    ///// </summary>
 | 
					    ///// </summary>
 | 
				
			||||||
    //private static readonly ConcurrentDictionary<Type, bool> IsApiControllerCached;
 | 
					    //private static readonly NonBlockingDictionary<Type, bool> IsApiControllerCached;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
    /// 是否是Api控制器
 | 
					    /// 是否是Api控制器
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -61,7 +61,7 @@ internal sealed class EventBusHostedService : BackgroundService
 | 
				
			|||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
    /// 事件处理程序集合
 | 
					    /// 事件处理程序集合
 | 
				
			||||||
    /// </summary>
 | 
					    /// </summary>
 | 
				
			||||||
    private readonly ConcurrentDictionary<EventHandlerWrapper, EventHandlerWrapper> _eventHandlers = new();
 | 
					    private readonly NonBlockingDictionary<EventHandlerWrapper, EventHandlerWrapper> _eventHandlers = new();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
    /// 构造函数
 | 
					    /// 构造函数
 | 
				
			||||||
@@ -295,7 +295,8 @@ internal sealed class EventBusHostedService : BackgroundService
 | 
				
			|||||||
                        , retryAction: (total, times) =>
 | 
					                        , 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);
 | 
					                        }).ConfigureAwait(false);
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    else
 | 
					                    else
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,7 +17,7 @@ using System.ComponentModel.DataAnnotations;
 | 
				
			|||||||
using System.Diagnostics;
 | 
					using System.Diagnostics;
 | 
				
			||||||
using System.Reflection;
 | 
					using System.Reflection;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
using ThingsGateway.Extensions;
 | 
					using ThingsGateway.Extension;
 | 
				
			||||||
using ThingsGateway.Templates.Extensions;
 | 
					using ThingsGateway.Templates.Extensions;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace ThingsGateway.FriendlyException;
 | 
					namespace ThingsGateway.FriendlyException;
 | 
				
			||||||
@@ -31,7 +31,7 @@ public static class Oops
 | 
				
			|||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
    /// 方法错误异常特性
 | 
					    /// 方法错误异常特性
 | 
				
			||||||
    /// </summary>
 | 
					    /// </summary>
 | 
				
			||||||
    private static readonly ConcurrentDictionary<MethodBase, MethodIfException> _errorMethods;
 | 
					    private static readonly NonBlockingDictionary<MethodBase, MethodIfException> _errorMethods;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
    /// 错误代码类型
 | 
					    /// 错误代码类型
 | 
				
			||||||
@@ -41,7 +41,7 @@ public static class Oops
 | 
				
			|||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
    /// 错误消息字典
 | 
					    /// 错误消息字典
 | 
				
			||||||
    /// </summary>
 | 
					    /// </summary>
 | 
				
			||||||
    private static readonly ConcurrentDictionary<string, string> _errorCodeMessages;
 | 
					    private static readonly NonBlockingDictionary<string, string> _errorCodeMessages;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
    /// 友好异常设置
 | 
					    /// 友好异常设置
 | 
				
			||||||
@@ -53,7 +53,7 @@ public static class Oops
 | 
				
			|||||||
    /// </summary>
 | 
					    /// </summary>
 | 
				
			||||||
    static Oops()
 | 
					    static Oops()
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        _errorMethods = new ConcurrentDictionary<MethodBase, MethodIfException>();
 | 
					        _errorMethods = new NonBlockingDictionary<MethodBase, MethodIfException>();
 | 
				
			||||||
        _friendlyExceptionSettings = App.GetConfig<FriendlyExceptionSettingsOptions>("FriendlyExceptionSettings", true);
 | 
					        _friendlyExceptionSettings = App.GetConfig<FriendlyExceptionSettingsOptions>("FriendlyExceptionSettings", true);
 | 
				
			||||||
        _errorCodeTypes = GetErrorCodeTypes();
 | 
					        _errorCodeTypes = GetErrorCodeTypes();
 | 
				
			||||||
        _errorCodeMessages = GetErrorCodeMessages();
 | 
					        _errorCodeMessages = GetErrorCodeMessages();
 | 
				
			||||||
@@ -258,9 +258,9 @@ public static class Oops
 | 
				
			|||||||
    /// 获取所有错误消息
 | 
					    /// 获取所有错误消息
 | 
				
			||||||
    /// </summary>
 | 
					    /// </summary>
 | 
				
			||||||
    /// <returns></returns>
 | 
					    /// <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] 元数据定义
 | 
					        // 查找所有 [ErrorCodeType] 类型中的 [ErrorCodeMetadata] 元数据定义
 | 
				
			||||||
        var errorCodeMessages = _errorCodeTypes.SelectMany(u => u.GetFields().Where(u => u.IsDefined(typeof(ErrorCodeItemMetadataAttribute))))
 | 
					        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 System.Reflection;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
using ThingsGateway;
 | 
					using ThingsGateway;
 | 
				
			||||||
using ThingsGateway.Extensions;
 | 
					using ThingsGateway.Extension;
 | 
				
			||||||
using ThingsGateway.InstantMessaging;
 | 
					using ThingsGateway.InstantMessaging;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Microsoft.AspNetCore.Builder;
 | 
					namespace Microsoft.AspNetCore.Builder;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -23,7 +23,7 @@ public static class ILoggerExtensions
 | 
				
			|||||||
    /// 设置日志上下文
 | 
					    /// 设置日志上下文
 | 
				
			||||||
    /// </summary>
 | 
					    /// </summary>
 | 
				
			||||||
    /// <param name="logger"></param>
 | 
					    /// <param name="logger"></param>
 | 
				
			||||||
    /// <param name="properties">建议使用 ConcurrentDictionary 类型</param>
 | 
					    /// <param name="properties">建议使用 NonBlockingDictionary 类型</param>
 | 
				
			||||||
    /// <returns></returns>
 | 
					    /// <returns></returns>
 | 
				
			||||||
    public static IDisposable ScopeContext(this ILogger logger, IDictionary<string, object> properties)
 | 
					    public static IDisposable ScopeContext(this ILogger logger, IDictionary<string, object> properties)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,7 +9,7 @@
 | 
				
			|||||||
// 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。
 | 
					// 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。
 | 
				
			||||||
// ------------------------------------------------------------------------
 | 
					// ------------------------------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
using ThingsGateway.Extensions;
 | 
					using ThingsGateway.Extension;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace ThingsGateway.Logging;
 | 
					namespace ThingsGateway.Logging;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -82,7 +82,7 @@ public static class StringLoggingExtensions
 | 
				
			|||||||
    /// 配置日志上下文
 | 
					    /// 配置日志上下文
 | 
				
			||||||
    /// </summary>
 | 
					    /// </summary>
 | 
				
			||||||
    /// <param name="message"></param>
 | 
					    /// <param name="message"></param>
 | 
				
			||||||
    /// <param name="properties">建议使用 ConcurrentDictionary 类型</param>
 | 
					    /// <param name="properties">建议使用 NonBlockingDictionary 类型</param>
 | 
				
			||||||
    /// <returns></returns>
 | 
					    /// <returns></returns>
 | 
				
			||||||
    public static StringLoggingPart ScopeContext(this string message, IDictionary<string, object> properties)
 | 
					    public static StringLoggingPart ScopeContext(this string message, IDictionary<string, object> properties)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -44,7 +44,7 @@ public sealed class FileLoggerProvider : ILoggerProvider, ISupportExternalScope
 | 
				
			|||||||
    /// 记录日志所有滚动文件名
 | 
					    /// 记录日志所有滚动文件名
 | 
				
			||||||
    /// </summary>
 | 
					    /// </summary>
 | 
				
			||||||
    /// <remarks>只有 MaxRollingFiles 和 FileSizeLimitBytes 大于 0 有效</remarks>
 | 
					    /// <remarks>只有 MaxRollingFiles 和 FileSizeLimitBytes 大于 0 有效</remarks>
 | 
				
			||||||
    internal readonly ConcurrentDictionary<string, FileInfo> _rollingFileNames = new();
 | 
					    internal readonly NonBlockingDictionary<string, FileInfo> _rollingFileNames = new();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
    /// 文件日志写入器
 | 
					    /// 文件日志写入器
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -36,7 +36,7 @@ using System.Text.Json;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
using ThingsGateway;
 | 
					using ThingsGateway;
 | 
				
			||||||
using ThingsGateway.DataValidation;
 | 
					using ThingsGateway.DataValidation;
 | 
				
			||||||
using ThingsGateway.Extensions;
 | 
					using ThingsGateway.Extension;
 | 
				
			||||||
using ThingsGateway.FriendlyException;
 | 
					using ThingsGateway.FriendlyException;
 | 
				
			||||||
using ThingsGateway.JsonSerialization;
 | 
					using ThingsGateway.JsonSerialization;
 | 
				
			||||||
using ThingsGateway.Logging;
 | 
					using ThingsGateway.Logging;
 | 
				
			||||||
@@ -151,7 +151,7 @@ public sealed class LoggingMonitorAttribute : Attribute, IAsyncActionFilter, IAs
 | 
				
			|||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        await MonitorAsync(actionMethod, context.ActionArguments, context, next).ConfigureAwait(false);
 | 
					        await MonitorAsync(actionMethod, context.ActionArguments, context, () => next()).ConfigureAwait(false);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
@@ -183,7 +183,7 @@ public sealed class LoggingMonitorAttribute : Attribute, IAsyncActionFilter, IAs
 | 
				
			|||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        await MonitorAsync(actionMethod, context.HandlerArguments, context, next).ConfigureAwait(false);
 | 
					        await MonitorAsync(actionMethod, context.HandlerArguments, context, () => next()).ConfigureAwait(false);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
@@ -789,12 +789,12 @@ public sealed class LoggingMonitorAttribute : Attribute, IAsyncActionFilter, IAs
 | 
				
			|||||||
        return typeName;
 | 
					        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 请求处理
 | 
					        // 排除 WebSocket 请求处理
 | 
				
			||||||
        if (context.HttpContext.IsWebSocketRequest())
 | 
					        if (context.HttpContext.IsWebSocketRequest())
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            _ = await next();
 | 
					            _ = await next().ConfigureAwait(false);
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -805,14 +805,14 @@ public sealed class LoggingMonitorAttribute : Attribute, IAsyncActionFilter, IAs
 | 
				
			|||||||
        if (actionMethod.IsDefined(typeof(SuppressMonitorAttribute), true)
 | 
					        if (actionMethod.IsDefined(typeof(SuppressMonitorAttribute), true)
 | 
				
			||||||
            || actionMethod.DeclaringType.IsDefined(typeof(SuppressMonitorAttribute), true))
 | 
					            || actionMethod.DeclaringType.IsDefined(typeof(SuppressMonitorAttribute), true))
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            _ = await next();
 | 
					            _ = await next().ConfigureAwait(false);
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // 判断是否自定义了日志筛选器,如果是则检查是否符合条件
 | 
					        // 判断是否自定义了日志筛选器,如果是则检查是否符合条件
 | 
				
			||||||
        if (LoggingMonitorSettings.InternalWriteFilter?.Invoke(context) == false)
 | 
					        if (LoggingMonitorSettings.InternalWriteFilter?.Invoke(context) == false)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            _ = await next();
 | 
					            _ = await next().ConfigureAwait(false);
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -825,7 +825,7 @@ public sealed class LoggingMonitorAttribute : Attribute, IAsyncActionFilter, IAs
 | 
				
			|||||||
        // 解决局部和全局触发器同时配置触发两次问题
 | 
					        // 解决局部和全局触发器同时配置触发两次问题
 | 
				
			||||||
        if (isDefinedScopedAttribute && Settings.FromGlobalFilter == true)
 | 
					        if (isDefinedScopedAttribute && Settings.FromGlobalFilter == true)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            _ = await next();
 | 
					            _ = await next().ConfigureAwait(false);
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -839,7 +839,7 @@ public sealed class LoggingMonitorAttribute : Attribute, IAsyncActionFilter, IAs
 | 
				
			|||||||
                    && !Settings.IncludeOfMethods.Contains(methodFullName, StringComparer.OrdinalIgnoreCase))
 | 
					                    && !Settings.IncludeOfMethods.Contains(methodFullName, StringComparer.OrdinalIgnoreCase))
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    // 查找是否包含匹配,忽略大小写
 | 
					                    // 查找是否包含匹配,忽略大小写
 | 
				
			||||||
                    _ = await next();
 | 
					                    _ = await next().ConfigureAwait(false);
 | 
				
			||||||
                    return;
 | 
					                    return;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -847,7 +847,7 @@ public sealed class LoggingMonitorAttribute : Attribute, IAsyncActionFilter, IAs
 | 
				
			|||||||
                if (Settings.GlobalEnabled
 | 
					                if (Settings.GlobalEnabled
 | 
				
			||||||
                    && Settings.ExcludeOfMethods.Contains(methodFullName, StringComparer.OrdinalIgnoreCase))
 | 
					                    && Settings.ExcludeOfMethods.Contains(methodFullName, StringComparer.OrdinalIgnoreCase))
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    _ = await next();
 | 
					                    _ = await next().ConfigureAwait(false);
 | 
				
			||||||
                    return;
 | 
					                    return;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@@ -958,7 +958,8 @@ public sealed class LoggingMonitorAttribute : Attribute, IAsyncActionFilter, IAs
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        // 计算接口执行时间
 | 
					        // 计算接口执行时间
 | 
				
			||||||
        var timeOperation = Stopwatch.StartNew();
 | 
					        var timeOperation = Stopwatch.StartNew();
 | 
				
			||||||
        var resultContext = await next();
 | 
					
 | 
				
			||||||
 | 
					        var resultContext = await next().ConfigureAwait(false);
 | 
				
			||||||
        timeOperation.Stop();
 | 
					        timeOperation.Stop();
 | 
				
			||||||
        writer.WriteNumber("timeOperationElapsedMilliseconds", timeOperation.ElapsedMilliseconds);
 | 
					        writer.WriteNumber("timeOperationElapsedMilliseconds", timeOperation.ElapsedMilliseconds);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1014,8 +1015,13 @@ public sealed class LoggingMonitorAttribute : Attribute, IAsyncActionFilter, IAs
 | 
				
			|||||||
        var environment = httpContext.RequestServices.GetRequiredService<IWebHostEnvironment>().EnvironmentName;
 | 
					        var environment = httpContext.RequestServices.GetRequiredService<IWebHostEnvironment>().EnvironmentName;
 | 
				
			||||||
        writer.WriteString(nameof(environment), environment);
 | 
					        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)
 | 
					        if (exception == null)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            // 解析存储的验证信息
 | 
					            // 解析存储的验证信息
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -94,7 +94,7 @@ public sealed partial class StringLoggingPart
 | 
				
			|||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
    /// 配置日志上下文
 | 
					    /// 配置日志上下文
 | 
				
			||||||
    /// </summary>
 | 
					    /// </summary>
 | 
				
			||||||
    /// <param name="properties">建议使用 ConcurrentDictionary 类型</param>
 | 
					    /// <param name="properties">建议使用 NonBlockingDictionary 类型</param>
 | 
				
			||||||
    /// <returns></returns>
 | 
					    /// <returns></returns>
 | 
				
			||||||
    public StringLoggingPart ScopeContext(IDictionary<string, object> properties)
 | 
					    public StringLoggingPart ScopeContext(IDictionary<string, object> properties)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -57,7 +57,7 @@ public static class Log
 | 
				
			|||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
    /// 配置日志上下文
 | 
					    /// 配置日志上下文
 | 
				
			||||||
    /// </summary>
 | 
					    /// </summary>
 | 
				
			||||||
    /// <param name="properties">建议使用 ConcurrentDictionary 类型</param>
 | 
					    /// <param name="properties">建议使用 NonBlockingDictionary 类型</param>
 | 
				
			||||||
    /// <returns></returns>
 | 
					    /// <returns></returns>
 | 
				
			||||||
    public static (ILogger logger, IDisposable scope) ScopeContext(IDictionary<string, object> properties)
 | 
					    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.Linq.Expressions;
 | 
				
			||||||
using System.Reflection;
 | 
					using System.Reflection;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
using ThingsGateway.Extensions;
 | 
					using ThingsGateway.Extension;
 | 
				
			||||||
using ThingsGateway.Options;
 | 
					using ThingsGateway.Options;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Microsoft.Extensions.Options;
 | 
					namespace Microsoft.Extensions.Options;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -21,7 +21,7 @@ internal sealed class JobCancellationToken : IJobCancellationToken
 | 
				
			|||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
    /// 取消作业执行 Token 集合
 | 
					    /// 取消作业执行 Token 集合
 | 
				
			||||||
    /// </summary>
 | 
					    /// </summary>
 | 
				
			||||||
    private readonly ConcurrentDictionary<string, CancellationTokenSource> _cancellationTokenSources;
 | 
					    private readonly NonBlockingDictionary<string, CancellationTokenSource> _cancellationTokenSources;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
    /// 作业调度器日志服务
 | 
					    /// 作业调度器日志服务
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -167,7 +167,7 @@ public partial class JobDetail
 | 
				
			|||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
    /// 带命名规则的数据库列名
 | 
					    /// 带命名规则的数据库列名
 | 
				
			||||||
    /// </summary>
 | 
					    /// </summary>
 | 
				
			||||||
    private readonly ConcurrentDictionary<NamingConventions, string[]> _namingColumnNames = new();
 | 
					    private readonly NonBlockingDictionary<NamingConventions, string[]> _namingColumnNames = new();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
    /// 获取数据库列名
 | 
					    /// 获取数据库列名
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -65,7 +65,7 @@ internal sealed partial class SchedulerFactory : ISchedulerFactory
 | 
				
			|||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
    /// 作业计划集合
 | 
					    /// 作业计划集合
 | 
				
			||||||
    /// </summary>
 | 
					    /// </summary>
 | 
				
			||||||
    private readonly ConcurrentDictionary<string, Scheduler> _schedulers = new();
 | 
					    private readonly NonBlockingDictionary<string, Scheduler> _schedulers = new();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
    /// 作业计划构建器集合
 | 
					    /// 作业计划构建器集合
 | 
				
			||||||
@@ -204,7 +204,7 @@ internal sealed partial class SchedulerFactory : ISchedulerFactory
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
            else
 | 
					            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)
 | 
					                            if (executionException == null)
 | 
				
			||||||
                            {
 | 
					                            {
 | 
				
			||||||
                                jobLogger?.LogInformation("{jobExecutingContext}", jobExecutingContext);
 | 
					                                if (jobLogger?.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Information) == true)
 | 
				
			||||||
 | 
					                                    jobLogger?.LogInformation("{jobExecutingContext}", jobExecutingContext);
 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
                            else
 | 
					                            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>
 | 
				
			||||||
    /// 带命名规则的数据库列名
 | 
					    /// 带命名规则的数据库列名
 | 
				
			||||||
    /// </summary>
 | 
					    /// </summary>
 | 
				
			||||||
    private readonly ConcurrentDictionary<NamingConventions, string[]> _namingColumnNames = new();
 | 
					    private readonly NonBlockingDictionary<NamingConventions, string[]> _namingColumnNames = new();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
    /// 获取数据库列名
 | 
					    /// 获取数据库列名
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -31,7 +31,7 @@ using System.Xml.Linq;
 | 
				
			|||||||
using System.Xml.XPath;
 | 
					using System.Xml.XPath;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
using ThingsGateway.DynamicApiController;
 | 
					using ThingsGateway.DynamicApiController;
 | 
				
			||||||
using ThingsGateway.Extensions;
 | 
					using ThingsGateway.Extension;
 | 
				
			||||||
using ThingsGateway.Reflection;
 | 
					using ThingsGateway.Reflection;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace ThingsGateway.SpecificationDocument;
 | 
					namespace ThingsGateway.SpecificationDocument;
 | 
				
			||||||
@@ -83,11 +83,11 @@ public static class SpecificationDocumentBuilder
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        // 初始化常量
 | 
					        // 初始化常量
 | 
				
			||||||
        _groupOrderRegex = new Regex(@"@(?<order>[0-9]+$)");
 | 
					        _groupOrderRegex = new Regex(@"@(?<order>[0-9]+$)");
 | 
				
			||||||
        GetActionGroupsCached = new ConcurrentDictionary<MethodInfo, IEnumerable<GroupExtraInfo>>();
 | 
					        GetActionGroupsCached = new NonBlockingDictionary<MethodInfo, IEnumerable<GroupExtraInfo>>();
 | 
				
			||||||
        GetControllerGroupsCached = new ConcurrentDictionary<Type, IEnumerable<GroupExtraInfo>>();
 | 
					        GetControllerGroupsCached = new NonBlockingDictionary<Type, IEnumerable<GroupExtraInfo>>();
 | 
				
			||||||
        GetGroupOpenApiInfoCached = new ConcurrentDictionary<string, SpecificationOpenApiInfo>();
 | 
					        GetGroupOpenApiInfoCached = new NonBlockingDictionary<string, SpecificationOpenApiInfo>();
 | 
				
			||||||
        GetControllerTagCached = new ConcurrentDictionary<ControllerActionDescriptor, string>();
 | 
					        GetControllerTagCached = new NonBlockingDictionary<ControllerActionDescriptor, string>();
 | 
				
			||||||
        GetActionTagCached = new ConcurrentDictionary<ApiDescription, string>();
 | 
					        GetActionTagCached = new NonBlockingDictionary<ApiDescription, string>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // 默认分组,支持多个逗号分割
 | 
					        // 默认分组,支持多个逗号分割
 | 
				
			||||||
        DocumentGroupExtras = new List<GroupExtraInfo> { ResolveGroupExtraInfo(_specificationDocumentSettings.DefaultGroupName) };
 | 
					        DocumentGroupExtras = new List<GroupExtraInfo> { ResolveGroupExtraInfo(_specificationDocumentSettings.DefaultGroupName) };
 | 
				
			||||||
@@ -143,7 +143,7 @@ public static class SpecificationDocumentBuilder
 | 
				
			|||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
    /// 获取分组信息缓存集合
 | 
					    /// 获取分组信息缓存集合
 | 
				
			||||||
    /// </summary>
 | 
					    /// </summary>
 | 
				
			||||||
    private static readonly ConcurrentDictionary<string, SpecificationOpenApiInfo> GetGroupOpenApiInfoCached;
 | 
					    private static readonly NonBlockingDictionary<string, SpecificationOpenApiInfo> GetGroupOpenApiInfoCached;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
    /// 获取分组配置信息
 | 
					    /// 获取分组配置信息
 | 
				
			||||||
@@ -738,7 +738,7 @@ public static class SpecificationDocumentBuilder
 | 
				
			|||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
    /// 获取控制器组缓存集合
 | 
					    /// 获取控制器组缓存集合
 | 
				
			||||||
    /// </summary>
 | 
					    /// </summary>
 | 
				
			||||||
    private static readonly ConcurrentDictionary<Type, IEnumerable<GroupExtraInfo>> GetControllerGroupsCached;
 | 
					    private static readonly NonBlockingDictionary<Type, IEnumerable<GroupExtraInfo>> GetControllerGroupsCached;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
    /// 获取控制器分组列表
 | 
					    /// 获取控制器分组列表
 | 
				
			||||||
@@ -773,7 +773,7 @@ public static class SpecificationDocumentBuilder
 | 
				
			|||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
    /// <see cref="GetActionGroups(MethodInfo)"/> 缓存集合
 | 
					    /// <see cref="GetActionGroups(MethodInfo)"/> 缓存集合
 | 
				
			||||||
    /// </summary>
 | 
					    /// </summary>
 | 
				
			||||||
    private static readonly ConcurrentDictionary<MethodInfo, IEnumerable<GroupExtraInfo>> GetActionGroupsCached;
 | 
					    private static readonly NonBlockingDictionary<MethodInfo, IEnumerable<GroupExtraInfo>> GetActionGroupsCached;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
    /// 获取动作方法分组列表
 | 
					    /// 获取动作方法分组列表
 | 
				
			||||||
@@ -808,7 +808,7 @@ public static class SpecificationDocumentBuilder
 | 
				
			|||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
    /// <see cref="GetActionTag(ApiDescription)"/> 缓存集合
 | 
					    /// <see cref="GetActionTag(ApiDescription)"/> 缓存集合
 | 
				
			||||||
    /// </summary>
 | 
					    /// </summary>
 | 
				
			||||||
    private static readonly ConcurrentDictionary<ControllerActionDescriptor, string> GetControllerTagCached;
 | 
					    private static readonly NonBlockingDictionary<ControllerActionDescriptor, string> GetControllerTagCached;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
    /// 获取控制器标签
 | 
					    /// 获取控制器标签
 | 
				
			||||||
@@ -835,7 +835,7 @@ public static class SpecificationDocumentBuilder
 | 
				
			|||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
    /// <see cref="GetActionTag(ApiDescription)"/> 缓存集合
 | 
					    /// <see cref="GetActionTag(ApiDescription)"/> 缓存集合
 | 
				
			||||||
    /// </summary>
 | 
					    /// </summary>
 | 
				
			||||||
    private static readonly ConcurrentDictionary<ApiDescription, string> GetActionTagCached;
 | 
					    private static readonly NonBlockingDictionary<ApiDescription, string> GetActionTagCached;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
    /// 获取动作方法标签
 | 
					    /// 获取动作方法标签
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,7 +20,7 @@ using System.Reflection;
 | 
				
			|||||||
using System.Text;
 | 
					using System.Text;
 | 
				
			||||||
using System.Text.RegularExpressions;
 | 
					using System.Text.RegularExpressions;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
using ThingsGateway.Extensions;
 | 
					using ThingsGateway.Extension;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace ThingsGateway.SpecificationDocument;
 | 
					namespace ThingsGateway.SpecificationDocument;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,7 +11,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
using System.Text.RegularExpressions;
 | 
					using System.Text.RegularExpressions;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
using ThingsGateway.Extensions;
 | 
					using ThingsGateway.Extension;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace ThingsGateway.Templates.Extensions;
 | 
					namespace ThingsGateway.Templates.Extensions;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,7 +4,7 @@
 | 
				
			|||||||
	<Import Project="..\..\PackNuget.props" />
 | 
						<Import Project="..\..\PackNuget.props" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<PropertyGroup>
 | 
						<PropertyGroup>
 | 
				
			||||||
		<TargetFrameworks>net8.0;net9.0;</TargetFrameworks>
 | 
							<TargetFrameworks>net8.0;$(OtherTargetFrameworks);</TargetFrameworks>
 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
	</PropertyGroup>
 | 
						</PropertyGroup>
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
@@ -29,8 +29,8 @@
 | 
				
			|||||||
	</ItemGroup>
 | 
						</ItemGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<ItemGroup>
 | 
						<ItemGroup>
 | 
				
			||||||
		<PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.4" />
 | 
							<PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.6" />
 | 
				
			||||||
		<PackageReference Include="System.Text.Encoding.CodePages" Version="$(NET9Version)" />
 | 
							<PackageReference Include="System.Text.Encoding.CodePages" Version="$(NET10Version)" />
 | 
				
			||||||
	</ItemGroup>
 | 
						</ItemGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<ItemGroup Condition=" '$(TargetFramework)' == 'net8.0' ">
 | 
						<ItemGroup Condition=" '$(TargetFramework)' == 'net8.0' ">
 | 
				
			||||||
@@ -39,10 +39,10 @@
 | 
				
			|||||||
		<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="8.0.2" />
 | 
							<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="8.0.2" />
 | 
				
			||||||
	</ItemGroup>
 | 
						</ItemGroup>
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	<ItemGroup Condition=" '$(TargetFramework)' == 'net9.0' ">
 | 
						<ItemGroup Condition=" '$(TargetFramework)' == 'net10.0' ">
 | 
				
			||||||
		<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="$(NET9Version)" />
 | 
							<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="$(NET10Version)" />
 | 
				
			||||||
		<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="$(NET9Version)" />
 | 
							<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="$(NET10Version)" />
 | 
				
			||||||
		<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="$(NET9Version)" />
 | 
							<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="$(NET10Version)" />
 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	</ItemGroup>
 | 
						</ItemGroup>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,7 +13,7 @@ using Microsoft.AspNetCore.Http;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
using System.Reflection;
 | 
					using System.Reflection;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
using ThingsGateway.Extensions;
 | 
					using ThingsGateway.Extension;
 | 
				
			||||||
using ThingsGateway.UnifyResult;
 | 
					using ThingsGateway.UnifyResult;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Microsoft.AspNetCore.Mvc;
 | 
					namespace Microsoft.AspNetCore.Mvc;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -22,7 +22,7 @@ using Microsoft.Extensions.Options;
 | 
				
			|||||||
using System.Collections.Concurrent;
 | 
					using System.Collections.Concurrent;
 | 
				
			||||||
using System.Reflection;
 | 
					using System.Reflection;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
using ThingsGateway.Extensions;
 | 
					using ThingsGateway.Extension;
 | 
				
			||||||
using ThingsGateway.FriendlyException;
 | 
					using ThingsGateway.FriendlyException;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace ThingsGateway.UnifyResult;
 | 
					namespace ThingsGateway.UnifyResult;
 | 
				
			||||||
@@ -51,12 +51,12 @@ public static class UnifyContext
 | 
				
			|||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
    /// 规范化结果提供器
 | 
					    /// 规范化结果提供器
 | 
				
			||||||
    /// </summary>
 | 
					    /// </summary>
 | 
				
			||||||
    internal static ConcurrentDictionary<string, UnifyMetadata> UnifyProviders = new();
 | 
					    internal static NonBlockingDictionary<string, UnifyMetadata> UnifyProviders = new();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
    /// 规范化序列化配置
 | 
					    /// 规范化序列化配置
 | 
				
			||||||
    /// </summary>
 | 
					    /// </summary>
 | 
				
			||||||
    internal static ConcurrentDictionary<string, object> UnifySerializerSettings = new();
 | 
					    internal static NonBlockingDictionary<string, object> UnifySerializerSettings = new();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
    /// 获取异常元数据
 | 
					    /// 获取异常元数据
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,7 +12,7 @@
 | 
				
			|||||||
using System.Text.Json;
 | 
					using System.Text.Json;
 | 
				
			||||||
using System.Text.Json.Serialization;
 | 
					using System.Text.Json.Serialization;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
using ThingsGateway.Extensions;
 | 
					using ThingsGateway.Extension;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace ThingsGateway.Converters.Json;
 | 
					namespace ThingsGateway.Converters.Json;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,7 +11,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
using System.Reflection;
 | 
					using System.Reflection;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace ThingsGateway.Extensions;
 | 
					namespace ThingsGateway.Extension;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// <summary>
 | 
					/// <summary>
 | 
				
			||||||
///     <see cref="Assembly" /> 拓展类
 | 
					///     <see cref="Assembly" /> 拓展类
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,10 +11,10 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
using System.Collections.Concurrent;
 | 
					using System.Collections.Concurrent;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace ThingsGateway.Extensions;
 | 
					namespace ThingsGateway.Extension;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// <summary>
 | 
					/// <summary>
 | 
				
			||||||
///     <see cref="ConcurrentDictionary{TKey, TValue}" /> 拓展类
 | 
					///     <see cref="NonBlockingDictionary{TKey, TValue}" /> 拓展类
 | 
				
			||||||
/// </summary>
 | 
					/// </summary>
 | 
				
			||||||
internal static class ConcurrentDictionaryExtensions
 | 
					internal static class ConcurrentDictionaryExtensions
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
@@ -24,7 +24,7 @@ internal static class ConcurrentDictionaryExtensions
 | 
				
			|||||||
    /// <typeparam name="TKey">字典键类型</typeparam>
 | 
					    /// <typeparam name="TKey">字典键类型</typeparam>
 | 
				
			||||||
    /// <typeparam name="TValue">字典值类型</typeparam>
 | 
					    /// <typeparam name="TValue">字典值类型</typeparam>
 | 
				
			||||||
    /// <param name="dictionary">
 | 
					    /// <param name="dictionary">
 | 
				
			||||||
    ///     <see cref="ConcurrentDictionary{TKey, TValue}" />
 | 
					    ///     <see cref="NonBlockingDictionary{TKey, TValue}" />
 | 
				
			||||||
    /// </param>
 | 
					    /// </param>
 | 
				
			||||||
    /// <param name="key">
 | 
					    /// <param name="key">
 | 
				
			||||||
    ///     <typeparamref name="TKey" />
 | 
					    ///     <typeparamref name="TKey" />
 | 
				
			||||||
@@ -36,7 +36,7 @@ internal static class ConcurrentDictionaryExtensions
 | 
				
			|||||||
    /// <returns>
 | 
					    /// <returns>
 | 
				
			||||||
    ///     <see cref="bool" />
 | 
					    ///     <see cref="bool" />
 | 
				
			||||||
    /// </returns>
 | 
					    /// </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
 | 
					        , TKey key
 | 
				
			||||||
        , Func<TValue, TValue> updateFactory
 | 
					        , Func<TValue, TValue> updateFactory
 | 
				
			||||||
        , out TValue? value)
 | 
					        , out TValue? value)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,7 +15,7 @@ using Microsoft.Extensions.Hosting;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
using System.Reflection;
 | 
					using System.Reflection;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace ThingsGateway.Extensions;
 | 
					namespace ThingsGateway.Extension;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// <summary>
 | 
					/// <summary>
 | 
				
			||||||
///     核心模块 <see cref="IServiceCollection" /> 拓展类
 | 
					///     核心模块 <see cref="IServiceCollection" /> 拓展类
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,7 +11,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
using System.Data;
 | 
					using System.Data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace ThingsGateway.Extensions;
 | 
					namespace ThingsGateway.Extension;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// <summary>
 | 
					/// <summary>
 | 
				
			||||||
///     <see cref="DataTable" /> 和 <see cref="DataSet" /> 拓展类
 | 
					///     <see cref="DataTable" /> 和 <see cref="DataSet" /> 拓展类
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,7 +9,7 @@
 | 
				
			|||||||
// 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。
 | 
					// 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。
 | 
				
			||||||
// ------------------------------------------------------------------------
 | 
					// ------------------------------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace ThingsGateway.Extensions;
 | 
					namespace ThingsGateway.Extension;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// <summary>
 | 
					/// <summary>
 | 
				
			||||||
///     委托拓展类
 | 
					///     委托拓展类
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,7 +12,7 @@
 | 
				
			|||||||
using System.ComponentModel;
 | 
					using System.ComponentModel;
 | 
				
			||||||
using System.Reflection;
 | 
					using System.Reflection;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace ThingsGateway.Extensions;
 | 
					namespace ThingsGateway.Extension;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// <summary>
 | 
					/// <summary>
 | 
				
			||||||
///     枚举拓展类
 | 
					///     枚举拓展类
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,7 +9,7 @@
 | 
				
			|||||||
// 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。
 | 
					// 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。
 | 
				
			||||||
// ------------------------------------------------------------------------
 | 
					// ------------------------------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace ThingsGateway.Extensions;
 | 
					namespace ThingsGateway.Extension;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// <summary>
 | 
					/// <summary>
 | 
				
			||||||
///     <see cref="EventHandler{TEventArgs}" /> 拓展类
 | 
					///     <see cref="EventHandler{TEventArgs}" /> 拓展类
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,7 +11,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
using System.Diagnostics.CodeAnalysis;
 | 
					using System.Diagnostics.CodeAnalysis;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace ThingsGateway.Extensions;
 | 
					namespace ThingsGateway.Extension;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// <summary>
 | 
					/// <summary>
 | 
				
			||||||
///     <see cref="ICollection{T}" /> 拓展类
 | 
					///     <see cref="ICollection{T}" /> 拓展类
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,7 +11,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
using System.Collections.Concurrent;
 | 
					using System.Collections.Concurrent;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace ThingsGateway.Extensions;
 | 
					namespace ThingsGateway.Extension;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// <summary>
 | 
					/// <summary>
 | 
				
			||||||
///     <see cref="IDictionary{TKey, TValue}" /> 拓展类
 | 
					///     <see cref="IDictionary{TKey, TValue}" /> 拓展类
 | 
				
			||||||
@@ -241,7 +241,7 @@ internal static class IDictionaryExtensions
 | 
				
			|||||||
    /// </summary>
 | 
					    /// </summary>
 | 
				
			||||||
    /// <remarks>其中键是由值通过给定的选择器函数生成的。</remarks>
 | 
					    /// <remarks>其中键是由值通过给定的选择器函数生成的。</remarks>
 | 
				
			||||||
    /// <param name="dictionary">
 | 
					    /// <param name="dictionary">
 | 
				
			||||||
    ///     <see cref="ConcurrentDictionary{TKey, TValue}" />
 | 
					    ///     <see cref="NonBlockingDictionary{TKey, TValue}" />
 | 
				
			||||||
    /// </param>
 | 
					    /// </param>
 | 
				
			||||||
    /// <param name="values">
 | 
					    /// <param name="values">
 | 
				
			||||||
    ///     <see cref="IEnumerable{T}" />
 | 
					    ///     <see cref="IEnumerable{T}" />
 | 
				
			||||||
@@ -249,7 +249,7 @@ internal static class IDictionaryExtensions
 | 
				
			|||||||
    /// <param name="keySelector">键选择器</param>
 | 
					    /// <param name="keySelector">键选择器</param>
 | 
				
			||||||
    /// <typeparam name="TKey">字典键类型</typeparam>
 | 
					    /// <typeparam name="TKey">字典键类型</typeparam>
 | 
				
			||||||
    /// <typeparam name="TValue">字典值类型</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)
 | 
					        IEnumerable<TValue>? values, Func<TValue, TKey> keySelector)
 | 
				
			||||||
        where TKey : notnull
 | 
					        where TKey : notnull
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,7 +9,7 @@
 | 
				
			|||||||
// 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。
 | 
					// 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。
 | 
				
			||||||
// ------------------------------------------------------------------------
 | 
					// ------------------------------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace ThingsGateway.Extensions;
 | 
					namespace ThingsGateway.Extension;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// <summary>
 | 
					/// <summary>
 | 
				
			||||||
///     <see cref="IEnumerable" /> 拓展类
 | 
					///     <see cref="IEnumerable" /> 拓展类
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -18,7 +18,7 @@ using System.Text.RegularExpressions;
 | 
				
			|||||||
using System.Xml;
 | 
					using System.Xml;
 | 
				
			||||||
using System.Xml.Linq;
 | 
					using System.Xml.Linq;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace ThingsGateway.Extensions;
 | 
					namespace ThingsGateway.Extension;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// <summary>
 | 
					/// <summary>
 | 
				
			||||||
///     System.Text.Json 拓展类
 | 
					///     System.Text.Json 拓展类
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,7 +12,7 @@
 | 
				
			|||||||
using System.Linq.Expressions;
 | 
					using System.Linq.Expressions;
 | 
				
			||||||
using System.Reflection;
 | 
					using System.Reflection;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace ThingsGateway.Extensions;
 | 
					namespace ThingsGateway.Extension;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// <summary>
 | 
					/// <summary>
 | 
				
			||||||
///     <see cref="Expression" /> 拓展类
 | 
					///     <see cref="Expression" /> 拓展类
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,7 +12,7 @@
 | 
				
			|||||||
using System.Diagnostics.CodeAnalysis;
 | 
					using System.Diagnostics.CodeAnalysis;
 | 
				
			||||||
using System.Reflection;
 | 
					using System.Reflection;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace ThingsGateway.Extensions;
 | 
					namespace ThingsGateway.Extension;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// <summary>
 | 
					/// <summary>
 | 
				
			||||||
///     <see cref="MethodInfo" /> 拓展类
 | 
					///     <see cref="MethodInfo" /> 拓展类
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,7 +9,7 @@
 | 
				
			|||||||
// 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。
 | 
					// 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。
 | 
				
			||||||
// ------------------------------------------------------------------------
 | 
					// ------------------------------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace ThingsGateway.Extensions;
 | 
					namespace ThingsGateway.Extension;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// <summary>
 | 
					/// <summary>
 | 
				
			||||||
///     数值类型拓展类
 | 
					///     数值类型拓展类
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,7 +17,7 @@ using System.Reflection;
 | 
				
			|||||||
using System.Text;
 | 
					using System.Text;
 | 
				
			||||||
using System.Text.RegularExpressions;
 | 
					using System.Text.RegularExpressions;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace ThingsGateway.Extensions;
 | 
					namespace ThingsGateway.Extension;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// <summary>
 | 
					/// <summary>
 | 
				
			||||||
///     <see cref="string" /> 拓展类
 | 
					///     <see cref="string" /> 拓展类
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,7 +15,7 @@ using System.Reflection;
 | 
				
			|||||||
using System.Reflection.Emit;
 | 
					using System.Reflection.Emit;
 | 
				
			||||||
using System.Runtime.CompilerServices;
 | 
					using System.Runtime.CompilerServices;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace ThingsGateway.Extensions;
 | 
					namespace ThingsGateway.Extension;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// <summary>
 | 
					/// <summary>
 | 
				
			||||||
///     <see cref="Type" /> 拓展类
 | 
					///     <see cref="Type" /> 拓展类
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,7 +13,7 @@ using System.Buffers;
 | 
				
			|||||||
using System.Text;
 | 
					using System.Text;
 | 
				
			||||||
using System.Text.Json;
 | 
					using System.Text.Json;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace ThingsGateway.Extensions;
 | 
					namespace ThingsGateway.Extension;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// <summary>
 | 
					/// <summary>
 | 
				
			||||||
///     <see cref="Utf8JsonReader" /> 拓展类
 | 
					///     <see cref="Utf8JsonReader" /> 拓展类
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,7 +16,7 @@ using System.Text.Json;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
using ThingsGateway.Utilities;
 | 
					using ThingsGateway.Utilities;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace ThingsGateway.Extensions;
 | 
					namespace ThingsGateway.Extension;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// <summary>
 | 
					/// <summary>
 | 
				
			||||||
///     <see cref="object" /> 拓展类
 | 
					///     <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