Compare commits
	
		
			23 Commits
		
	
	
		
			10.12.6.0
			...
			10.12.28.0
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					44c3c4b30a | ||
| 
						 | 
					5deef89b96 | ||
| 
						 | 
					ce5fa0f37d | ||
| 
						 | 
					694437c7d5 | ||
| 
						 | 
					0e78cdefe7 | ||
| 
						 | 
					087dc9aaa3 | ||
| 
						 | 
					dacf255f1a | ||
| 
						 | 
					e3960ce115 | ||
| 
						 | 
					0cf098be85 | ||
| 
						 | 
					c93c80468c | ||
| 
						 | 
					a464594885 | ||
| 
						 | 
					b42f8afa35 | ||
| 
						 | 
					facf8bd401 | ||
| 
						 | 
					feeb17eca3 | ||
| 
						 | 
					b3405cd674 | ||
| 
						 | 
					c35f9cef93 | ||
| 
						 | 
					3f382202db | ||
| 
						 | 
					2a3493cc82 | ||
| 
						 | 
					aaa459ebe0 | ||
| 
						 | 
					51a8acbc3e | ||
| 
						 | 
					dc132a1999 | ||
| 
						 | 
					0c31cfcbc5 | ||
| 
						 | 
					0e44bc67cd | 
							
								
								
									
										55
									
								
								README.md
									
									
									
									
									
								
							
							
						
						@@ -1,35 +1,44 @@
 | 
			
		||||
# ThingsGateway
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
<p align="center">
 | 
			
		||||
<img src="logo.svg" width = "400" height = "200" alt="The name of the image" align=center />
 | 
			
		||||
</p>
 | 
			
		||||
 | 
			
		||||
[](https://gitee.com/ThingsGateway/ThingsGateway/stargazers) 
 | 
			
		||||
[](https://github.com/ThingsGateway/ThingsGateway)
 | 
			
		||||
[](https://deepwiki.com/ThingsGateway/ThingsGateway)
 | 
			
		||||
[](https://www.nuget.org/packages/ThingsGateway.Foundation/)
 | 
			
		||||
[](https://www.nuget.org/packages/ThingsGateway.Foundation/)
 | 
			
		||||
[](https://thingsgateway.cn/docs/1)
 | 
			
		||||
<a href="http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=NnBjPO-8kcNFzo_RzSbdICflb97u2O1i&authKey=V1MI3iJtpDMHc08myszP262kDykbx2Yev6ebE4Me0elTe0P0IFAmtU5l7Sy5w0jx&noverify=0&group_code=605534569">
 | 
			
		||||
<img src="https://img.shields.io/badge/QQ群-605534569-red" alt="QQ">
 | 
			
		||||
</a>
 | 
			
		||||
 | 
			
		||||
## Introduction
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
A cross-platform, high-performance edge data collection gateway based on net9.
 | 
			
		||||
 | 
			
		||||
A cross-platform, high-performance edge data collection gateway based on net8/10.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## Documentation
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
[Documentation](https://thingsgateway.cn/).
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
[NuGet](https://www.nuget.org/packages?q=Tags%3A%22ThingsGateway%22)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## Demo
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
[Demo](https://demo.thingsgateway.cn/)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Account: **SuperAdmin**
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Password: **111111**
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
**In the upper-right corner, switch to the IoT Gateway module in the personal popup box**
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## Docker
 | 
			
		||||
 | 
			
		||||
@@ -44,7 +53,7 @@ docker pull registry.cn-shenzhen.aliyuncs.com/thingsgateway/thingsgateway_arm64
 | 
			
		||||
 | 
			
		||||
### Plugin List
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#### Data Collection Plugins
 | 
			
		||||
 | 
			
		||||
@@ -74,28 +83,28 @@ docker pull registry.cn-shenzhen.aliyuncs.com/thingsgateway/thingsgateway_arm64
 | 
			
		||||
| TDengineDB       | Time-series database storage                                                                      |
 | 
			
		||||
| QuestDB          | Time-series database storage                                                                      |
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## License
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
[License](https://thingsgateway.cn/docs/1)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## Sponsorship
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
[Sponsorship Approach](https://thingsgateway.cn/docs/1000)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## Community
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
QQ Group: 605534569 [Jump](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=NnBjPO-8kcNFzo_RzSbdICflb97u2O1i&authKey=V1MI3iJtpDMHc08myszP262kDykbx2Yev6ebE4Me0elTe0P0IFAmtU5l7Sy5w0jx&noverify=0&group_code=605534569)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## Pro Plugins
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
[Plugin List](https://thingsgateway.cn/docs/1001)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,21 @@
 | 
			
		||||
# ThingsGateway
 | 
			
		||||
 | 
			
		||||
<p align="center">
 | 
			
		||||
<img src="logo.svg" width = "400" height = "200" alt="The name of the image" align=center />
 | 
			
		||||
</p>
 | 
			
		||||
 | 
			
		||||
[](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 +32,6 @@
 | 
			
		||||
 | 
			
		||||
密码 : **111111**
 | 
			
		||||
 | 
			
		||||
**右上角个人弹出框中,切换到物联网关模块**
 | 
			
		||||
 | 
			
		||||
## Docker
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								icon.ico
									
									
									
									
									
								
							
							
						
						| 
		 Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 257 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								icon.png
									
									
									
									
									
								
							
							
						
						| 
		 Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 177 KiB  | 
							
								
								
									
										9
									
								
								logo.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 236 KiB  | 
@@ -8,7 +8,8 @@
 | 
			
		||||
//  QQ群:605534569
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
using ThingsGateway.NewLife;
 | 
			
		||||
using System.ComponentModel;
 | 
			
		||||
using System.Runtime;
 | 
			
		||||
 | 
			
		||||
namespace ThingsGateway.Admin.Application;
 | 
			
		||||
 | 
			
		||||
@@ -20,10 +21,6 @@ public class HardwareInfo
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public DriveInfo DriveInfo { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 硬件信息获取
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public MachineInfo? MachineInfo { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 主机环境
 | 
			
		||||
@@ -40,19 +37,118 @@ public class HardwareInfo
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public string OsArchitecture { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 唯一编码
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public string UUID { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 进程占用内存
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    [AutoGenerateColumn(Ignore = true)]
 | 
			
		||||
    public int WorkingSet { get; set; }
 | 
			
		||||
    /// <summary>系统名称</summary>
 | 
			
		||||
    public String OSName { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>系统版本</summary>
 | 
			
		||||
    public String OSVersion { get; set; }
 | 
			
		||||
    public String UUID { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>内存总量。单位MB</summary>
 | 
			
		||||
    public UInt64 Memory { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>可用内存。单位MB</summary>
 | 
			
		||||
    public UInt64 AvailableMemory { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>CPU占用率</summary>
 | 
			
		||||
    public Double CpuRate { get; set; }
 | 
			
		||||
    public Double Battery { get; set; }
 | 
			
		||||
    public Double Temperature { get; set; }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /// <summary>处理器型号</summary>
 | 
			
		||||
    public String? Processor { get; set; }
 | 
			
		||||
    #region GC与进程内存信息
 | 
			
		||||
 | 
			
		||||
    /// <summary>GC 认为“内存吃紧”的阈值。单位:MB</summary>
 | 
			
		||||
    [DisplayName("GC高内存阈值")]
 | 
			
		||||
    public UInt64 HighMemoryLoadThreshold { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>GC 可用内存上限。单位:MB</summary>
 | 
			
		||||
    [DisplayName("GC可用内存上限")]
 | 
			
		||||
    public UInt64 TotalAvailableMemory { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>当前托管堆容量。单位:MB</summary>
 | 
			
		||||
    [DisplayName("托管堆容量")]
 | 
			
		||||
    public UInt64 HeapSize { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>托管堆已用内存。单位:MB</summary>
 | 
			
		||||
    [DisplayName("托管堆已用")]
 | 
			
		||||
    public UInt64 TotalMemory { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>托管堆碎片大小。单位:MB</summary>
 | 
			
		||||
    [DisplayName("托管堆碎片")]
 | 
			
		||||
    public UInt64 FragmentedBytes { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>GC识别可用内存。单位:MB</summary>
 | 
			
		||||
    [DisplayName("GC识别可用内存")]
 | 
			
		||||
    public UInt64 GCAvailableMemory { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>GC 已提交的内存。单位:MB</summary>
 | 
			
		||||
    [DisplayName("GC已提交内存")]
 | 
			
		||||
    public UInt64 CommittedBytes { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>GC 累计分配的托管内存。单位:MB</summary>
 | 
			
		||||
    [DisplayName("GC累计分配")]
 | 
			
		||||
    public UInt64 TotalAllocatedBytes { get; set; }
 | 
			
		||||
    /// <summary>GC 暂停累计时间。单位:毫秒</summary>
 | 
			
		||||
    [DisplayName("GC累计暂停时间")]
 | 
			
		||||
    public UInt64 TotalPauseDurationMs { get; set; }
 | 
			
		||||
    /// <summary>GC 代0收集次数</summary>
 | 
			
		||||
    [DisplayName("GC Gen0 次数")]
 | 
			
		||||
    public Int32 GcGen0Count { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>GC 代1收集次数</summary>
 | 
			
		||||
    [DisplayName("GC Gen1 次数")]
 | 
			
		||||
    public Int32 GcGen1Count { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>GC 代2收集次数</summary>
 | 
			
		||||
    [DisplayName("GC Gen2 次数")]
 | 
			
		||||
    public Int32 GcGen2Count { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>Server GC 是否启用</summary>
 | 
			
		||||
    [DisplayName("是否使用Server GC")]
 | 
			
		||||
    public Boolean IsServerGC { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>GC 延迟模式</summary>
 | 
			
		||||
    [DisplayName("GC延迟模式")]
 | 
			
		||||
    public GCLatencyMode? GCLatencyMode { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>GC 固定对象数</summary>
 | 
			
		||||
    [DisplayName("固定对象数")]
 | 
			
		||||
    public Int64 PinnedObjectsCount { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>终结队列挂起对象数</summary>
 | 
			
		||||
    [DisplayName("终结挂起数")]
 | 
			
		||||
    public Int64 FinalizationPendingCount { get; set; }
 | 
			
		||||
 | 
			
		||||
    #endregion
 | 
			
		||||
 | 
			
		||||
    #region 进程内存信息
 | 
			
		||||
 | 
			
		||||
    /// <summary>进程虚拟内存使用量。单位:MB</summary>
 | 
			
		||||
    [DisplayName("虚拟内存")]
 | 
			
		||||
    public UInt64 VirtualMemory { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>进程私有内存使用量。单位:MB</summary>
 | 
			
		||||
    [DisplayName("私有内存")]
 | 
			
		||||
    public UInt64 PrivateMemory { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>进程峰值工作集。单位:MB</summary>
 | 
			
		||||
    [DisplayName("峰值工作集")]
 | 
			
		||||
    public UInt64 PeakWorkingSet { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>进程当前工作集。单位:MB</summary>
 | 
			
		||||
    [DisplayName("当前工作集")]
 | 
			
		||||
    public UInt64 WorkingSet { get; set; }
 | 
			
		||||
 | 
			
		||||
    #endregion
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 更新时间
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public string UpdateTime { get; set; }
 | 
			
		||||
    public DateTime UpdateTime { get; set; }
 | 
			
		||||
    public ulong AppRunTotalMinute { get; set; }
 | 
			
		||||
    public ulong SystemRunTotalMinute { get; set; }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -8,12 +8,10 @@
 | 
			
		||||
// QQ群:605534569
 | 
			
		||||
// ------------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
using Microsoft.Extensions.Hosting;
 | 
			
		||||
using Microsoft.Extensions.Logging;
 | 
			
		||||
 | 
			
		||||
using System.Runtime.InteropServices;
 | 
			
		||||
 | 
			
		||||
using ThingsGateway.Extension;
 | 
			
		||||
using ThingsGateway.NewLife;
 | 
			
		||||
using ThingsGateway.NewLife.Caching;
 | 
			
		||||
using ThingsGateway.NewLife.Threading;
 | 
			
		||||
@@ -43,7 +41,7 @@ public class HardwareJob : IJob, IHardwareJob
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 运行信息获取
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public HardwareInfo HardwareInfo { get; } = new();
 | 
			
		||||
    public HardwareInfo HardwareInfo { get; private set; }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public HardwareInfoOptions HardwareInfoOptions { get; private set; }
 | 
			
		||||
@@ -76,9 +74,10 @@ public class HardwareJob : IJob, IHardwareJob
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                if (HardwareInfo.MachineInfo == null)
 | 
			
		||||
                var machine = MachineInfo.GetCurrent();
 | 
			
		||||
                if (HardwareInfo == null)
 | 
			
		||||
                {
 | 
			
		||||
                    HardwareInfo.MachineInfo = MachineInfo.GetCurrent();
 | 
			
		||||
                    HardwareInfo = machine.AdaptHardwareInfo();
 | 
			
		||||
 | 
			
		||||
                    string currentPath = Directory.GetCurrentDirectory();
 | 
			
		||||
                    DriveInfo drive = new(Path.GetPathRoot(currentPath));
 | 
			
		||||
@@ -88,10 +87,9 @@ public class HardwareJob : IJob, IHardwareJob
 | 
			
		||||
                    HardwareInfo.DriveInfo = drive;
 | 
			
		||||
                    HardwareInfo.OsArchitecture = Environment.OSVersion.Platform.ToString() + " " + RuntimeInformation.OSArchitecture.ToString(); // 系统架构
 | 
			
		||||
                    HardwareInfo.FrameworkDescription = RuntimeInformation.FrameworkDescription; // NET框架
 | 
			
		||||
                    HardwareInfo.Environment = App.HostEnvironment.IsDevelopment() ? "Development" : "Production";
 | 
			
		||||
                    HardwareInfo.UUID = HardwareInfo.MachineInfo.UUID;
 | 
			
		||||
                    HardwareInfo.Environment = App.HostEnvironment.EnvironmentName;
 | 
			
		||||
 | 
			
		||||
                    HardwareInfo.UpdateTime = TimerX.Now.ToDefaultDateTimeFormat();
 | 
			
		||||
                    HardwareInfo.UpdateTime = TimerX.Now;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            catch
 | 
			
		||||
@@ -99,9 +97,12 @@ public class HardwareJob : IJob, IHardwareJob
 | 
			
		||||
            }
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                HardwareInfo.MachineInfo.Refresh();
 | 
			
		||||
                HardwareInfo.UpdateTime = TimerX.Now.ToDefaultDateTimeFormat();
 | 
			
		||||
                HardwareInfo.WorkingSet = (Environment.WorkingSet / 1024.0 / 1024.0).ToInt();
 | 
			
		||||
                var machine = MachineInfo.GetCurrent();
 | 
			
		||||
                machine.Refresh();
 | 
			
		||||
                machine.AdaptHardwareInfo(HardwareInfo);
 | 
			
		||||
                HardwareInfo.AppRunTotalMinute = (ulong)Runtime.AppTickCount64 / 1000 / 60;
 | 
			
		||||
                HardwareInfo.SystemRunTotalMinute = (ulong)Runtime.TickCount64 / 1000 / 60;
 | 
			
		||||
                HardwareInfo.UpdateTime = TimerX.Now;
 | 
			
		||||
                error = false;
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
@@ -123,10 +124,10 @@ public class HardwareJob : IJob, IHardwareJob
 | 
			
		||||
                            {
 | 
			
		||||
                                Date = TimerX.Now,
 | 
			
		||||
                                DriveUsage = (100 - (HardwareInfo.DriveInfo.TotalFreeSpace * 100.00 / HardwareInfo.DriveInfo.TotalSize)).ToInt(),
 | 
			
		||||
                                Battery = (HardwareInfo.MachineInfo.Battery * 100).ToInt(),
 | 
			
		||||
                                Battery = (HardwareInfo.Battery * 100).ToInt(),
 | 
			
		||||
                                MemoryUsage = (HardwareInfo.WorkingSet),
 | 
			
		||||
                                CpuUsage = (HardwareInfo.MachineInfo.CpuRate * 100).ToInt(),
 | 
			
		||||
                                Temperature = (HardwareInfo.MachineInfo.Temperature).ToInt(),
 | 
			
		||||
                                CpuUsage = (HardwareInfo.CpuRate * 100).ToInt(),
 | 
			
		||||
                                Temperature = (HardwareInfo.Temperature).ToInt(),
 | 
			
		||||
                            };
 | 
			
		||||
                            await _db.InsertableT(his).ExecuteCommandAsync(stoppingToken).ConfigureAwait(false);
 | 
			
		||||
                            MemoryCache.Remove(CacheKey);
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,7 @@ public class HistoryHardwareInfo
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    [SugarColumn(ColumnDescription = "内存")]
 | 
			
		||||
    public int MemoryUsage { get; set; }
 | 
			
		||||
    public UInt64 MemoryUsage { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    [SugarColumn(ColumnDescription = "CPU使用率")]
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,7 @@
 | 
			
		||||
    "UserNoModule": "This account has not been assigned a module. Please contact the administrator",
 | 
			
		||||
    "UserNull": "User {0} does not exist"
 | 
			
		||||
  },
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
  "ThingsGateway.Admin.Application.BlazorAuthenticationHandler": {
 | 
			
		||||
    "UserExpire": "User expired, please login again"
 | 
			
		||||
  },
 | 
			
		||||
@@ -46,12 +46,44 @@
 | 
			
		||||
    "FileTypeError": "Not supported format {0}"
 | 
			
		||||
  },
 | 
			
		||||
  "ThingsGateway.Admin.Application.HardwareInfo": {
 | 
			
		||||
    "Environment": "HostEnvironment",
 | 
			
		||||
    "FrameworkDescription": ".NETFramework",
 | 
			
		||||
    "OsArchitecture": "System Architecture",
 | 
			
		||||
    "UpdateTime": "UpdateTime",
 | 
			
		||||
    "UUID": "UUID"
 | 
			
		||||
    "DriveInfo": "Current Disk Info",
 | 
			
		||||
    "AppRunTotalMinute": "AppRunTotalMinute(min)",
 | 
			
		||||
    "SystemRunTotalMinute": "SystemRunTotalMinute(min)",
 | 
			
		||||
    "Environment": "Host Environment",
 | 
			
		||||
    "FrameworkDescription": ".NET Framework",
 | 
			
		||||
    "OsArchitecture": "OS Architecture",
 | 
			
		||||
    "OSName": "OS Name",
 | 
			
		||||
    "OSVersion": "OS Version",
 | 
			
		||||
    "UUID": "UUID",
 | 
			
		||||
    "Memory": "Total Memory",
 | 
			
		||||
    "AvailableMemory": "Available Memory",
 | 
			
		||||
    "CpuRate": "CPU Usage",
 | 
			
		||||
    "Battery": "Battery Level",
 | 
			
		||||
    "Temperature": "Temperature",
 | 
			
		||||
    "Processor": "Processor Model",
 | 
			
		||||
    "HighMemoryLoadThreshold": "GC High Memory Threshold",
 | 
			
		||||
    "TotalAvailableMemory": "GC Total Available Memory",
 | 
			
		||||
    "HeapSize": "Managed Heap Size",
 | 
			
		||||
    "TotalMemory": "Managed Heap Used",
 | 
			
		||||
    "FragmentedBytes": "Managed Heap Fragmented",
 | 
			
		||||
    "GCAvailableMemory": "GC Available Memory",
 | 
			
		||||
    "CommittedBytes": "GC Committed Bytes",
 | 
			
		||||
    "TotalAllocatedBytes": "GC Total Allocated (MB)",
 | 
			
		||||
    "TotalPauseDurationMs": "GC Total Pause Duration (ms)",
 | 
			
		||||
    "GcGen0Count": "GC Gen0 Count",
 | 
			
		||||
    "GcGen1Count": "GC Gen1 Count",
 | 
			
		||||
    "GcGen2Count": "GC Gen2 Count",
 | 
			
		||||
    "IsServerGC": "Server GC",
 | 
			
		||||
    "GCLatencyMode": "GC Latency Mode",
 | 
			
		||||
    "PinnedObjectsCount": "Pinned Objects Count",
 | 
			
		||||
    "FinalizationPendingCount": "Finalization Pending Count",
 | 
			
		||||
    "VirtualMemory": "Virtual Memory",
 | 
			
		||||
    "PrivateMemory": "Private Memory",
 | 
			
		||||
    "PeakWorkingSet": "Peak Working Set",
 | 
			
		||||
    "WorkingSet": "Current Working Set",
 | 
			
		||||
    "UpdateTime": "Update Time"
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  "ThingsGateway.Admin.Application.HardwareJob": {
 | 
			
		||||
    "GetHardwareInfoFail": "Get Hardwareinfo Fail"
 | 
			
		||||
  },
 | 
			
		||||
 
 | 
			
		||||
@@ -46,11 +46,42 @@
 | 
			
		||||
    "FileTypeError": "不支持 {0} 格式"
 | 
			
		||||
  },
 | 
			
		||||
  "ThingsGateway.Admin.Application.HardwareInfo": {
 | 
			
		||||
    "DriveInfo": "当前磁盘信息",
 | 
			
		||||
    "AppRunTotalMinute": "软件运行时长(min)",
 | 
			
		||||
    "SystemRunTotalMinute": "系统运行时长(min)",
 | 
			
		||||
    "Environment": "主机环境",
 | 
			
		||||
    "FrameworkDescription": "NET框架",
 | 
			
		||||
    "FrameworkDescription": ".NET 框架",
 | 
			
		||||
    "OsArchitecture": "系统架构",
 | 
			
		||||
    "UpdateTime": "更新时间",
 | 
			
		||||
    "UUID": "唯一编码"
 | 
			
		||||
    "OSName": "系统名称",
 | 
			
		||||
    "OSVersion": "系统版本",
 | 
			
		||||
    "UUID": "UUID",
 | 
			
		||||
    "Memory": "系统总内存",
 | 
			
		||||
    "AvailableMemory": "系统可用内存",
 | 
			
		||||
    "CpuRate": "CPU 占用率",
 | 
			
		||||
    "Battery": "电池电量",
 | 
			
		||||
    "Temperature": "温度",
 | 
			
		||||
    "Processor": "处理器型号",
 | 
			
		||||
    "HighMemoryLoadThreshold": "GC 高内存阈值",
 | 
			
		||||
    "TotalAvailableMemory": "GC 总内存阈值",
 | 
			
		||||
    "HeapSize": "托管堆容量",
 | 
			
		||||
    "TotalMemory": "托管对象占用",
 | 
			
		||||
    "FragmentedBytes": "托管堆碎片",
 | 
			
		||||
    "GCAvailableMemory": "GC 可用内存",
 | 
			
		||||
    "CommittedBytes": "GC 提交内存总量",
 | 
			
		||||
    "TotalAllocatedBytes": "GC 累计分配(MB)",
 | 
			
		||||
    "TotalPauseDurationMs": "GC 累计暂停时间(ms)",
 | 
			
		||||
    "GcGen0Count": "GC Gen0 次数",
 | 
			
		||||
    "GcGen1Count": "GC Gen1 次数",
 | 
			
		||||
    "GcGen2Count": "GC Gen2 次数",
 | 
			
		||||
    "IsServerGC": "Server GC",
 | 
			
		||||
    "GCLatencyMode": "GC 延迟模式",
 | 
			
		||||
    "PinnedObjectsCount": "固定对象数",
 | 
			
		||||
    "FinalizationPendingCount": "终结挂起数",
 | 
			
		||||
    "VirtualMemory": "虚拟内存",
 | 
			
		||||
    "PrivateMemory": "私有内存",
 | 
			
		||||
    "PeakWorkingSet": "峰值工作集",
 | 
			
		||||
    "WorkingSet": "当前工作集",
 | 
			
		||||
    "UpdateTime": "更新时间"
 | 
			
		||||
  },
 | 
			
		||||
  "ThingsGateway.Admin.Application.HardwareJob": {
 | 
			
		||||
    "GetHardwareInfoFail": "获取硬件信息出错"
 | 
			
		||||
 
 | 
			
		||||
@@ -10,11 +10,16 @@
 | 
			
		||||
 | 
			
		||||
using Riok.Mapperly.Abstractions;
 | 
			
		||||
 | 
			
		||||
using ThingsGateway.NewLife;
 | 
			
		||||
 | 
			
		||||
namespace ThingsGateway.Admin.Application;
 | 
			
		||||
 | 
			
		||||
[Mapper(UseDeepCloning = true, EnumMappingStrategy = EnumMappingStrategy.ByName, RequiredMappingStrategy = RequiredMappingStrategy.None)]
 | 
			
		||||
public static partial class AdminMapper
 | 
			
		||||
{
 | 
			
		||||
    public static partial HardwareInfo AdaptHardwareInfo(this MachineInfo src);
 | 
			
		||||
    public static partial void AdaptHardwareInfo(this MachineInfo src, HardwareInfo dto);
 | 
			
		||||
 | 
			
		||||
    public static partial LoginInput AdaptLoginInput(this OpenApiLoginInput src);
 | 
			
		||||
    public static partial OpenApiLoginOutput AdaptOpenApiLoginOutput(this LoginOutput src);
 | 
			
		||||
    public static partial SessionOutput AdaptSessionOutput(this SysUser src);
 | 
			
		||||
 
 | 
			
		||||
@@ -22,6 +22,9 @@
 | 
			
		||||
    "SetDefaultModule": "Set as default module"
 | 
			
		||||
  },
 | 
			
		||||
  "ThingsGateway.Admin.Razor.HardwareInfoPage": {
 | 
			
		||||
    "GCMemoryInfoConfig": "GCMemoryInfoConfig(MB)",
 | 
			
		||||
    "GCMemoryInfo": "GCMemoryInfo(MB)",
 | 
			
		||||
    "GCAnalyzeInfo": "GCAnalyzeInfo",
 | 
			
		||||
    "AvailableDisk": "Available Disk",
 | 
			
		||||
    "AvailableMemory": "Available Memory",
 | 
			
		||||
    "CpuUsage": "CPU Usage",
 | 
			
		||||
 
 | 
			
		||||
@@ -22,6 +22,10 @@
 | 
			
		||||
    "SetDefaultModule": "设置为默认模块"
 | 
			
		||||
  },
 | 
			
		||||
  "ThingsGateway.Admin.Razor.HardwareInfoPage": {
 | 
			
		||||
    "GCMemoryInfoConfig": "GC配置信息(MB)",
 | 
			
		||||
    "GCMemoryInfo": "GC内存信息(MB)",
 | 
			
		||||
    "GCAnalyzeInfo": "GC统计",
 | 
			
		||||
 | 
			
		||||
    "AvailableDisk": "可用磁盘",
 | 
			
		||||
    "AvailableMemory": "可用内存",
 | 
			
		||||
    "CpuUsage": "CPU使用率",
 | 
			
		||||
 
 | 
			
		||||
@@ -5,131 +5,189 @@
 | 
			
		||||
@using ThingsGateway.Admin.Application
 | 
			
		||||
@namespace ThingsGateway.Admin.Razor
 | 
			
		||||
<div class="row g-2 mx-1 form-inline">
 | 
			
		||||
 | 
			
		||||
    <div class="col-12 col-md-4">
 | 
			
		||||
    <div class="col-12 col-md-12">
 | 
			
		||||
 | 
			
		||||
        <Card IsShadow=true class="m-2 flex-fill" Color="Color.Primary">
 | 
			
		||||
            <HeaderTemplate>
 | 
			
		||||
                @Localizer["SystemInfo"]
 | 
			
		||||
            </HeaderTemplate>
 | 
			
		||||
 | 
			
		||||
            <BodyTemplate>
 | 
			
		||||
                <EditorFormObject IsDisplay Model="HardwareJob.HardwareInfo" ItemsPerRow="1" RowType=RowType.Inline LabelWidth="160">
 | 
			
		||||
                    <FieldItems>
 | 
			
		||||
                        <EditorItem @bind-Field="@context.MachineInfo">
 | 
			
		||||
                            <EditTemplate Context="value">
 | 
			
		||||
                                <div class="col-12  col-md-12">
 | 
			
		||||
                                    <Display @bind-Value="@value.MachineInfo.OSName" DisplayText="@Localizer[nameof(value.MachineInfo.OSName)]" ShowTooltip ShowLabel=true>
 | 
			
		||||
                                    </Display>
 | 
			
		||||
                                </div>
 | 
			
		||||
                                <div class="col-12  col-md-12">
 | 
			
		||||
                                    <Display @bind-Value="@value.MachineInfo.OSVersion" DisplayText="@Localizer[nameof(value.MachineInfo.OSVersion)]" ShowTooltip ShowLabel=true>
 | 
			
		||||
                                    </Display>
 | 
			
		||||
                                </div>
 | 
			
		||||
                            </EditTemplate>
 | 
			
		||||
                        </EditorItem>
 | 
			
		||||
 | 
			
		||||
                        <EditorItem @bind-Field="@context.UUID">
 | 
			
		||||
                            <EditTemplate Context="value">
 | 
			
		||||
                                <div class="col-12  col-md-12">
 | 
			
		||||
                                    <Display @bind-Value="@value.UUID" ShowTooltip ShowLabel=true>
 | 
			
		||||
                                    </Display>
 | 
			
		||||
                                </div>
 | 
			
		||||
 | 
			
		||||
                            </EditTemplate>
 | 
			
		||||
                        </EditorItem>
 | 
			
		||||
                        <EditorItem @bind-Field="@context.DriveInfo" Ignore=true>
 | 
			
		||||
                        </EditorItem>
 | 
			
		||||
                    </FieldItems>
 | 
			
		||||
                </EditorFormObject>
 | 
			
		||||
 | 
			
		||||
            </BodyTemplate>
 | 
			
		||||
        </Card>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="col-12 col-md-8">
 | 
			
		||||
        <Card IsShadow=true class="m-2 flex-fill" Color="Color.Primary">
 | 
			
		||||
            <HeaderTemplate>
 | 
			
		||||
                @Localizer["HardwareInfo"]
 | 
			
		||||
            </HeaderTemplate>
 | 
			
		||||
 | 
			
		||||
            <BodyTemplate>
 | 
			
		||||
                <div class="d-flex align-items-center justify-content-center w-100 h-100" >
 | 
			
		||||
 | 
			
		||||
                    <div class="row g-2 mx-1 form-inline d-flex justify-content-center w-100">
 | 
			
		||||
                        <div class="col-12 col-md-4  justify-content-center h-100" >
 | 
			
		||||
                            @{
 | 
			
		||||
                                var item = HardwareJob.HardwareInfo.MachineInfo.CpuRate;
 | 
			
		||||
                            }
 | 
			
		||||
                            <div class="d-flex flex-column align-items-center">
 | 
			
		||||
                                <Circle Width="200" class="m-3"
 | 
			
		||||
                                        Value=@((int)(item*100<=100?item*100:100))
 | 
			
		||||
                                        Color=@((item*100>70?Color.Warning:Color.Success))
 | 
			
		||||
                                        StrokeWidth="4" ShowProgress=false>
 | 
			
		||||
                                    <div class="circle-hardware">
 | 
			
		||||
                                        <span>
 | 
			
		||||
                                            @Localizer["CpuUsage"] <i> @((item * 100).ToString("F0") + " %")</i>
 | 
			
		||||
                                        </span>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                </Circle>
 | 
			
		||||
                                <div class="mt-1">
 | 
			
		||||
                                    <span>@(HardwareJob.HardwareInfo.MachineInfo.Processor)</span>
 | 
			
		||||
                                </div>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <div class="col-12 col-md-4 justify-content-center h-100">
 | 
			
		||||
                            @{
 | 
			
		||||
                                var availableMemory = HardwareJob.HardwareInfo.MachineInfo.AvailableMemory;
 | 
			
		||||
                                var memory = HardwareJob.HardwareInfo.MachineInfo.Memory;
 | 
			
		||||
                            }
 | 
			
		||||
                            <div class="d-flex flex-column align-items-center ">
 | 
			
		||||
                                <Circle Width="200" class="m-3"
 | 
			
		||||
                                        Value=@((int)(memory>availableMemory?100 - (availableMemory * 100.00 / memory):100))
 | 
			
		||||
                                        Color=@((availableMemory * 100.00 / memory<20?Color.Warning:Color.Success))
 | 
			
		||||
                                        StrokeWidth="4">
 | 
			
		||||
                                    <div class="circle-hardware">
 | 
			
		||||
                                        <h6>   @((100 - (availableMemory * 100.00 / memory)).ToString("F2") + " %")  </h6>
 | 
			
		||||
 | 
			
		||||
                                        <span>   @Localizer["WorkingSet"] <i> @(HardwareJob.HardwareInfo.WorkingSet + " MB")</i></span>
 | 
			
		||||
                                        <span>   @Localizer["AvailableMemory"] <i> @((availableMemory / 1024.00 / 1024).ToString("F2") + " GB")</i></span>
 | 
			
		||||
                                        <span>   @Localizer["TotalMemory"] <i> @((memory / 1024.00 / 1024).ToString("F2") + " GB")</i></span>
 | 
			
		||||
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                </Circle>
 | 
			
		||||
                                <div class="mt-1">
 | 
			
		||||
                                    <span>  @Localizer["MemoryUsage"] </span>
 | 
			
		||||
                                </div>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <div class="col-12 col-md-4 justify-content-center h-100">
 | 
			
		||||
                            @{
 | 
			
		||||
                                var totalFreeSpace = HardwareJob.HardwareInfo.DriveInfo.TotalFreeSpace;
 | 
			
		||||
                                var totalSize = HardwareJob.HardwareInfo.DriveInfo.TotalSize;
 | 
			
		||||
                            }
 | 
			
		||||
                            <div class="d-flex flex-column align-items-center ">
 | 
			
		||||
                                <Circle Width="200" class="m-3"
 | 
			
		||||
                                        Value=@((int)(totalSize>totalFreeSpace?100 - (totalFreeSpace * 100.00 / totalSize):100))
 | 
			
		||||
                                        Color=@((totalFreeSpace * 100.00 / totalSize<20?Color.Warning:Color.Success))
 | 
			
		||||
                                        StrokeWidth="4">
 | 
			
		||||
                                    <div class="circle-hardware">
 | 
			
		||||
                                        <h6>   @((100 - (totalFreeSpace * 100.00 / totalSize)).ToString("F2") + " %")</h6>
 | 
			
		||||
                                        <span>   @(HardwareJob.HardwareInfo.DriveInfo.VolumeLabel) </span>
 | 
			
		||||
                                        <span>   @Localizer["AvailableDisk"] <i> @((totalFreeSpace / 1024.00 / 1024 / 1024).ToString("F2") + " GB")</i></span>
 | 
			
		||||
                                        <span>   @Localizer["TotalDisk"] <i> @((totalSize / 1024.00 / 1024 / 1024).ToString("F2") + " GB")</i></span>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                </Circle>
 | 
			
		||||
                                <div class="mt-1">
 | 
			
		||||
                                    <span>  @Localizer["DiskUsage"] </span>
 | 
			
		||||
                                </div>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                <div class="d-flex justify-content-between align-items-center w-100">
 | 
			
		||||
                    <span>@Localizer["SystemInfo"]</span>
 | 
			
		||||
                    <small class="text-muted">
 | 
			
		||||
                        @HardwareJob.HardwareInfo.UpdateTime.ToString("yyyy-MM-dd HH:mm:ss")
 | 
			
		||||
                    </small>
 | 
			
		||||
                </div>
 | 
			
		||||
            </HeaderTemplate>
 | 
			
		||||
 | 
			
		||||
            <BodyTemplate>
 | 
			
		||||
                <EditorForm class="mt-3" AutoGenerateAllItem="false" IsDisplay Model="HardwareJob.HardwareInfo" ItemsPerRow=3 RowType=RowType.Inline LabelWidth=300>
 | 
			
		||||
                    <FieldItems>
 | 
			
		||||
 | 
			
		||||
                        <EditorItem @bind-Field="@context.OSName" GroupName="@Localizer["SystemInfo"]" />
 | 
			
		||||
                        <EditorItem @bind-Field="@context.OsArchitecture" GroupName="@Localizer["SystemInfo"]" />
 | 
			
		||||
                        <EditorItem @bind-Field="@context.OSVersion" GroupName="@Localizer["SystemInfo"]" />
 | 
			
		||||
                        <EditorItem @bind-Field="@context.Environment" GroupName="@Localizer["SystemInfo"]" />
 | 
			
		||||
                        <EditorItem @bind-Field="@context.FrameworkDescription" GroupName="@Localizer["SystemInfo"]" />
 | 
			
		||||
                        <EditorItem @bind-Field="@context.UUID" GroupName="@Localizer["SystemInfo"]" />
 | 
			
		||||
                        <EditorItem @bind-Field="@context.SystemRunTotalMinute" GroupName="@Localizer["SystemInfo"]" />
 | 
			
		||||
                        <EditorItem @bind-Field="@context.AppRunTotalMinute" GroupName="@Localizer["SystemInfo"]" />
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                        <EditorItem @bind-Field="@context.Memory" GroupName="@Localizer["GCMemoryInfoConfig"]" GroupOrder=2 />
 | 
			
		||||
                        <EditorItem @bind-Field="@context.TotalAvailableMemory" GroupName="@Localizer["GCMemoryInfoConfig"]" GroupOrder=2 />
 | 
			
		||||
                        <EditorItem @bind-Field="@context.HighMemoryLoadThreshold" GroupName="@Localizer["GCMemoryConfig"]" GroupOrder=2 />
 | 
			
		||||
 | 
			
		||||
                        <EditorItem TValue="bool" TModel="HardwareInfo" @bind-Field="@context.IsServerGC" GroupName="@Localizer["GCMemoryConfig"]" GroupOrder=2>
 | 
			
		||||
                            <EditTemplate Context="value">
 | 
			
		||||
                                <div class="col-12">
 | 
			
		||||
                                    <h6></h6>
 | 
			
		||||
                                </div>
 | 
			
		||||
                            </EditTemplate>
 | 
			
		||||
                        </EditorItem>
 | 
			
		||||
 | 
			
		||||
                        <EditorItem @bind-Field="@context.IsServerGC" GroupName="@Localizer["GCMemoryInfoConfig"]" GroupOrder=2 />
 | 
			
		||||
                        <EditorItem @bind-Field="@context.GCLatencyMode" GroupName="@Localizer["GCMemoryInfoConfig"]" GroupOrder=2 />
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                        <EditorItem @bind-Field="@context.WorkingSet" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3 />
 | 
			
		||||
                        <EditorItem @bind-Field="@context.PrivateMemory" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3 />
 | 
			
		||||
                        <EditorItem @bind-Field="@context.PeakWorkingSet" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3 />
 | 
			
		||||
                        <EditorItem TValue="ulong" TModel="HardwareInfo" @bind-Field="@context.HeapSize" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3>
 | 
			
		||||
                            <EditTemplate Context="value">
 | 
			
		||||
                                <div class="col-12">
 | 
			
		||||
                                    <h6></h6>
 | 
			
		||||
                                </div>
 | 
			
		||||
                            </EditTemplate>
 | 
			
		||||
                        </EditorItem>
 | 
			
		||||
                        <EditorItem @bind-Field="@context.HeapSize" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3 />
 | 
			
		||||
                        <EditorItem @bind-Field="@context.TotalMemory" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3 />
 | 
			
		||||
                        <EditorItem @bind-Field="@context.FragmentedBytes" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3 />
 | 
			
		||||
                        <EditorItem @bind-Field="@context.CommittedBytes" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3 />
 | 
			
		||||
 | 
			
		||||
                        <EditorItem TValue="ulong" TModel="HardwareInfo" @bind-Field="@context.AvailableMemory" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3>
 | 
			
		||||
                            <EditTemplate Context="value">
 | 
			
		||||
                                <div class="col-12">
 | 
			
		||||
                                    <h6></h6>
 | 
			
		||||
                                </div>
 | 
			
		||||
                            </EditTemplate>
 | 
			
		||||
                        </EditorItem>
 | 
			
		||||
 | 
			
		||||
                        <EditorItem @bind-Field="@context.AvailableMemory" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3 />
 | 
			
		||||
                        <EditorItem @bind-Field="@context.GCAvailableMemory" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3 />
 | 
			
		||||
                        <EditorItem @bind-Field="@context.VirtualMemory" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3 />
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                        <EditorItem @bind-Field="@context.GcGen0Count" GroupName="@Localizer["GCAnalyzeInfo"]" GroupOrder=4 />
 | 
			
		||||
                        <EditorItem @bind-Field="@context.GcGen1Count" GroupName="@Localizer["GCAnalyzeInfo"]" GroupOrder=4 />
 | 
			
		||||
                        <EditorItem @bind-Field="@context.GcGen2Count" GroupName="@Localizer["GCAnalyzeInfo"]" GroupOrder=4 />
 | 
			
		||||
                        <EditorItem TValue="ulong" TModel="HardwareInfo" @bind-Field="@context.TotalAllocatedBytes" GroupName="@Localizer["GCAnalyzeInfo"]" GroupOrder=4>
 | 
			
		||||
                            <EditTemplate Context="value">
 | 
			
		||||
                                <div class="col-12">
 | 
			
		||||
                                    <h6></h6>
 | 
			
		||||
                                </div>
 | 
			
		||||
                            </EditTemplate>
 | 
			
		||||
                        </EditorItem>
 | 
			
		||||
 | 
			
		||||
                        <EditorItem @bind-Field="@context.TotalAllocatedBytes" GroupName="@Localizer["GCAnalyzeInfo"]" GroupOrder=4 />
 | 
			
		||||
                        <EditorItem @bind-Field="@context.TotalPauseDurationMs" GroupName="@Localizer["GCAnalyzeInfo"]" GroupOrder=4 />
 | 
			
		||||
 | 
			
		||||
                    </FieldItems>
 | 
			
		||||
                </EditorForm>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            </BodyTemplate>
 | 
			
		||||
        </Card>
 | 
			
		||||
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<div class="row g-2 mx-1 form-inline mt-2">
 | 
			
		||||
 | 
			
		||||
    <div class="col-12 col-md-12">
 | 
			
		||||
        <Card IsShadow=true class="m-2 flex-fill" Color="Color.Primary">
 | 
			
		||||
            <HeaderTemplate>
 | 
			
		||||
                @Localizer["HardwareInfo"]
 | 
			
		||||
            </HeaderTemplate>
 | 
			
		||||
 | 
			
		||||
            <BodyTemplate>
 | 
			
		||||
                <div class="d-flex align-items-center justify-content-center w-100 h-100">
 | 
			
		||||
                    <div class="row g-2 mx-1 form-inline d-flex justify-content-center w-100">
 | 
			
		||||
                        <!-- ✅ CPU 使用率 -->
 | 
			
		||||
                        <div class="col-12 col-md-4 justify-content-center h-100">
 | 
			
		||||
                            @{
 | 
			
		||||
                                var cpuUsage = HardwareJob.HardwareInfo.CpuRate;
 | 
			
		||||
                            }
 | 
			
		||||
                            <div class="d-flex flex-column align-items-center">
 | 
			
		||||
                                <Circle Width="200" class="m-3"
 | 
			
		||||
                                        Value=@((int)(cpuUsage*100<=100?cpuUsage*100:100))
 | 
			
		||||
                                        Color=@((cpuUsage*100>70?Color.Warning:Color.Success))
 | 
			
		||||
                                        StrokeWidth="4" ShowProgress=false>
 | 
			
		||||
                                    <div class="circle-hardware">
 | 
			
		||||
                                        <span>
 | 
			
		||||
                                            @Localizer["CpuUsage"] <i>@((cpuUsage * 100).ToString("F0") + " %")</i>
 | 
			
		||||
                                        </span>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                </Circle>
 | 
			
		||||
                                <div class="mt-1">
 | 
			
		||||
                                    <span>@(HardwareJob.HardwareInfo.Processor)</span>
 | 
			
		||||
                                </div>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
 | 
			
		||||
                        <!-- ✅ 内存使用率 -->
 | 
			
		||||
                        <div class="col-12 col-md-4 justify-content-center h-100">
 | 
			
		||||
                            @{
 | 
			
		||||
                                var availableMemory = HardwareJob.HardwareInfo.AvailableMemory;
 | 
			
		||||
                                var memory = HardwareJob.HardwareInfo.Memory;
 | 
			
		||||
                            }
 | 
			
		||||
                            <div class="d-flex flex-column align-items-center">
 | 
			
		||||
                                <Circle Width="200" class="m-3"
 | 
			
		||||
                                        Value=@((int)(memory>availableMemory?100 - (availableMemory * 100.00 / memory):100))
 | 
			
		||||
                                        Color=@((availableMemory * 100.00 / memory<20?Color.Warning:Color.Success))
 | 
			
		||||
                                        StrokeWidth="4">
 | 
			
		||||
                                    <div class="circle-hardware">
 | 
			
		||||
                                        <h6>@((100 - (availableMemory * 100.00 / memory)).ToString("F2") + " %")</h6>
 | 
			
		||||
                                        <span>@Localizer["WorkingSet"] <i>@(HardwareJob.HardwareInfo.WorkingSet + " MB")</i></span>
 | 
			
		||||
                                        <span>@Localizer["AvailableMemory"] <i>@((availableMemory / 1024.00).ToString("F2") + " GB")</i></span>
 | 
			
		||||
                                        <span>@Localizer["TotalMemory"] <i>@((memory / 1024.00).ToString("F2") + " GB")</i></span>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                </Circle>
 | 
			
		||||
                                <div class="mt-1">
 | 
			
		||||
                                    <span>@Localizer["MemoryUsage"]</span>
 | 
			
		||||
                                </div>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
 | 
			
		||||
                        <!-- ✅ 磁盘使用率 -->
 | 
			
		||||
                        <div class="col-12 col-md-4 justify-content-center h-100">
 | 
			
		||||
                            @{
 | 
			
		||||
                                var totalFreeSpace = HardwareJob.HardwareInfo.DriveInfo.TotalFreeSpace;
 | 
			
		||||
                                var totalSize = HardwareJob.HardwareInfo.DriveInfo.TotalSize;
 | 
			
		||||
                            }
 | 
			
		||||
                            <div class="d-flex flex-column align-items-center">
 | 
			
		||||
                                <Circle Width="200" class="m-3"
 | 
			
		||||
                                        Value=@((int)(totalSize>totalFreeSpace?100 - (totalFreeSpace * 100.00 / totalSize):100))
 | 
			
		||||
                                        Color=@((totalFreeSpace * 100.00 / totalSize<20?Color.Warning:Color.Success))
 | 
			
		||||
                                        StrokeWidth="4">
 | 
			
		||||
                                    <div class="circle-hardware">
 | 
			
		||||
                                        <h6>@((100 - (totalFreeSpace * 100.00 / totalSize)).ToString("F2") + " %")</h6>
 | 
			
		||||
                                        <span>@(HardwareJob.HardwareInfo.DriveInfo.VolumeLabel)</span>
 | 
			
		||||
                                        <span>@Localizer["AvailableDisk"] <i>@((totalFreeSpace / 1024.00 / 1024 / 1024).ToString("F2") + " GB")</i></span>
 | 
			
		||||
                                        <span>@Localizer["TotalDisk"] <i>@((totalSize / 1024.00 / 1024 / 1024).ToString("F2") + " GB")</i></span>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                </Circle>
 | 
			
		||||
                                <div class="mt-1">
 | 
			
		||||
                                    <span>@Localizer["DiskUsage"]</span>
 | 
			
		||||
                                </div>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </BodyTemplate>
 | 
			
		||||
        </Card>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
<div class="row g-2 mx-1 form-inline">
 | 
			
		||||
 | 
			
		||||
    <div class="col-12 col-md-12">
 | 
			
		||||
@@ -140,7 +198,7 @@
 | 
			
		||||
            </HeaderTemplate>
 | 
			
		||||
 | 
			
		||||
            <BodyTemplate>
 | 
			
		||||
                <Chart @ref=LineChart OnInitAsync="OnInit" Height="var(--line-chart-height)" Width="100%" OnAfterInitAsync="()=>{chartInit=true;return Task.CompletedTask;}" />
 | 
			
		||||
                <Chart @ref=LineChart OnInitAsync="OnInit" Height="var(--line-chart-height)" Width="100%" OnAfterInitAsync="() => { chartInit = true; return Task.CompletedTask; }" />
 | 
			
		||||
            </BodyTemplate>
 | 
			
		||||
        </Card>
 | 
			
		||||
    </div>
 | 
			
		||||
 
 | 
			
		||||
@@ -87,45 +87,45 @@ public class Startup : AppStartup
 | 
			
		||||
#if NET8_0_OR_GREATER
 | 
			
		||||
        services
 | 
			
		||||
         .AddRazorComponents(options => options.TemporaryRedirectionUrlValidityDuration = TimeSpan.FromMinutes(10))
 | 
			
		||||
         .AddInteractiveServerComponents(options =>
 | 
			
		||||
         {
 | 
			
		||||
             options.RootComponents.MaxJSRootComponents = 500;
 | 
			
		||||
             options.JSInteropDefaultCallTimeout = TimeSpan.FromMinutes(2);
 | 
			
		||||
             options.MaxBufferedUnacknowledgedRenderBatches = 20;
 | 
			
		||||
             options.DisconnectedCircuitMaxRetained = 1;
 | 
			
		||||
             options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromSeconds(10);
 | 
			
		||||
         })
 | 
			
		||||
         .AddHubOptions(options =>
 | 
			
		||||
         {
 | 
			
		||||
             //单个传入集线器消息的最大大小。默认 32 KB
 | 
			
		||||
             options.MaximumReceiveMessageSize = 32 * 1024 * 1024;
 | 
			
		||||
             //可为客户端上载流缓冲的最大项数。 如果达到此限制,则会阻止处理调用,直到服务器处理流项。
 | 
			
		||||
             options.StreamBufferCapacity = 30;
 | 
			
		||||
             options.ClientTimeoutInterval = TimeSpan.FromMinutes(2);
 | 
			
		||||
             options.KeepAliveInterval = TimeSpan.FromSeconds(15);
 | 
			
		||||
             options.HandshakeTimeout = TimeSpan.FromSeconds(30);
 | 
			
		||||
 | 
			
		||||
         });
 | 
			
		||||
            .AddInteractiveServerComponents(options =>
 | 
			
		||||
            {
 | 
			
		||||
                options.RootComponents.MaxJSRootComponents = 500;
 | 
			
		||||
                options.JSInteropDefaultCallTimeout = TimeSpan.FromSeconds(30);
 | 
			
		||||
                options.MaxBufferedUnacknowledgedRenderBatches = 5;
 | 
			
		||||
                options.DisconnectedCircuitMaxRetained = 1;
 | 
			
		||||
                options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromSeconds(10);
 | 
			
		||||
            })
 | 
			
		||||
            .AddHubOptions(options =>
 | 
			
		||||
            {
 | 
			
		||||
                //单个传入集线器消息的最大大小。默认 32 KB
 | 
			
		||||
                options.MaximumReceiveMessageSize = 32 * 1024 * 1024;
 | 
			
		||||
                //可为客户端上载流缓冲的最大项数。 如果达到此限制,则会阻止处理调用,直到服务器处理流项。
 | 
			
		||||
                options.StreamBufferCapacity = 30;
 | 
			
		||||
                options.ClientTimeoutInterval = TimeSpan.FromSeconds(30);
 | 
			
		||||
                options.KeepAliveInterval = TimeSpan.FromSeconds(15);
 | 
			
		||||
                options.HandshakeTimeout = TimeSpan.FromSeconds(15);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
#else
 | 
			
		||||
 | 
			
		||||
        services.AddServerSideBlazor(options =>
 | 
			
		||||
        {
 | 
			
		||||
            options.RootComponents.MaxJSRootComponents = 500;
 | 
			
		||||
            options.JSInteropDefaultCallTimeout = TimeSpan.FromMinutes(2);
 | 
			
		||||
            options.MaxBufferedUnacknowledgedRenderBatches = 20;
 | 
			
		||||
             options.DisconnectedCircuitMaxRetained = 1;
 | 
			
		||||
             options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromSeconds(10);
 | 
			
		||||
        }).AddHubOptions(options =>
 | 
			
		||||
        {
 | 
			
		||||
            //单个传入集线器消息的最大大小。默认 32 KB
 | 
			
		||||
            options.MaximumReceiveMessageSize = 32 * 1024 * 1024;
 | 
			
		||||
            //可为客户端上载流缓冲的最大项数。 如果达到此限制,则会阻止处理调用,直到服务器处理流项。
 | 
			
		||||
            options.StreamBufferCapacity = 30;
 | 
			
		||||
            options.ClientTimeoutInterval = TimeSpan.FromMinutes(2);
 | 
			
		||||
            options.KeepAliveInterval = TimeSpan.FromSeconds(15);
 | 
			
		||||
            options.HandshakeTimeout = TimeSpan.FromSeconds(30);
 | 
			
		||||
        });
 | 
			
		||||
             {
 | 
			
		||||
                 options.RootComponents.MaxJSRootComponents = 500;
 | 
			
		||||
                 options.JSInteropDefaultCallTimeout = TimeSpan.FromSeconds(30);
 | 
			
		||||
                 options.MaxBufferedUnacknowledgedRenderBatches = 5;
 | 
			
		||||
                 options.DisconnectedCircuitMaxRetained = 1;
 | 
			
		||||
                 options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromSeconds(10);
 | 
			
		||||
             })
 | 
			
		||||
             .AddHubOptions(options =>
 | 
			
		||||
             {
 | 
			
		||||
                 //单个传入集线器消息的最大大小。默认 32 KB
 | 
			
		||||
                 options.MaximumReceiveMessageSize = 32 * 1024 * 1024;
 | 
			
		||||
                 //可为客户端上载流缓冲的最大项数。 如果达到此限制,则会阻止处理调用,直到服务器处理流项。
 | 
			
		||||
                 options.StreamBufferCapacity = 30;
 | 
			
		||||
                 options.ClientTimeoutInterval = TimeSpan.FromSeconds(30);
 | 
			
		||||
                 options.KeepAliveInterval = TimeSpan.FromSeconds(15);
 | 
			
		||||
                 options.HandshakeTimeout = TimeSpan.FromSeconds(15);
 | 
			
		||||
             });
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 257 KiB  | 
| 
		 Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 177 KiB  | 
@@ -0,0 +1,134 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
//  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
 | 
			
		||||
//  此代码版权(除特别声明外的代码)归作者本人Diego所有
 | 
			
		||||
//  源代码使用协议遵循本仓库的开源协议及附加协议
 | 
			
		||||
//  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
 | 
			
		||||
//  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
 | 
			
		||||
//  使用文档:https://thingsgateway.cn/
 | 
			
		||||
//  QQ群:605534569
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
namespace ThingsGateway.Common;
 | 
			
		||||
 | 
			
		||||
/// <inheritdoc/>
 | 
			
		||||
[ThingsGateway.DependencyInjection.SuppressSniffer]
 | 
			
		||||
public static class EnumerableQueryPageOptionsExtensions
 | 
			
		||||
{
 | 
			
		||||
    public static IEnumerable<T> GetData<T>(this IEnumerable<T> datas, QueryPageOptions option, out int totalCount, FilterKeyValueAction where = null)
 | 
			
		||||
    {
 | 
			
		||||
        totalCount = 0;
 | 
			
		||||
        if (datas == null)
 | 
			
		||||
            return new List<T>();
 | 
			
		||||
        where ??= option.ToFilter();
 | 
			
		||||
        if (where.HasFilters())
 | 
			
		||||
        {
 | 
			
		||||
            datas = datas.Where(where.GetFilterFunc<T>());//name asc模式
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (option.SortList.Count > 0)
 | 
			
		||||
        {
 | 
			
		||||
            datas = datas.Sort(option.SortList);//name asc模式
 | 
			
		||||
        }
 | 
			
		||||
        if (option.AdvancedSortList.Count > 0)
 | 
			
		||||
        {
 | 
			
		||||
            datas = datas.Sort(option.AdvancedSortList);//name asc模式
 | 
			
		||||
        }
 | 
			
		||||
        if (option.SortOrder != SortOrder.Unset && !option.SortName.IsNullOrWhiteSpace())
 | 
			
		||||
        {
 | 
			
		||||
            datas = datas.Sort(option.SortName, option.SortOrder);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        totalCount = datas.Count();
 | 
			
		||||
 | 
			
		||||
        if (option.IsPage)
 | 
			
		||||
        {
 | 
			
		||||
            datas = datas.Skip((option.PageIndex - 1) * option.PageItems).Take(option.PageItems);
 | 
			
		||||
        }
 | 
			
		||||
        else if (option.IsVirtualScroll)
 | 
			
		||||
        {
 | 
			
		||||
            datas = datas.Skip((option.StartIndex) * option.PageItems).Take(option.PageItems);
 | 
			
		||||
        }
 | 
			
		||||
        return datas;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    public static IEnumerable<T> GetQuery<T>(this IEnumerable<T> query, QueryPageOptions option, Func<IEnumerable<T>, IEnumerable<T>>? queryFunc = null, FilterKeyValueAction where = null)
 | 
			
		||||
    {
 | 
			
		||||
        if (queryFunc != null)
 | 
			
		||||
            query = queryFunc(query);
 | 
			
		||||
        where ??= option.ToFilter();
 | 
			
		||||
 | 
			
		||||
        if (where.HasFilters())
 | 
			
		||||
        {
 | 
			
		||||
            query = query.Where(where.GetFilterFunc<T>());//name asc模式
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (option.SortOrder != SortOrder.Unset && !string.IsNullOrEmpty(option.SortName))
 | 
			
		||||
        {
 | 
			
		||||
            var invoker = Utility.GetSortFunc<T>();
 | 
			
		||||
            query = invoker(query, option.SortName, option.SortOrder);
 | 
			
		||||
        }
 | 
			
		||||
        else if (option.SortList.Count > 0)
 | 
			
		||||
        {
 | 
			
		||||
            var invoker = Utility.GetSortListFunc<T>();
 | 
			
		||||
            query = invoker(query, option.SortList);
 | 
			
		||||
        }
 | 
			
		||||
        else if (option.AdvancedSortList.Count > 0)
 | 
			
		||||
        {
 | 
			
		||||
            var invoker = Utility.GetSortListFunc<T>();
 | 
			
		||||
            query = invoker(query, option.AdvancedSortList);
 | 
			
		||||
        }
 | 
			
		||||
        return query;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 根据查询条件返回QueryData
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public static QueryData<T> GetQueryData<T>(this IEnumerable<T> datas, QueryPageOptions option, FilterKeyValueAction where = null)
 | 
			
		||||
    {
 | 
			
		||||
        var ret = new QueryData<T>()
 | 
			
		||||
        {
 | 
			
		||||
            IsSorted = option.SortOrder != SortOrder.Unset,
 | 
			
		||||
            IsFiltered = option.Filters.Count > 0,
 | 
			
		||||
            IsAdvanceSearch = option.AdvanceSearches.Count > 0 || option.CustomerSearches.Count > 0,
 | 
			
		||||
            IsSearch = option.Searches.Count > 0
 | 
			
		||||
        };
 | 
			
		||||
        var items = datas.GetData(option, out var totalCount, where);
 | 
			
		||||
        ret.TotalCount = totalCount;
 | 
			
		||||
 | 
			
		||||
        if (totalCount > 0)
 | 
			
		||||
        {
 | 
			
		||||
            if (!items.Any() && option.PageIndex != 1)
 | 
			
		||||
            {
 | 
			
		||||
                option.PageIndex = 1;
 | 
			
		||||
                items = datas.GetData(option, out totalCount, where);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        ret.Items = items.ToList();
 | 
			
		||||
        return ret;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 根据查询条件返回QueryData
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public static QueryData<SelectedItem> GetQueryData<T>(this IEnumerable<T> datas, VirtualizeQueryOption option, Func<IEnumerable<T>, IEnumerable<SelectedItem>> func, FilterKeyValueAction where = null)
 | 
			
		||||
    {
 | 
			
		||||
        var ret = new QueryData<SelectedItem>()
 | 
			
		||||
        {
 | 
			
		||||
            IsSorted = false,
 | 
			
		||||
            IsFiltered = false,
 | 
			
		||||
            IsAdvanceSearch = false,
 | 
			
		||||
            IsSearch = !option.SearchText.IsNullOrWhiteSpace()
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        var items = datas.Skip((option.StartIndex)).Take(option.Count);
 | 
			
		||||
        ret.TotalCount = datas.Count();
 | 
			
		||||
 | 
			
		||||
        ret.Items = func(items).ToList();
 | 
			
		||||
        return ret;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -19,7 +19,7 @@ using System.Reflection;
 | 
			
		||||
 | 
			
		||||
using ThingsGateway.Common.Extension;
 | 
			
		||||
 | 
			
		||||
namespace ThingsGateway.DB;
 | 
			
		||||
namespace ThingsGateway.Common;
 | 
			
		||||
 | 
			
		||||
/// <summary>
 | 
			
		||||
/// 导出excel扩展
 | 
			
		||||
@@ -11,7 +11,7 @@
 | 
			
		||||
using Microsoft.AspNetCore.Components.Forms;
 | 
			
		||||
using Microsoft.AspNetCore.Http;
 | 
			
		||||
 | 
			
		||||
namespace ThingsGateway.DB;
 | 
			
		||||
namespace ThingsGateway.Common;
 | 
			
		||||
 | 
			
		||||
/// <inheritdoc/>
 | 
			
		||||
[ThingsGateway.DependencyInjection.SuppressSniffer]
 | 
			
		||||
@@ -14,6 +14,7 @@ using System.ComponentModel.DataAnnotations;
 | 
			
		||||
 | 
			
		||||
using ThingsGateway.Common.Extension;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#if NET8_0_OR_GREATER
 | 
			
		||||
using System.Collections.Frozen;
 | 
			
		||||
#endif
 | 
			
		||||
@@ -104,37 +105,6 @@ internal class CacheManager
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 设置 App 开始时间
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public void SetStartTime() => SetStartTime(DateTimeOffset.Now);
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 设置 App 开始时间
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    private void SetStartTime(DateTimeOffset startDateTimeOffset)
 | 
			
		||||
    {
 | 
			
		||||
        GetOrCreate("BootstrapBlazor_StartTime", entry =>
 | 
			
		||||
        {
 | 
			
		||||
            entry.Priority = CacheItemPriority.NeverRemove;
 | 
			
		||||
            return startDateTimeOffset;
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 获取 App 开始时间
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    public DateTimeOffset GetStartTime()
 | 
			
		||||
    {
 | 
			
		||||
        var ret = DateTimeOffset.MinValue;
 | 
			
		||||
        if (Cache.TryGetValue("BootstrapBlazor_StartTime", out var v) && v is DateTimeOffset d)
 | 
			
		||||
        {
 | 
			
		||||
            ret = d;
 | 
			
		||||
        }
 | 
			
		||||
        return ret;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 获得 缓存数量
 | 
			
		||||
    /// </summary>
 | 
			
		||||
@@ -286,8 +256,15 @@ internal class CacheManager
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="assembly">Assembly 程序集实例</param>
 | 
			
		||||
    /// <param name="typeName">类型名称</param>
 | 
			
		||||
    public static IEnumerable<LocalizedString>? GetAllStringsByTypeName(Assembly assembly, string typeName)
 | 
			
		||||
    public static FrozenSet<LocalizedString>? GetAllStringsByTypeName(Assembly assembly, string typeName)
 | 
			
		||||
        => GetJsonStringByTypeName(GetJsonLocalizationOption(), assembly, typeName, CultureInfo.CurrentUICulture.Name);
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 获取指定文化本地化资源集合
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="assembly">Assembly 程序集实例</param>
 | 
			
		||||
    /// <param name="typeName">类型名称</param>
 | 
			
		||||
    public static FrozenDictionary<string, string>? GetAllHasValueStringsByTypeName(Assembly assembly, string typeName)
 | 
			
		||||
        => GetHasValueJsonStringByTypeName(GetJsonLocalizationOption(), assembly, typeName, CultureInfo.CurrentUICulture.Name);
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 通过指定程序集获取所有本地化信息键值集合
 | 
			
		||||
@@ -298,7 +275,7 @@ internal class CacheManager
 | 
			
		||||
    /// <param name="cultureName">cultureName 未空时使用 CultureInfo.CurrentUICulture.Name</param>
 | 
			
		||||
    /// <param name="forceLoad">默认 false 使用缓存值 设置 true 时内部强制重新加载</param>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    public static IEnumerable<LocalizedString>? GetJsonStringByTypeName(JsonLocalizationOptions option, Assembly assembly, string typeName, string? cultureName = null, bool forceLoad = false)
 | 
			
		||||
    public static FrozenSet<LocalizedString>? GetJsonStringByTypeName(JsonLocalizationOptions option, Assembly assembly, string typeName, string? cultureName = null, bool forceLoad = false)
 | 
			
		||||
    {
 | 
			
		||||
        if (assembly.IsDynamic)
 | 
			
		||||
        {
 | 
			
		||||
@@ -308,13 +285,15 @@ internal class CacheManager
 | 
			
		||||
        cultureName ??= CultureInfo.CurrentUICulture.Name;
 | 
			
		||||
        if (string.IsNullOrEmpty(cultureName))
 | 
			
		||||
        {
 | 
			
		||||
            return [];
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var key = $"{CacheKeyPrefix}-{nameof(GetJsonStringByTypeName)}-{assembly.GetUniqueName()}-{cultureName}";
 | 
			
		||||
        var typeKey = $"{CacheKeyPrefix}-{nameof(GetJsonStringByTypeName)}-{assembly.GetUniqueName()}-{typeName}-{cultureName}";
 | 
			
		||||
        if (forceLoad)
 | 
			
		||||
        {
 | 
			
		||||
            Instance.Cache.Remove(key);
 | 
			
		||||
            Instance.Cache.Remove(typeKey);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var localizedItems = Instance.GetOrCreate(key, entry =>
 | 
			
		||||
@@ -335,16 +314,77 @@ internal class CacheManager
 | 
			
		||||
            return items.ToHashSet();
 | 
			
		||||
#endif
 | 
			
		||||
        });
 | 
			
		||||
        return localizedItems.Where(item => item.SearchedLocation!.Equals(typeName, StringComparison.OrdinalIgnoreCase));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        var typeLocalizedItems = Instance.GetOrCreate(typeKey, entry =>
 | 
			
		||||
        {
 | 
			
		||||
            return localizedItems.Where(item => item.SearchedLocation!.Equals(typeName, StringComparison.OrdinalIgnoreCase)).ToFrozenSet();
 | 
			
		||||
        });
 | 
			
		||||
        return typeLocalizedItems;
 | 
			
		||||
    }
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 通过 ILocalizationResolve 接口实现类获得本地化键值集合
 | 
			
		||||
    /// 通过指定程序集获取所有本地化信息键值集合
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="typeName"></param>
 | 
			
		||||
    /// <param name="includeParentCultures"></param>
 | 
			
		||||
    /// <param name="option">JsonLocalizationOptions 实例</param>
 | 
			
		||||
    /// <param name="assembly">Assembly 程序集实例</param>
 | 
			
		||||
    /// <param name="typeName">类型名称</param>
 | 
			
		||||
    /// <param name="cultureName">cultureName 未空时使用 CultureInfo.CurrentUICulture.Name</param>
 | 
			
		||||
    /// <param name="forceLoad">默认 false 使用缓存值 设置 true 时内部强制重新加载</param>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    public static IEnumerable<LocalizedString> GetTypeStringsFromResolve(string typeName, bool includeParentCultures = true) => Provider.GetRequiredService<ILocalizationResolve>().GetAllStringsByType(typeName, includeParentCultures);
 | 
			
		||||
    public static FrozenDictionary<string, string>? GetHasValueJsonStringByTypeName(JsonLocalizationOptions option, Assembly assembly, string typeName, string? cultureName = null, bool forceLoad = false)
 | 
			
		||||
    {
 | 
			
		||||
        if (assembly.IsDynamic)
 | 
			
		||||
        {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        cultureName ??= CultureInfo.CurrentUICulture.Name;
 | 
			
		||||
        if (string.IsNullOrEmpty(cultureName))
 | 
			
		||||
        {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var key = $"{CacheKeyPrefix}-{nameof(GetJsonStringByTypeName)}-{assembly.GetUniqueName()}-{cultureName}";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        var typeKey = $"{CacheKeyPrefix}-{nameof(GetHasValueJsonStringByTypeName)}-{assembly.GetUniqueName()}-{typeName}-{cultureName}";
 | 
			
		||||
        if (forceLoad)
 | 
			
		||||
        {
 | 
			
		||||
            Instance.Cache.Remove(key);
 | 
			
		||||
            Instance.Cache.Remove(typeKey);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var localizedItems = Instance.GetOrCreate(key, entry =>
 | 
			
		||||
        {
 | 
			
		||||
            var sections = option.GetJsonStringFromAssembly(assembly, cultureName);
 | 
			
		||||
            var items = sections.SelectMany(section => section.GetChildren().Select(kv =>
 | 
			
		||||
            {
 | 
			
		||||
                var value = kv.Value;
 | 
			
		||||
                if (value == null && option.UseKeyWhenValueIsNull == true)
 | 
			
		||||
                {
 | 
			
		||||
                    value = kv.Key;
 | 
			
		||||
                }
 | 
			
		||||
                return new LocalizedString(kv.Key, value ?? "", false, section.Key);
 | 
			
		||||
            }));
 | 
			
		||||
#if NET8_0_OR_GREATER
 | 
			
		||||
            return items.ToFrozenSet();
 | 
			
		||||
#else
 | 
			
		||||
            return items.ToHashSet();
 | 
			
		||||
#endif
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        var typeLocalizedItems = Instance.GetOrCreate(typeKey, entry =>
 | 
			
		||||
        {
 | 
			
		||||
            return localizedItems.Where(item => item.SearchedLocation!.Equals(typeName, StringComparison.OrdinalIgnoreCase) && !item.ResourceNotFound).ToFrozenDictionary(a => a.Name, a => a.Value);
 | 
			
		||||
        });
 | 
			
		||||
        return typeLocalizedItems;
 | 
			
		||||
    }
 | 
			
		||||
    ///// <summary>
 | 
			
		||||
    ///// 通过 ILocalizationResolve 接口实现类获得本地化键值集合
 | 
			
		||||
    ///// </summary>
 | 
			
		||||
    ///// <param name="typeName"></param>
 | 
			
		||||
    ///// <param name="includeParentCultures"></param>
 | 
			
		||||
    ///// <returns></returns>
 | 
			
		||||
    //public static IEnumerable<LocalizedString> GetTypeStringsFromResolve(string typeName, bool includeParentCultures = true) => Provider.GetRequiredService<ILocalizationResolve>().GetAllStringsByType(typeName, includeParentCultures);
 | 
			
		||||
    #endregion
 | 
			
		||||
 | 
			
		||||
    #region DisplayName
 | 
			
		||||
 
 | 
			
		||||
@@ -81,50 +81,16 @@ internal class JsonStringLocalizer(Assembly assembly, string typeName, string ba
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    private string? GetStringSafely(string name) => GetStringFromJson(name);
 | 
			
		||||
 | 
			
		||||
    private string? GetStringFromService(string name)
 | 
			
		||||
    {
 | 
			
		||||
        // get string from inject service
 | 
			
		||||
        string? ret = null;
 | 
			
		||||
        if (jsonLocalizationOptions.DisableGetLocalizerFromService == false)
 | 
			
		||||
        {
 | 
			
		||||
            var localizer = Utility.GetStringLocalizerFromService(Assembly, typeName);
 | 
			
		||||
            if (localizer != null && localizer is not JsonStringLocalizer)
 | 
			
		||||
            {
 | 
			
		||||
                var l = localizer[name];
 | 
			
		||||
                if (!l.ResourceNotFound)
 | 
			
		||||
                {
 | 
			
		||||
                    ret = l.Value;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return ret;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private string? GetStringFromResourceManager(string name)
 | 
			
		||||
    {
 | 
			
		||||
        string? ret = null;
 | 
			
		||||
        if (jsonLocalizationOptions.DisableGetLocalizerFromResourceManager == false)
 | 
			
		||||
        {
 | 
			
		||||
            ret = GetStringSafely(name, CultureInfo.CurrentUICulture);
 | 
			
		||||
        }
 | 
			
		||||
        return ret;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private readonly ConcurrentHashSet<string> _missingManifestCache = [];
 | 
			
		||||
    private string? GetStringFromJson(string name)
 | 
			
		||||
    {
 | 
			
		||||
        // get string from json localization file
 | 
			
		||||
        var localizerStrings = MegerResolveLocalizers(CacheManager.GetAllStringsByTypeName(Assembly, typeName));
 | 
			
		||||
        var cacheKey = $"name={name}&culture={CultureInfo.CurrentUICulture.Name}";
 | 
			
		||||
        string? ret = null;
 | 
			
		||||
        if (!_missingManifestCache.Contain(cacheKey))
 | 
			
		||||
        {
 | 
			
		||||
            var l = localizerStrings.Find(i => i.Name == name);
 | 
			
		||||
            if (l is { ResourceNotFound: false })
 | 
			
		||||
            {
 | 
			
		||||
                ret = l.Value;
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            var localizerStrings = CacheManager.GetAllHasValueStringsByTypeName(Assembly, typeName);
 | 
			
		||||
            if (localizerStrings?.TryGetValue(name, out ret) != true)
 | 
			
		||||
            {
 | 
			
		||||
                // 如果没有找到资源信息则尝试从父类中查找
 | 
			
		||||
                ret ??= GetStringFromBaseType(name);
 | 
			
		||||
@@ -150,28 +116,13 @@ internal class JsonStringLocalizer(Assembly assembly, string typeName, string ba
 | 
			
		||||
            if (baseType != type)
 | 
			
		||||
            {
 | 
			
		||||
                var baseAssembly = baseType.Assembly;
 | 
			
		||||
                var localizerStrings = MegerResolveLocalizers(CacheManager.GetAllStringsByTypeName(baseAssembly, baseType.FullName!));
 | 
			
		||||
                var l = localizerStrings.Find(i => i.Name == name);
 | 
			
		||||
                if (l is { ResourceNotFound: false })
 | 
			
		||||
                {
 | 
			
		||||
                    ret = l.Value;
 | 
			
		||||
                }
 | 
			
		||||
                var localizerStrings = CacheManager.GetAllHasValueStringsByTypeName(baseAssembly, baseType.FullName!);
 | 
			
		||||
                _ = localizerStrings?.TryGetValue(name, out ret);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return ret;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private List<LocalizedString> MegerResolveLocalizers(IEnumerable<LocalizedString>? localizerStrings)
 | 
			
		||||
    {
 | 
			
		||||
        var localizers = new List<LocalizedString>(CacheManager.GetTypeStringsFromResolve(typeName));
 | 
			
		||||
 | 
			
		||||
        if (localizerStrings != null)
 | 
			
		||||
        {
 | 
			
		||||
            localizers.AddRange(localizerStrings);
 | 
			
		||||
        }
 | 
			
		||||
        return localizers;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void HandleMissingResourceItem(string name)
 | 
			
		||||
    {
 | 
			
		||||
        localizationMissingItemHandler.HandleMissingItem(name, typeName, CultureInfo.CurrentUICulture.Name);
 | 
			
		||||
@@ -183,7 +134,7 @@ internal class JsonStringLocalizer(Assembly assembly, string typeName, string ba
 | 
			
		||||
        _missingManifestCache.TryAdd($"name={name}&culture={CultureInfo.CurrentUICulture.Name}");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private List<LocalizedString>? _allLocalizerdStrings;
 | 
			
		||||
    private LocalizedString[]? _allLocalizerdStrings;
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 获取当前语言的所有资源信息
 | 
			
		||||
@@ -198,7 +149,7 @@ internal class JsonStringLocalizer(Assembly assembly, string typeName, string ba
 | 
			
		||||
                ?? GetAllStringsFromBase()
 | 
			
		||||
                ?? GetAllStringsFromJson();
 | 
			
		||||
 | 
			
		||||
            _allLocalizerdStrings = MegerResolveLocalizers(items);
 | 
			
		||||
            _allLocalizerdStrings = items.ToArray();
 | 
			
		||||
        }
 | 
			
		||||
        return _allLocalizerdStrings;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,7 @@
 | 
			
		||||
	<ItemGroup>
 | 
			
		||||
		<PackageReference Include="BootstrapBlazor.TableExport" Version="9.2.7" />
 | 
			
		||||
		<PackageReference Include="Yitter.IdGenerator" Version="1.0.14" />
 | 
			
		||||
		<PackageReference Include="BootstrapBlazor" Version="9.11.4" />
 | 
			
		||||
		<PackageReference Include="BootstrapBlazor" Version="9.12.0" />
 | 
			
		||||
	</ItemGroup>
 | 
			
		||||
 | 
			
		||||
	<ItemGroup>
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,7 @@
 | 
			
		||||
 | 
			
		||||
using Yitter.IdGenerator;
 | 
			
		||||
 | 
			
		||||
namespace ThingsGateway.DB;
 | 
			
		||||
namespace ThingsGateway.Common;
 | 
			
		||||
 | 
			
		||||
/// <summary>
 | 
			
		||||
/// 公共功能
 | 
			
		||||
@@ -16,74 +16,6 @@ namespace ThingsGateway.DB;
 | 
			
		||||
[ThingsGateway.DependencyInjection.SuppressSniffer]
 | 
			
		||||
public static class QueryPageOptionsExtensions
 | 
			
		||||
{
 | 
			
		||||
    public static IEnumerable<T> GetData<T>(this IEnumerable<T> datas, QueryPageOptions option, out int totalCount, FilterKeyValueAction where = null)
 | 
			
		||||
    {
 | 
			
		||||
        totalCount = 0;
 | 
			
		||||
        if (datas == null)
 | 
			
		||||
            return new List<T>();
 | 
			
		||||
        where ??= option.ToFilter();
 | 
			
		||||
        if (where.HasFilters())
 | 
			
		||||
        {
 | 
			
		||||
            datas = datas.Where(where.GetFilterFunc<T>());//name asc模式
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (option.SortList.Count > 0)
 | 
			
		||||
        {
 | 
			
		||||
            datas = datas.Sort(option.SortList);//name asc模式
 | 
			
		||||
        }
 | 
			
		||||
        if (option.AdvancedSortList.Count > 0)
 | 
			
		||||
        {
 | 
			
		||||
            datas = datas.Sort(option.AdvancedSortList);//name asc模式
 | 
			
		||||
        }
 | 
			
		||||
        if (option.SortOrder != SortOrder.Unset && !option.SortName.IsNullOrWhiteSpace())
 | 
			
		||||
        {
 | 
			
		||||
            datas = datas.Sort(option.SortName, option.SortOrder);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        totalCount = datas.Count();
 | 
			
		||||
 | 
			
		||||
        if (option.IsPage)
 | 
			
		||||
        {
 | 
			
		||||
            datas = datas.Skip((option.PageIndex - 1) * option.PageItems).Take(option.PageItems);
 | 
			
		||||
        }
 | 
			
		||||
        else if (option.IsVirtualScroll)
 | 
			
		||||
        {
 | 
			
		||||
            datas = datas.Skip((option.StartIndex) * option.PageItems).Take(option.PageItems);
 | 
			
		||||
        }
 | 
			
		||||
        return datas;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    public static IEnumerable<T> GetQuery<T>(this IEnumerable<T> query, QueryPageOptions option, Func<IEnumerable<T>, IEnumerable<T>>? queryFunc = null, FilterKeyValueAction where = null)
 | 
			
		||||
    {
 | 
			
		||||
        if (queryFunc != null)
 | 
			
		||||
            query = queryFunc(query);
 | 
			
		||||
        where ??= option.ToFilter();
 | 
			
		||||
 | 
			
		||||
        if (where.HasFilters())
 | 
			
		||||
        {
 | 
			
		||||
            query = query.Where(where.GetFilterFunc<T>());//name asc模式
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (option.SortOrder != SortOrder.Unset && !string.IsNullOrEmpty(option.SortName))
 | 
			
		||||
        {
 | 
			
		||||
            var invoker = Utility.GetSortFunc<T>();
 | 
			
		||||
            query = invoker(query, option.SortName, option.SortOrder);
 | 
			
		||||
        }
 | 
			
		||||
        else if (option.SortList.Count > 0)
 | 
			
		||||
        {
 | 
			
		||||
            var invoker = Utility.GetSortListFunc<T>();
 | 
			
		||||
            query = invoker(query, option.SortList);
 | 
			
		||||
        }
 | 
			
		||||
        else if (option.AdvancedSortList.Count > 0)
 | 
			
		||||
        {
 | 
			
		||||
            var invoker = Utility.GetSortListFunc<T>();
 | 
			
		||||
            query = invoker(query, option.AdvancedSortList);
 | 
			
		||||
        }
 | 
			
		||||
        return query;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 根据查询条件返回sqlsugar ISugarQueryable
 | 
			
		||||
    /// </summary>
 | 
			
		||||
@@ -111,50 +43,4 @@ public static class QueryPageOptionsExtensions
 | 
			
		||||
        return query;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 根据查询条件返回QueryData
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public static QueryData<T> GetQueryData<T>(this IEnumerable<T> datas, QueryPageOptions option, FilterKeyValueAction where = null)
 | 
			
		||||
    {
 | 
			
		||||
        var ret = new QueryData<T>()
 | 
			
		||||
        {
 | 
			
		||||
            IsSorted = option.SortOrder != SortOrder.Unset,
 | 
			
		||||
            IsFiltered = option.Filters.Count > 0,
 | 
			
		||||
            IsAdvanceSearch = option.AdvanceSearches.Count > 0 || option.CustomerSearches.Count > 0,
 | 
			
		||||
            IsSearch = option.Searches.Count > 0
 | 
			
		||||
        };
 | 
			
		||||
        var items = datas.GetData(option, out var totalCount, where);
 | 
			
		||||
        ret.TotalCount = totalCount;
 | 
			
		||||
 | 
			
		||||
        if (totalCount > 0)
 | 
			
		||||
        {
 | 
			
		||||
            if (!items.Any() && option.PageIndex != 1)
 | 
			
		||||
            {
 | 
			
		||||
                option.PageIndex = 1;
 | 
			
		||||
                items = datas.GetData(option, out totalCount, where);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        ret.Items = items.ToList();
 | 
			
		||||
        return ret;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 根据查询条件返回QueryData
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public static QueryData<SelectedItem> GetQueryData<T>(this IEnumerable<T> datas, VirtualizeQueryOption option, Func<IEnumerable<T>, IEnumerable<SelectedItem>> func, FilterKeyValueAction where = null)
 | 
			
		||||
    {
 | 
			
		||||
        var ret = new QueryData<SelectedItem>()
 | 
			
		||||
        {
 | 
			
		||||
            IsSorted = false,
 | 
			
		||||
            IsFiltered = false,
 | 
			
		||||
            IsAdvanceSearch = false,
 | 
			
		||||
            IsSearch = !option.SearchText.IsNullOrWhiteSpace()
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        var items = datas.Skip((option.StartIndex)).Take(option.Count);
 | 
			
		||||
        ret.TotalCount = datas.Count();
 | 
			
		||||
 | 
			
		||||
        ret.Items = func(items).ToList();
 | 
			
		||||
        return ret;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 257 KiB  | 
| 
		 After Width: | Height: | Size: 177 KiB  | 
@@ -8,7 +8,7 @@ namespace ThingsGateway.NewLife.Collections;
 | 
			
		||||
/// 文档 https://newlifex.com/core/object_pool
 | 
			
		||||
/// </remarks>
 | 
			
		||||
/// <typeparam name="T"></typeparam>
 | 
			
		||||
public class ObjectPoolLock<T> : DisposeBase, IPool<T> where T : class
 | 
			
		||||
public class ObjectPoolLock<T> : DisposeBase where T : class
 | 
			
		||||
{
 | 
			
		||||
    #region 属性
 | 
			
		||||
    /// <summary>名称</summary>
 | 
			
		||||
@@ -22,11 +22,6 @@ public class ObjectPoolLock<T> : DisposeBase, IPool<T> where T : class
 | 
			
		||||
    /// <summary>繁忙个数</summary>
 | 
			
		||||
    public Int32 BusyCount => _BusyCount;
 | 
			
		||||
 | 
			
		||||
    /// <summary>最大个数。默认0,0表示无上限</summary>
 | 
			
		||||
    public Int32 Max { get; set; } = 0;
 | 
			
		||||
 | 
			
		||||
    private readonly object _syncRoot = new();
 | 
			
		||||
 | 
			
		||||
    /// <summary>基础空闲集合。只保存最小个数,最热部分</summary>
 | 
			
		||||
    private readonly Stack<T> _free = new();
 | 
			
		||||
 | 
			
		||||
@@ -73,7 +68,7 @@ public class ObjectPoolLock<T> : DisposeBase, IPool<T> where T : class
 | 
			
		||||
            if (_inited) return;
 | 
			
		||||
            _inited = true;
 | 
			
		||||
 | 
			
		||||
            WriteLog($"Init {typeof(T).FullName} Max={Max}");
 | 
			
		||||
            WriteLog($"Init {typeof(T).FullName}");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    #endregion
 | 
			
		||||
@@ -86,7 +81,7 @@ public class ObjectPoolLock<T> : DisposeBase, IPool<T> where T : class
 | 
			
		||||
        T? pi = null;
 | 
			
		||||
        do
 | 
			
		||||
        {
 | 
			
		||||
            lock (_syncRoot)
 | 
			
		||||
            lock (lockThis)
 | 
			
		||||
            {
 | 
			
		||||
                if (_free.Count > 0)
 | 
			
		||||
                {
 | 
			
		||||
@@ -95,13 +90,6 @@ public class ObjectPoolLock<T> : DisposeBase, IPool<T> where T : class
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    if (Max > 0 && BusyCount >= Max)
 | 
			
		||||
                    {
 | 
			
		||||
                        var msg = $"申请失败,已有 {BusyCount:n0} 达到或超过最大值 {Max:n0}";
 | 
			
		||||
                        WriteLog("Acquire Max " + msg);
 | 
			
		||||
                        throw new Exception(Name + " " + msg);
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    pi = OnCreate();
 | 
			
		||||
                    if (BusyCount == 0) Init();
 | 
			
		||||
 | 
			
		||||
@@ -114,7 +102,7 @@ public class ObjectPoolLock<T> : DisposeBase, IPool<T> where T : class
 | 
			
		||||
            // 如果拿到的对象不可用,则重新借
 | 
			
		||||
        } while (pi == null || !OnGet(pi));
 | 
			
		||||
 | 
			
		||||
        lock (_syncRoot)
 | 
			
		||||
        lock (lockThis)
 | 
			
		||||
        {
 | 
			
		||||
            // 加入繁忙集合
 | 
			
		||||
            _busy.Add(pi);
 | 
			
		||||
@@ -129,16 +117,12 @@ public class ObjectPoolLock<T> : DisposeBase, IPool<T> where T : class
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    protected virtual Boolean OnGet(T value) => true;
 | 
			
		||||
 | 
			
		||||
    /// <summary>申请资源包装项,Dispose时自动归还到池中</summary>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    public PoolItem<T> GetItem() => new(this, Get());
 | 
			
		||||
 | 
			
		||||
    /// <summary>归还</summary>
 | 
			
		||||
    /// <param name="value"></param>
 | 
			
		||||
    public virtual Boolean Return(T value)
 | 
			
		||||
    {
 | 
			
		||||
        if (value == null) return false;
 | 
			
		||||
        lock (_syncRoot)
 | 
			
		||||
        lock (lockThis)
 | 
			
		||||
        {
 | 
			
		||||
            // 从繁忙队列找到并移除缓存项
 | 
			
		||||
            if (!_busy.Remove(value))
 | 
			
		||||
@@ -163,7 +147,7 @@ public class ObjectPoolLock<T> : DisposeBase, IPool<T> where T : class
 | 
			
		||||
        {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        lock (_syncRoot)
 | 
			
		||||
        lock (lockThis)
 | 
			
		||||
        {
 | 
			
		||||
            _free.Push(value);
 | 
			
		||||
            _FreeCount++;
 | 
			
		||||
@@ -180,18 +164,9 @@ public class ObjectPoolLock<T> : DisposeBase, IPool<T> where T : class
 | 
			
		||||
    /// <summary>清空已有对象</summary>
 | 
			
		||||
    public virtual Int32 Clear()
 | 
			
		||||
    {
 | 
			
		||||
        var count = _FreeCount + _BusyCount;
 | 
			
		||||
 | 
			
		||||
        //_busy.Clear();
 | 
			
		||||
        //_BusyCount = 0;
 | 
			
		||||
 | 
			
		||||
        //_free.Clear();
 | 
			
		||||
        //while (_free2.TryDequeue(out var rs)) ;
 | 
			
		||||
        //_FreeCount = 0;
 | 
			
		||||
 | 
			
		||||
        lock (_syncRoot)
 | 
			
		||||
        lock (lockThis)
 | 
			
		||||
        {
 | 
			
		||||
            count = _FreeCount + _BusyCount;
 | 
			
		||||
            var count = _FreeCount + _BusyCount;
 | 
			
		||||
 | 
			
		||||
            while (_free.Count > 0)
 | 
			
		||||
            {
 | 
			
		||||
@@ -207,9 +182,9 @@ public class ObjectPoolLock<T> : DisposeBase, IPool<T> where T : class
 | 
			
		||||
            }
 | 
			
		||||
            _busy.Clear();
 | 
			
		||||
            _BusyCount = 0;
 | 
			
		||||
            return count;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return count;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>销毁</summary>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,23 +1,27 @@
 | 
			
		||||
using System.ComponentModel;
 | 
			
		||||
using System.Diagnostics;
 | 
			
		||||
using System.Diagnostics.CodeAnalysis;
 | 
			
		||||
using System.Net.NetworkInformation;
 | 
			
		||||
using System.Reflection;
 | 
			
		||||
using System.Runtime;
 | 
			
		||||
using System.Runtime.InteropServices;
 | 
			
		||||
using System.Runtime.Versioning;
 | 
			
		||||
using System.Security;
 | 
			
		||||
 | 
			
		||||
using ThingsGateway.NewLife.Collections;
 | 
			
		||||
using ThingsGateway.NewLife.Data;
 | 
			
		||||
using ThingsGateway.NewLife.Log;
 | 
			
		||||
using ThingsGateway.NewLife.Model;
 | 
			
		||||
using ThingsGateway.NewLife.Reflection;
 | 
			
		||||
using ThingsGateway.NewLife.Serialization;
 | 
			
		||||
using ThingsGateway.NewLife.Windows;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#if NETFRAMEWORK
 | 
			
		||||
using System.Management;
 | 
			
		||||
 | 
			
		||||
using Microsoft.VisualBasic.Devices;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
#if NETFRAMEWORK || NET6_0_OR_GREATER
 | 
			
		||||
using Microsoft.Win32;
 | 
			
		||||
@@ -42,7 +46,7 @@ public interface IMachineInfo
 | 
			
		||||
/// 
 | 
			
		||||
/// 刷新信息成本较高,建议采用单例模式
 | 
			
		||||
/// </remarks>
 | 
			
		||||
public class MachineInfo : IExtend
 | 
			
		||||
public class MachineInfo
 | 
			
		||||
{
 | 
			
		||||
    #region 属性
 | 
			
		||||
    /// <summary>系统名称</summary>
 | 
			
		||||
@@ -88,11 +92,11 @@ public class MachineInfo : IExtend
 | 
			
		||||
    [DisplayName("磁盘序列号")]
 | 
			
		||||
    public String? DiskID { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>内存总量。单位KB</summary>
 | 
			
		||||
    /// <summary>内存总量。单位MB</summary>
 | 
			
		||||
    [DisplayName("内存总量")]
 | 
			
		||||
    public UInt64 Memory { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>可用内存。单位KB</summary>
 | 
			
		||||
    /// <summary>可用内存。单位MB</summary>
 | 
			
		||||
    [DisplayName("可用内存")]
 | 
			
		||||
    public UInt64 AvailableMemory { get; set; }
 | 
			
		||||
 | 
			
		||||
@@ -116,13 +120,98 @@ public class MachineInfo : IExtend
 | 
			
		||||
    [DisplayName("电池剩余")]
 | 
			
		||||
    public Double Battery { get; set; }
 | 
			
		||||
 | 
			
		||||
    private readonly Dictionary<String, Object?> _items = [];
 | 
			
		||||
    IDictionary<String, Object?> IExtend.Items => _items;
 | 
			
		||||
#if NET6_0_OR_GREATER
 | 
			
		||||
    #region GC与进程内存信息
 | 
			
		||||
 | 
			
		||||
    /// <summary>GC 认为“内存吃紧”的阈值。单位:MB</summary>
 | 
			
		||||
    [DisplayName("GC高内存阈值")]
 | 
			
		||||
    public UInt64 HighMemoryLoadThreshold { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>GC 可用内存上限。单位:MB</summary>
 | 
			
		||||
    [DisplayName("GC可用内存上限")]
 | 
			
		||||
    public UInt64 TotalAvailableMemory { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>当前托管堆容量。单位:MB</summary>
 | 
			
		||||
    [DisplayName("托管堆容量")]
 | 
			
		||||
    public UInt64 HeapSize { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>托管堆已用内存。单位:MB</summary>
 | 
			
		||||
    [DisplayName("托管堆已用")]
 | 
			
		||||
    public UInt64 TotalMemory { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>托管堆碎片大小。单位:MB</summary>
 | 
			
		||||
    [DisplayName("托管堆碎片")]
 | 
			
		||||
    public UInt64 FragmentedBytes { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>GC识别可用内存。单位:MB</summary>
 | 
			
		||||
    [DisplayName("GC识别可用内存")]
 | 
			
		||||
    public UInt64 GCAvailableMemory { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>GC 已提交的内存。单位:MB</summary>
 | 
			
		||||
    [DisplayName("GC已提交内存")]
 | 
			
		||||
    public UInt64 CommittedBytes { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>GC 累计分配的托管内存。单位:MB</summary>
 | 
			
		||||
    [DisplayName("GC累计分配")]
 | 
			
		||||
    public UInt64 TotalAllocatedBytes { get; set; }
 | 
			
		||||
#if NET8_0_OR_GREATER
 | 
			
		||||
    /// <summary>GC 暂停累计时间。单位:毫秒</summary>
 | 
			
		||||
    [DisplayName("GC累计暂停时间")]
 | 
			
		||||
    public UInt64 TotalPauseDurationMs { get; set; }
 | 
			
		||||
#endif
 | 
			
		||||
    /// <summary>GC 代0收集次数</summary>
 | 
			
		||||
    [DisplayName("GC Gen0 次数")]
 | 
			
		||||
    public Int32 GcGen0Count { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>GC 代1收集次数</summary>
 | 
			
		||||
    [DisplayName("GC Gen1 次数")]
 | 
			
		||||
    public Int32 GcGen1Count { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>GC 代2收集次数</summary>
 | 
			
		||||
    [DisplayName("GC Gen2 次数")]
 | 
			
		||||
    public Int32 GcGen2Count { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>Server GC 是否启用</summary>
 | 
			
		||||
    [DisplayName("是否使用Server GC")]
 | 
			
		||||
    public Boolean IsServerGC { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>GC 延迟模式</summary>
 | 
			
		||||
    [DisplayName("GC延迟模式")]
 | 
			
		||||
    public GCLatencyMode? GCLatencyMode { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>GC 固定对象数</summary>
 | 
			
		||||
    [DisplayName("固定对象数")]
 | 
			
		||||
    public Int64 PinnedObjectsCount { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>终结队列挂起对象数</summary>
 | 
			
		||||
    [DisplayName("终结挂起数")]
 | 
			
		||||
    public Int64 FinalizationPendingCount { get; set; }
 | 
			
		||||
 | 
			
		||||
    #endregion
 | 
			
		||||
 | 
			
		||||
    #region 进程内存信息
 | 
			
		||||
 | 
			
		||||
    /// <summary>进程虚拟内存使用量。单位:MB</summary>
 | 
			
		||||
    [DisplayName("虚拟内存")]
 | 
			
		||||
    public UInt64 VirtualMemory { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>进程私有内存使用量。单位:MB</summary>
 | 
			
		||||
    [DisplayName("私有内存")]
 | 
			
		||||
    public UInt64 PrivateMemory { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>进程峰值工作集。单位:MB</summary>
 | 
			
		||||
    [DisplayName("峰值工作集")]
 | 
			
		||||
    public UInt64 PeakWorkingSet { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>进程当前工作集。单位:MB</summary>
 | 
			
		||||
    [DisplayName("当前工作集")]
 | 
			
		||||
    public UInt64 WorkingSet { get; set; }
 | 
			
		||||
 | 
			
		||||
    #endregion
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /// <summary>获取 或 设置 扩展属性数据</summary>
 | 
			
		||||
    /// <param name="key"></param>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    public Object? this[String key] { get => _items.TryGetValue(key, out var obj) ? obj : null; set => _items[key] = value; }
 | 
			
		||||
    #endregion
 | 
			
		||||
 | 
			
		||||
    #region 全局静态
 | 
			
		||||
@@ -323,7 +412,7 @@ public class MachineInfo : IExtend
 | 
			
		||||
#if NETFRAMEWORK || WINDOWS
 | 
			
		||||
        {
 | 
			
		||||
            var ci = new Microsoft.VisualBasic.Devices.ComputerInfo();
 | 
			
		||||
            Memory = (ulong)(ci.TotalPhysicalMemory / 1024.0);
 | 
			
		||||
            Memory = (ulong)(ci.TotalPhysicalMemory / 1024.0 / 1024.0);
 | 
			
		||||
        }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
@@ -543,7 +632,7 @@ public class MachineInfo : IExtend
 | 
			
		||||
            //if (dic2.TryGetValue("Model Name", out str)) Product = str;
 | 
			
		||||
            if (dic.TryGetValue("Model Identifier", out var str)) Product = str;
 | 
			
		||||
            if (dic.TryGetValue("Processor Name", out str)) Processor = str;
 | 
			
		||||
            if (dic.TryGetValue("Memory", out str)) Memory = (UInt64)str.TrimEnd("GB").Trim().ToLong() * 1024 * 1024;
 | 
			
		||||
            if (dic.TryGetValue("Memory", out str)) Memory = (UInt64)str.TrimEnd("GB").Trim().ToLong() * 1024;
 | 
			
		||||
            if (dic.TryGetValue("Serial Number (system)", out str)) Serial = str;
 | 
			
		||||
            if (dic.TryGetValue("Hardware UUID", out str)) UUID = str;
 | 
			
		||||
            if (dic.TryGetValue("Processor Name", out str)) Processor = str;
 | 
			
		||||
@@ -569,10 +658,50 @@ public class MachineInfo : IExtend
 | 
			
		||||
        else if (Runtime.Linux)
 | 
			
		||||
            RefreshLinux();
 | 
			
		||||
 | 
			
		||||
        // 刷新 GC 与进程内存信息
 | 
			
		||||
        RefreshMemoryInfo();
 | 
			
		||||
 | 
			
		||||
        RefreshSpeed();
 | 
			
		||||
 | 
			
		||||
        Provider?.Refresh(this);
 | 
			
		||||
    }
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 刷新 GC 与进程内存相关信息
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    private void RefreshMemoryInfo()
 | 
			
		||||
    {
 | 
			
		||||
#if NET6_0_OR_GREATER
 | 
			
		||||
        var info = GC.GetGCMemoryInfo();
 | 
			
		||||
        var proc = Process.GetCurrentProcess();
 | 
			
		||||
 | 
			
		||||
        // GC 信息(单位:MB)
 | 
			
		||||
        HighMemoryLoadThreshold = (ulong)(info.HighMemoryLoadThresholdBytes / 1024 / 1024);
 | 
			
		||||
        TotalAvailableMemory = (ulong)(info.TotalAvailableMemoryBytes / 1024 / 1024);
 | 
			
		||||
        HeapSize = (ulong)(info.HeapSizeBytes / 1024 / 1024);
 | 
			
		||||
        TotalMemory = (ulong)(GC.GetTotalMemory(false) / 1024 / 1024);
 | 
			
		||||
        FragmentedBytes = (ulong)(info.FragmentedBytes / 1024 / 1024);
 | 
			
		||||
        GCAvailableMemory = (ulong)Math.Max(0, (info.TotalAvailableMemoryBytes - info.MemoryLoadBytes) / 1024 / 1024);
 | 
			
		||||
        CommittedBytes = (ulong)(info.TotalCommittedBytes / 1024 / 1024);
 | 
			
		||||
        TotalAllocatedBytes = (ulong)(GC.GetTotalAllocatedBytes(false) / 1024 / 1024);
 | 
			
		||||
#if NET8_0_OR_GREATER
 | 
			
		||||
        TotalPauseDurationMs = (ulong)GC.GetTotalPauseDuration().TotalMilliseconds;
 | 
			
		||||
#endif
 | 
			
		||||
        GcGen0Count = GC.CollectionCount(0);
 | 
			
		||||
        GcGen1Count = GC.CollectionCount(1);
 | 
			
		||||
        GcGen2Count = GC.CollectionCount(2);
 | 
			
		||||
        IsServerGC = System.Runtime.GCSettings.IsServerGC;
 | 
			
		||||
        GCLatencyMode = System.Runtime.GCSettings.LatencyMode;
 | 
			
		||||
        PinnedObjectsCount = info.PinnedObjectsCount;
 | 
			
		||||
        FinalizationPendingCount = info.FinalizationPendingCount;
 | 
			
		||||
 | 
			
		||||
        // 进程信息(单位:MB)
 | 
			
		||||
        VirtualMemory = (ulong)(proc.VirtualMemorySize64 / 1024 / 1024);
 | 
			
		||||
        PrivateMemory = (ulong)(proc.PrivateMemorySize64 / 1024 / 1024);
 | 
			
		||||
        PeakWorkingSet = (ulong)(proc.PeakWorkingSet64 / 1024 / 1024);
 | 
			
		||||
        WorkingSet = (ulong)(proc.WorkingSet64 / 1024 / 1024);
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void RefreshWindows()
 | 
			
		||||
    {
 | 
			
		||||
@@ -580,8 +709,8 @@ public class MachineInfo : IExtend
 | 
			
		||||
        ms.Init();
 | 
			
		||||
        if (GlobalMemoryStatusEx(ref ms))
 | 
			
		||||
        {
 | 
			
		||||
            Memory = (ulong)(ms.ullTotalPhys / 1024.0);
 | 
			
		||||
            AvailableMemory = (ulong)(ms.ullAvailPhys / 1024.0);
 | 
			
		||||
            Memory = ms.ullTotalPhys / 1024 / 1024;
 | 
			
		||||
            AvailableMemory = ms.ullAvailPhys / 1024 / 1024;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        GetSystemTimes(out var idleTime, out var kernelTime, out var userTime);
 | 
			
		||||
@@ -675,28 +804,87 @@ public class MachineInfo : IExtend
 | 
			
		||||
#endif
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 🐳 容器内存使用
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    private static (ulong Total, ulong Used) GetCGroupMemoryUsage()
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
 | 
			
		||||
            string[] limitPaths = {
 | 
			
		||||
            "/sys/fs/cgroup/memory/memory.limit_in_bytes", // cgroup v1
 | 
			
		||||
            "/sys/fs/cgroup/memory.max"                    // cgroup v2
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
            string[] usagePaths = {
 | 
			
		||||
            "/sys/fs/cgroup/memory/memory.usage_in_bytes",  // cgroup v1
 | 
			
		||||
            "/sys/fs/cgroup/memory.current"                 // cgroup v2
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
            ulong total = ReadFirstAvailable(limitPaths);
 | 
			
		||||
            ulong used = ReadFirstAvailable(usagePaths);
 | 
			
		||||
 | 
			
		||||
            // 容器无内存限制时 total 通常是 2^63-1,忽略
 | 
			
		||||
            if (total > (1UL << 60)) total = 0;
 | 
			
		||||
 | 
			
		||||
            return (total, used);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception)
 | 
			
		||||
        {
 | 
			
		||||
            return (0, 0);
 | 
			
		||||
        }
 | 
			
		||||
        static ulong ReadFirstAvailable(string[] paths)
 | 
			
		||||
        {
 | 
			
		||||
            foreach (var path in paths)
 | 
			
		||||
            {
 | 
			
		||||
                if (File.Exists(path))
 | 
			
		||||
                {
 | 
			
		||||
                    var content = File.ReadAllText(path).Trim();
 | 
			
		||||
                    if (content == "max") return ulong.MaxValue;
 | 
			
		||||
                    if (ulong.TryParse(content, out var value)) return value;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            return 0;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void RefreshLinux()
 | 
			
		||||
    {
 | 
			
		||||
        var dic = ReadInfo("/proc/meminfo");
 | 
			
		||||
        if (dic != null)
 | 
			
		||||
        var (totalMemory, usedMemory) = GetCGroupMemoryUsage();
 | 
			
		||||
        if (totalMemory > 0 && usedMemory > 0 && totalMemory < ulong.MaxValue / 2)
 | 
			
		||||
        {
 | 
			
		||||
            if (dic.TryGetValue("MemTotal", out var str) && !str.IsNullOrEmpty())
 | 
			
		||||
                Memory = (UInt64)str.TrimEnd(" kB").ToLong();
 | 
			
		||||
 | 
			
		||||
            ulong ma = 0;
 | 
			
		||||
            if (dic.TryGetValue("MemAvailable", out str) && !str.IsNullOrEmpty())
 | 
			
		||||
            Memory = totalMemory / 1024 / 1024;
 | 
			
		||||
            AvailableMemory = (totalMemory - usedMemory) / 1024 / 1024;
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            var dic = ReadInfo("/proc/meminfo");
 | 
			
		||||
            if (dic != null)
 | 
			
		||||
            {
 | 
			
		||||
                ma = (UInt64)(str.TrimEnd(" kB").ToLong());
 | 
			
		||||
                if (dic.TryGetValue("MemTotal", out var str) && !str.IsNullOrEmpty())
 | 
			
		||||
                    Memory = (UInt64)(str.TrimEnd(" kB").ToLong() / 1024.0);
 | 
			
		||||
 | 
			
		||||
                ulong ma = 0;
 | 
			
		||||
                if (dic.TryGetValue("MemAvailable", out str) && !str.IsNullOrEmpty())
 | 
			
		||||
                {
 | 
			
		||||
                    ma = (UInt64)(str.TrimEnd(" kB").ToLong() / 1024);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                //低于3.14内核的版本用 free+cache
 | 
			
		||||
                var mf = (UInt64)(dic["MemFree"]?.TrimEnd(" kB").ToLong() / 1024 ?? 0);
 | 
			
		||||
                var mc = (UInt64)(dic["Cached"]?.TrimEnd(" kB").ToLong() / 1024 ?? 0);
 | 
			
		||||
                var bf = (UInt64)(dic["Buffers"]?.TrimEnd(" kB").ToLong() / 1024 ?? 0);
 | 
			
		||||
 | 
			
		||||
                var free = mf + mc + bf;
 | 
			
		||||
 | 
			
		||||
                AvailableMemory = ma > free ? ma : free;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            //低于3.14内核的版本用 free+cache
 | 
			
		||||
            var mf = (UInt64)(dic["MemFree"]?.TrimEnd(" kB").ToLong() ?? 0);
 | 
			
		||||
            var mc = (UInt64)(dic["Cached"]?.TrimEnd(" kB").ToLong() ?? 0);
 | 
			
		||||
            var bf = (UInt64)(dic["Buffers"]?.TrimEnd(" kB").ToLong() ?? 0);
 | 
			
		||||
 | 
			
		||||
            var free = mf + mc + bf;
 | 
			
		||||
 | 
			
		||||
            AvailableMemory = ma > free ? ma : free;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // A2/A4温度获取,Buildroot,CPU温度和主板温度
 | 
			
		||||
 
 | 
			
		||||
@@ -180,6 +180,12 @@ public static class Runtime
 | 
			
		||||
    #endregion
 | 
			
		||||
 | 
			
		||||
    #region 扩展
 | 
			
		||||
 | 
			
		||||
    public static Int64 AppStartTick = TickCount64;
 | 
			
		||||
 | 
			
		||||
    /// <summary>软件启动以来的毫秒数</summary>
 | 
			
		||||
    public static Int64 AppTickCount64 => TickCount64 - AppStartTick;
 | 
			
		||||
 | 
			
		||||
#if NETCOREAPP3_1_OR_GREATER
 | 
			
		||||
    /// <summary>系统启动以来的毫秒数</summary>
 | 
			
		||||
    public static Int64 TickCount64 => Environment.TickCount64;
 | 
			
		||||
@@ -196,6 +202,8 @@ public static class Runtime
 | 
			
		||||
    }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /// <summary>获取当前UTC时间。基于全局时间提供者,在星尘应用中会屏蔽服务器时间差</summary>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    public static DateTimeOffset UtcNow => TimerScheduler.GlobalTimeProvider.GetUtcNow();
 | 
			
		||||
 
 | 
			
		||||
@@ -65,6 +65,7 @@ public static class DictionaryExtensions
 | 
			
		||||
        this IDictionary<TKey, TValue> dictionary,
 | 
			
		||||
        IEnumerable<TKey> keys)
 | 
			
		||||
    {
 | 
			
		||||
        if(keys==null) yield break;
 | 
			
		||||
        foreach (var key in keys)
 | 
			
		||||
        {
 | 
			
		||||
            if (dictionary.TryGetValue(key, out var value))
 | 
			
		||||
 
 | 
			
		||||
@@ -94,6 +94,7 @@ public static class XTrace
 | 
			
		||||
 | 
			
		||||
    static XTrace()
 | 
			
		||||
    {
 | 
			
		||||
        _ = Runtime.AppTickCount64;
 | 
			
		||||
        AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
 | 
			
		||||
        TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
 | 
			
		||||
        AppDomain.CurrentDomain.ProcessExit += OnProcessExit;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,29 +0,0 @@
 | 
			
		||||
<Project Sdk="Microsoft.NET.Sdk">
 | 
			
		||||
	<Import Project="..\..\Foundation.props" />
 | 
			
		||||
 | 
			
		||||
	<PropertyGroup>
 | 
			
		||||
		<IncludeAsyncInterfaces>false</IncludeAsyncInterfaces>
 | 
			
		||||
	</PropertyGroup>
 | 
			
		||||
 | 
			
		||||
	<PropertyGroup Condition="$(TargetFramework)=='net6.0'">
 | 
			
		||||
		<DefineConstants>$(DefineConstants);PLAT_THREADPOOLWORKITEM;PLAT_MRVTSC</DefineConstants>
 | 
			
		||||
	</PropertyGroup>
 | 
			
		||||
	<PropertyGroup Condition="$(TargetFramework)=='net8.0'">
 | 
			
		||||
		<DefineConstants>$(DefineConstants);PLAT_THREADPOOLWORKITEM;PLAT_MRVTSC</DefineConstants>
 | 
			
		||||
	</PropertyGroup>
 | 
			
		||||
 | 
			
		||||
	<PropertyGroup Condition="$(TargetFramework)=='netstandard2.0'">
 | 
			
		||||
		<DefineConstants>$(DefineConstants);PLAT_MRVTSC</DefineConstants>
 | 
			
		||||
		<IncludeAsyncInterfaces>true</IncludeAsyncInterfaces>
 | 
			
		||||
	</PropertyGroup>
 | 
			
		||||
 | 
			
		||||
	<PropertyGroup Condition="$(TargetFramework)=='net462'">
 | 
			
		||||
		<DefineConstants>$(DefineConstants);PLAT_MRVTSC</DefineConstants>
 | 
			
		||||
		<IncludeAsyncInterfaces>true</IncludeAsyncInterfaces>
 | 
			
		||||
	</PropertyGroup>
 | 
			
		||||
 | 
			
		||||
	<ItemGroup>
 | 
			
		||||
		<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="1.1.0" Condition="$(IncludeAsyncInterfaces)=='true'" />
 | 
			
		||||
	</ItemGroup>
 | 
			
		||||
 | 
			
		||||
</Project>
 | 
			
		||||
@@ -1,5 +0,0 @@
 | 
			
		||||
using System.Runtime.CompilerServices;
 | 
			
		||||
 | 
			
		||||
[assembly: InternalsVisibleTo("Benchmark, PublicKey=0024000004800000940000000602000000240000525341310004000001000100d136a87a0c11ded39332f7ead1f2ce53256a42a350ee847df1dc520bc781210a5f1fe73b3f91a4001fdcfa35340a212e70443de46e6928510f57a55f4ab9b95257f5067d4de4be8cdad460781866b20a6ca20f66302df61b52e55e16c080cad95aae8b94ffdd54718b9963d0240b0d0d5afe655b05c91771f7dc8a314387d3c3")]
 | 
			
		||||
 | 
			
		||||
[assembly: InternalsVisibleTo("PooledAwait.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100d136a87a0c11ded39332f7ead1f2ce53256a42a350ee847df1dc520bc781210a5f1fe73b3f91a4001fdcfa35340a212e70443de46e6928510f57a55f4ab9b95257f5067d4de4be8cdad460781866b20a6ca20f66302df61b52e55e16c080cad95aae8b94ffdd54718b9963d0240b0d0d5afe655b05c91771f7dc8a314387d3c3")]
 | 
			
		||||
@@ -4,7 +4,7 @@
 | 
			
		||||
	<Import Project="..\..\PackNuget.props" />
 | 
			
		||||
 | 
			
		||||
	<PropertyGroup>
 | 
			
		||||
		<TargetFrameworks>net47;netstandard2.0;net6.0;net6.0-windows;net8.0;$(OtherTargetFrameworks);net8.0-windows;</TargetFrameworks>
 | 
			
		||||
		<TargetFrameworks>net462;netstandard2.0;net6.0;net6.0-windows;net8.0;$(OtherTargetFrameworks);net8.0-windows;</TargetFrameworks>
 | 
			
		||||
		<AssemblyName>ThingsGateway.NewLife.X</AssemblyName>
 | 
			
		||||
		<RootNamespace>ThingsGateway.NewLife</RootNamespace>
 | 
			
		||||
		<AssemblyTitle>工具核心库</AssemblyTitle>
 | 
			
		||||
@@ -12,9 +12,6 @@
 | 
			
		||||
		<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
 | 
			
		||||
		<SignAssembly>True</SignAssembly>
 | 
			
		||||
		<AssemblyOriginatorKeyFile>newlife.snk</AssemblyOriginatorKeyFile>
 | 
			
		||||
		
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	</PropertyGroup>
 | 
			
		||||
 | 
			
		||||
	<ItemGroup>
 | 
			
		||||
@@ -24,7 +21,7 @@
 | 
			
		||||
		<None Remove="..\..\..\README.zh-CN.md" Pack="false" PackagePath="\" />
 | 
			
		||||
	</ItemGroup>
 | 
			
		||||
 | 
			
		||||
	<PropertyGroup Condition="'$(TargetFramework)'=='net47' or '$(TargetFramework)'=='net5.0-windows' or '$(TargetFramework)'=='net6.0-windows' or '$(TargetFramework)'=='net7.0-windows' or '$(TargetFramework)'=='net8.0-windows'">
 | 
			
		||||
	<PropertyGroup Condition="'$(TargetFramework)'=='net462' or '$(TargetFramework)'=='net5.0-windows' or '$(TargetFramework)'=='net6.0-windows' or '$(TargetFramework)'=='net7.0-windows' or '$(TargetFramework)'=='net8.0-windows'">
 | 
			
		||||
		<DefineConstants>__WIN__</DefineConstants>
 | 
			
		||||
	</PropertyGroup>
 | 
			
		||||
 | 
			
		||||
@@ -35,11 +32,12 @@
 | 
			
		||||
	<ItemGroup Condition="'$(TargetFramework)'=='netstandard2.0'">
 | 
			
		||||
		<PackageReference Include="System.Memory" Version="4.6.3" />
 | 
			
		||||
	</ItemGroup>
 | 
			
		||||
	<ItemGroup Condition="'$(TargetFramework)'=='net47'">
 | 
			
		||||
	<ItemGroup Condition="'$(TargetFramework)'=='net462'">
 | 
			
		||||
		<PackageReference Include="System.Memory" Version="4.6.3" />
 | 
			
		||||
		<PackageReference Include="System.ValueTuple" Version="4.6.1" />
 | 
			
		||||
	</ItemGroup>
 | 
			
		||||
 | 
			
		||||
	<ItemGroup Condition="'$(TargetFramework)'=='net47'">
 | 
			
		||||
	<ItemGroup Condition="'$(TargetFramework)'=='net462'">
 | 
			
		||||
		<Using Include="System.Net.Http" />
 | 
			
		||||
		<Reference Include="Microsoft.VisualBasic" />
 | 
			
		||||
		<Reference Include="System.Management" />
 | 
			
		||||
@@ -54,10 +52,7 @@
 | 
			
		||||
		<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
 | 
			
		||||
	</ItemGroup>
 | 
			
		||||
 | 
			
		||||
	<PropertyGroup Condition="$(TargetFramework)=='net6.0'">
 | 
			
		||||
		<DefineConstants>$(DefineConstants);PLAT_THREADPOOLWORKITEM;PLAT_MRVTSC</DefineConstants>
 | 
			
		||||
	</PropertyGroup>
 | 
			
		||||
	<PropertyGroup Condition="$(TargetFramework)=='net8.0'">
 | 
			
		||||
	<PropertyGroup Condition="$(TargetFramework)=='net6.0' OR $(TargetFramework)=='net6.0-windows' OR $(TargetFramework)=='net8.0' OR $(TargetFramework)=='net8.0-windows' OR $(TargetFramework)=='net10.0' OR $(TargetFramework)=='net10.0-windows'">
 | 
			
		||||
		<DefineConstants>$(DefineConstants);PLAT_THREADPOOLWORKITEM;PLAT_MRVTSC</DefineConstants>
 | 
			
		||||
	</PropertyGroup>
 | 
			
		||||
 | 
			
		||||
@@ -66,7 +61,7 @@
 | 
			
		||||
		<IncludeAsyncInterfaces>true</IncludeAsyncInterfaces>
 | 
			
		||||
	</PropertyGroup>
 | 
			
		||||
 | 
			
		||||
	<PropertyGroup Condition="$(TargetFramework)=='net47'">
 | 
			
		||||
	<PropertyGroup Condition="$(TargetFramework)=='net462'">
 | 
			
		||||
		<DefineConstants>$(DefineConstants);PLAT_MRVTSC</DefineConstants>
 | 
			
		||||
		<IncludeAsyncInterfaces>true</IncludeAsyncInterfaces>
 | 
			
		||||
	</PropertyGroup>
 | 
			
		||||
@@ -88,6 +83,6 @@
 | 
			
		||||
		<PackageReference Include="Microsoft.Extensions.Options" Version="8.0.2" />
 | 
			
		||||
	</ItemGroup>-->
 | 
			
		||||
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
</Project>
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,6 @@
 | 
			
		||||
	</PropertyGroup>
 | 
			
		||||
 | 
			
		||||
	<ItemGroup>
 | 
			
		||||
		<FrameworkReference Include="Microsoft.AspNetCore.App" />
 | 
			
		||||
		<PackageReference Include="Photino.NET" Version="4.0.16" />
 | 
			
		||||
	</ItemGroup>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 257 KiB  | 
| 
		 Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 177 KiB  | 
@@ -694,8 +694,12 @@ namespace ThingsGateway.SqlSugar
 | 
			
		||||
            var enumerator = table.GetEnumerator();
 | 
			
		||||
            while (enumerator.MoveNext())
 | 
			
		||||
            {
 | 
			
		||||
                var cur = enumerator.Current;
 | 
			
		||||
                yield return cur.Value.Item2[rowIndex];
 | 
			
		||||
                var kvp = enumerator.Current;
 | 
			
		||||
                var list = kvp.Value.Item2;
 | 
			
		||||
                if (list != null && rowIndex < list.Count)
 | 
			
		||||
                    yield return list[rowIndex];
 | 
			
		||||
                else
 | 
			
		||||
                    yield return new DataInfos { ColumnName = kvp.Key, Value = DBNull.Value };
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -201,6 +201,7 @@ namespace ThingsGateway.SqlSugar
 | 
			
		||||
            {
 | 
			
		||||
                foreach (var column in columns)
 | 
			
		||||
                {
 | 
			
		||||
 | 
			
		||||
                    if (column.IsIgnore)
 | 
			
		||||
                    {
 | 
			
		||||
                        continue;
 | 
			
		||||
@@ -210,6 +211,12 @@ namespace ThingsGateway.SqlSugar
 | 
			
		||||
                    {
 | 
			
		||||
                        name = column.PropertyName;
 | 
			
		||||
                    }
 | 
			
		||||
                    if (!results.TryGetValue(name, out var tuple) || tuple.Item2 == null)
 | 
			
		||||
                    {
 | 
			
		||||
                        // 某些列可能不在 DataTable 中(例如数据库多了列)
 | 
			
		||||
                        continue;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    var value = ValueConverter(column, GetValue(item, column));
 | 
			
		||||
                    if (column.SqlParameterDbType != null && column.SqlParameterDbType is Type && UtilMethods.HasInterface((Type)column.SqlParameterDbType, typeof(ISugarDataConverter)))
 | 
			
		||||
                    {
 | 
			
		||||
 
 | 
			
		||||
@@ -24,7 +24,7 @@
 | 
			
		||||
 | 
			
		||||
	<ItemGroup>
 | 
			
		||||
		<PackageReference Include="SqlSugarCore.Dm" Version="8.8.2" />
 | 
			
		||||
		<PackageReference Include="SqlSugarCore.Kdbndp" Version="9.3.7.905" />
 | 
			
		||||
		<PackageReference Include="SqlSugarCore.Kdbndp" Version="9.3.7.1032" />
 | 
			
		||||
		<PackageReference Include="Microsoft.Data.Sqlite" Version="7.0.20" />
 | 
			
		||||
		<!--<PackageReference Include="Microsoft.Data.Sqlite" Version="$(NET10Version)" />-->
 | 
			
		||||
		<PackageReference Include="MySqlConnector" Version="2.4.0" />
 | 
			
		||||
 
 | 
			
		||||
@@ -1,18 +1,18 @@
 | 
			
		||||
<Project>
 | 
			
		||||
 | 
			
		||||
	<PropertyGroup>
 | 
			
		||||
		<PluginVersion>10.12.6</PluginVersion>
 | 
			
		||||
		<ProPluginVersion>10.12.6</ProPluginVersion>
 | 
			
		||||
		<DefaultVersion>10.12.6</DefaultVersion>
 | 
			
		||||
		<AuthenticationVersion>10.11.6</AuthenticationVersion>
 | 
			
		||||
		<SourceGeneratorVersion>10.11.6</SourceGeneratorVersion>
 | 
			
		||||
		<PluginVersion>10.12.28</PluginVersion>
 | 
			
		||||
		<ProPluginVersion>10.12.28</ProPluginVersion>
 | 
			
		||||
		<DefaultVersion>10.12.28</DefaultVersion>
 | 
			
		||||
		<AuthenticationVersion>10.11.7</AuthenticationVersion>
 | 
			
		||||
		<SourceGeneratorVersion>10.11.7</SourceGeneratorVersion>
 | 
			
		||||
		<NET8Version>8.0.21</NET8Version>
 | 
			
		||||
		<NET10Version>10.0.0-rc.2.25502.107</NET10Version>
 | 
			
		||||
		<SatelliteResourceLanguages>zh-Hans;en-US</SatelliteResourceLanguages>
 | 
			
		||||
		<IsTrimmable>false</IsTrimmable>
 | 
			
		||||
		<ManagementProPluginVersion>10.11.87</ManagementProPluginVersion>
 | 
			
		||||
		<ManagementPluginVersion>10.11.87</ManagementPluginVersion>
 | 
			
		||||
		<TSVersion>4.0.0-beta.140</TSVersion>
 | 
			
		||||
		<TSVersion>4.0.0-rc.15</TSVersion>
 | 
			
		||||
		
 | 
			
		||||
		
 | 
			
		||||
	</PropertyGroup>
 | 
			
		||||
@@ -22,7 +22,9 @@
 | 
			
		||||
		<OtherTargetFrameworks>net10.0</OtherTargetFrameworks>
 | 
			
		||||
	</PropertyGroup>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	<PropertyGroup>
 | 
			
		||||
		<RestoreEnablePackagePruning Condition="'$(TargetFramework)' == 'net462' "> false</RestoreEnablePackagePruning>
 | 
			
		||||
	</PropertyGroup>
 | 
			
		||||
 | 
			
		||||
	<PropertyGroup Condition=" '$(TargetFramework)' != 'net8.0' ">
 | 
			
		||||
		<PluginTargetFramework>net10.0</PluginTargetFramework>
 | 
			
		||||
 
 | 
			
		||||
@@ -30,7 +30,6 @@ public interface IClientChannel : IChannel, ISender, IClient, IClientSender, IOn
 | 
			
		||||
    WaitHandlePool<MessageBase> WaitHandlePool { get; }
 | 
			
		||||
 | 
			
		||||
    WaitLock GetLock(string key);
 | 
			
		||||
    void LogSeted(bool logSeted);
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 设置数据处理适配器
 | 
			
		||||
 
 | 
			
		||||
@@ -80,13 +80,11 @@ public class OtherChannel : SetupConfigObject, IClientChannel
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    private bool logSet;
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public void SetDataHandlingAdapterLogger(ILog log)
 | 
			
		||||
    {
 | 
			
		||||
        if (!logSet && ReadOnlyDataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter)
 | 
			
		||||
        if (ReadOnlyDataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter)
 | 
			
		||||
        {
 | 
			
		||||
            logSet = true;
 | 
			
		||||
            handleAdapter.Logger = log;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -96,12 +94,8 @@ public class OtherChannel : SetupConfigObject, IClientChannel
 | 
			
		||||
        if (adapter is SingleStreamDataHandlingAdapter singleStreamDataHandlingAdapter)
 | 
			
		||||
            SetAdapter(singleStreamDataHandlingAdapter);
 | 
			
		||||
 | 
			
		||||
        logSet = false;
 | 
			
		||||
    }
 | 
			
		||||
    public void LogSeted(bool logSeted)
 | 
			
		||||
    {
 | 
			
		||||
        logSet = logSeted;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 设置数据处理适配器。
 | 
			
		||||
    /// </summary>
 | 
			
		||||
 
 | 
			
		||||
@@ -97,13 +97,15 @@ public static class PluginUtil
 | 
			
		||||
        {
 | 
			
		||||
            action += a =>
 | 
			
		||||
            {
 | 
			
		||||
                a.UseTcpSessionCheckClear()
 | 
			
		||||
        .SetCheckClearType(CheckClearType.All)
 | 
			
		||||
        .SetTick(TimeSpan.FromMilliseconds(channelOptions.CheckClearTime))
 | 
			
		||||
        .SetOnClose((c, t) =>
 | 
			
		||||
        {
 | 
			
		||||
            return c.CloseAsync($"{channelOptions.CheckClearTime}ms Timeout");
 | 
			
		||||
        });
 | 
			
		||||
                a.UseTcpSessionCheckClear(options =>
 | 
			
		||||
                {
 | 
			
		||||
                    options.CheckClearType = CheckClearType.All;
 | 
			
		||||
                    options.Tick = TimeSpan.FromMilliseconds(channelOptions.CheckClearTime);
 | 
			
		||||
                    options.OnClose = (c, t) =>
 | 
			
		||||
                    {
 | 
			
		||||
                        return c.CloseAsync($"{channelOptions.CheckClearTime}ms Timeout");
 | 
			
		||||
                    };
 | 
			
		||||
                });
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
        return action;
 | 
			
		||||
 
 | 
			
		||||
@@ -52,13 +52,11 @@ public class SerialPortChannel : SerialPortClient, IClientChannel
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public DataHandlingAdapter ReadOnlyDataHandlingAdapter => ProtectedDataHandlingAdapter;
 | 
			
		||||
 | 
			
		||||
    private bool logSet;
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public void SetDataHandlingAdapterLogger(ILog log)
 | 
			
		||||
    {
 | 
			
		||||
        if (!logSet && ProtectedDataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter)
 | 
			
		||||
        if (ProtectedDataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter)
 | 
			
		||||
        {
 | 
			
		||||
            logSet = true;
 | 
			
		||||
            handleAdapter.Logger = log;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -68,13 +66,9 @@ public class SerialPortChannel : SerialPortClient, IClientChannel
 | 
			
		||||
        if (adapter is SingleStreamDataHandlingAdapter singleStreamDataHandlingAdapter)
 | 
			
		||||
            SetAdapter(singleStreamDataHandlingAdapter);
 | 
			
		||||
 | 
			
		||||
        logSet = false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void LogSeted(bool logSeted)
 | 
			
		||||
    {
 | 
			
		||||
        logSet = logSeted;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
 
 | 
			
		||||
@@ -34,12 +34,10 @@ public class TcpClientChannel : TcpClient, IClientChannel
 | 
			
		||||
        WaitHandlePool = new WaitHandlePool<MessageBase>(minSign, maxSign);
 | 
			
		||||
        pool?.CancelAll();
 | 
			
		||||
    }
 | 
			
		||||
    private bool logSet;
 | 
			
		||||
    public void SetDataHandlingAdapterLogger(ILog log)
 | 
			
		||||
    {
 | 
			
		||||
        if (!logSet && DataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter)
 | 
			
		||||
        if (DataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter)
 | 
			
		||||
        {
 | 
			
		||||
            logSet = true;
 | 
			
		||||
            handleAdapter.Logger = log;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -49,12 +47,8 @@ public class TcpClientChannel : TcpClient, IClientChannel
 | 
			
		||||
        if (adapter is SingleStreamDataHandlingAdapter singleStreamDataHandlingAdapter)
 | 
			
		||||
            SetAdapter(singleStreamDataHandlingAdapter);
 | 
			
		||||
 | 
			
		||||
        logSet = false;
 | 
			
		||||
    }
 | 
			
		||||
    public void LogSeted(bool logSeted)
 | 
			
		||||
    {
 | 
			
		||||
        logSet = logSeted;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public ChannelReceivedEventHandler ChannelReceived { get; } = new();
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -25,13 +25,11 @@ public class TcpSessionClientChannel : TcpSessionClient, IClientChannel
 | 
			
		||||
    public TcpSessionClientChannel()
 | 
			
		||||
    {
 | 
			
		||||
    }
 | 
			
		||||
    private bool logSet;
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public void SetDataHandlingAdapterLogger(ILog log)
 | 
			
		||||
    {
 | 
			
		||||
        if (!logSet && DataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter)
 | 
			
		||||
        if (DataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter)
 | 
			
		||||
        {
 | 
			
		||||
            logSet = true;
 | 
			
		||||
            handleAdapter.Logger = log;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -41,12 +39,8 @@ public class TcpSessionClientChannel : TcpSessionClient, IClientChannel
 | 
			
		||||
        if (adapter is SingleStreamDataHandlingAdapter singleStreamDataHandlingAdapter)
 | 
			
		||||
            SetAdapter(singleStreamDataHandlingAdapter);
 | 
			
		||||
 | 
			
		||||
        logSet = false;
 | 
			
		||||
    }
 | 
			
		||||
    public void LogSeted(bool logSeted)
 | 
			
		||||
    {
 | 
			
		||||
        logSet = logSeted;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void ResetSign(int minSign = 1, int maxSign = ushort.MaxValue - 1)
 | 
			
		||||
    {
 | 
			
		||||
        var pool = WaitHandlePool;
 | 
			
		||||
 
 | 
			
		||||
@@ -30,27 +30,21 @@ public class UdpSessionChannel : UdpSession, IClientChannel
 | 
			
		||||
        ResetSign();
 | 
			
		||||
    }
 | 
			
		||||
    public override TouchSocketConfig Config => base.Config ?? ChannelOptions.Config;
 | 
			
		||||
    private bool logSet;
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public void SetDataHandlingAdapterLogger(ILog log)
 | 
			
		||||
    {
 | 
			
		||||
        if (!logSet && DataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter)
 | 
			
		||||
        if (DataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter)
 | 
			
		||||
        {
 | 
			
		||||
            logSet = true;
 | 
			
		||||
            handleAdapter.Logger = log;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    public void LogSeted(bool logSeted)
 | 
			
		||||
    {
 | 
			
		||||
        logSet = logSeted;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public void SetDataHandlingAdapter(DataHandlingAdapter adapter)
 | 
			
		||||
    {
 | 
			
		||||
        if (adapter is UdpDataHandlingAdapter udpDataHandlingAdapter)
 | 
			
		||||
            SetAdapter(udpDataHandlingAdapter);
 | 
			
		||||
 | 
			
		||||
        logSet = false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -333,6 +333,10 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    private Task SendAsync(ISendMessage sendMessage, IClientChannel channel, CancellationToken token = default)
 | 
			
		||||
    {
 | 
			
		||||
        if(!channel.Online)
 | 
			
		||||
        {
 | 
			
		||||
            throw new InvalidOperationException("Channel is offline");
 | 
			
		||||
        }
 | 
			
		||||
        return SendAsync(this, sendMessage, channel, token);
 | 
			
		||||
 | 
			
		||||
        static async PooledTask SendAsync(DeviceBase @this, ISendMessage sendMessage, IClientChannel channel, CancellationToken token)
 | 
			
		||||
@@ -1060,10 +1064,6 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                Channel.Collects.Remove(this);
 | 
			
		||||
                if (Channel is IClientChannel clientChannel)
 | 
			
		||||
                {
 | 
			
		||||
                    clientChannel.LogSeted(false);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
@@ -1118,10 +1118,6 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
 | 
			
		||||
 | 
			
		||||
            Channel.Collects.Remove(this);
 | 
			
		||||
 | 
			
		||||
            if (Channel is IClientChannel clientChannel)
 | 
			
		||||
            {
 | 
			
		||||
                clientChannel.LogSeted(false);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -428,8 +428,9 @@ where TWriter : IByteBlockWriter
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    public static int WriteNormalString(this Span<byte> span, string value, Encoding encoding)
 | 
			
		||||
    public static int WriteNormalString(this Span<byte> span, string value, Encoding encoding=null)
 | 
			
		||||
    {
 | 
			
		||||
        encoding ??= Encoding.UTF8;
 | 
			
		||||
        var maxSize = encoding.GetMaxByteCount(value.Length);
 | 
			
		||||
        var chars = value.AsSpan();
 | 
			
		||||
 | 
			
		||||
@@ -439,7 +440,7 @@ where TWriter : IByteBlockWriter
 | 
			
		||||
            {
 | 
			
		||||
                fixed (byte* p1 = &span[0])
 | 
			
		||||
                {
 | 
			
		||||
                    var len = Encoding.UTF8.GetBytes(p, chars.Length, p1, maxSize);
 | 
			
		||||
                    var len = encoding.GetBytes(p, chars.Length, p1, maxSize);
 | 
			
		||||
                    return len;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,7 @@
 | 
			
		||||
			<CopyToOutputDirectory>Never</CopyToOutputDirectory>
 | 
			
		||||
		</EmbeddedResource>
 | 
			
		||||
	</ItemGroup>
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	<ItemGroup>
 | 
			
		||||
		<ProjectReference Include="..\..\Admin\ThingsGateway.NewLife.X\ThingsGateway.NewLife.X.csproj" />
 | 
			
		||||
 
 | 
			
		||||
@@ -59,14 +59,15 @@ public class CronScheduledTask : DisposeBase, IScheduledTask
 | 
			
		||||
        }
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
    private static volatile int NextId = 0;
 | 
			
		||||
    public void Start()
 | 
			
		||||
    {
 | 
			
		||||
        _timer?.Dispose();
 | 
			
		||||
        if (Check()) return;
 | 
			
		||||
        if (_taskAction != null)
 | 
			
		||||
            _timer = new TimerX(TimerCallback, _state, _interval, nameof(CronScheduledTask)) { Async = true, Reentrant = false };
 | 
			
		||||
            _timer = new TimerX(TimerCallback, _state, _interval, $"{nameof(CronScheduledTask)}{(Interlocked.Increment(ref NextId) / 100)}") { Async = true, Reentrant = false };
 | 
			
		||||
        else if (_taskFunc != null || _valueTaskFunc != null)
 | 
			
		||||
            _timer = new TimerX(TimerCallbackAsync, _state, _interval, nameof(CronScheduledTask)) { Async = true, Reentrant = false };
 | 
			
		||||
            _timer = new TimerX(TimerCallbackAsync, _state, _interval, $"{nameof(CronScheduledTask)}{(Interlocked.Increment(ref NextId) / 100)}") { Async = true, Reentrant = false };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private ValueTask TimerCallbackAsync(object? state)
 | 
			
		||||
 
 | 
			
		||||
@@ -46,11 +46,13 @@ public class ScheduledAsyncTask : DisposeBase, IScheduledTask, IScheduledIntInte
 | 
			
		||||
        }
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static volatile int NextId = 0;
 | 
			
		||||
    public void Start()
 | 
			
		||||
    {
 | 
			
		||||
        _timer?.Dispose();
 | 
			
		||||
        if (!Check())
 | 
			
		||||
            _timer = new TimerX(DoAsync, _state, IntervalMS, IntervalMS, nameof(ScheduledAsyncTask)) { Async = true, Reentrant = false };
 | 
			
		||||
            _timer = new TimerX(DoAsync, _state, IntervalMS, IntervalMS, $"{nameof(ScheduledAsyncTask)}{(Interlocked.Increment(ref NextId) / 100)}") { Async = true, Reentrant = false };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private ValueTask DoAsync(object? state)
 | 
			
		||||
 
 | 
			
		||||
@@ -36,11 +36,12 @@ public class ScheduledSyncTask : DisposeBase, IScheduledTask, IScheduledIntInter
 | 
			
		||||
        }
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
    private static volatile int NextId = 0;
 | 
			
		||||
    public void Start()
 | 
			
		||||
    {
 | 
			
		||||
        _timer?.Dispose();
 | 
			
		||||
        if (!Check())
 | 
			
		||||
            _timer = new TimerX(TimerCallback, _state, IntervalMS, IntervalMS, nameof(ScheduledSyncTask)) { Async = true, Reentrant = false };
 | 
			
		||||
            _timer = new TimerX(TimerCallback, _state, IntervalMS, IntervalMS, $"{nameof(ScheduledSyncTask)}{(Interlocked.Increment(ref NextId) / 100)}") { Async = true, Reentrant = false };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void TimerCallback(object? state)
 | 
			
		||||
 
 | 
			
		||||
@@ -430,7 +430,7 @@ public abstract partial class CollectBase : DriverBase
 | 
			
		||||
 | 
			
		||||
                readErrorCount++;
 | 
			
		||||
                if (@this.LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
 | 
			
		||||
                    @this.LogMessage?.Trace(string.Format("{0} - Collection [{1} - {2}] failed - {3}", @this.DeviceName, variableSourceRead?.RegisterAddress, variableSourceRead?.Length, readResult.ErrorMessage));
 | 
			
		||||
                    @this.LogMessage?.Trace(string.Format("{0} - Failed to collect data [{1} - {2}] - {3}", @this.DeviceName, variableSourceRead?.RegisterAddress, variableSourceRead?.Length, readResult.ErrorMessage));
 | 
			
		||||
 | 
			
		||||
                //if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
 | 
			
		||||
                //    LogMessage?.Trace(string.Format("{0} - Collecting [{1} - {2}]", DeviceName, variableSourceRead?.RegisterAddress, variableSourceRead?.Length));
 | 
			
		||||
@@ -450,7 +450,7 @@ public abstract partial class CollectBase : DriverBase
 | 
			
		||||
            {
 | 
			
		||||
                // 读取成功时记录日志并增加成功计数器
 | 
			
		||||
                if (@this.LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
 | 
			
		||||
                    @this.LogMessage?.Trace(string.Format("{0} - Collection [{1} - {2}] data succeeded {3}", @this.DeviceName, variableSourceRead?.RegisterAddress, variableSourceRead?.Length, readResult.Content.Span.ToHexString(' ')));
 | 
			
		||||
                    @this.LogMessage?.Trace(string.Format("{0} - Collected [{1} - {2}] data successfully {3}", @this.DeviceName, variableSourceRead?.RegisterAddress, variableSourceRead?.Length, readResult.Content.Span.ToHexString(' ')));
 | 
			
		||||
                @this.CurrentDevice.SetDeviceStatus(TimerX.Now, null);
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
@@ -475,7 +475,7 @@ public abstract partial class CollectBase : DriverBase
 | 
			
		||||
                    if (!cancellationToken.IsCancellationRequested)
 | 
			
		||||
                    {
 | 
			
		||||
                        if (@this.LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
 | 
			
		||||
                            @this.LogMessage?.Trace(string.Format("{0} - Collection [{1} - {2}] data failed - {3}", @this.DeviceName, variableSourceRead?.RegisterAddress, variableSourceRead?.Length, readResult.ErrorMessage));
 | 
			
		||||
                            @this.LogMessage?.Trace(string.Format("{0} - Failed to collect data [{1} - {2}] - {3}", @this.DeviceName, variableSourceRead?.RegisterAddress, variableSourceRead?.Length, readResult.ErrorMessage));
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
@@ -566,10 +566,24 @@ public abstract partial class CollectBase : DriverBase
 | 
			
		||||
                    {
 | 
			
		||||
                        foreach (var item in varRead)
 | 
			
		||||
                        {
 | 
			
		||||
                            if (!item.Value.Equals(writeInfoLists[item].ToObject(item.Value?.GetType())))
 | 
			
		||||
                            var cValue = writeInfoLists[item].ToObject(item.RawValue?.GetType());
 | 
			
		||||
                            if (!item.RawValue.Equals(cValue))
 | 
			
		||||
                            {
 | 
			
		||||
                                // 如果写入值与读取值不同,则更新操作结果为失败
 | 
			
		||||
                                operResults[item.Name] = new OperResult($"The write value is inconsistent with the read value,  Write value: {writeInfoLists[item].ToObject(item.Value?.GetType())}, read value: {item.Value}");
 | 
			
		||||
                                if (cValue is IComparable)
 | 
			
		||||
                                {
 | 
			
		||||
                                    operResults[item.Name] = new OperResult($"The write value is inconsistent with the read value,  Write value: {writeInfoLists[item].ToObject(item.Value?.GetType())}, read value: {item.Value}");
 | 
			
		||||
                                }
 | 
			
		||||
                                else
 | 
			
		||||
                                {
 | 
			
		||||
                                    if (cValue != null)
 | 
			
		||||
                                    {
 | 
			
		||||
                                        if (item.RawValue.ToSystemTextJsonString(false) != cValue.ToSystemTextJsonString(false))
 | 
			
		||||
                                            operResults[item.Name] = new OperResult($"The write value is inconsistent with the read value,  Write value: {writeInfoLists[item].ToObject(item.Value?.GetType())}, read value: {item.Value}");
 | 
			
		||||
                                    }
 | 
			
		||||
                                    else
 | 
			
		||||
                                        operResults[item.Name] = new OperResult($"The write value is inconsistent with the read value,  Write value: {writeInfoLists[item].ToObject(item.Value?.GetType())}, read value: {item.Value}");
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
 
 | 
			
		||||
@@ -509,34 +509,16 @@ internal sealed class AlarmTask : IDisposable
 | 
			
		||||
            {
 | 
			
		||||
                scheduledTask.Change(100, 100);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            ParallelOptions.CancellationToken = cancellation;
 | 
			
		||||
            // 遍历设备变量列表
 | 
			
		||||
            if (!GlobalData.AlarmEnableIdVariables.IsEmpty)
 | 
			
		||||
            {
 | 
			
		||||
                // 使用 Parallel.ForEach 执行指定的操作
 | 
			
		||||
                Parallel.ForEach(GlobalData.AlarmEnableIdVariables, ParallelOptions, (item, state, index) =>
 | 
			
		||||
            {
 | 
			
		||||
                // 如果取消请求已经被触发,则结束任务
 | 
			
		||||
                if (cancellation.IsCancellationRequested)
 | 
			
		||||
                    return;
 | 
			
		||||
 | 
			
		||||
                // 如果该变量的报警功能未启用,则跳过该变量
 | 
			
		||||
                if (!item.Value.AlarmEnable)
 | 
			
		||||
                    return;
 | 
			
		||||
 | 
			
		||||
                // 如果该变量离线,则跳过该变量
 | 
			
		||||
                if (!item.Value.IsOnline)
 | 
			
		||||
                    return;
 | 
			
		||||
 | 
			
		||||
                // 对该变量进行报警分析
 | 
			
		||||
                AlarmAnalysis(item.Value);
 | 
			
		||||
            });
 | 
			
		||||
                Parallel.ForEach(GlobalData.AlarmEnableIdVariables, ParallelOptions, Analysis);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                //if (scheduledTask.Period != 5000)
 | 
			
		||||
                //    scheduledTask.Change(0, 5000); // 如果没有启用报警的变量,则设置下次执行时间为5秒后
 | 
			
		||||
                scheduledTask.SetNext(5000); // 如果没有启用报警的变量,则设置下次执行时间为5秒后
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@@ -552,6 +534,21 @@ internal sealed class AlarmTask : IDisposable
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static void Analysis(KeyValuePair<long, VariableRuntime> item, ParallelLoopState state, long index)
 | 
			
		||||
    {
 | 
			
		||||
        // 如果取消请求已经被触发,则结束任务
 | 
			
		||||
        if (state.ShouldExitCurrentIteration)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        // 如果该变量的报警功能未启用,则跳过该变量
 | 
			
		||||
        if (!item.Value.AlarmEnable)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        // 如果该变量离线,则跳过该变量
 | 
			
		||||
        if (!item.Value.IsOnline)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        // 对该变量进行报警分析
 | 
			
		||||
        AlarmAnalysis(item.Value);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -386,7 +386,7 @@ internal sealed class ChannelService : BaseService<Channel>, IChannelService
 | 
			
		||||
        ManageHelper.CheckChannelCount(insertData.Count);
 | 
			
		||||
 | 
			
		||||
        using var db = GetDB();
 | 
			
		||||
        if (GlobalData.HardwareJob.HardwareInfo.MachineInfo.AvailableMemory < 2 * 1024 * 1024 || WebEnableVariable.WebEnable == false)
 | 
			
		||||
        if (GlobalData.HardwareJob.HardwareInfo.AvailableMemory < 2 * 1024 * 1024 || WebEnableVariable.WebEnable == false)
 | 
			
		||||
        {
 | 
			
		||||
            await db.BulkCopyAsync(insertData, 10000).ConfigureAwait(false);
 | 
			
		||||
            await db.BulkUpdateAsync(upData, 10000).ConfigureAwait(false);
 | 
			
		||||
 
 | 
			
		||||
@@ -387,7 +387,7 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService
 | 
			
		||||
        ManageHelper.CheckDeviceCount(insertData.Count);
 | 
			
		||||
 | 
			
		||||
        using var db = GetDB();
 | 
			
		||||
        if (GlobalData.HardwareJob.HardwareInfo.MachineInfo.AvailableMemory < 2 * 1024 * 1024 || WebEnableVariable.WebEnable == false)
 | 
			
		||||
        if (GlobalData.HardwareJob.HardwareInfo.AvailableMemory < 2 * 1024 * 1024 || WebEnableVariable.WebEnable == false)
 | 
			
		||||
        {
 | 
			
		||||
            await db.BulkCopyAsync(insertData, 10000).ConfigureAwait(false);
 | 
			
		||||
            await db.BulkUpdateAsync(upData, 10000).ConfigureAwait(false);
 | 
			
		||||
 
 | 
			
		||||
@@ -113,6 +113,28 @@ public partial class ManagementTask : AsyncDisposableObject
 | 
			
		||||
               .ConfigurePlugins(a =>
 | 
			
		||||
               {
 | 
			
		||||
                   a.UseTcpSessionCheckClear();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                   a.UseReconnection<TcpDmtpClient>(options =>
 | 
			
		||||
                   {
 | 
			
		||||
                       options.TryCount = -1;
 | 
			
		||||
                       options.PollingInterval = TimeSpan.FromMilliseconds(_managementOptions.HeartbeatInterval);
 | 
			
		||||
                       options.PrintLog = true;
 | 
			
		||||
                       options.CheckAction = async (c, count) =>
 | 
			
		||||
                       {
 | 
			
		||||
                           using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
 | 
			
		||||
                           if ((await c.PingAsync(cts.Token).ConfigureAwait(false)).IsSuccess)
 | 
			
		||||
                           {
 | 
			
		||||
                               return ConnectionCheckResult.Alive;
 | 
			
		||||
                           }
 | 
			
		||||
                           else
 | 
			
		||||
                           {
 | 
			
		||||
                               return ConnectionCheckResult.Dead;
 | 
			
		||||
                           }
 | 
			
		||||
                       };
 | 
			
		||||
 | 
			
		||||
                   });
 | 
			
		||||
 | 
			
		||||
                   a.UseDmtpRpc(a => a.ConfigureDefaultSerializationSelector(b =>
 | 
			
		||||
                   {
 | 
			
		||||
                       b.UseSystemTextJson(json =>
 | 
			
		||||
@@ -131,10 +153,6 @@ public partial class ManagementTask : AsyncDisposableObject
 | 
			
		||||
                   a.Add<FilePlugin>();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                   a.UseDmtpHeartbeat()//使用Dmtp心跳
 | 
			
		||||
                   .SetTick(TimeSpan.FromMilliseconds(_managementOptions.HeartbeatInterval))
 | 
			
		||||
                   .SetMaxFailCount(3);
 | 
			
		||||
 | 
			
		||||
                   a.AddDmtpCreatedChannelPlugin(async () =>
 | 
			
		||||
                   {
 | 
			
		||||
                       try
 | 
			
		||||
@@ -194,9 +212,6 @@ public partial class ManagementTask : AsyncDisposableObject
 | 
			
		||||
 | 
			
		||||
                   a.Add<FilePlugin>();
 | 
			
		||||
 | 
			
		||||
                   a.UseDmtpHeartbeat()//使用Dmtp心跳
 | 
			
		||||
                   .SetTick(TimeSpan.FromMilliseconds(_managementOptions.HeartbeatInterval))
 | 
			
		||||
                   .SetMaxFailCount(3);
 | 
			
		||||
               });
 | 
			
		||||
 | 
			
		||||
        await tcpDmtpService.SetupAsync(config).ConfigureAwait(false);
 | 
			
		||||
 
 | 
			
		||||
@@ -63,7 +63,7 @@ internal sealed class RedundancyTask : IRpcDriver, IAsyncDisposable
 | 
			
		||||
        const int highMemorySize = 100000;
 | 
			
		||||
        const long memoryThreshold = 2L * 1024 * 1024; // 2GB,单位KB
 | 
			
		||||
 | 
			
		||||
        return (GlobalData.HardwareJob.HardwareInfo.MachineInfo.AvailableMemory > memoryThreshold && WebEnableVariable.WebEnable == true)
 | 
			
		||||
        return (GlobalData.HardwareJob.HardwareInfo.AvailableMemory > memoryThreshold && WebEnableVariable.WebEnable == true)
 | 
			
		||||
            ? highMemorySize
 | 
			
		||||
            : defaultSize;
 | 
			
		||||
    }
 | 
			
		||||
@@ -295,6 +295,7 @@ internal sealed class RedundancyTask : IRpcDriver, IAsyncDisposable
 | 
			
		||||
            catch
 | 
			
		||||
            {
 | 
			
		||||
            }
 | 
			
		||||
            _tcpDmtpService.TryDispose();
 | 
			
		||||
        }
 | 
			
		||||
        if (_tcpDmtpClient != null)
 | 
			
		||||
        {
 | 
			
		||||
@@ -305,6 +306,7 @@ internal sealed class RedundancyTask : IRpcDriver, IAsyncDisposable
 | 
			
		||||
            catch
 | 
			
		||||
            {
 | 
			
		||||
            }
 | 
			
		||||
            _tcpDmtpClient.TryDispose();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -345,7 +347,25 @@ internal sealed class RedundancyTask : IRpcDriver, IAsyncDisposable
 | 
			
		||||
               .ConfigurePlugins(a =>
 | 
			
		||||
               {
 | 
			
		||||
                   a.UseTcpSessionCheckClear();
 | 
			
		||||
                   a.UseReconnection<TcpDmtpClient>(options =>
 | 
			
		||||
                   {
 | 
			
		||||
                       options.TryCount = -1;
 | 
			
		||||
                       options.PollingInterval = TimeSpan.FromMilliseconds(redundancy.HeartbeatInterval);
 | 
			
		||||
                       options.PrintLog = true;
 | 
			
		||||
                       options.CheckAction = async (c, count) =>
 | 
			
		||||
                       {
 | 
			
		||||
                           using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
 | 
			
		||||
                           if ((await c.PingAsync(cts.Token).ConfigureAwait(false)).IsSuccess)
 | 
			
		||||
                           {
 | 
			
		||||
                               return ConnectionCheckResult.Alive;
 | 
			
		||||
                           }
 | 
			
		||||
                           else
 | 
			
		||||
                           {
 | 
			
		||||
                               return ConnectionCheckResult.Dead;
 | 
			
		||||
                           }
 | 
			
		||||
                       };
 | 
			
		||||
 | 
			
		||||
                   });
 | 
			
		||||
                   a.UseDmtpRpc(a => a.ConfigureDefaultSerializationSelector(b =>
 | 
			
		||||
                   {
 | 
			
		||||
                       b.UseSystemTextJson(json =>
 | 
			
		||||
@@ -358,9 +378,7 @@ internal sealed class RedundancyTask : IRpcDriver, IAsyncDisposable
 | 
			
		||||
                           json.Converters.Add(new JArraySystemTextJsonConverter());
 | 
			
		||||
                       });
 | 
			
		||||
                   }));
 | 
			
		||||
                   a.UseDmtpHeartbeat()//使用Dmtp心跳
 | 
			
		||||
                   .SetTick(TimeSpan.FromMilliseconds(redundancy.HeartbeatInterval))
 | 
			
		||||
                   .SetMaxFailCount(redundancy.MaxErrorCount);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
               });
 | 
			
		||||
 | 
			
		||||
@@ -405,9 +423,7 @@ internal sealed class RedundancyTask : IRpcDriver, IAsyncDisposable
 | 
			
		||||
                           json.Converters.Add(new JArraySystemTextJsonConverter());
 | 
			
		||||
                       });
 | 
			
		||||
                   }));
 | 
			
		||||
                   a.UseDmtpHeartbeat()//使用Dmtp心跳
 | 
			
		||||
                   .SetTick(TimeSpan.FromMilliseconds(redundancy.HeartbeatInterval))
 | 
			
		||||
                   .SetMaxFailCount(redundancy.MaxErrorCount);
 | 
			
		||||
 | 
			
		||||
               });
 | 
			
		||||
 | 
			
		||||
        await tcpDmtpService.SetupAsync(config).ConfigureAwait(false);
 | 
			
		||||
 
 | 
			
		||||
@@ -26,7 +26,7 @@ internal static class RuntimeServiceHelper
 | 
			
		||||
    public static async Task InitAsync(List<ChannelRuntime> newChannelRuntimes, List<DeviceRuntime> newDeviceRuntimes, ILogger logger)
 | 
			
		||||
    {
 | 
			
		||||
        //批量修改之后,需要重新加载通道
 | 
			
		||||
        foreach (var newChannelRuntime in newChannelRuntimes)
 | 
			
		||||
        await newChannelRuntimes.ParallelForEachAsync(async (newChannelRuntime, token) =>
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
@@ -44,7 +44,7 @@ internal static class RuntimeServiceHelper
 | 
			
		||||
            {
 | 
			
		||||
                logger.LogWarning(ex, "Init Channel");
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        }).ConfigureAwait(false);
 | 
			
		||||
        GlobalData.ChannelDeviceRuntimeDispatchService.Dispatch(null);
 | 
			
		||||
        GlobalData.VariableRuntimeDispatchService.Dispatch(null);
 | 
			
		||||
    }
 | 
			
		||||
@@ -71,28 +71,28 @@ internal static class RuntimeServiceHelper
 | 
			
		||||
 | 
			
		||||
    public static async Task InitAsync(List<DeviceRuntime> newDeviceRuntimes, ILogger logger)
 | 
			
		||||
    {
 | 
			
		||||
        foreach (var newDeviceRuntime in newDeviceRuntimes)
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
        await newDeviceRuntimes.ParallelForEachAsync(async (newDeviceRuntime, token) =>
 | 
			
		||||
            {
 | 
			
		||||
                if (GlobalData.IdChannels.TryGetValue(newDeviceRuntime.ChannelId, out var newChannelRuntime))
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    newDeviceRuntime.Init(newChannelRuntime);
 | 
			
		||||
                    if (GlobalData.IdChannels.TryGetValue(newDeviceRuntime.ChannelId, out var newChannelRuntime))
 | 
			
		||||
                    {
 | 
			
		||||
                        newDeviceRuntime.Init(newChannelRuntime);
 | 
			
		||||
 | 
			
		||||
                    var newVariableRuntimes = (await GlobalData.VariableService.GetAllAsync(newDeviceRuntime.Id).ConfigureAwait(false)).AdaptEnumerableVariableRuntime();
 | 
			
		||||
                        var newVariableRuntimes = (await GlobalData.VariableService.GetAllAsync(newDeviceRuntime.Id).ConfigureAwait(false)).AdaptEnumerableVariableRuntime();
 | 
			
		||||
 | 
			
		||||
                    newVariableRuntimes.ParallelForEach(item => item.Init(newDeviceRuntime));
 | 
			
		||||
                        newVariableRuntimes.ParallelForEach(item => item.Init(newDeviceRuntime));
 | 
			
		||||
                    }
 | 
			
		||||
                    else
 | 
			
		||||
                    {
 | 
			
		||||
                        logger.LogWarning("Channel not found");
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                catch (Exception ex)
 | 
			
		||||
                {
 | 
			
		||||
                    logger.LogWarning("Channel not found");
 | 
			
		||||
                    logger.LogWarning(ex, "Init Device");
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
            {
 | 
			
		||||
                logger.LogWarning(ex, "Init Device");
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
            }).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
        GlobalData.ChannelDeviceRuntimeDispatchService.Dispatch(null);
 | 
			
		||||
        GlobalData.VariableRuntimeDispatchService.Dispatch(null);
 | 
			
		||||
@@ -234,16 +234,16 @@ internal static class RuntimeServiceHelper
 | 
			
		||||
    public static async Task RestartDeviceAsync(List<DeviceRuntime> newDeviceRuntimes)
 | 
			
		||||
    {
 | 
			
		||||
        var groups = GlobalData.GetDeviceThreadManages(newDeviceRuntimes);
 | 
			
		||||
        foreach (var group in groups)
 | 
			
		||||
        await groups.ParallelForEachAsync(async (group, token) =>
 | 
			
		||||
        {
 | 
			
		||||
            if (group.Key != null)
 | 
			
		||||
                await group.Key.RestartDeviceAsync(group.Value, false).ConfigureAwait(false);
 | 
			
		||||
        }
 | 
			
		||||
        foreach (var group in GlobalData.GetAllVariableBusinessDeviceRuntime().Where(a => !newDeviceRuntimes.Contains(a)).Where(a => a.Driver?.DeviceThreadManage != null).GroupBy(a => a.Driver.DeviceThreadManage))
 | 
			
		||||
        }).ConfigureAwait(false);
 | 
			
		||||
        await GlobalData.GetAllVariableBusinessDeviceRuntime().Where(a => !newDeviceRuntimes.Contains(a)).Where(a => a.Driver?.DeviceThreadManage != null).GroupBy(a => a.Driver.DeviceThreadManage).ParallelForEachAsync(async (group, token) =>
 | 
			
		||||
        {
 | 
			
		||||
            if (group.Key != null)
 | 
			
		||||
                await group.Key.RestartDeviceAsync(group.ToArray(), false).ConfigureAwait(false);
 | 
			
		||||
        }
 | 
			
		||||
        }).ConfigureAwait(false);
 | 
			
		||||
    }
 | 
			
		||||
    public static async Task RemoveDeviceAsync(HashSet<long> newDeciceIds)
 | 
			
		||||
    {
 | 
			
		||||
@@ -254,17 +254,17 @@ internal static class RuntimeServiceHelper
 | 
			
		||||
    public static async Task RemoveDeviceAsync(IEnumerable<DeviceRuntime> deviceRuntimes)
 | 
			
		||||
    {
 | 
			
		||||
        var groups = GlobalData.GetDeviceThreadManages(deviceRuntimes);
 | 
			
		||||
        foreach (var group in groups)
 | 
			
		||||
        {
 | 
			
		||||
            if (group.Key != null)
 | 
			
		||||
                await group.Key.RemoveDeviceAsync(group.Value.Select(a => a.Id).ToArray()).ConfigureAwait(false);
 | 
			
		||||
        }
 | 
			
		||||
        await groups.ParallelForEachAsync(async (group, token) =>
 | 
			
		||||
         {
 | 
			
		||||
             if (group.Key != null)
 | 
			
		||||
                 await group.Key.RemoveDeviceAsync(group.Value.Select(a => a.Id).ToArray()).ConfigureAwait(false);
 | 
			
		||||
         }).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
        foreach (var group in GlobalData.GetAllVariableBusinessDeviceRuntime().Where(a => !deviceRuntimes.Contains(a)).Where(a => a.Driver?.DeviceThreadManage != null).GroupBy(a => a.Driver.DeviceThreadManage))
 | 
			
		||||
        await GlobalData.GetAllVariableBusinessDeviceRuntime().Where(a => !deviceRuntimes.Contains(a)).Where(a => a.Driver?.DeviceThreadManage != null).GroupBy(a => a.Driver.DeviceThreadManage).ParallelForEachAsync(async (group, token) =>
 | 
			
		||||
        {
 | 
			
		||||
            if (group.Key != null)
 | 
			
		||||
                await group.Key.RestartDeviceAsync(group.ToArray(), false).ConfigureAwait(false);
 | 
			
		||||
        }
 | 
			
		||||
        }).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -189,7 +189,7 @@ public class VariableRuntimeService : IVariableRuntimeService
 | 
			
		||||
        {
 | 
			
		||||
            // await WaitLock.WaitAsync().ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
            var result = await GlobalData.VariableService.DeleteVariableAsync(null).ConfigureAwait(false);
 | 
			
		||||
            var result = await GlobalData.VariableService.DeleteVariableAsync(GlobalData.IdVariables.Keys).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
            ConcurrentHashSet<IDriver> changedDriver = new();
 | 
			
		||||
            var variableIds = GlobalData.IdVariables.Select(a => a.Key).ToHashSet();
 | 
			
		||||
 
 | 
			
		||||
@@ -207,7 +207,7 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
 | 
			
		||||
 | 
			
		||||
        var result = await db.UseTranAsync(async () =>
 | 
			
		||||
        {
 | 
			
		||||
            if (GlobalData.HardwareJob.HardwareInfo.MachineInfo.AvailableMemory < 2 * 1024 * 1024 || WebEnableVariable.WebEnable == false)
 | 
			
		||||
            if (GlobalData.HardwareJob.HardwareInfo.AvailableMemory < 2 * 1024 * 1024 || WebEnableVariable.WebEnable == false)
 | 
			
		||||
            {
 | 
			
		||||
                await db.BulkCopyAsync(newChannels, 10000).ConfigureAwait(false);
 | 
			
		||||
                await db.BulkCopyAsync(newDevices, 10000).ConfigureAwait(false);
 | 
			
		||||
@@ -342,7 +342,7 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
 | 
			
		||||
 | 
			
		||||
        var result = await db.UseTranAsync(async () =>
 | 
			
		||||
        {
 | 
			
		||||
            if (GlobalData.HardwareJob.HardwareInfo.MachineInfo.AvailableMemory < 2 * 1024 * 1024 || WebEnableVariable.WebEnable == false)
 | 
			
		||||
            if (GlobalData.HardwareJob.HardwareInfo.AvailableMemory < 2 * 1024 * 1024 || WebEnableVariable.WebEnable == false)
 | 
			
		||||
            {
 | 
			
		||||
                await db.BulkCopyAsync(newChannels, 10000).ConfigureAwait(false);
 | 
			
		||||
                await db.BulkCopyAsync(newDevices, 10000).ConfigureAwait(false);
 | 
			
		||||
@@ -620,7 +620,7 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
 | 
			
		||||
    [OperDesc("ExportVariable", isRecordPar: false, localizerType: typeof(Variable))]
 | 
			
		||||
    public async Task<Dictionary<string, object>> ExportVariableAsync(GatewayExportFilter exportFilter)
 | 
			
		||||
    {
 | 
			
		||||
        if (GlobalData.HardwareJob.HardwareInfo.MachineInfo.AvailableMemory < 4 * 1024 * 1024 || WebEnableVariable.WebEnable == false)
 | 
			
		||||
        if (GlobalData.HardwareJob.HardwareInfo.AvailableMemory < 4 * 1024 * 1024 || WebEnableVariable.WebEnable == false)
 | 
			
		||||
        {
 | 
			
		||||
            var whereQuery = await GetWhereEnumerableFunc(exportFilter).ConfigureAwait(false);
 | 
			
		||||
            //导出
 | 
			
		||||
@@ -694,7 +694,7 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
 | 
			
		||||
    {
 | 
			
		||||
        ManageHelper.CheckVariableCount(insertData.Count);
 | 
			
		||||
        using var db = GetDB();
 | 
			
		||||
        if (GlobalData.HardwareJob.HardwareInfo.MachineInfo.AvailableMemory < 2 * 1024 * 1024 || WebEnableVariable.WebEnable == false)
 | 
			
		||||
        if (GlobalData.HardwareJob.HardwareInfo.AvailableMemory < 2 * 1024 * 1024 || WebEnableVariable.WebEnable == false)
 | 
			
		||||
        {
 | 
			
		||||
            await db.BulkCopyAsync(insertData, 10000).ConfigureAwait(false);
 | 
			
		||||
            await db.BulkUpdateAsync(upData, 10000).ConfigureAwait(false);
 | 
			
		||||
 
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 257 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src/Plugin/ThingsGateway.Debug.Photino/favicon.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 177 KiB  | 
| 
		 Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 257 KiB  | 
| 
		 Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 177 KiB  | 
@@ -58,7 +58,6 @@ public class ModbusBenchmark : IDisposable
 | 
			
		||||
            thingsgatewaymodbuss.Add(thingsgatewaymodbus);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        for (int i = 0; i < ClientCount; i++)
 | 
			
		||||
        {
 | 
			
		||||
 | 
			
		||||
@@ -68,7 +67,6 @@ public class ModbusBenchmark : IDisposable
 | 
			
		||||
            nmodbuss.Add(nmodbus);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        for (int i = 0; i < ClientCount; i++)
 | 
			
		||||
        {
 | 
			
		||||
            var client = new ModbusTcpMaster();
 | 
			
		||||
 
 | 
			
		||||
@@ -41,7 +41,7 @@
 | 
			
		||||
	</ItemGroup>
 | 
			
		||||
 | 
			
		||||
	<ItemGroup>
 | 
			
		||||
		<PackageReference Include="BenchmarkDotNet" Version="0.15.4" />
 | 
			
		||||
		<PackageReference Include="BenchmarkDotNet" Version="0.15.5" />
 | 
			
		||||
		<PackageReference Include="NModbus" Version="3.0.81" />
 | 
			
		||||
		<PackageReference Include="S7netplus" Version="0.20.0" />
 | 
			
		||||
		<!--<PackageReference Include="ThingsGateway.Foundation.Modbus" Version="$(DefaultVersion)" />
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,8 @@
 | 
			
		||||
using ThingsGateway.Foundation.Modbus;
 | 
			
		||||
using ThingsGateway.NewLife.Json.Extension;
 | 
			
		||||
 | 
			
		||||
using TouchSocket.Sockets;
 | 
			
		||||
 | 
			
		||||
namespace ThingsGateway.Foundation.Demo;
 | 
			
		||||
 | 
			
		||||
#pragma warning disable CA1861 // 不要将常量数组作为参数
 | 
			
		||||
@@ -55,6 +57,7 @@ public class ModbusMasterDemo
 | 
			
		||||
 | 
			
		||||
        //获取协议对象
 | 
			
		||||
        using var device = GetDevice(channel);
 | 
			
		||||
        await channel.SetupAsync(channel.Config);
 | 
			
		||||
 | 
			
		||||
        //读取具体类型数据
 | 
			
		||||
        var data = await device.ReadDoubleAsync("400001"); //通过字符串转化地址,读取保持寄存器地址0
 | 
			
		||||
 
 | 
			
		||||
@@ -55,6 +55,7 @@ public class SiemensS7MasterDemo
 | 
			
		||||
 | 
			
		||||
        //获取协议对象
 | 
			
		||||
        using var device = GetDevice(channel);
 | 
			
		||||
        await channel.SetupAsync(channel.Config);
 | 
			
		||||
 | 
			
		||||
        //读取具体类型数据
 | 
			
		||||
        var data = await device.ReadDoubleAsync("V1"); //通过字符串转化地址,读取v1
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,7 @@
 | 
			
		||||
 | 
			
		||||
using Riok.Mapperly.Abstractions;
 | 
			
		||||
 | 
			
		||||
using ThingsGateway.Common;
 | 
			
		||||
using ThingsGateway.DB;
 | 
			
		||||
 | 
			
		||||
namespace ThingsGateway.Plugin.DB;
 | 
			
		||||
 
 | 
			
		||||
@@ -171,10 +171,10 @@ public partial class MqttCollect : CollectBase
 | 
			
		||||
                    mqttClientSubscribeOptionsBuilder = mqttClientSubscribeOptionsBuilder.WithTopicFilter(
 | 
			
		||||
                        f => f.WithTopic(item));
 | 
			
		||||
                }
 | 
			
		||||
                var mqttClientSubscribeOptions = mqttClientSubscribeOptionsBuilder.Build();
 | 
			
		||||
                if (mqttClientSubscribeOptions.TopicFilters.Count > 0)
 | 
			
		||||
                    _mqttSubscribeOptions = mqttClientSubscribeOptions;
 | 
			
		||||
            }
 | 
			
		||||
            var mqttClientSubscribeOptions = mqttClientSubscribeOptionsBuilder.Build();
 | 
			
		||||
            if (mqttClientSubscribeOptions.TopicFilters.Count > 0)
 | 
			
		||||
                _mqttSubscribeOptions = mqttClientSubscribeOptions;
 | 
			
		||||
 | 
			
		||||
            return Task.FromResult(dataResult);
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -20,6 +20,8 @@ using System.Diagnostics.CodeAnalysis;
 | 
			
		||||
using ThingsGateway.Extension;
 | 
			
		||||
using ThingsGateway.Foundation.OpcDa;
 | 
			
		||||
using ThingsGateway.Foundation.OpcDa.Rcw;
 | 
			
		||||
using ThingsGateway.Common;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#if Plugin
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -35,6 +35,8 @@ using ThingsGateway.Razor;
 | 
			
		||||
 | 
			
		||||
using TouchSocket.Core;
 | 
			
		||||
 | 
			
		||||
using ThingsGateway.Common;
 | 
			
		||||
 | 
			
		||||
namespace ThingsGateway.Debug;
 | 
			
		||||
 | 
			
		||||
/// <summary>
 | 
			
		||||
@@ -73,7 +75,7 @@ public partial class OpcUaImportVariable
 | 
			
		||||
            {
 | 
			
		||||
                Items = BuildTreeItemList(await PopulateBranchAsync(ObjectIds.ObjectsFolder), RenderTreeItem).ToList();
 | 
			
		||||
                ShowSkeleton = false;
 | 
			
		||||
                await InvokeAsync(StateHasChanged);
 | 
			
		||||
                return InvokeAsync(StateHasChanged);
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        await base.OnAfterRenderAsync(firstRender);
 | 
			
		||||
 
 | 
			
		||||
@@ -17,6 +17,8 @@ using Photino.Blazor;
 | 
			
		||||
 | 
			
		||||
using System.Text;
 | 
			
		||||
 | 
			
		||||
using ThingsGateway.NewLife;
 | 
			
		||||
using ThingsGateway.NewLife.Json.Extension;
 | 
			
		||||
using ThingsGateway.NewLife.Log;
 | 
			
		||||
 | 
			
		||||
namespace ThingsGateway.Server;
 | 
			
		||||
@@ -28,6 +30,23 @@ internal sealed class Program
 | 
			
		||||
    [STAThread]
 | 
			
		||||
    private static void Main(string[] args)
 | 
			
		||||
    {
 | 
			
		||||
        AppDomain.CurrentDomain.UnhandledException += (sender, e) =>
 | 
			
		||||
        {
 | 
			
		||||
            if (e.ExceptionObject is OutOfMemoryException)
 | 
			
		||||
            {
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    XTrace.WriteLine($"[OOM DETECTED]");
 | 
			
		||||
                    XTrace.WriteLine(MachineInfo.GetCurrent().ToJsonNetString());
 | 
			
		||||
                }
 | 
			
		||||
                catch
 | 
			
		||||
                {
 | 
			
		||||
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        //当前工作目录设为程序集的基目录
 | 
			
		||||
        System.IO.Directory.SetCurrentDirectory(AppContext.BaseDirectory);
 | 
			
		||||
        // 增加中文编码支持
 | 
			
		||||
 
 | 
			
		||||
@@ -83,6 +83,9 @@
 | 
			
		||||
		<Content Include="..\ThingsGateway.Server\Layout\MainLayout.razor" Link="Layout\MainLayout.razor" />
 | 
			
		||||
		<Compile Include="..\ThingsGateway.Server\Layout\MainLayout.razor.cs" Link="Layout\MainLayout.razor.cs" />
 | 
			
		||||
		<Content Include="..\ThingsGateway.Server\Layout\MainLayout.razor.css" Link="Layout\MainLayout.razor.css" />
 | 
			
		||||
		<Content Include="..\ThingsGateway.Server\Layout\Gitee2025opensource.razor" Link="Layout\Gitee2025opensource.razor" />
 | 
			
		||||
		<Compile Include="..\ThingsGateway.Server\Layout\Gitee2025opensource.razor.cs" Link="Layout\Gitee2025opensource.razor.cs" />
 | 
			
		||||
		<Content Include="..\ThingsGateway.Server\Layout\Gitee2025opensource.razor.css" Link="Layout\Gitee2025opensource.razor.css" />
 | 
			
		||||
		<Content Include="..\ThingsGateway.Server\Layout\AccessDenied.razor" Link="Layout\AccessDenied.razor" />
 | 
			
		||||
		<Compile Include="..\ThingsGateway.Server\Layout\AccessDenied.razor.cs" Link="Layout\AccessDenied.razor.cs" />
 | 
			
		||||
		<Content Include="..\ThingsGateway.Server\Layout\Login.razor" Link="Layout\Login.razor" />
 | 
			
		||||
 
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 257 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src/ThingsGateway.Photino/favicon.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 177 KiB  | 
| 
		 Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 257 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src/ThingsGateway.RemoteWebApp/favicon.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 177 KiB  | 
| 
		 Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 257 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src/ThingsGateway.ScriptDebug/favicon.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 177 KiB  | 
@@ -3,6 +3,6 @@
 | 
			
		||||
    "CheckInterval": 1800000, //检查间隔
 | 
			
		||||
    "MaxChannelCount": 50, //最大通道数量
 | 
			
		||||
    "MaxDeviceCount": 50, //最大设备数量
 | 
			
		||||
    "MaxVariableCount": 10000 //最大变量数量
 | 
			
		||||
    "MaxVariableCount": 5000 //最大变量数量
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										18
									
								
								src/ThingsGateway.Server/Layout/Gitee2025opensource.razor
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,18 @@
 | 
			
		||||
@inherits ComponentBase
 | 
			
		||||
@namespace ThingsGateway.Server
 | 
			
		||||
 | 
			
		||||
<div class="popup-overlay">
 | 
			
		||||
	<div class="popup-window text-align: center; ">
 | 
			
		||||
		<p>
 | 
			
		||||
			🎉 <strong>ThingsGateway</strong>  正在参加
 | 
			
		||||
			<strong> Gitee 2025 最受欢迎的开源软件评选活动 </strong>,
 | 
			
		||||
			需要你的支持!
 | 
			
		||||
		</p>
 | 
			
		||||
		<a href="https://gitee.com/activity/2025opensource?ident=I4XWR9"
 | 
			
		||||
		   target="_blank"
 | 
			
		||||
		   rel="noopener noreferrer"
 | 
			
		||||
		   class="popup-link" onclick="@OnClick">
 | 
			
		||||
			👉 前往投票支持
 | 
			
		||||
		</a>
 | 
			
		||||
	</div>
 | 
			
		||||
</div>
 | 
			
		||||
							
								
								
									
										28
									
								
								src/ThingsGateway.Server/Layout/Gitee2025opensource.razor.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,28 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
//  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
 | 
			
		||||
//  此代码版权(除特别声明外的代码)归作者本人Diego所有
 | 
			
		||||
//  源代码使用协议遵循本仓库的开源协议及附加协议
 | 
			
		||||
//  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
 | 
			
		||||
//  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
 | 
			
		||||
//  使用文档:https://thingsgateway.cn/
 | 
			
		||||
//  QQ群:605534569
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
#pragma warning disable CA2007 // 考虑对等待的任务调用 ConfigureAwait
 | 
			
		||||
using Microsoft.AspNetCore.Components;
 | 
			
		||||
 | 
			
		||||
namespace ThingsGateway.Server;
 | 
			
		||||
 | 
			
		||||
public partial class Gitee2025opensource
 | 
			
		||||
{
 | 
			
		||||
    [CascadingParameter]
 | 
			
		||||
    private Func<Task>? OnCloseAsync { get; set; }
 | 
			
		||||
 | 
			
		||||
    private async Task OnClick()
 | 
			
		||||
    {
 | 
			
		||||
        if (OnCloseAsync != null)
 | 
			
		||||
        {
 | 
			
		||||
            await OnCloseAsync();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,43 @@
 | 
			
		||||
/* 弹窗遮罩 */
 | 
			
		||||
.popup-overlay {
 | 
			
		||||
    position: fixed;
 | 
			
		||||
    top: 0;
 | 
			
		||||
    left: 0;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    background: rgba(10, 10, 10, 0.6);
 | 
			
		||||
    display: flex;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    justify-content: center;
 | 
			
		||||
    z-index: 9999;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 弹窗主体 */
 | 
			
		||||
.popup-window {
 | 
			
		||||
    background: #fff;
 | 
			
		||||
    border-radius: 12px;
 | 
			
		||||
    padding: 30px 40px;
 | 
			
		||||
    max-width: 420px;
 | 
			
		||||
    box-shadow: 0 0 30px rgba(0, 0, 0, 0.3);
 | 
			
		||||
    text-align: center;
 | 
			
		||||
    animation: popup-in 0.3s ease-out;
 | 
			
		||||
}
 | 
			
		||||
/* 按钮与链接 */
 | 
			
		||||
.popup-link {
 | 
			
		||||
  display: inline-block;
 | 
			
		||||
  margin-top: 10px;
 | 
			
		||||
  margin-right: 20px; /* ✅ 按钮之间留空隙 */
 | 
			
		||||
  margin-bottom: 20px; /* ✅ 按钮之间留空隙 */
 | 
			
		||||
  background-color: #e4405f;
 | 
			
		||||
  color: white;
 | 
			
		||||
  padding: 8px 16px;
 | 
			
		||||
  border-radius: 8px;
 | 
			
		||||
  text-decoration: none;
 | 
			
		||||
  font-weight: bold;
 | 
			
		||||
  transition: background 0.2s;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.popup-link:hover {
 | 
			
		||||
  background-color: #c8324f;
 | 
			
		||||
}
 | 
			
		||||
@@ -112,3 +112,4 @@
 | 
			
		||||
    </CascadingValue>
 | 
			
		||||
</CascadingValue>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -239,4 +239,36 @@ public partial class MainLayout : IDisposable
 | 
			
		||||
        };
 | 
			
		||||
        await DialogService.Show(op);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 显示投票弹窗
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    public async Task ShowGitee()
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
        await DialogService.Show(new DialogOption()
 | 
			
		||||
        {
 | 
			
		||||
            IsScrolling = false,
 | 
			
		||||
            ShowFooter = false,
 | 
			
		||||
            Title = "Gitee 评选活动",
 | 
			
		||||
            BodyTemplate = BootstrapDynamicComponent.CreateComponent<Gitee2025opensource>().Render(),
 | 
			
		||||
            ShowCloseButton = false,
 | 
			
		||||
            ShowHeaderCloseButton = false,
 | 
			
		||||
            Size = Size.Small,
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected override async Task OnAfterRenderAsync(bool firstRender)
 | 
			
		||||
    {
 | 
			
		||||
        if (firstRender)
 | 
			
		||||
        {
 | 
			
		||||
            if (WebsiteOption.Value.Demo)
 | 
			
		||||
            {
 | 
			
		||||
                await ShowGitee();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        await base.OnAfterRenderAsync(firstRender);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -16,6 +16,7 @@ using System.Text;
 | 
			
		||||
using ThingsGateway.Admin.Application;
 | 
			
		||||
using ThingsGateway.DB;
 | 
			
		||||
using ThingsGateway.NewLife;
 | 
			
		||||
using ThingsGateway.NewLife.Json.Extension;
 | 
			
		||||
using ThingsGateway.NewLife.Log;
 | 
			
		||||
using ThingsGateway.SqlSugar;
 | 
			
		||||
 | 
			
		||||
@@ -27,6 +28,24 @@ public class Program
 | 
			
		||||
 | 
			
		||||
    public static async Task Main(string[] args)
 | 
			
		||||
    {
 | 
			
		||||
        AppDomain.CurrentDomain.UnhandledException += (sender, e) =>
 | 
			
		||||
        {
 | 
			
		||||
            if (e.ExceptionObject is OutOfMemoryException)
 | 
			
		||||
            {
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    XTrace.WriteLine($"[OOM DETECTED]");
 | 
			
		||||
                    XTrace.WriteLine(MachineInfo.GetCurrent().ToJsonNetString());
 | 
			
		||||
                }
 | 
			
		||||
                catch
 | 
			
		||||
                {
 | 
			
		||||
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        await Task.Delay(2000).ConfigureAwait(false);
 | 
			
		||||
        //当前工作目录设为程序集的基目录
 | 
			
		||||
        System.IO.Directory.SetCurrentDirectory(AppContext.BaseDirectory);
 | 
			
		||||
 
 | 
			
		||||
@@ -109,8 +109,8 @@ public class Startup : AppStartup
 | 
			
		||||
             .AddInteractiveServerComponents(options =>
 | 
			
		||||
             {
 | 
			
		||||
                 options.RootComponents.MaxJSRootComponents = 500;
 | 
			
		||||
                 options.JSInteropDefaultCallTimeout = TimeSpan.FromMinutes(2);
 | 
			
		||||
                 options.MaxBufferedUnacknowledgedRenderBatches = 20;
 | 
			
		||||
                 options.JSInteropDefaultCallTimeout = TimeSpan.FromSeconds(30);
 | 
			
		||||
                 options.MaxBufferedUnacknowledgedRenderBatches = 5;
 | 
			
		||||
                 options.DisconnectedCircuitMaxRetained = 1;
 | 
			
		||||
                 options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromSeconds(10);
 | 
			
		||||
             })
 | 
			
		||||
@@ -120,30 +120,31 @@ public class Startup : AppStartup
 | 
			
		||||
                 options.MaximumReceiveMessageSize = 32 * 1024 * 1024;
 | 
			
		||||
                 //可为客户端上载流缓冲的最大项数。 如果达到此限制,则会阻止处理调用,直到服务器处理流项。
 | 
			
		||||
                 options.StreamBufferCapacity = 30;
 | 
			
		||||
                 options.ClientTimeoutInterval = TimeSpan.FromMinutes(2);
 | 
			
		||||
                 options.ClientTimeoutInterval = TimeSpan.FromSeconds(30);
 | 
			
		||||
                 options.KeepAliveInterval = TimeSpan.FromSeconds(15);
 | 
			
		||||
                 options.HandshakeTimeout = TimeSpan.FromSeconds(30);
 | 
			
		||||
                 options.HandshakeTimeout = TimeSpan.FromSeconds(15);
 | 
			
		||||
             });
 | 
			
		||||
 | 
			
		||||
#else
 | 
			
		||||
 | 
			
		||||
                    services.AddServerSideBlazor(options =>
 | 
			
		||||
                    {
 | 
			
		||||
                        options.RootComponents.MaxJSRootComponents = 500;
 | 
			
		||||
                        options.JSInteropDefaultCallTimeout = TimeSpan.FromMinutes(2);
 | 
			
		||||
                        options.MaxBufferedUnacknowledgedRenderBatches = 20;
 | 
			
		||||
                        options.DisconnectedCircuitMaxRetained = 1;
 | 
			
		||||
                        options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromSeconds(10);
 | 
			
		||||
                    }).AddHubOptions(options =>
 | 
			
		||||
                    {
 | 
			
		||||
                        //单个传入集线器消息的最大大小。默认 32 KB
 | 
			
		||||
                        options.MaximumReceiveMessageSize =32 * 1024 * 1024;
 | 
			
		||||
                        //可为客户端上载流缓冲的最大项数。 如果达到此限制,则会阻止处理调用,直到服务器处理流项。
 | 
			
		||||
                        options.StreamBufferCapacity = 30;
 | 
			
		||||
                        options.ClientTimeoutInterval = TimeSpan.FromMinutes(2);
 | 
			
		||||
                        options.KeepAliveInterval = TimeSpan.FromSeconds(15);
 | 
			
		||||
                        options.HandshakeTimeout = TimeSpan.FromSeconds(30);
 | 
			
		||||
                    });
 | 
			
		||||
                     {
 | 
			
		||||
                         options.RootComponents.MaxJSRootComponents = 500;
 | 
			
		||||
                         options.JSInteropDefaultCallTimeout = TimeSpan.FromSeconds(30);
 | 
			
		||||
                         options.MaxBufferedUnacknowledgedRenderBatches = 5;
 | 
			
		||||
                         options.DisconnectedCircuitMaxRetained = 1;
 | 
			
		||||
                         options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromSeconds(10);
 | 
			
		||||
                     })
 | 
			
		||||
                     .AddHubOptions(options =>
 | 
			
		||||
                     {
 | 
			
		||||
                         //单个传入集线器消息的最大大小。默认 32 KB
 | 
			
		||||
                         options.MaximumReceiveMessageSize = 32 * 1024 * 1024;
 | 
			
		||||
                         //可为客户端上载流缓冲的最大项数。 如果达到此限制,则会阻止处理调用,直到服务器处理流项。
 | 
			
		||||
                         options.StreamBufferCapacity = 30;
 | 
			
		||||
                         options.ClientTimeoutInterval = TimeSpan.FromSeconds(30);
 | 
			
		||||
                         options.KeepAliveInterval = TimeSpan.FromSeconds(15);
 | 
			
		||||
                         options.HandshakeTimeout = TimeSpan.FromSeconds(15);
 | 
			
		||||
                     });
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
@@ -207,10 +208,7 @@ public class Startup : AppStartup
 | 
			
		||||
 | 
			
		||||
            var websiteOptions = App.GetConfig<WebsiteOptions>("Website");
 | 
			
		||||
 | 
			
		||||
            if (websiteOptions.BlazorConnectionLimitEnable)
 | 
			
		||||
            {
 | 
			
		||||
                services.AddSingleton<CircuitHandler, ConnectionLimiterCircuitHandler>();
 | 
			
		||||
            }
 | 
			
		||||
            services.AddSingleton<CircuitHandler, ConnectionLimiterCircuitHandler>();
 | 
			
		||||
            if (websiteOptions.Demo)
 | 
			
		||||
            {
 | 
			
		||||
                authenticationBuilder.AddOAuth<GiteeOAuthOptions, AdminOAuthHandler<GiteeOAuthOptions>>("Gitee", "Gitee", options =>
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,8 @@
 | 
			
		||||
 | 
			
		||||
using Microsoft.AspNetCore.Components.Server.Circuits;
 | 
			
		||||
 | 
			
		||||
using System.Runtime;
 | 
			
		||||
 | 
			
		||||
using ThingsGateway.Common;
 | 
			
		||||
 | 
			
		||||
namespace ThingsGateway.Server;
 | 
			
		||||
@@ -22,6 +24,12 @@ public class ConnectionLimiterCircuitHandler : CircuitHandler
 | 
			
		||||
 | 
			
		||||
    public override Task OnCircuitOpenedAsync(Circuit circuit, CancellationToken cancellationToken)
 | 
			
		||||
    {
 | 
			
		||||
        //主动触发垃圾回收,释放上个链路资源
 | 
			
		||||
        GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
 | 
			
		||||
        GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true, true);
 | 
			
		||||
        GC.WaitForPendingFinalizers();
 | 
			
		||||
        GC.Collect(1, GCCollectionMode.Optimized, blocking: false, compacting: false);
 | 
			
		||||
 | 
			
		||||
        WebsiteOptions ??= App.GetOptions<WebsiteOptions>();
 | 
			
		||||
 | 
			
		||||
        if (!WebsiteOptions.BlazorConnectionLimitEnable)
 | 
			
		||||
 
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 257 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src/ThingsGateway.Server/favicon.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 177 KiB  | 
@@ -3,7 +3,8 @@
 | 
			
		||||
    "System.Runtime.EnableWriteXorExecute": false,
 | 
			
		||||
    "System.GC.HeapHardLimitPercent": 95, //堆限制百分比
 | 
			
		||||
    "System.GC.HighMemoryPercent": 90, //高内存百分比
 | 
			
		||||
    "System.GC.DynamicAdaptationMode": 1 //动态适应模式
 | 
			
		||||
    "System.GC.DynamicAdaptationMode": 1, //动态适应模式
 | 
			
		||||
    "System.GC.ConserveMemory": 5 //节省内存模式,0-9
 | 
			
		||||
    //"System.GC.RegionRange": 549755813888 //8GB, 区域范围,保留的虚拟内存,如DOCKER内出现OOM,可以调大,一般是进程内存限制的2倍
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 257 KiB  | 
| 
		 Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 177 KiB  |